diff --git a/.changeset/eleven-experts-rule.md b/.changeset/eleven-experts-rule.md
new file mode 100644
index 0000000000..4486e9140e
--- /dev/null
+++ b/.changeset/eleven-experts-rule.md
@@ -0,0 +1,9 @@
+---
+'@nhost/hasura-auth-js': minor
+'@nhost-examples/react-apollo': minor
+'@nhost-examples/vue-apollo': minor
+'@nhost/react': minor
+'@nhost/vue': minor
+---
+
+feat: add 'elevateEmailSecurityKey' to the SDKs along with integration into react-apollo and vue-apollo examples
diff --git a/.changeset/spicy-ducks-dress.md b/.changeset/spicy-ducks-dress.md
new file mode 100644
index 0000000000..0fc882e751
--- /dev/null
+++ b/.changeset/spicy-ducks-dress.md
@@ -0,0 +1,7 @@
+---
+'@nhost-examples/nextjs-server-components': minor
+'@nhost/hasura-storage-js': minor
+'@nhost/docs': minor
+---
+
+feat: Add support for authenticated download of files
diff --git a/docs/guides/ai/local-development.mdx b/docs/guides/ai/local-development.mdx
new file mode 100644
index 0000000000..226c5d7d99
--- /dev/null
+++ b/docs/guides/ai/local-development.mdx
@@ -0,0 +1,45 @@
+---
+title: "Local Development"
+icon: code
+---
+
+If you are using the Nhost CLI for local development, as of [v0.12.0](https://github.com/nhost/cli/releases/tag/v1.12.0) you can also start Graphite locally. To do so, follow the next steps:
+
+
+
+
+
+ Follow the steps highlighed in the ["Enabling Service"](enabling-service) guide and don't forget to add the relevant secrets to your `.secrets` file.
+
+
+ Run `nhost up`:
+
+ ![nhost up](/images/guides/ai/local-development/nhost_up.png)
+
+ After starting the service the first thing you will notice is that there is a new `ai` service running.
+
+
+ As you start the AI service metadata changes may be proposed:
+
+ ![git status](/images/guides/ai/local-development/git_status.png)
+
+ We strongly recommmend you to commit them to your git repository so they can be deployed alongside your application.
+
+
+
+
+### Synhcronizing Auto-Embeddings
+
+If you add [auto-embeddings](/guides/ai/auto-embeddings) configuration locally and want to synchronize them with the cloud we recommend inserting them using a migration rather than with the auto-embeddings UI:
+
+![migration](/images/guides/ai/local-development/migration.png)
+
+And then running `nhost up` to download the updated metadata. Afterwards you should see both database migrations and functions' metadata changes in your local project:
+
+![git status](/images/guides/ai/local-development/git_status_functions.png)
+
+Pushing them to your deployment branch will also deploy them to your cloud project.
+
+### Synhcronizing Assistants
+
+Similar to auto-embeddings, if you want to synchronize [assistants](/guides/ai/assistants) we recommend you to insert them using a migration and then running `nhost up` to update any metadata if necessary. After pushing the proposed changes to the deployment branch all the changes should be deployed to the cloud project.
diff --git a/docs/images/guides/ai/local-development/git_status.png b/docs/images/guides/ai/local-development/git_status.png
new file mode 100644
index 0000000000..24524f0001
Binary files /dev/null and b/docs/images/guides/ai/local-development/git_status.png differ
diff --git a/docs/images/guides/ai/local-development/git_status_functions.png b/docs/images/guides/ai/local-development/git_status_functions.png
new file mode 100644
index 0000000000..52b8b7ad0e
Binary files /dev/null and b/docs/images/guides/ai/local-development/git_status_functions.png differ
diff --git a/docs/images/guides/ai/local-development/migration.png b/docs/images/guides/ai/local-development/migration.png
new file mode 100644
index 0000000000..34fdff9f29
Binary files /dev/null and b/docs/images/guides/ai/local-development/migration.png differ
diff --git a/docs/images/guides/ai/local-development/nhost_up.png b/docs/images/guides/ai/local-development/nhost_up.png
new file mode 100644
index 0000000000..1f903b2a26
Binary files /dev/null and b/docs/images/guides/ai/local-development/nhost_up.png differ
diff --git a/docs/mint.json b/docs/mint.json
index 3b85c409ba..50953b9ce0 100644
--- a/docs/mint.json
+++ b/docs/mint.json
@@ -102,7 +102,7 @@
"group": "AI",
"pages": [
"guides/ai/enabling-service",
- "guides/ai/local_development",
+ "guides/ai/local-development",
"guides/ai/auto-embeddings",
"guides/ai/assistants",
"guides/ai/dev-assistant"
@@ -319,12 +319,13 @@
"group": "Storage",
"pages": [
"reference/javascript/storage/hasura-storage-client",
- "reference/javascript/storage/delete",
+ "reference/javascript/storage/upload",
+ "reference/javascript/storage/download",
"reference/javascript/storage/get-presigned-url",
"reference/javascript/storage/get-public-url",
+ "reference/javascript/storage/delete",
"reference/javascript/storage/set-access-token",
- "reference/javascript/storage/set-admin-secret",
- "reference/javascript/storage/upload"
+ "reference/javascript/storage/set-admin-secret"
]
},
{
diff --git a/docs/reference/javascript/storage/download.mdx b/docs/reference/javascript/storage/download.mdx
new file mode 100644
index 0000000000..341299e3af
--- /dev/null
+++ b/docs/reference/javascript/storage/download.mdx
@@ -0,0 +1,27 @@
+---
+title: download()
+sidebarTitle: download()
+---
+
+Use `nhost.storage.download` to download a file. To download a file the user must have permission to select the file in the `storage.files` table.
+
+```ts
+const { file, error } = await nhost.storage.download({ fileId: '' })
+```
+
+## Parameters
+
+---
+
+**params** required [`StorageDownloadFileParams`](/reference/javascript/storage/types/storage-download-file-params)
+
+| Property | Type | Required | Notes |
+| :----------------------------------------------------------------------------------------- | :---------------------------------------- | :------: | :----------------------------------------------------------- |
+| params.fileId | string | ✔️ | |
+| params.blur | number | | Image blur, between 0 and 100 |
+| params.quality | number | | Image quality, between 1 and 100, 100 being the best quality |
+| params.height | number | | Image height, in pixels |
+| params.width | number | | Image width, in pixels |
+| params.headers | Record<string, string> | | Optional headers to be sent with the request |
+
+---
diff --git a/docs/reference/javascript/storage/types/storage-download-file-params.mdx b/docs/reference/javascript/storage/types/storage-download-file-params.mdx
new file mode 100644
index 0000000000..0a2312f44a
--- /dev/null
+++ b/docs/reference/javascript/storage/types/storage-download-file-params.mdx
@@ -0,0 +1,45 @@
+---
+title: StorageDownloadFileParams
+sidebarTitle: StorageDownloadFileParams
+description: No description provided.
+---
+
+# `StorageDownloadFileParams`
+
+## Parameters
+
+---
+
+**fileId** requiredstring
+
+---
+
+**blur** optionalnumber
+
+Image blur, between 0 and 100
+
+---
+
+**quality** optionalnumber
+
+Image quality, between 1 and 100, 100 being the best quality
+
+---
+
+**height** optionalnumber
+
+Image height, in pixels
+
+---
+
+**width** optionalnumber
+
+Image width, in pixels
+
+---
+
+**headers** optionalRecord<string, string>
+
+Optional headers to be sent with the request
+
+---
diff --git a/docs/reference/javascript/storage/types/storage-download-file-response.mdx b/docs/reference/javascript/storage/types/storage-download-file-response.mdx
new file mode 100644
index 0000000000..1962d71853
--- /dev/null
+++ b/docs/reference/javascript/storage/types/storage-download-file-response.mdx
@@ -0,0 +1,13 @@
+---
+title: StorageDownloadFileResponse
+sidebarTitle: StorageDownloadFileResponse
+description: No description provided.
+---
+
+# `StorageDownloadFileResponse`
+
+```ts
+type StorageDownloadFileResponse =
+ | { file: Blob; error: null }
+ | { file: null; error: Error }
+```
diff --git a/examples/quickstarts/nextjs-server-components/src/components/input.tsx b/examples/quickstarts/nextjs-server-components/src/components/input.tsx
index ba4111d257..51dd76f04a 100644
--- a/examples/quickstarts/nextjs-server-components/src/components/input.tsx
+++ b/examples/quickstarts/nextjs-server-components/src/components/input.tsx
@@ -2,7 +2,7 @@
import { DetailedHTMLProps, HTMLProps } from 'react'
// @ts-ignore
-import { experimental_useFormStatus as useFormStatus } from 'react-dom'
+import { useFormStatus } from 'react-dom'
export default function Input({
id,
diff --git a/examples/quickstarts/nextjs-server-components/src/components/submit-button.tsx b/examples/quickstarts/nextjs-server-components/src/components/submit-button.tsx
index 137c2ff432..6603b92f9e 100644
--- a/examples/quickstarts/nextjs-server-components/src/components/submit-button.tsx
+++ b/examples/quickstarts/nextjs-server-components/src/components/submit-button.tsx
@@ -2,12 +2,12 @@
import { ButtonHTMLAttributes, DetailedHTMLProps } from 'react'
// @ts-ignore
-import { experimental_useFormStatus as useFormStatus } from 'react-dom'
+import { useFormStatus } from 'react-dom'
import { twMerge } from 'tailwind-merge'
type ButtonProps = {
- type?: 'button' | 'submit' | 'reset' | undefined;
-} & DetailedHTMLProps, HTMLButtonElement>;
+ type?: 'button' | 'submit' | 'reset' | undefined
+} & DetailedHTMLProps, HTMLButtonElement>
export default function SubmitButton({
disabled,
@@ -16,7 +16,7 @@ export default function SubmitButton({
children,
...rest
}: ButtonProps) {
- const { pending } = useFormStatus();
+ const { pending } = useFormStatus()
return (
- );
-}
\ No newline at end of file
+ )
+}
diff --git a/examples/quickstarts/nextjs-server-components/src/components/todo-item.tsx b/examples/quickstarts/nextjs-server-components/src/components/todo-item.tsx
index 4d0ec172bb..453ea366d8 100644
--- a/examples/quickstarts/nextjs-server-components/src/components/todo-item.tsx
+++ b/examples/quickstarts/nextjs-server-components/src/components/todo-item.tsx
@@ -32,6 +32,20 @@ const TodoItem = ({ todo }: { todo: Todo }) => {
await deleteTodo(todo.id)
}
+ const handleDownloadAttachment = async () => {
+ if (todo.attachment) {
+ const response = await nhost.storage.download({ fileId: todo.attachment.id })
+ if (response.file) {
+ const url = window.URL.createObjectURL(response.file)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = todo.title
+ a.click()
+ window.URL.revokeObjectURL(url)
+ }
+ }
+ }
+
return (