Skip to content
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
6 changes: 3 additions & 3 deletions .github/workflows/autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ jobs:

- name: Install dependencies
run: pnpm install

- name: prepare
run: pnpm dev:prepare

- name: Release PR version
run: pnpm dlx pkg-pr-new@0.0 publish
# - name: Release PR version
# run: pnpm dlx pkg-pr-new@0.0 publish

- name: Lint (code)
run: pnpm lint --fix
Expand Down
176 changes: 93 additions & 83 deletions docs/content/1.docs/2.storage/3.blob.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ Returns a paginated list of blobs.

```ts [server/api/files.get.ts]
export default eventHandler(async () => {
return hubBlob().list()
const { blobs } = await hubBlob().list({ limit: 10 })

return blobs
})
```

Expand All @@ -38,17 +40,19 @@ export default eventHandler(async () => {
::field-group
::field{name="options" type="Object"}
The list options.
::field{name="limit" type="Number"}
The maximum number of blobs to return per request. Defaults to `1000`.
::
::field{name="prefix" type="String"}
Filters the results to only those that begin with the specified prefix.
::
::field{name="cursor" type="String"}
The cursor to continue from a previous list operation.
::
::field{name="folded" type="Boolean"}
If `true`, the list will be folded using `/` separator and list of folders will be returned.
::collapsible
::field{name="limit" type="Number"}
The maximum number of blobs to return per request. Defaults to `1000`.
::
::field{name="prefix" type="String"}
Filters the results to only those that begin with the specified prefix.
::
::field{name="cursor" type="String"}
The cursor to continue from a previous list operation.
::
::field{name="folded" type="Boolean"}
If `true`, the list will be folded using `/` separator and list of folders will be returned.
::
::
::
::
Expand Down Expand Up @@ -174,17 +178,22 @@ async function uploadImage (e: Event) {
::
::field{name="options" type="Object"}
The put options. Any other provided field will be stored in the blob's metadata.
::field{name="contentType" type="String"}
The content type of the blob. If not given, it will be inferred from the Blob or the file extension.
::
::field{name="contentLength" type="String"}
The content length of the blob.
::
::field{name="addRandomSuffix" type="Boolean"}
If `true`, a random suffix will be added to the blob's name. Defaults to `false`.
::
::field{name="prefix" type="string"}
The prefix to use for the blob pathname.
::collapsible
::field{name="contentType" type="String"}
The content type of the blob. If not given, it will be inferred from the Blob or the file extension.
::
::field{name="contentLength" type="String"}
The content length of the blob.
::
::field{name="addRandomSuffix" type="Boolean"}
If `true`, a random suffix will be added to the blob's name. Defaults to `false`.
::
::field{name="prefix" type="string"}
The prefix to use for the blob pathname.
::
::field{name="customMetadata" type="Record<string, string>"}
An object with custom metadata to store with the blob.
::
::
::
::
Expand Down Expand Up @@ -231,16 +240,26 @@ Returns nothing.

### `handleUpload()`

`handleUpload` is a all in one function to validate a `Blob` by checking its size and type and upload it to the storage.
It can used to handle file uploads in API routes.
This is an "all in one" function to validate a `Blob` by checking its size and type and upload it to the storage.

::note
This server util is made to be used with the [`useUpload()`](#useupload) Vue composable.
::

It can be used to handle file uploads in API routes.

::code-group
```ts [server/api/blob.put.ts]
export default eventHandler(async (event) => {
return hubBlob().handleUpload(event, {
formKey: 'files', // read file or files form the `formKey` field of request body (body should be a `FormData` object)
multiple: true, // when `true`, the `formKey` field will be an array of `Blob` objects
contentType: ['image/jpeg', 'images/png'], // allowed types of the file
ensure: {
contentType: ['image/jpeg', 'images/png'], // allowed types of the file
},
put: {
addRandomSuffix: true
}
})
})
```
Expand Down Expand Up @@ -269,25 +288,11 @@ async function onFileSelect(event: Event) {
::field{name="multiple" type="boolean"}
When `true`, the `formKey` field will be an array of `Blob` objects.
::
::field{name="maxSize" type="BlobSize"}
The maximum size of the file, should be: :br
(`1` | `2` | `4` | `8` | `16` | `32` | `64` | `128` | `256` | `512` | `1024`) + (`B` | `KB` | `MB` | `GB`) :br
e.g. `'512KB'`, `'1MB'`, `'2GB'`, etc.
::
::field{name="types" type="BlobType[]"}
Allowed types of the file, e.g. `['image/jpeg']`.
::
::field{name="contentType" type="string"}
The content type of the blob.
::
::field{name="contentLength" type="string"}
The content length of the blob.
::
::field{name="addRandomSuffix" type="boolean"}
If `true`, a random suffix will be added to the blob's name. Defaults to `false`.
::field{name="ensure" type="BlobEnsureOptions"}
See [`ensureBlob()`](#ensureblob) options for more details.
::
::field{name="prefix" type="string"}
The prefix to use for the blob pathname.
::field{name="put" type="BlobPutOptions"}
See [`put()`](#put) options for more details.
::
::

Expand Down Expand Up @@ -370,14 +375,16 @@ export default eventHandler(async (event) => {
::
::field{name="options" type="Object"}
The put options. Any other provided field will be stored in the blob's metadata.
::field{name="contentType" type="String"}
The content type of the blob. If not given, it will be inferred from the Blob or the file extension.
::
::field{name="contentLength" type="String"}
The content length of the blob.
::
::field{name="addRandomSuffix" type="Boolean"}
If `true`, a random suffix will be added to the blob's name. Defaults to `true`.
::collapsible
::field{name="contentType" type="String"}
The content type of the blob. If not given, it will be inferred from the Blob or the file extension.
::
::field{name="contentLength" type="String"}
The content length of the blob.
::
::field{name="addRandomSuffix" type="Boolean"}
If `true`, a random suffix will be added to the blob's name. Defaults to `true`.
::
::
::
::
Expand Down Expand Up @@ -520,13 +527,15 @@ ensureBlob(file, { maxSize: '1MB', types: ['image' ]})
::
::field{name="options" type="Object" required}
Note that at least `maxSize` or `types` should be provided.
::field{name="maxSize" type="BlobSize"}
The maximum size of the file, should be: :br
(`1` | `2` | `4` | `8` | `16` | `32` | `64` | `128` | `256` | `512` | `1024`) + (`B` | `KB` | `MB` | `GB`) :br
e.g. `'512KB'`, `'1MB'`, `'2GB'`, etc.
::
::field{name="types" type="BlobType[]"}
Allowed types of the file, e.g. `['image/jpeg']`.
::collapsible
::field{name="maxSize" type="BlobSize"}
The maximum size of the file, should be: :br
(`1` | `2` | `4` | `8` | `16` | `32` | `64` | `128` | `256` | `512` | `1024`) + (`B` | `KB` | `MB` | `GB`) :br
e.g. `'512KB'`, `'1MB'`, `'2GB'`, etc.
::
::field{name="types" type="BlobType[]"}
Allowed types of the file, e.g. `['image/jpeg']`.
::
::
::
::
Expand Down Expand Up @@ -577,14 +586,13 @@ async function onFileSelect({ target }: Event) {
::
::field{name="options" type="Object" required}
Optionally, you can pass Fetch options to the request. Read more about Fetch API [here](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options).
::field{name="formKey" type="string"}
The key to add the file/files to the request form. Defaults to `'files'`.
::
::field{name="multiple" type="boolean"}
Whether to allow multiple files to be uploaded. Defaults to `true`.
::
::field{name="prefix" type="string"}
The prefix to use for the blob pathname.
::collapsible
::field{name="formKey" type="string"}
The key to add the file/files to the request form. Defaults to `'files'`.
::
::field{name="multiple" type="boolean"}
Whether to allow multiple files to be uploaded. Defaults to `true`.
::
::
::
::
Expand Down Expand Up @@ -614,21 +622,23 @@ export const mpu = useMultipartUpload('/api/files/multipart')
::
::field{name="options"}
The options for the multipart upload helper.
::field{name="partSize" type="number"}
The size of each part of the file to be uploaded. Defaults to `10MB`.
::
::field{name="concurrent" type="number"}
The maximum number of concurrent uploads. Defaults to `1`.
::
::field{name="maxRetry" type="number"}
The maximum number of retry attempts for the whole upload. Defaults to `3`.
::
::field{name="prefix" type="string"}
The prefix to use for the blob pathname.
::
::field{name="fetchOptions" type="Omit<FetchOptions, 'method' | 'baseURL' | 'body' | 'parseResponse' | 'responseType'>"}
Override the ofetch options.
The `query` and `headers` will be merged with the options provided by the uploader.
::collapsible
::field{name="partSize" type="number"}
The size of each part of the file to be uploaded. Defaults to `10MB`.
::
::field{name="concurrent" type="number"}
The maximum number of concurrent uploads. Defaults to `1`.
::
::field{name="maxRetry" type="number"}
The maximum number of retry attempts for the whole upload. Defaults to `3`.
::
::field{name="prefix" type="string"}
The prefix to use for the blob pathname.
::
::field{name="fetchOptions" type="Omit<FetchOptions, 'method' | 'baseURL' | 'body' | 'parseResponse' | 'responseType'>"}
Override the ofetch options.
The `query` and `headers` will be merged with the options provided by the uploader.
::
::
::
::
Expand Down Expand Up @@ -728,7 +738,7 @@ async function loadMore() {
return
}
const res = await $fetch('/api/blobs', {
params: {
query: {
limit: 3,
cursor: cursor.value
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"citty": "^0.1.6",
"confbox": "^0.1.7",
"defu": "^6.1.4",
"destr": "^2.0.3",
"h3": "^1.11.1",
"mime": "^4.0.3",
"nitro-cloudflare-dev": "^0.1.4",
Expand Down
5 changes: 2 additions & 3 deletions playground/app/pages/blob.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const limit = ref(5)
const prefix = computed(() => prefixes.value?.[prefixes.value.length - 1])
const toast = useToast()
const { data: blobData } = await useFetch('/api/blob', {
params: {
query: {
folded,
prefix,
limit
Expand All @@ -23,7 +23,7 @@ const folders = computed(() => blobData.value?.folders || [])
async function loadMore() {
if (!blobData.value?.hasMore) return
const nextPage = await $fetch('/api/blob', {
params: {
query: {
folded: folded.value,
prefix: prefix.value,
limit: limit.value,
Expand Down Expand Up @@ -66,7 +66,6 @@ async function uploadFiles(files: File[]) {

let uploadedFiles: any[] = []
// upload small files
console.log('upload small files', smallFiles.length)
if (smallFiles.length) {
uploadedFiles = await useUpload('/api/blob', {
method: 'PUT',
Expand Down
7 changes: 6 additions & 1 deletion playground/server/api/blob/index.put.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ export default eventHandler(async (event) => {
return hubBlob().handleUpload(event, {
formKey: 'files', // default
multiple: true, // default
prefix: String(prefix || '')
put: {
prefix: String(prefix || ''),
customMetadata: {
hello: 'world'
}
}
})
})
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 0 additions & 9 deletions src/runtime/blob/app/composables/useUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ interface UploadOptions extends FetchOptions {
* @default true
*/
multiple?: boolean

/**
* The prefix to use for the blobs pathname.
*/
prefix?: string
}

export function useUpload(apiBase: string, options?: UploadOptions & { multiple: false }): (data: FileList | HTMLInputElement | File[] | File) => Promise<BlobObject>
Expand Down Expand Up @@ -51,10 +46,6 @@ export function useUpload(apiBase: string, options: UploadOptions = {}) {
return $fetch(apiBase, {
...fetchOptions,
method: (method || 'POST') as any,
params: {
...fetchOptions.params,
prefix: options.prefix
},
body: formData
}).then(result => (multiple === false || data instanceof File) ? result[0] : result)
}
Expand Down
8 changes: 5 additions & 3 deletions src/runtime/blob/server/api/_hub/blob/index.post.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { eventHandler, getQuery } from 'h3'
import { destr } from 'destr'
import { hubBlob } from '../../../utils/blob'
import { requireNuxtHubAuthorization } from '../../../../../utils/auth'
import { requireNuxtHubFeature } from '../../../../../utils/features'
Expand All @@ -10,8 +11,9 @@ export default eventHandler(async (event) => {
const query = getQuery(event)

return hubBlob().handleUpload(event, {
formKey: 'files',
...query,
multiple: query.multiple !== 'false'
formKey: query.formKey || 'files',
multiple: query.multiple !== 'false',
put: destr(query.put),
ensure: destr(query.ensure)
})
})
Loading