Merge pull request #6213 from supabase/feat/launch-week-4-thursday
No files matched your search
@@ -0,0 +1,285 @@
|
||||
---
|
||||
id: functions
|
||||
title: Edge Functions
|
||||
description: 'Globally distributed Typescript functions.'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
const examples = [
|
||||
{
|
||||
name: 'With supabase-js',
|
||||
description: 'Use the Supabase client inside your Edge Function.',
|
||||
href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions',
|
||||
},
|
||||
{
|
||||
name: 'With Stripe',
|
||||
description: 'Full example for using Supabase and Stripe, with Expo.',
|
||||
href: 'https://github.com/supabase-community/expo-stripe-payments-with-supabase-functions',
|
||||
},
|
||||
]
|
||||
|
||||
:::caution
|
||||
|
||||
Edge Functions are Experimental until 1 August 2022. There will be breaking changes. Do not use them in Production.
|
||||
|
||||
:::
|
||||
|
||||
Edge Functions are server-side Typescript functions, distributed globally at the edge - close to your users. They can be used for listening to webhooks or integrating your Supabase project with third-parties, like Stripe.
|
||||
|
||||
Edge Functions are developed using [Deno](https://deno.com), which offers a few benefits to you as a developer:
|
||||
|
||||
- It is open source.
|
||||
- It is portable. Supabase Edge Functions run locally, and on any other Deno-compatible platform (including self-hosted infrastructure).
|
||||
- It is Typescript first and supports WASM.
|
||||
- Edge Functions are globally distributed for low-latency.
|
||||
|
||||
## Quickstart
|
||||
|
||||
Learn how to develop Edge Functions in less than 7 minutes:
|
||||
|
||||
<iframe
|
||||
className="w-full video-with-border"
|
||||
width="640"
|
||||
height="385"
|
||||
src="https://www.youtube-nocookie.com/embed/9TK6oTzibmk"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Follow the steps to prepare your Supabase project on your local machine.
|
||||
|
||||
- Install the Supabase CLI. [Docs](/docs/reference/cli/installing-and-updating).
|
||||
- Login to the CLI using the command: `supabase login`. [Docs](/docs/reference/cli/supabase-login).
|
||||
- Initialize Supabase inside your project using the command: `supabase init`. [Docs](/docs/guides/local-development#getting-started).
|
||||
- Link to your Remote Project using the command `supabase link --project-ref your-project-ref`. [Docs](/docs/reference/cli/supabase-link).
|
||||
- Optional: Setup your environment: Follow [this setup guide](https://deno.land/manual/getting_started/setup_your_environment) to integrate the Deno language server with your editor.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Let's build an Edge Function locally, then deploy it to the Supabase Platform.
|
||||
|
||||
### Creating a function
|
||||
|
||||
Let's create a new Edge Function called `hello-world` inside your project:
|
||||
|
||||
```bash
|
||||
supabase functions new hello-world
|
||||
```
|
||||
|
||||
This creates a function stub in your `supabase` folder at `./functions/hello-world/index.ts`.
|
||||
|
||||
### Deploy to production
|
||||
|
||||
```bash
|
||||
supabase functions deploy hello-world
|
||||
```
|
||||
|
||||
This command bundles your Edge Function from `./functions/hello-world/index.ts` and deploys it to the Supabase platform.
|
||||
The command outputs a URL to the Supabase Dashboard which you can open to find view more details. Let's open the link to find the execution command.
|
||||
|
||||
### Executing Remote Functions
|
||||
|
||||
You can execute Edge Functions using curl. Copy the curl command from the Dashboard. It should look like this:
|
||||
|
||||
```bash
|
||||
curl --request POST 'https://<project_ref>.functions.supabase.co/hello-world' \
|
||||
--header 'Authorization: Bearer ANON_KEY' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{ "name":"Functions" }'
|
||||
```
|
||||
|
||||
If you receive an error `Invalid JWT`, find the `ANON_KEY` of your project in the Dashboard under `Settings > API`.
|
||||
|
||||
After invoking your Edge Function you should see the response `{ "message":"Hello Functions!" }`.
|
||||
|
||||
## Debugging your Functions
|
||||
|
||||
You can debug your deployed Edge Functions using the "Functions" section of the Dashboard. There are two types debugging tools available:
|
||||
|
||||
- Invocations: shows the Request and Response for each execution.
|
||||
- Logs: shows any platform events, including deployments and errors.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
## Developing locally
|
||||
|
||||
You can run your Edge Function locally using [`supabase functions serve`](/docs/reference/cli/supabase-functions-serve):
|
||||
|
||||
```bash
|
||||
supabase start # start the supabase stack
|
||||
supabase functions serve hello-world # start the Function watcher
|
||||
```
|
||||
|
||||
The `functions serve` command has hot-reloading capabilities. It will watch for any changes to your files and restart the Deno server.
|
||||
|
||||
### Invoking Functions locally
|
||||
|
||||
While serving your local Function, you can execute it using curl:
|
||||
|
||||
```bash
|
||||
curl --request POST 'http://localhost:54321/functions/v1/hello-world' \
|
||||
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{ "name":"Functions" }'
|
||||
```
|
||||
|
||||
You should see the response `{ "message":"Hello Functions!" }`.
|
||||
|
||||
<details>
|
||||
<summary>Implementation details</summary>
|
||||
|
||||
- All Edge Functions are `POST` requests.
|
||||
- The `Authorization` header is required. You can use either the `ANON` key, the `SERVICE_ROLE` key, or a logged-in user's JWT.
|
||||
- The Function is proxied through the local API (`http://localhost:54321`)
|
||||
|
||||
</details>
|
||||
<br />
|
||||
|
||||
If you execute Function with a different payload the response will change. <br />
|
||||
Modify the `--data '{"name":"Functions"}'` line to `--data '{"name":"World"}'` and try invoking the command again!
|
||||
|
||||
## Secrets and Environment Variables
|
||||
|
||||
It's common that you will need to use sensitive information or environment-specific variables inside your Edge Functions. You can access these using Deno's built-in handler
|
||||
|
||||
```js
|
||||
Deno.env.get(MY_SECRET_NAME)
|
||||
```
|
||||
|
||||
### Default secrets
|
||||
|
||||
By default, Edge Functions have access to these secrets:
|
||||
|
||||
- `SUPABASE_URL`: The API gateway for your Supabase project.
|
||||
- `SUPABASE_ANON_KEY`: The `anon` key for your Supabase API. This is safe to use in a browser when you have [Row Level Security](/docs/guides/auth/row-level-security) enabled.
|
||||
- `SUPABASE_SERVICE_ROLE_KEY`: The `service_role` key for your Supabase API. This is safe to use in Edge Functions, but it should NEVER be used in a browser. This key will bypass [Row Level Security](/docs/guides/auth/row-level-security).
|
||||
- `SUPABASE_DB_URL`: The URL for your [PostgreSQL database](/docs/guides/database). You can use this to connect directly to your database.
|
||||
|
||||
### Local secrets
|
||||
|
||||
Let's create a local file for storing our secrets, and inside it we can store a secret `MY_NAME`:
|
||||
|
||||
```jsx
|
||||
echo "MY_NAME=Yoda" >> ./supabase/.env.local
|
||||
```
|
||||
|
||||
This creates a new file `./supabase/.env.local` for storing your local development secrets.
|
||||
|
||||
:::caution
|
||||
|
||||
Never check your .env files into Git!
|
||||
|
||||
:::
|
||||
|
||||
Now let's use this function inside our Function. Anywhere in your function, add this line:
|
||||
|
||||
```jsx
|
||||
console.log(Deno.env.get('MY_NAME'))
|
||||
```
|
||||
|
||||
Now we can invoke our function locally, by serving it with our new `.env.local` file:
|
||||
|
||||
```bash
|
||||
supabase functions serve hello-world --env-file ./supabase/.env.local
|
||||
```
|
||||
|
||||
When the function starts you should see the name “Yoda” output to the terminal.
|
||||
|
||||
### Production secrets
|
||||
|
||||
Let's create a `.env` for production. In this case we'll just use the same as our local secrets:
|
||||
|
||||
```bash
|
||||
cp ./supabase/.env.local ./supabase/.env
|
||||
```
|
||||
|
||||
This creates a new file `./supabase/.env` for storing your production secrets.
|
||||
|
||||
:::caution
|
||||
|
||||
Never check your `.env` files into Git!
|
||||
|
||||
:::
|
||||
|
||||
Let's push all the secrets from the `.env` file to our remote project using [`supabase secrets set`](/docs/reference/cli/supabase-secrets-set):
|
||||
|
||||
```bash
|
||||
supabase secrets set --env-file ./supabase/.env
|
||||
|
||||
# You can also set secrets individually using:
|
||||
supabase secrets set MY_NAME=Chewbacca
|
||||
```
|
||||
|
||||
You don't need to re-deploy after setting your secrets.
|
||||
|
||||
To see all the secrets which you have set remotely, use [`supabase secrets list`](/docs/reference/cli/supabase-secrets-list):
|
||||
|
||||
```bash
|
||||
supabase secrets list
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
You can find a list of useful [Edge Function Examples](https://github.com/supabase/supabase/tree/master/examples/flutter-user-management) in our GitHub repository.
|
||||
|
||||
<div class="container" style={{ padding: 0 }}>
|
||||
<div class="row is-multiline">
|
||||
{examples.map((x) => (
|
||||
<div class="col col--6">
|
||||
<a class="card" to={x.href} style={{ height: '100%' }}>
|
||||
<div class="card__body">
|
||||
<h4>{x.name}</h4>
|
||||
<p>{x.description}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Suggestions and Limitations
|
||||
|
||||
### Database vs Edge Functions
|
||||
|
||||
For data-intensive operations we recommend using [Database Functions](/docs/guides/database/functions), which are executed within your database,
|
||||
and can be called remotely using the [REST and GraphQL API](/docs/guides/database/functions).
|
||||
|
||||
### Organizing your Edge Functions
|
||||
|
||||
We recommend developing “fat functions”. This means that you should develop few large functions, rather than many small functions. One common pattern when developing Functions is that you need to share code between two or more Functions. To do this, you can store any shared code in a folder prefixed with an underscore (`_`). We recommend this folder structure:
|
||||
|
||||
```bash
|
||||
└── supabase
|
||||
├── functions
|
||||
│ ├── _shared
|
||||
│ | ├── supabaseAdmin.ts # Supabase client with SERVICE_ROLE key
|
||||
│ │ └── supabaseClient.ts # Supabase client with ANON key
|
||||
│ ├── function-one # use hyphens to name functions
|
||||
│ │ └── index.ts
|
||||
│ └── function-two
|
||||
│ └── index.ts
|
||||
├── migrations
|
||||
└── config.toml
|
||||
```
|
||||
|
||||
### Naming Edge Functions
|
||||
|
||||
We recommend using hyphens to name functions because hyphens are the most URL-friendly of all the naming conventions (snake_case, camelCase, PascalCase).
|
||||
|
||||
### Limitations
|
||||
|
||||
Edge Functions are Experimental. They aren't going away, but there will be breaking changes.
|
||||
|
||||
- Deno Deploy limitations
|
||||
- Deno does not support outgoing connections to ports `25`, `465`, and `587`.
|
||||
- Cannot write to File System
|
||||
- Edge Functions
|
||||
- Local development - only one function at a time
|
||||
- Supabase Functions only supports `POST` responses.
|
||||
- Supabase Functions does not support HTML responses.
|
||||
@@ -111,6 +111,12 @@ module.exports = {
|
||||
collapsed: false,
|
||||
items: ['guides/api'],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Functions',
|
||||
collapsed: false,
|
||||
items: ['guides/functions'],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Auth',
|
||||
|
||||
|
After Width: | Height: | Size: 1.0 MiB |
@@ -112,8 +112,10 @@ Wednesday 30 March 2022: 9am PT
|
||||
|
||||
### What?
|
||||
|
||||
This one's gonna be hotter than the Human Torch 🔥, more scalable than Mister Fantastic, more rock solid than The Thing,
|
||||
and the latency will be harder to find than Invisible Woman.
|
||||
~This one's gonna be hotter than the Human Torch 🔥, more scalable than Mister Fantastic, more rock solid than The Thing,~
|
||||
~and the latency will be harder to find than Invisible Woman.~
|
||||
|
||||
[Edge Functions are now available in Supabase](/blog/2022/03/31/supabase-edge-functions)
|
||||
|
||||
### When?
|
||||
|
||||
@@ -121,8 +123,9 @@ Wednesday 31 March 2022: 9am PT
|
||||
|
||||
### Where?
|
||||
|
||||
Blog post coming soon. Follow our [Twitter](https://twitter.com/supabase) to get notified immediately.
|
||||
Set a reminder for our [YouTube live stream](https://www.youtube.com/watch?v=Xci5SOv76k0).
|
||||
- Read the [blog post](/blog/2022/03/31/supabase-edge-functions).
|
||||
- Tune in live on [Twitter Spaces](https://twitter.com/i/spaces/1dRKZlAdaawJB).
|
||||
- Watch live on [YouTube](https://youtu.be/fApxu_U_hdw).
|
||||
|
||||
## Friday: Something for everybody
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
---
|
||||
title: 'Edge Functions are now available in Supabase'
|
||||
description: 'Today we're launching Edge Functions. Edge Functions let you execute Typescript code close to your users, no matter where they're located.'
|
||||
author: inian,paul_copplestone
|
||||
image: launch-week-4/thursday-functions/functions-thumb.png
|
||||
thumb: launch-week-4/thursday-functions/functions-thumb.png
|
||||
tags:
|
||||
- launch-week
|
||||
- functions
|
||||
date: '2022-03-31'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
Today we're launching one of our [most](https://twitter.com/lau_cazanove/status/1506530181946691592) [requested](https://twitter.com/edgarasben/status/1506653203458363393) and [highly-](https://twitter.com/runjep/status/1507462077216088069)[anticipated](https://twitter.com/marcopolotwo/status/1506431782362632195) [features](https://github.com/supabase/supabase/discussions/4269) — Edge Functions. Edge Functions let you execute Typescript code close to your users, no matter where they're located.
|
||||
|
||||
Edge Functions are executed on the secure-by-default [Deno runtime](https://deno.land/), and deployed around the world in seconds using Deno's [hosted Deno Deploy offering](https://deno.com/deploy). However, as a user of Supabase Edge Functions, you don't need to know or worry about any of that; we handle all the gnarly details, delivering a seamless experience that allows you to focus on your business logic.
|
||||
|
||||
## Choosing a platform
|
||||
|
||||
Serverless compute options can be broken down into two broad categories:
|
||||
|
||||
- Containers as a Service (e.g. Google Cloud Run, Fly.io)
|
||||
- Functions as a Service (e.g. Cloudflare Workers, Fastly Compute @ Edge, Suborbital)
|
||||
|
||||
Additionally, there are self-hosted offerings within each category as well (e.g. Firecracker sort-of for the former, OpenFaaS for the latter).
|
||||
|
||||
Our first critical decision was to choose Functions as a Service (”FaaS”) over Containers and other alternatives. While Containers can allow for more flexible workloads, authoring and maintaining Functions allows for a substantially simpler developer experience-something Supabase has always focused on. As a Supabase user, all you need to think about is the business logic you wanted to write a Function for in the first place; you don't need to think about optimizing docker image builds, updating your system dependencies, etc.
|
||||
|
||||
Additionally, while a lot of work has been invested in improving container start-up times, Functions generally still enjoy significantly faster start-ups.
|
||||
|
||||
Our next major decision was to choose global deployments instead of a deploying to a specific geographical region.
|
||||

|
||||
Launching Functions in a single region (e.g. close to your database) can present a lot of advantages if there are a lot of database operations being executed within your business logic.
|
||||
|
||||
However, Supabase already offers a flexible solution for that - [Database Functions](https://supabase.com/docs/guides/database/functions)! As such, for Supabase [Edge] Functions, we decided to deploy far-and-wide so that they are as close to your end-users as possible. You can even invoke a Database Function from an Edge Function, allowing you to enjoy the best of both worlds.
|
||||
|
||||
## The Deno runtime
|
||||
|
||||
Deno is a modern Typescript and JavaScript runtime created with first-class support for web technologies. Here some of the reasons why we're excited about Deno:
|
||||
|
||||
### Open-source
|
||||
|
||||
_Deno Deploy_ is Deno's hosted service and, although it is proprietary, the Deno CLI is open source. This means that the open source offering from Deno maintains feature-parity with the proprietary service. If you use Deno to develop functions locally, its behavior will be the same as when deployed on the edge, unlocking an incredible developer experience. We couldn't achieve a similarly seamless experience with e.g. AWS Lambda, no matter how much we tried.
|
||||
|
||||
Deno even goes a step further than maintaining feature-parity, releasing all new APIs to open-source **first** before rolling them out to their hosted offering. This ensures that you can always develop and test bleeding-edge functionality locally, with all the tools you're used to and familiar with, before deploying to a hosted cloud environment.
|
||||
|
||||
### Batteries included
|
||||
|
||||
Deno comes with all the tools needed for a modern, productive developer experience, so you can spend less time searching through third party modules, and more time being productive. Out of the box, it includes support for Typescript, ES modules, and provides [test runners](https://deno.land/manual/tools/testing), [formatters](https://deno.land/manual/tools/formatter), [linters](https://deno.land/manual/tools/linter), [bundlers](https://deno.land/manual/tools/bundler) and a package manager.
|
||||
|
||||
### Secure by default
|
||||
|
||||
Like modern web browsers, the Deno runtime executes your code in a secure sandbox by default. Functions cannot access environment variables, network or filesystem unless they are explicitly allowed. Supply chain attacks have been a daunting problem in the Node ecosystem and Deno mitigates many of these attacks by having a security-first model.
|
||||
|
||||
## The Supa-Deno platform
|
||||
|
||||
When a request comes in to an Edge Function you deploy on Supabase, it first lands at a “Relay”. The [Relay](https://github.com/supabase/functions-relay) acts as an API gateway, authenticating the JWT, and providing some additional functionality like logging and rate-limiting. Upon receiving the incoming request, the Relay retrieves the metadata for your Function, and uses it to construct a unique identifier (”Deployment ID”) for the Function. This Deployment ID then gets passed over to the Deno Deploy platform, which takes care of invoking your Function securely, and passing your output back to the Relay, which in turn passes it back to your end-user.
|
||||
|
||||
If a user's request comes in for your Function before it has been cached by the Deno Deploy platform (a ”cold start”), it reaches out to Supabase to retrieve your Functions code, and any associated permissions and secrets. Even though this might sound like a lot of work, we've worked to ensure that these cold starts are still pretty spiffy.
|
||||
|
||||

|
||||
|
||||
The primary goal of this architecture is to reduce the amount of work done in the hot path (marked in purple above). Each component in the hot path is globally distributed so that Edge Functions start up and respond to your users' requests fast.
|
||||
|
||||
### Region: Earth 🌎
|
||||
|
||||
Supabase Edge Functions are deployed to one region - Earth (and we're exploring Mars as an additional region 👽). Edge Functions are deployed to over 30 data centers in a matter of seconds, ensuring fast response times no matter where your users are located.
|
||||
|
||||
[Database Functions](https://supabase.com/docs/guides/database/functions) (also served via our [REST and GraphQL APIs](https://supabase.com/docs/guides/api)) are colocated with your Postgres database, making them ideal for DB-intensive operations. These APIs can also be called seamlessly from your Supabase Edge Functions, making for a powerful combination.
|
||||
|
||||
### Scale to zero
|
||||
|
||||
Functions scale down to zero and you only pay for what you use. This makes them great for an extremely wide variety of workloads-whether you're starting out with extremely sporadic usage, or bursty request patterns, or using them consistently and heavily.
|
||||
|
||||
## A fun and efficient Developer Experience
|
||||
|
||||
Delivering a phenomenal Developer Experience is something we care about a lot at Supabase. In the serverless functions space, we've found that local development and testing, coupled with fast deploys are critical.
|
||||
|
||||
We've [extended the Supabase CLI](https://supabase.com/docs/guides/local-development) to optimize the Edge Functions DX along these two dimensions. You get live-reloading, and a local Deno environment that matches the production environment, making for a great local iteration environment.
|
||||
|
||||
<video width="99%" autoPlay="autoplay" muted playsInline controls={true}>
|
||||
<source
|
||||
src="/videos/blog/launch-week-4/functions-hot-reloading.mp4"
|
||||
type="video/mp4"
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
</video>
|
||||
|
||||
Once you've tested your Function locally (or gone straight to production cos you live _on the edge_ ... get it?) it takes just seconds to deploy a new function and see the logs streaming in your Dashboard—ensuring a tight iteration loop with your production environment.
|
||||
|
||||
<video width="99%" autoPlay="autoplay" muted playsInline controls={true}>
|
||||
<source src="/videos/blog/launch-week-4/logs-polish.mov" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
Our observability pipeline has been built [using Logflare](https://supabase.com/blog/2021/12/02/supabase-acquires-logflare). Structured logs from your Edge Functions get sent to Logflare, which also processes them into metrics. Your Supabase Dashboard is able to reach out to Logflare endpoints, and with some of the [(not so) secret sauce](https://supabase.com/blog/2021/11/30/supabase-studio) from our front-end team, you get responsive charts and logs for your Edge Functions.
|
||||
|
||||

|
||||
new —> deploy —> invoke —> logs
|
||||
|
||||
To round off the end-to-end experience, `supabase-js` now works in Deno, making it easy to interact with the rest of your Supabase project from within your Edge Functions. Your project's URLs, API keys, and database connection strings are made available as environment variables within your Function to make this even easier. And if you want to interact with third-party APIs which require a secret key, such as Stripe, you can easily and securely make these available to your Function as environment variables via the Supabase CLI. To get an idea for what is possible, check out our examples page [here](https://github.com/supabase/supabase/tree/master/examples/edge-functions).
|
||||
|
||||
## Quickstart
|
||||
|
||||

|
||||
|
||||
Can't wait to get started? Develop, test, and deploy your first Supabase Edge Function in less than 7 minutes with our [Quickstart Video Tutorial](https://youtu.be/9TK6oTzibmk)!
|
||||
Check the [Edge Functions Docs](https://supabase.com/docs/guides/functions) for the steps.
|
||||
|
||||
## Example apps: Mobile payments with React Native and Flutter
|
||||
|
||||
When researching use-cases with all of you, [processing payments](https://github.com/supabase/supabase/discussions/4269#discussioncomment-1748067) was being pointed out the most, so naturally, to support the launch, we've created example apps in collaboration with Stripe for [React Native](https://github.com/supabase-community/expo-stripe-payments-with-supabase-functions) and [Flutter](https://github.com/supabase-community/flutter-stripe-payments-with-supabase-functions), to allow you to get started and earn money even faster.
|
||||
|
||||

|
||||
|
||||
## Can I host static sites
|
||||
|
||||
Even though Deno supports [rendering `jsx` and `tsx` files](https://deno.land/manual/jsx_dom/jsx), we don't plan on becoming a website hosting provider with Edge Functions. For now, Edge Functions can only be invoked via the `POST` method. Offering secure and fast web hosting with a good developer experience is a hard problem to solve and there are [other](https://www.netlify.com/) [fantastic](https://vercel.com/) [providers](https://pages.cloudflare.com/) which do exactly that.
|
||||
|
||||
From our experience launching Supabase Storage last year, becoming a hosting provider is not something you want to take lightly. Malicious folks will upload their phishing sites whenever they get a chance and this can quickly spiral out of control, causing you to land on the denylist in the [Safe Browsing dataset](https://safebrowsing.google.com/). Even though we removed the project within minutes after being notified, various ISPs had already cached the information from the Safe browsing dataset and were “helping” their users by restricting access to the Supabase domain. This isn't something we're planning to repeat. But as always, we'd love to hear your use cases and feature requests, we're just one [GitHub discussion](https://github.com/supabase/supabase/discussions) away!
|
||||
|
||||
## Roadmap
|
||||
|
||||
These are some of the features that are on our roadmap for functions.
|
||||
|
||||
- Cron functions to trigger your function on a schedule
|
||||
- Studio support and self hosting multiple functions
|
||||
- Optimizing cold start times
|
||||
- Wasm is a great fit for running functions and companies like [Suborbital](https://suborbital.dev/) are leading the charge by making it easy to write functions that run in a Wasm-based runtime. Deno supports Wasm natively and we plan on allowing users to write their functions in languages that compile down to Wasm like Rust.
|
||||
- We can expose Deno's permission model so that you can lock down your functions further by restricting outbound connections to specific domains.
|
||||
- To invoke functions, you need a valid JWT signed by your project's JWT secret. We plan on making this verification optional. This enables more use cases like hooking up Supabase functions to Stripe webhooks where the function manages it's own authentication.
|
||||
|
||||
Give us a shout with your feedback and excited to see what you build in the hackathon!
|
||||
@@ -1,22 +1,35 @@
|
||||
.code-block {
|
||||
@apply rounded-lg overflow-hidden;
|
||||
@apply border border-scale-500;
|
||||
@apply overflow-hidden;
|
||||
}
|
||||
|
||||
.code-block::before {
|
||||
content: '';
|
||||
background: #1e1e1e;
|
||||
height: 10px;
|
||||
width: 48px;
|
||||
display: block;
|
||||
.code-line {
|
||||
@apply block;
|
||||
transition: filter 500ms ease, opacity 500ms ease;
|
||||
transition-delay: 200ms;
|
||||
}
|
||||
|
||||
.code-block::after {
|
||||
content: '';
|
||||
background: #1e1e1e;
|
||||
height: 12px;
|
||||
width: 48px;
|
||||
display: block;
|
||||
.code-block .code-line:first-of-type {
|
||||
@apply flex items-end h-8;
|
||||
}
|
||||
|
||||
.code-block .code-line:first-of-type :global(.linenumber) {
|
||||
@apply items-end self-stretch;
|
||||
}
|
||||
|
||||
.code-block .code-line:first-of-type > :global(*:not(.linenumber)) {
|
||||
@apply pb-1;
|
||||
}
|
||||
|
||||
.code-block .code-line:last-of-type {
|
||||
@apply flex items-start h-8;
|
||||
}
|
||||
|
||||
.code-block .code-line:last-of-type :global(.linenumber) {
|
||||
@apply items-start self-stretch;
|
||||
}
|
||||
|
||||
.code-block .code-line:last-of-type > :global(*:not(.linenumber)) {
|
||||
@apply pt-1;
|
||||
}
|
||||
|
||||
.code-block code .linenumber {
|
||||
@@ -27,3 +40,7 @@
|
||||
.code-block code::before {
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
.code-line--flash {
|
||||
@apply animate-flash-code-slow;
|
||||
}
|
||||
@@ -1,23 +1,39 @@
|
||||
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import reactSyntaxHighlighter, { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
|
||||
import monokaiCustomTheme from 'data/CodeEditorTheme'
|
||||
import CodeBlockStyles from './CodeBlock.module.css'
|
||||
import { Button, IconCopy } from '@supabase/ui'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
import rangeParser from 'parse-numeric-range'
|
||||
|
||||
import classNames from 'classnames'
|
||||
|
||||
import js from 'react-syntax-highlighter/dist/cjs/languages/hljs/javascript'
|
||||
import ts from 'react-syntax-highlighter/dist/cjs/languages/hljs/typescript'
|
||||
import py from 'react-syntax-highlighter/dist/cjs/languages/hljs/python'
|
||||
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql'
|
||||
|
||||
interface Props {
|
||||
lang: 'js' | 'sql' | 'py'
|
||||
export interface CodeBlockProps {
|
||||
lang: 'js' | 'ts' | 'sql' | 'py'
|
||||
startingLineNumber?: number
|
||||
hideCopy?: boolean
|
||||
className?: string
|
||||
children?: string
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
/**
|
||||
* Inline styling
|
||||
* Supports CSS Properties in camelcase
|
||||
*/
|
||||
style?: React.CSSProperties | undefined
|
||||
/**
|
||||
* Lines to be highlighted.
|
||||
* Supports individual lines: '14', multiple lines: '14,15', or a range of lines '14..19'
|
||||
*/
|
||||
highlightLines?: string
|
||||
hideBorder?: boolean
|
||||
}
|
||||
|
||||
function CodeBlock(props: Props) {
|
||||
function CodeBlock(props: CodeBlockProps) {
|
||||
let lang = props.lang
|
||||
? props.lang
|
||||
: props.className
|
||||
@@ -25,44 +41,78 @@ function CodeBlock(props: Props) {
|
||||
: 'js'
|
||||
// force jsx to be js highlighted
|
||||
if (lang === 'jsx') lang = 'js'
|
||||
if (lang === 'tsx') lang = 'ts'
|
||||
|
||||
SyntaxHighlighter.registerLanguage('js', js)
|
||||
SyntaxHighlighter.registerLanguage('ts', ts)
|
||||
SyntaxHighlighter.registerLanguage('py', py)
|
||||
SyntaxHighlighter.registerLanguage('sql', sql)
|
||||
|
||||
// const large = props.size === 'large' ? true : false
|
||||
const large = false
|
||||
|
||||
const shouldHighlightLines = props.highlightLines !== undefined
|
||||
const highlightLines = useMemo(
|
||||
() => new Set(rangeParser(props.highlightLines ?? '')),
|
||||
[props.highlightLines]
|
||||
)
|
||||
|
||||
const lineProps = useCallback(
|
||||
(lineNumber: number) => {
|
||||
const shouldHighlightLine = !shouldHighlightLines || highlightLines.has(lineNumber)
|
||||
|
||||
const style = shouldHighlightLine ? {} : { filter: 'grayscale(75%)', opacity: 0.5 }
|
||||
|
||||
return {
|
||||
class: classNames(
|
||||
CodeBlockStyles['code-line'],
|
||||
shouldHighlightLines && shouldHighlightLine && CodeBlockStyles['code-line--flash']
|
||||
),
|
||||
style,
|
||||
}
|
||||
},
|
||||
[highlightLines]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SyntaxHighlighter
|
||||
language={lang}
|
||||
style={monokaiCustomTheme}
|
||||
className={CodeBlockStyles['code-block']}
|
||||
className={[
|
||||
CodeBlockStyles['code-block'],
|
||||
'!bg-scale-1200 dark:!bg-scale-100',
|
||||
props.hideBorder ? '' : 'rounded-lg border border-scale-1100 dark:border-scale-400',
|
||||
].join(' ')}
|
||||
customStyle={{
|
||||
padding: 0,
|
||||
fontSize: large ? 18 : 12,
|
||||
lineHeight: large ? 1.2 : 1.2,
|
||||
borderTop: '1px solid #393939',
|
||||
background: '#181818',
|
||||
|
||||
...props.style,
|
||||
}}
|
||||
showLineNumbers={lang === 'cli' ? false : true}
|
||||
lineNumberContainerStyle={{
|
||||
paddingTop: '128px',
|
||||
}}
|
||||
lineNumberStyle={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'flex-end',
|
||||
minWidth: '48px',
|
||||
background: '#1e1e1e',
|
||||
// background: 'var(--colors-fixed-scale12)',
|
||||
paddingLeft: '21px',
|
||||
marginRight: '12px',
|
||||
color: '#828282',
|
||||
color: 'var(--colors-fixed-scale7)',
|
||||
fontSize: large ? 14 : 12,
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
}}
|
||||
wrapLines={true}
|
||||
lineProps={lineProps}
|
||||
>
|
||||
{props.children}
|
||||
</SyntaxHighlighter>
|
||||
|
||||
{!props.hideCopy && props.children ? (
|
||||
<div className="absolute right-2 top-2 dark">
|
||||
<CopyToClipboard text={props.children}>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import rangeParser from 'parse-numeric-range'
|
||||
import { useMemo } from 'react'
|
||||
import CodeBlock, { CodeBlockProps } from './CodeBlock/CodeBlock'
|
||||
|
||||
interface ScrollableCodeBlockProps extends CodeBlockProps {
|
||||
highlightLines: string
|
||||
children: string
|
||||
/**
|
||||
* Shows an application toolbar at the top
|
||||
*/
|
||||
showToolbar?: boolean
|
||||
}
|
||||
|
||||
const MAX_HEIGHT = 520
|
||||
const CODE_LINE_HEIGHT = 22
|
||||
const TOP_OFFSET = 4
|
||||
|
||||
const ScrollableCodeBlock = ({
|
||||
highlightLines: highlightLinesRange,
|
||||
children,
|
||||
showToolbar,
|
||||
...props
|
||||
}: ScrollableCodeBlockProps) => {
|
||||
const highlightLines = useMemo(
|
||||
() => rangeParser(highlightLinesRange ?? ''),
|
||||
[highlightLinesRange]
|
||||
)
|
||||
const firstLine = Math.min(...highlightLines)
|
||||
const lastLine = Math.max(...highlightLines)
|
||||
|
||||
const firstLinePosition = firstLine * CODE_LINE_HEIGHT
|
||||
const lastLinePosition = lastLine * CODE_LINE_HEIGHT
|
||||
|
||||
const middlePosition = (firstLinePosition + lastLinePosition) / 2
|
||||
const position = Math.max(middlePosition - MAX_HEIGHT / 2 + TOP_OFFSET, 0)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{showToolbar && (
|
||||
<div className="bg-scale-1200 dark:bg-scale-200 border border-scale-1200 dark:border-scale-400 border-b-0 h-7 w-full rounded-t-lg flex gap-1.5 items-center px-4">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-2.5 h-2.5 bg-scale-1100 dark:bg-scale-400 rounded-full"></div>
|
||||
<div className="w-2.5 h-2.5 bg-scale-1100 dark:bg-scale-400 rounded-full"></div>
|
||||
<div className="w-2.5 h-2.5 bg-scale-1100 dark:bg-scale-400 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="overflow-hidden border rounded-b-lg border-scale-1100 dark:border-scale-400"
|
||||
style={{ maxHeight: MAX_HEIGHT, transform: 'translateZ(0)' }}
|
||||
>
|
||||
<div
|
||||
className="transition-transform duration-500"
|
||||
style={{ transform: `translate3d(0, -${position}px, 0)` }}
|
||||
>
|
||||
<CodeBlock highlightLines={highlightLinesRange} hideBorder={true} {...props}>
|
||||
{children + '\n'.repeat(100)}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScrollableCodeBlock
|
||||
@@ -0,0 +1,103 @@
|
||||
import { useState } from 'react'
|
||||
// Import Swiper React components
|
||||
import { Button, IconArrowUpRight, Tabs } from '@supabase/ui'
|
||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
import CodeBlock from '../CodeBlock/CodeBlock'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface Example {
|
||||
lang: 'js' | 'py' | 'sql'
|
||||
title: string
|
||||
code: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
size?: 'small' | 'large'
|
||||
content: Example[]
|
||||
title: string | React.ReactNode
|
||||
footer?: React.ReactNode
|
||||
text?: React.ReactNode
|
||||
autoHeight?: boolean
|
||||
documentation_link?: string
|
||||
}
|
||||
|
||||
function FunctionsUsecases(props: Props) {
|
||||
// store API swiper instance
|
||||
const [apiSwiper, setApiSwiper] = useState(undefined)
|
||||
const [apiSwiperActiveIndex, setApiSwiperActiveIndex] = useState(0)
|
||||
|
||||
function handleApiSwiperNavChange(e: number) {
|
||||
setApiSwiperActiveIndex(e)
|
||||
// @ts-ignore
|
||||
apiSwiper.slideTo(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12 lg:gap-16">
|
||||
<div className="col-span-12 lg:col-span-5 xl:col-span-5 pb-8">
|
||||
<h2 className="h3">{props.title}</h2>
|
||||
<p className="p">{props.text}</p>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
{props.content.map((feat, i) => {
|
||||
const active = i == apiSwiperActiveIndex
|
||||
return (
|
||||
<button
|
||||
key={`featureHighlighted-${i}`}
|
||||
className={
|
||||
'group transition-all border px-6 py-4 text-left rounded-md bg-scale-200 hover:bg-scale-100 hover:dark:bg-scale-300 hover:boder' +
|
||||
(active ? ' bg-white dark:bg-scale-400 border-scale-500' : ' border-scale-300')
|
||||
}
|
||||
// onClick={() => setCurrentSelection(feat.highlightLines)}
|
||||
onClick={() => handleApiSwiperNavChange(i)}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
'transition-colors ' +
|
||||
(active ? ' text-scale-1200' : ' text-scale-900 group-hover:text-scale-1200')
|
||||
}
|
||||
>
|
||||
{feat.title}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
'transition-colors text-sm ' +
|
||||
(active ? ' text-scale-1100' : ' text-scale-800 group-hover:text-scale-1100 ')
|
||||
}
|
||||
>
|
||||
{feat.description}
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-12 lg:col-span-7 xl:col-span-6 xl:col-start-7 sbui-tabs--alt">
|
||||
<div className="overflow-hidden">
|
||||
<Swiper
|
||||
// @ts-ignore
|
||||
onSwiper={setApiSwiper}
|
||||
style={{ zIndex: 0, marginRight: '1px' }}
|
||||
initialSlide={apiSwiperActiveIndex}
|
||||
spaceBetween={0}
|
||||
slidesPerView={1}
|
||||
speed={300}
|
||||
allowTouchMove={false}
|
||||
autoHeight={props.autoHeight ? props.autoHeight : false}
|
||||
>
|
||||
{props.content &&
|
||||
props.content.map((content: Example, i) => (
|
||||
<SwiperSlide key={i}>
|
||||
<CodeBlock key={i} lang={content.lang} size={props.size ? props.size : 'small'}>
|
||||
{content.code}
|
||||
</CodeBlock>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FunctionsUsecases
|
||||
@@ -16,5 +16,11 @@
|
||||
"job_title": "Head of Growth",
|
||||
"username": "roryw10",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/10214025?v=4"
|
||||
},
|
||||
"inian": {
|
||||
"name": "Inian Parameshwaran",
|
||||
"job_title": "Engineer",
|
||||
"username": "everconfusedguy",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/2155545?v=4"
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
},
|
||||
{
|
||||
"text": "Functions",
|
||||
"url": null
|
||||
"url": "/edge-functions"
|
||||
},
|
||||
{
|
||||
"text": "Pricing",
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
"label": "",
|
||||
"url": "/storage"
|
||||
},
|
||||
"functions": {
|
||||
"name": "Functions",
|
||||
"icon": "M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z",
|
||||
"description": "Write custom code and cron jobs without deploying or scaling servers.",
|
||||
"edge-functions": {
|
||||
"name": "Edge Functions",
|
||||
"icon": "M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4",
|
||||
"description": "Write custom code without deploying or scaling servers.",
|
||||
"description_short": "",
|
||||
"label": "Coming soon",
|
||||
"url": "https://supabasesubscribe.vercel.app/subscription/functions"
|
||||
"label": "",
|
||||
"url": "/edge-functions"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* description
|
||||
*/
|
||||
export default [
|
||||
{
|
||||
lang: 'js',
|
||||
title: 'Sending an email',
|
||||
description: 'Send an email via SMTP directly in the function',
|
||||
size: 'large',
|
||||
code: `import { serve } from "https://deno.land/std@0.114.0/http/server.ts";
|
||||
import { SmtpClient } from "https://deno.land/x/smtp@v0.7.0/mod.ts";
|
||||
const client = new SmtpClient();
|
||||
const { hostname, port, username, password } = Deno.env.toObject();
|
||||
|
||||
await client.connect({
|
||||
hostname,
|
||||
port: Number(port),
|
||||
username,
|
||||
password,
|
||||
});
|
||||
|
||||
serve(async (_req) => {
|
||||
await client.send({
|
||||
from: "jonny@supabase.io",
|
||||
to: "div@supabase.io",
|
||||
subject: "Notifications Ui has launched",
|
||||
content: "And this is a notification about that",
|
||||
});
|
||||
|
||||
return new Response("Email sent", { status: 200 });
|
||||
});`,
|
||||
},
|
||||
{
|
||||
lang: 'js',
|
||||
title: 'Connect to database directly',
|
||||
description: `Connect to your database via pg_bouncer`,
|
||||
size: 'large',
|
||||
code: `import { serve } from "https://deno.land/std@0.114.0/http/server.ts";
|
||||
import * as postgres from "https://deno.land/x/postgres@v0.14.2/mod.ts";
|
||||
|
||||
const databaseUrl = Deno.env.get("DATABASE_URL") ?? "";
|
||||
const pool = new postgres.Pool(databaseUrl, 3, true);
|
||||
const connection = await pool.connect();
|
||||
|
||||
serve(async (req: Request) => {
|
||||
// sql inject your database
|
||||
const { query } = await req.json();
|
||||
const response = await connection.queryObject(query);
|
||||
console.log(response);
|
||||
return new Response("Query executed", { status: 200 });
|
||||
});`,
|
||||
},
|
||||
{
|
||||
lang: 'js',
|
||||
title: 'Read from storage',
|
||||
description: `Read and write to any of your buckets, while also respecting storage auth policies.`,
|
||||
size: 'large',
|
||||
code: `import { serve } from "https://deno.land/std@0.114.0/http/server.ts";
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@1.33.1";
|
||||
|
||||
serve(async (req) => {
|
||||
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? "";
|
||||
const SERVICE_KEY = Deno.env.get("SERVICE_KEY") ?? "";
|
||||
const supabase = createClient(SUPABASE_URL, SERVICE_KEY);
|
||||
if (req.headers.get("Authorization") === "super-secret-key") {
|
||||
const { data } = await supabase.storage
|
||||
.from("newbucket")
|
||||
.download("supameme.png");
|
||||
return new Response(data, { headers: { "content-type": "image/png" } });
|
||||
} else {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
}
|
||||
});`,
|
||||
},
|
||||
{
|
||||
lang: 'js',
|
||||
title: 'Read from database',
|
||||
description: `Read, Write, Update, Insert anything on the database`,
|
||||
size: 'large',
|
||||
code: `import { serve } from "https://deno.land/std@0.114.0/http/server.ts";
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@1.33.1";
|
||||
|
||||
serve(async () => {
|
||||
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? "";
|
||||
const SERVICE_KEY = Deno.env.get("SERVICE_KEY") ?? "";
|
||||
const supabase = createClient(SUPABASE_URL, SERVICE_KEY);
|
||||
const { data } = await supabase.from("todos").select();
|
||||
return new Response(JSON.stringify(data), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
});`,
|
||||
},
|
||||
]
|
||||
@@ -13,6 +13,7 @@ module.exports = withMDX({
|
||||
'images.unsplash.com',
|
||||
'supabase.com',
|
||||
'obuldanrptloktxcffvn.supabase.co',
|
||||
'avatars.githubusercontent.com',
|
||||
],
|
||||
},
|
||||
async headers() {
|
||||
|
||||
@@ -1951,6 +1951,12 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
|
||||
},
|
||||
"@types/parse-numeric-range": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-numeric-range/-/parse-numeric-range-0.0.1.tgz",
|
||||
"integrity": "sha512-nI3rPGKk8BxedokP2VilnW5JyZHYNjGCUDsAZ2JQgISgDflHNUO0wXMfGYP8CkihrKYDm5tilD52XfGhO/ZFCA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/parse5": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz",
|
||||
@@ -4235,6 +4241,11 @@
|
||||
"lines-and-columns": "^1.1.6"
|
||||
}
|
||||
},
|
||||
"parse-numeric-range": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz",
|
||||
"integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ=="
|
||||
},
|
||||
"parse5": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
|
||||
@@ -5644,15 +5655,15 @@
|
||||
}
|
||||
},
|
||||
"react-syntax-highlighter": {
|
||||
"version": "15.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.4.5.tgz",
|
||||
"integrity": "sha512-RC90KQTxZ/b7+9iE6s9nmiFLFjWswUcfULi4GwVzdFVKVMQySkJWBuOmJFfjwjMVCo0IUUuJrWebNKyviKpwLQ==",
|
||||
"version": "15.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz",
|
||||
"integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"highlight.js": "^10.4.1",
|
||||
"lowlight": "^1.17.0",
|
||||
"prismjs": "^1.25.0",
|
||||
"refractor": "^3.2.0"
|
||||
"prismjs": "^1.27.0",
|
||||
"refractor": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"react-tooltip": {
|
||||
|
||||
@@ -30,12 +30,13 @@
|
||||
"next-mdx-remote": "^2.1.3",
|
||||
"next-seo": "^4.20.0",
|
||||
"nodemailer": "^6.7.3",
|
||||
"parse-numeric-range": "^1.3.0",
|
||||
"react": "^17.0.2",
|
||||
"react-copy-to-clipboard": "^5.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-ga": "^3.3.0",
|
||||
"react-markdown": "^5.0.3",
|
||||
"react-syntax-highlighter": "^15.3.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-tooltip": "^4.2.17",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"rehype-slug": "^4.0.1",
|
||||
@@ -55,10 +56,11 @@
|
||||
"@types/marked": "^4.0.3",
|
||||
"@types/node": "^14.0.1",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/parse-numeric-range": "^0.0.1",
|
||||
"@types/react": "^16.9.35",
|
||||
"@types/react-copy-to-clipboard": "^4.3.0",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-syntax-highlighter": "^13.5.0",
|
||||
"@types/react-syntax-highlighter": "^13.5.2",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"postcss": "^8.4.7",
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
import { Badge, IconCode, IconFastForward, IconGlobe, IconRefreshCcw } from '@supabase/ui'
|
||||
import UseCaseExamples from 'data/products/functions/usecase-examples'
|
||||
import Solutions from 'data/Solutions.json'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Image from 'next/image'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import 'swiper/swiper.min.css'
|
||||
import CTABanner from '~/components/CTABanner'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
import ScrollableCodeBlock from '~/components/ScrollableCodeBlock'
|
||||
import FunctionsUsecases from '~/components/Sections/FunctionsUsecases'
|
||||
import ProductHeader from '~/components/Sections/ProductHeader'
|
||||
|
||||
const featureBlocks = [
|
||||
{
|
||||
title: 'Instant deployment',
|
||||
description: 'Deploy Edge Functions in seconds',
|
||||
highlightLines: '8',
|
||||
icon: <IconFastForward strokeWidth={1.5} />,
|
||||
},
|
||||
{
|
||||
title: 'Global',
|
||||
description: 'Deployed to 29 regions worldwide',
|
||||
highlightLines: '8',
|
||||
icon: <IconGlobe strokeWidth={1.5} />,
|
||||
},
|
||||
{
|
||||
title: 'Typescript ready',
|
||||
description: 'TypeScript, WASM, ES Modules',
|
||||
highlightLines: '8',
|
||||
icon: <IconCode strokeWidth={1.5} />,
|
||||
},
|
||||
{
|
||||
title: 'Async triggers',
|
||||
description: 'Invoke Edge Functions based on any event in your database',
|
||||
highlightLines: '28',
|
||||
icon: <IconRefreshCcw strokeWidth={1.5} />,
|
||||
badge: 'Coming soon',
|
||||
},
|
||||
]
|
||||
|
||||
const featureHighlights = [
|
||||
{
|
||||
title: 'Set authentication',
|
||||
description: 'Use the JWT token to set the authentication of the user',
|
||||
highlightLines: '15..20',
|
||||
},
|
||||
{
|
||||
title: 'Use anything from Supabase',
|
||||
description:
|
||||
'supabase-js can interact with any part of the Supabase stack from Edge Functions, while respecting auth row level security policies.',
|
||||
highlightLines: '22..27',
|
||||
},
|
||||
{
|
||||
title: 'Use secrets to store senstive keys',
|
||||
description:
|
||||
'Set and edit secrets via the CLI, which can then be accessed via enviroment variables.',
|
||||
highlightLines: '7..13',
|
||||
},
|
||||
{
|
||||
title: 'No limits',
|
||||
description: `You're empowered to run any logic you like using any data from the Supabase database.`,
|
||||
highlightLines: '29..39',
|
||||
},
|
||||
]
|
||||
|
||||
function Database() {
|
||||
// base path for images
|
||||
const { basePath } = useRouter()
|
||||
|
||||
const [dashboardSwiper, setDashboardSwiper] = useState(undefined)
|
||||
const [dashboardSwiperActiveIndex, setDashboardSwiperActiveIndex] = useState(0)
|
||||
|
||||
function handleDashboardSwiperNav(e: number) {
|
||||
setDashboardSwiperActiveIndex(e)
|
||||
// @ts-ignore
|
||||
dashboardSwiper.slideTo(e)
|
||||
}
|
||||
|
||||
const title = 'Serverless Edge Functions that automatically scale'
|
||||
const subtitle = `Execute your code closest to your users with fast deploy times and low latency.`
|
||||
const meta_title = `${title}`
|
||||
const meta_description = subtitle
|
||||
|
||||
const [currentSelection, setCurrentSelection] = useState(featureHighlights[0].highlightLines)
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextSeo
|
||||
title={meta_title}
|
||||
description={meta_description}
|
||||
openGraph={{
|
||||
title: meta_title,
|
||||
description: meta_description,
|
||||
url: `https://supabase.com/edge-functions`,
|
||||
images: [
|
||||
{
|
||||
url: `https://supabase.com${basePath}/images/product/database/database-og.jpg`,
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
<DefaultLayout>
|
||||
<ProductHeader
|
||||
icon={Solutions['edge-functions'].icon}
|
||||
title={Solutions['edge-functions'].name}
|
||||
h1={[
|
||||
<span key={'database-h1'}>
|
||||
Serverless Edge Functions
|
||||
<br /> that automatically scale
|
||||
</span>,
|
||||
]}
|
||||
subheader={[subtitle, 'PostgreSQL is one of the worlds most scalable databases.']}
|
||||
image={[
|
||||
<div className="block w-full header--light" key="light">
|
||||
<Image
|
||||
src={`${basePath}/images/product/functions/functions-hero.png`}
|
||||
alt="database header"
|
||||
layout="responsive"
|
||||
width="1680"
|
||||
height="1116"
|
||||
/>
|
||||
</div>,
|
||||
<div className="w-full mr-0 header--dark dark:block" key="dark">
|
||||
<Image
|
||||
src={`${basePath}/images/product/functions/functions-hero.png`}
|
||||
alt="database header"
|
||||
layout="responsive"
|
||||
width="1680"
|
||||
height="1116"
|
||||
/>
|
||||
</div>,
|
||||
]}
|
||||
documentation_url={'/docs/guides/edge-functions'}
|
||||
/>
|
||||
|
||||
<SectionContainer>
|
||||
<div className="col-span-12 mb-10 space-y-12 lg:mb-0 lg:col-span-3 ">
|
||||
<div className="grid md:grid-cols-2 xl:grid-cols-4 gap-8 rounded">
|
||||
{featureBlocks.map((item) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 px-8 py-6 border rounded group bg-scale-100 dark:bg-scale-300">
|
||||
{item.img ? (
|
||||
<img
|
||||
src={`/images/product/functions/${item.img}`}
|
||||
className="w-12 h-12 rounded-md"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex items-center justify-center w-12 h-12 transition-all border rounded-md bg-scale-300 dark:bg-scale-500 text-scale-1200 group-hover:text-brand-900 group-hover:scale-105">
|
||||
{item.icon ? item.icon : <IconCode strokeWidth={2} />}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<h3 className="text-lg text-scale-1200">{item.title}</h3>
|
||||
<p className="text-sm text-scale-900">{item.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</SectionContainer>
|
||||
|
||||
<SectionContainer>
|
||||
<div className="col-span-12 mb-10 space-y-12 lg:mb-0 lg:col-span-3 ">
|
||||
<div className="grid lg:grid-cols-12 gap-6 lg:gap-32 items-center">
|
||||
<div className="flex flex-col lg:col-span-5 gap-8">
|
||||
<div>
|
||||
<h3 className="h3">Anatomy of an Edge Function</h3>
|
||||
<p className="p">
|
||||
Asynchronous tasks within minutes using Supabase Functions with simple
|
||||
authenticated access to the rest of the Supabase Ecosystem.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{featureHighlights.map((feat, i) => {
|
||||
const active = currentSelection == feat.highlightLines
|
||||
return (
|
||||
<button
|
||||
key={`featureHighlighted-${i}`}
|
||||
className={
|
||||
'group transition-all border px-6 py-4 text-left rounded-md bg-scale-200 hover:bg-scale-100 hover:dark:bg-scale-300 hover:boder' +
|
||||
(active
|
||||
? ' bg-white dark:bg-scale-400 border-scale-500'
|
||||
: ' border-scale-300')
|
||||
}
|
||||
onClick={() => setCurrentSelection(feat.highlightLines)}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
'transition-colors ' +
|
||||
(active
|
||||
? ' text-scale-1200'
|
||||
: ' text-scale-900 group-hover:text-scale-1200')
|
||||
}
|
||||
>
|
||||
{feat.title}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
'transition-colors text-sm ' +
|
||||
(active
|
||||
? ' text-scale-1100'
|
||||
: ' text-scale-800 group-hover:text-scale-1100 ')
|
||||
}
|
||||
>
|
||||
{feat.description}
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:col-span-7 overflow-hidden">
|
||||
<ScrollableCodeBlock
|
||||
lang="ts"
|
||||
highlightLines={currentSelection}
|
||||
showToolbar
|
||||
hideCopy
|
||||
>
|
||||
{`import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
|
||||
import Stripe from 'https://esm.sh/stripe?target=deno&no-check'
|
||||
import { Customer } from 'types'
|
||||
|
||||
serve(async (req) => {
|
||||
try {
|
||||
// create a supabase client
|
||||
const supabaseClient = createClient(
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
Deno.env.get('SUPABASE_ANON_KEY') ?? ''
|
||||
)
|
||||
// create a stripe client
|
||||
const stripe = Stripe(Deno.env.get('STRIPE_SECRET_KEY'))
|
||||
|
||||
// Get the authorization header from the request.
|
||||
const authHeader = req.headers.get('Authorization').replace("Bearer ","")
|
||||
// Client now respects auth policies for this user
|
||||
supabaseClient.auth.setAuth(authHeader)
|
||||
// set the user profile
|
||||
const user = supabase.auth.user()
|
||||
|
||||
// Retrieve user metadata that only the user is allowed to select
|
||||
const { data, error } = await supabaseClient
|
||||
.from<Customer>('user_profiles')
|
||||
.select('address, tax, billing_email, phone')
|
||||
|
||||
if (error) throw error
|
||||
|
||||
const customer = await stripe.customers.create({
|
||||
description: 'My First Stripe Customer (created by a Supabase edge function)',
|
||||
phone: data.phone,
|
||||
address: data.address,
|
||||
email: user.email,
|
||||
})
|
||||
|
||||
return new Response(JSON.stringify(customer), { status: 200 })
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify(error), { status: 400 })
|
||||
}
|
||||
})`}
|
||||
</ScrollableCodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SectionContainer>
|
||||
|
||||
<SectionContainer>
|
||||
<div className="col-span-12 mb-10 space-y-12 lg:mb-0 lg:col-span-3 ">
|
||||
<div className="grid grid-cols-4 gap-8 rounded">
|
||||
{featureBlocks.map((item) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 px-8 py-6 border rounded group bg-scale-100 dark:bg-scale-300">
|
||||
{item.img ? (
|
||||
<img
|
||||
src={`/images/product/functions/${item.img}`}
|
||||
className="w-12 h-12 rounded-md"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex items-center justify-center w-12 h-12 transition-all border rounded-md bg-scale-300 dark:bg-scale-500 text-scale-1200 group-hover:text-brand-900 group-hover:scale-105">
|
||||
{item.icon ? item.icon : <IconCode strokeWidth={2} />}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<h3 className="text-lg text-scale-1200">{item.title}</h3>
|
||||
<p className="text-sm text-scale-900">{item.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</SectionContainer>
|
||||
|
||||
<SectionContainer className="-mb-16">
|
||||
<FunctionsUsecases
|
||||
// @ts-ignore
|
||||
content={UseCaseExamples}
|
||||
title="Use functions for every server side function"
|
||||
text={[
|
||||
<p key={0}>
|
||||
Edge Functions are perfect for running code for sensitive use cases or interacting
|
||||
with 3rd party services.
|
||||
</p>,
|
||||
]}
|
||||
footer={[
|
||||
<div className="grid grid-cols-12" key={0}>
|
||||
<div className="mt-0 col-span-12 lg:col-span-6 xl:col-span-12 xl:mb-8 flex">
|
||||
<p>
|
||||
<p className="m-0 text-scale-1100">Libraries coming soon:</p>
|
||||
</p>
|
||||
<div className="space-x-1 ml-1">
|
||||
<Badge dot={false}>Python</Badge>
|
||||
<Badge dot={false}>Dart</Badge>
|
||||
<Badge dot={false}>C#</Badge>
|
||||
<Badge dot={false}>Kotlin</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="col-span-12 lg:col-span-6 xl:col-span-10 hidden xl:block"
|
||||
key={1}
|
||||
></div>
|
||||
</div>,
|
||||
]}
|
||||
documentation_link={'/docs/guides/database'}
|
||||
/>
|
||||
</SectionContainer>
|
||||
|
||||
<CTABanner />
|
||||
</DefaultLayout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Database
|
||||
@@ -0,0 +1,3 @@
|
||||
import EdgeFunctions from './edge-functions'
|
||||
|
||||
export default EdgeFunctions
|
||||
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 3.5 MiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 321 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="512" height="512" fill="none" version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<title>TypeScript logo</title>
|
||||
<rect width="512" height="512" rx="50" fill="#3178c6"/>
|
||||
<path d="m317 407v50c8.1 4.2 18 7.3 29 9.4s23 3.1 35 3.1c12 0 23-1.1 34-3.4 11-2.3 20-6.1 28-11 8.1-5.3 15-12 19-21s7.1-19 7.1-32c0-9.1-1.4-17-4.1-24s-6.6-13-12-18c-5.1-5.3-11-10-18-14s-15-8.2-24-12c-6.6-2.7-12-5.3-18-7.9-5.2-2.6-9.7-5.2-13-7.8-3.7-2.7-6.5-5.5-8.5-8.4-2-3-3-6.3-3-10 0-3.4 0.89-6.5 2.7-9.3s4.3-5.1 7.5-7.1c3.2-2 7.2-3.5 12-4.6 4.7-1.1 9.9-1.6 16-1.6 4.2 0 8.6 0.31 13 0.94 4.6 0.63 9.3 1.6 14 2.9 4.7 1.3 9.3 2.9 14 4.9 4.4 2 8.5 4.3 12 6.9v-47c-7.6-2.9-16-5.1-25-6.5s-19-2.1-31-2.1c-12 0-23 1.3-34 3.8s-20 6.5-28 12c-8.1 5.4-14 12-19 21-4.7 8.4-7 18-7 30 0 15 4.3 28 13 38 8.6 11 22 19 39 27 6.9 2.8 13 5.6 19 8.3s11 5.5 15 8.4c4.3 2.9 7.7 6.1 10 9.5 2.5 3.4 3.8 7.4 3.8 12 0 3.2-0.78 6.2-2.3 9s-3.9 5.2-7.1 7.2-7.1 3.6-12 4.8c-4.7 1.1-10 1.7-17 1.7-11 0-22-1.9-32-5.7-11-3.8-21-9.5-30-17zm-84-123h64v-41h-179v41h64v183h51z" clip-rule="evenodd" fill="#fff" fill-rule="evenodd" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -5,9 +5,17 @@
|
||||
<link>https://supabase.com</link>
|
||||
<description>Latest news from Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Tue, 29 Mar 2022 22:00:00 GMT</lastBuildDate>
|
||||
<lastBuildDate>Wed, 30 Mar 2022 22:00:00 GMT</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/blog/rss.xml" rel="self" type="application/rss+xml"/>
|
||||
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/2022/03/31/supabase-edge-functions</guid>
|
||||
<title>Edge Functions are now available in Supabase</title>
|
||||
<link>https://supabase.com/blog/2022/03/31/supabase-edge-functions</link>
|
||||
<description>Today we’re launching Edge Functions. Edge Functions let you execute Typescript code close to your users, no matter where they’re located.</description>
|
||||
<pubDate>Wed, 30 Mar 2022 22:00:00 GMT</pubDate>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/2022/03/30/supabase-enterprise</guid>
|
||||
<title>Introducing Supabase Enterprise</title>
|
||||
|
||||
@@ -54,6 +54,12 @@
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/edge-functions/edge-functions</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/database</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
@@ -61,13 +67,13 @@
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/storage</loc>
|
||||
<loc>https://supabase.com/partners/[slug]</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/partners/[slug]</loc>
|
||||
<loc>https://supabase.com/storage</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
@@ -589,4 +595,22 @@
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/blog/2022/03/29/graphql-now-available</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/blog/2022/03/30/supabase-enterprise</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/blog/2022/03/31/supabase-edge-functions</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -221,6 +221,16 @@ module.exports = ui({
|
||||
)}"><polygon points="5.5,0 2.5,3 1.1,3 4.1,0"/><polygon points="4,0 6,2 6,0.6 5.4,0"/><polygon points="0,2 1,3 2.4,3 0,0.6"/></svg>`
|
||||
)}")`,
|
||||
}),
|
||||
keyframes: {
|
||||
'flash-code': {
|
||||
'0%': { backgroundColor: 'rgba(63, 207, 142, 0.1)' },
|
||||
'100%': { backgroundColor: 'transparent' },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
'flash-code': 'flash-code 1s forwards',
|
||||
'flash-code-slow': 'flash-code 2s forwards',
|
||||
},
|
||||
},
|
||||
},
|
||||
// variants: {
|
||||
|
||||