diff --git a/.turbo-cookie b/.turbo-cookie
deleted file mode 100755
index 9e5d1817106..00000000000
--- a/.turbo-cookie
+++ /dev/null
@@ -1 +0,0 @@
-cookie
\ No newline at end of file
diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx
index e9862fe85f6..346c8d7f3bc 100644
--- a/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx
+++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx
@@ -210,7 +210,7 @@ Create a `/components/supabase-listener.jsx` file and add the following:
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
-import supabase from '../utils/supabase'
+import supabase from '../utils/supabase-browser'
export default function SupabaseListener({ accessToken }) {
const router = useRouter()
diff --git a/apps/docs/pages/guides/auth/auth-mfa.mdx b/apps/docs/pages/guides/auth/auth-mfa.mdx
index 286916d29de..0591e3af52b 100644
--- a/apps/docs/pages/guides/auth/auth-mfa.mdx
+++ b/apps/docs/pages/guides/auth/auth-mfa.mdx
@@ -7,13 +7,6 @@ export const meta = {
'Add an additional layer of security to your apps with Supabase Auth multi-factor authentication.',
}
-
-
-Multi-Factor Authentication is in early access preview only. Although we
-believe it is production ready, APIs and some behavior may change based on
-feedback we receive in the preview period.
-
-
Multi-factor authentication (MFA), sometimes called two-factor
authentication (2FA), adds an additional layer of security to your
@@ -94,17 +87,20 @@ for your application. JWTs without an `aal` claim are at the `aal1` level.
## Adding to your app
-Adding MFA to your app involves these three steps:
+Adding MFA to your app involves these four steps:
1. **Add enrollment flow.**
You need to provide a UI within your app that your users will be able to set-up
MFA in. You can add this right after sign-up, or as part of a separate flow in
the settings portion of your app.
-2. **Add challenge step to login.**
+2. **Add unenrollment flow.**
+ You need to support a UI through which users can see existing devices and unenroll
+ devices which are no longer relevant.
+3. **Add challenge step to login.**
If a user has set-up MFA, your app's login flow needs to present a challenge
screen to the user asking them to prove they have access to the additional
factor.
-3. **Enforce rules for MFA logins.**
+4. **Enforce rules for MFA logins.**
Once your users have a way to enroll and log in with MFA, you need to enforce
authorization rules across your app: on the frontend, backend, API servers or
Row-Level Security policies.
@@ -141,6 +137,7 @@ Enrolling a factor for use with MFA takes three steps:
immediately becomes active for the user account. If not, you should repeat
steps 2 and 3.
+
#### Example: React
Below is an example that creates a new `EnrollMFA` component that illustrates
@@ -241,6 +238,83 @@ export function EnrollMFA({
}
```
+### Add unenrollment flow
+
+An unenrollment flow provides a UI for users to manage and unenroll factors linked to their accounts.
+Most applications do so via a factor management page where users can view and unlink selected factors.
+
+When a user unenrolls a factor, call `supabase.auth.mfa.unenroll()` with the ID of the factor.
+For example, call `supabase.auth.mfa.unenroll({factorId: "d30fd651-184e-4748-a928-0a4b9be1d429"})` to unenroll a factor with ID `d30fd651-184e-4748-a928-0a4b9be1d429`.
+
+
+#### Example: React
+
+Below is an example that creates a new `UnenrollMFA` component that illustrates
+the important pieces of the MFA enrollment flow. Note that users can only unenroll a factor after completing the enrollment flow and obtaining
+an `aal2` JWT claim.Here are some points of note:
+
+- When the component appears on screen, the `supabase.auth.mfa.listFactors()` endpoint
+ fetches all existing factors together with their details.
+- The existing factors for a user are displayed in a table.
+- Once the user has selected a factor to unenroll, they can type in the factorId and click **Unenroll**
+ which creates a confirmation modal.
+
+
+Unenrolling a factor will downgrade the assurance level from `aal2` to `aal1` only after the refresh interval has lapsed.
+For an immediate downgrade from `aal2` to `aal1` after enrolling one will need to manually call `refreshSession()`
+
+
+```tsx
+/**
+ * UnenrollMFA shows a simple table with the list of factors together with a button to unenroll.
+ * When a user types in the factorId of the factor that they wish to unenroll and clicks unenroll
+ * the corresponding factor will be unenrolled.
+ */
+export function UnenrollMFA() {
+ const [factorId, setFactorId] = useState('')
+ const [factors, setFactors] = useState([])
+ const [error, setError] = useState('') // holds an error message
+
+ useEffect(() => {
+ ;(async () => {
+ const { data, error } = await supabase.auth.mfa.listFactors()
+ if (error) {
+ throw error
+ }
+
+ setFactors(data.totp)
+ })()
+ }, [])
+
+ return (
+ <>
+ {error &&
{error}
}
+
+
+
Factor ID
+
Friendly Name
+
Factor Status
+
+ {factors.map(factor => (
+
+
{factor.id}
+
{factor.friendly_name}
+
{factor.factor_type}
+
{factor.status}
+
+ ))}
+
+ setFactorId(e.target.value.trim())}
+ />
+
+ >
+ )
+}
+```
+
### Add challenge step to login
Once a user has logged in via their first factor (email+password, magic link,
@@ -460,7 +534,7 @@ create policy "Policy name."
select
case
when created_at >= '2022-12-12T00:00:00Z' then array['aal2']
- else array['aal1', 'aal2', NULL]
+ else array['aal1', 'aal2']
end as aal
from auth.users
where auth.uid() = id));
@@ -471,8 +545,6 @@ create policy "Policy name."
`aal2` for all other timestamps.
- The `<@` operator is PostgreSQL's ["contained in"
operator.](https://www.postgresql.org/docs/current/functions-array.html)
-- `NULL` appears because some JWTs originating from prior to the introduction
- of MFA in Supabase Auth will not contain an `aal` claim.
- **Using `as restrictive` ensures this policy will restrict all commands on the
table regardless of other policies!**
@@ -491,7 +563,7 @@ create policy "Policy name."
select
case
when count(id) > 0 then array['aal2']
- else array['aal1', 'aal2', NULL]
+ else array['aal1', 'aal2']
end as aal
from auth.mfa_factors
where auth.uid() = user_id and status = 'verified'
@@ -503,8 +575,6 @@ create policy "Policy name."
- Otherwise, it will accept both `aal1` and `aal2`.
- The `<@` operator is PostgreSQL's ["contained in"
operator.](https://www.postgresql.org/docs/current/functions-array.html)
-- `NULL` appears because some JWTs originating from prior to the introduction
- of MFA in Supabase Auth will not contain an `aal` claim.
- **Using `as restrictive` ensures this policy will restrict all commands on the
table regardless of other policies!**
@@ -589,7 +659,7 @@ seconds later. The entries are ordered most recent method first!
{
"amr": [
{
- "method": "mfa/totp",
+ "method": "totp",
"timestamp": 1666086056
},
{
@@ -625,7 +695,7 @@ Currently recognized methods are:
- `otp` - any one-time password based sign in (email code, SMS code, magic
link).
- `oauth` - any OAuth based sign in (social login).
-- `mfa/totp` - a TOTP additional factor.
+- `totp` - a TOTP additional factor.
This list will expand in the future.
diff --git a/apps/www/_blog/2022-12-06-the-supabase-content-storm.mdx b/apps/www/_blog/2022-12-06-the-supabase-content-storm.mdx
index 07404711e42..f25e4dc11fb 100644
--- a/apps/www/_blog/2022-12-06-the-supabase-content-storm.mdx
+++ b/apps/www/_blog/2022-12-06-the-supabase-content-storm.mdx
@@ -29,7 +29,7 @@ More than 30 creators participated in creating content in different formats and
- [Hoppscotch with GraphQL](https://www.youtube.com/watch?v=HoBXQng3aK4) by Jamie Barton.
- [Supabase with TypeScript: using tRPC and Prisma to achieve end-to-end typesafety](https://noahflk.com/blog/supabase-typescript-trpc) by Noah.
- [How to Connect Browser Extensions to Supabase](https://akoskm.com/how-to-connect-browser-extensions-to-supabase) by Ákos Kőműves.
-- [A Chili Cookoff with Rust, Rocket, Render, and Supabase](bradcypert.com/chili-cookoff-with-rust-rocket-render-and-supabase/) by Brad Cypert.
+- [A Chili Cookoff with Rust, Rocket, Render, and Supabase](https://bradcypert.com/chili-cookoff-with-rust-rocket-render-and-supabase/) by Brad Cypert.
- [Authenticating users with Remix and Supabase](https://makerkit.dev/blog/tutorials/remix-supabase-auth) by Giancarlo Buomprisco.
- [Automatic Spotify Playlist Creation with Pipedream and Supabase](https://www.ianwootten.co.uk/2022/12/06/automatic-spotify-playlist-creation-with-pipedream-and-supabase/) by Ian Wootten.
- [Accept Payments For Your African-Based Business Using Supabase Edge Functions and Paystack](https://blog.hijabicoder.dev/accept-payments-for-your-african-based-business-using-supabase-edge-functions-and-paystack) by Fatuma Abdullahi.
diff --git a/apps/www/_blog/2022-12-09-launch-week-6-hackathon.mdx b/apps/www/_blog/2022-12-09-launch-week-6-hackathon.mdx
new file mode 100644
index 00000000000..212e9cce132
--- /dev/null
+++ b/apps/www/_blog/2022-12-09-launch-week-6-hackathon.mdx
@@ -0,0 +1,115 @@
+---
+title: 'Launch Week 6 Hackathon'
+description: Build an Open Source Project, Win $1500 and the Supabase Darkmode Keyboard
+author: ant_wilson
+image: lw6-hackathon/thumbnail.jpg
+thumb: lw6-hackathon/thumbnail.jpg
+tags:
+ - launch-week
+ - hackathon
+date: '2022-12-09'
+toc_depth: 3
+---
+
+
+The official Supabase Hackathon starts on Friday 9th December at 8am Pacific Time and ends Monday 19th December at 00:01am.
+
+[Launch Week](https://supabase.com/launch-week) is about to kick off and we're running a hackathon in parallel.
+
+You can win $1500 in GitHub sponsorships and an official Supabase Keyboard (extremely limited edition), along with a bunch of other prizes!
+
+See all the [winners from all the previous hackathons](https://supabase.com/blog/tags/hackathon) for inspiration.
+
+
+
+## Key Facts
+
+- You have 10 days to build a new **Open Source** project using Supabase.
+ - Starting 8:00am PT Friday 9th December 2022
+- Build whatever you want - a project, app, tool, library, anything
+- Enter as an individual, or as a team of up to 5 people
+- Submission deadline is 00:01am early Monday morning PT 19th December 2022
+- There are 5 prize categories.
+ - Best overall project will win $1500 in GitHub sponsorships, and a Supabase Keyboard for each team member.
+ - Most fun/interesting, best Flutter project, and best storage project will all win a limited edition Hackathon swag kit for each team member!
+ - Our friends at [Deno](https://deno.land/) will be judging a special guest category: Best Edge Functions Project! They will be providing some [special swag](https://deno.com/blog/edge-functions-supabase-launch-week-6-hackathon) for the winners!
+
+## Details
+
+### Schedule
+
+- The Hackathon begins at 8:00am PT Friday 9th December 2022
+- Work on your project any time for the next 10 days
+- Submission deadline (00:01am early Monday morning PT 19th December 2022)
+
+### Prizes
+
+There are 5 chances to win, there will be prizes for:
+
+- Best Overall Project ($1500 in GitHub Sponsorship & Supabase Keyboards)
+ - to be paid as 3x$500 GitHub Sponsorships (over 3 months)
+- Most Fun/Interesting (Swag kit)
+- Best Flutter Project (Swag kit)
+- Best Storage Project (Swag kit)
+- Best Edge Functions Project (Deno Swag kit)
+
+
+There will be winner and runner-up prizes for each category. Every team member on winning/runner-up teams gets a supaverified swag kit.
+
+
+
+### Submission
+
+You should submit your project using [this form](https://www.madewithsupabase.com/launch-week-6).
+
+### Judges
+
+Best Edge Functions Project category will be judged by our friends at [Deno](https://deno.land/), and other categories will be judged by the Supabase team.
+We will be looking for:
+
+- creativity/inventiveness
+- functions correctly/smoothly
+- visually pleasing
+- technically impressive
+- use of Supabase features
+ - deep usage of a single feature or
+ - broad usage are both ok
+- FUN! 😃
+
+### Rules
+
+- Team size 1-5 (all team members on winning teams will receive a prize)
+- You cannot be in multiple teams
+- One submission per team
+- All design elements, code, etc. for your project must be created **during** the event
+- All entries must be Open Source (link to source code required in entry)
+- Must use Supabase in some capacity
+- Can be any language or framework
+- You must submit before the deadline (no late entries)
+
+### Community
+
+The Supabase Team will be taking part in the Hackathon and you'll find us live building in our discord all week. Please join us by building in public:
+
+- Text channel: hackathon
+- Audio channel: hackathon
+
+If you need help or advice when building, find other people to join your team, or if you just want to chill and watch people build, come and join us!
+
+[Join our Discord](https://discord.supabase.com)
+
+
+
+### Launch Week
+
+Don't forget to checkout the new feature's being announced as part of [Launch Week](/launch-week).
+
+- [Previous Hackathon Prize Winners](https://supabase.com/blog/launch-week-5-hackathon-winners)
+- [Who We Hire at Supabase - Founders Chat](https://youtu.be/-BG9XptyCKI)
+
+
+### Additional Info
+
+- Any intellectual property developed during the hackathon will belong to the team that developed it. We expect that each team will have an agreement between themselves regarding the IP, but this is not required
+- By making a submission you grant Supabase permission to use screenshots, code-snippets and/or links to your project or content of your README on our Twitter, blog, website, email updates, and in the Supabase discord server. Supabase does not make any claims over your IP.
+- $1500 prize will be paid by making a GitHub sponsorship to the winning repo, $500/month for 3 months. The goal here is to create a sustainable project that will be continue to be maintained for the duration of the sponsorship period (and hopefully beyond :) )
diff --git a/apps/www/_blog/2022-12-09-who-we-hire.mdx b/apps/www/_blog/2022-12-09-who-we-hire.mdx
new file mode 100644
index 00000000000..4ebfa438775
--- /dev/null
+++ b/apps/www/_blog/2022-12-09-who-we-hire.mdx
@@ -0,0 +1,106 @@
+---
+title: 'Who We Hire at Supabase'
+description: Traits we look for to maintain a culture of shipping fast and often
+author: ant_wilson
+image: who-we-hire.jpg
+thumb: who-we-hire-cover.jpg
+tags:
+ - launch-week
+date: '2022-12-09'
+toc_depth: 3
+youtubeHero: https://www.youtube-nocookie.com/embed/-BG9XptyCKI
+---
+
+It’s been 12 months since I wrote [How we launch at Supabase](https://supabase.com/blog/supabase-how-we-launch), which details our Launch strategy. This methodology helped us grow our hosted platform 46% month-over-month for the first 18 months since [first launching on Hacker News](https://news.ycombinator.com/item?id=23319901) in summer 2020.
+
+In the last 12 months we’ve continued to grow the number of databases deployed on our platform by a further 3.5x and increased revenue by 1300%, all thanks to a relentless team and intense shipping schedule.
+
+We’ve upgraded our methodology in a few ways, which I’ll touch on briefly, but it’s become clear in the time that we’ve been running Supabase that whilst methodology is important - having the right people on the bus is paramount. It’s the team who are relentlessly pushing us to do more, better, faster.
+
+Here I’ll discuss some of the traits that have shaped culture at Supabase, and defined what we look for in new candidates.
+
+
+
+Here’s some traits we’ve observed in people at Supabase that make them effective team members:
+
+### Extreme effectiveness when working asynchronously
+
+People who require a lot of face-to-face collaboration are unlikely to be successful at Supabase. Major projects have gone from inception, through implementation, to marketing and launch without a single synchronous meeting. Our team is now 60 people spread across 25 different countries. We focus on hiring the best person for the job regardless of geography. A nice side effect of this is excellent global support coverage (everyone from the CFO to the infra team has “front line support” listed as the first item in their job description) and a true appreciation of decreasing latency globally and not just for `us-east-1`.
+
+With so many timezones, a dependence on scheduled face time would kill our momentum, so meetings are used as a last resort. This isn’t to say that there are zero recurring meetings at Supabase, but in our ideal world there would be.
+
+### Egoless
+
+A willingness to do the [schlep](http://www.paulgraham.com/schlep.html) is essential to working at Supabase. Behind every shiny new feature we ship is thousands of hours of ruthless hard work and maintenance. The best people in the company have come to be known as “Supa-unblockers”; people who take work off your plate rather than piling it on. And being senior is no exception. We don’t have managers, which means all individuals need to:
+
+- do IC (independent contributor) work as their primary job.
+- self manage, and develop the skills necessary to ruthlessly prioritise tasks with the business goals always in mind.
+- have the full picture - everyone in the team has access to our metrics and we share a long list of product principles which enables everyone to make high-level decisions autonomously.
+
+### Kaizen mindset
+
+[Kaizen](https://en.wikipedia.org/wiki/Kaizen) in a Supabase context is the idea that each process should be continuously and incrementally improved, with small and frequent changes being preferred over larger ones. This is true for all process, from company culture to product updates. It allows us to chunk down large projects (with the bonus that new starters can start contributing in their first week) and ensures smooth gradual changes to culture.
+
+### Default to action
+
+The very first day of Supabase (6th Jan 2020), I met Copple for an all-day white-boarding session where we explored in detail everything that we thought Supabase could be, and how we were going to achieve it. We dumped down ideas on fund raising, product, competition, marketing, everything. At the end of what felt like a marathon, around the point that most people would pop the kettle on and start rummaging in the cupboards for the last remaining packet of Custard Creams (just me?), Copple opened his laptop and immediately started work. It was a simple act, but in that moment he set the pace for development at Supabase. Never would there ever be a whiff of “Let’s meet next week to discuss”. The words we encourage people to default to instead is “why not now?”.
+
+### Unreasonable levels of ambition
+
+When you find all of the above traits in a single person, your definition of the word ‘feasible’ changes dramatically. At the start of every product cycle, we ask the question “what is the most ambitious thing we can hope to achieve in the next 3 months?”, and the team never fail to push the boundary on what we previously considered reasonable. Maybe it’s because we work in Dev Tools, but it’s amazing to watch developers productise solutions to their own problems. The right people don’t need to run a customer survey to know what to work on next, they’re obsessed with solving their own problems, have strong opinions on what great looks like, and won’t stop until they get there.
+
+### A founder mindset
+
+We have a bias towards people who previously ran their own companies. This typically hints towards high levels of personal ambition. Ex-founders tend to be very flexible in their role. They can identify low hanging fruit and change focus quickly to address the needs of the business as a whole. If the person is technical, it also reduces the translation needed across the tech and business sides of the company. Something we especially value are team members who take on responsibility without asking and without being asked. Founders do this regularly. Clearly these traits are not only found in people who’ve started companies, but it can be an indication. It’s not the credentials that we value most, but the mindset. We expect everyone who works at Supabase to act like a business owner, and to solidify this part of everyone’s compensation package is an ESOP apportionment with [a 10 year exercise window](https://github.com/holman/extended-exercise-windows).
+
+---
+
+By now, it should be no surprise that I estimate our product and growth momentum is actually 90% about the people, and only 10% about structure. But I’ll detail the structure anyway, since it’s still an important part of keeping people unblocked.
+
+## Team structure
+
+We often joke that building Supabase is like building 5 startups simultaneously, due to the number of modular products that make up the Supabase stack (Database, Auth, Realtime, etc.). Our culture has grown to support this idea - with an extremely high degree of autonomy bestowed to teams (and in fact, to everyone who works here).
+
+### Independent teams
+
+Previously there was a lot of crossover when it came to who worked on what, in the early days we had more products to manage than people. But these days the product team is broken roughly into:
+
+- Postgres / PostgREST
+- Platform
+- Auth
+- Storage
+- Realtime
+- Functions
+- CLI / API
+- QA (cross cutting)
+- Docs (cross cutting)
+- Design (cross cutting)
+- Front End (cross cutting)
+
+With some of these breaking down further into sub-teams (e.g. Realtime is actually more like an Elixir team that handles Observability, Logflare, and more).
+
+Inter-team communication is one of the harder problems we’ve had to solve over the past year. As the teams grow and move faster internally, how do we make sure they can stay in sync with the rest of the org? For example, things like Front End, QA, and Docs need to have a level of consistency to ensure Supabase is a coherent product. One way we’ve solved this is by making sure the cross-cutting teams act like a consultancy rather than a service. When a product team like Auth require some Front End work, it’s very rarely a case of “throwing tasks over the fence” to the cross cutting teams, rather those teams will consult with the product team on how they can work together to get a project over the line. Another example is the docs team. They won’t write all the docs for a feature, that’s still the job of the team that implemented the feature (it’s important that we view docs as part of the product itself and not a byproduct). The team in question will consult with Team Docs on where the docs should live, and the format they should take to maintain coherence across all products in the stack.
+
+Staying aligned on goals is also key. Each team is aware of the overall business metrics, but also the number of active, and paying projects that make use of their product’s APIs. It’s up to them to dig into the data and best decide how to boost their metrics, and to establish a hypothesis of how their numbers drive the business goals.
+
+We receive a firehose of user feedback across multiple channels including Dashboard widget, support, socials, Discord, customer interviews, dogfooding. This feedback is triaged and piped directly to the relevant team for them to assess.
+
+Ultimately, a team and the individuals within should have all the tools they need to be able to make decisions quickly and independently. We’re also not prescriptive on how teams organise their work, it’s up to them to decide.
+
+### Cadence
+
+Thanks to the Agile Manifesto, most software teams for the last decade ended up running some variation of a 2 week sprint on an endless loop, and yes its as exhausting as it sounds. Due to our desire to [recreate a Y-combinator-like environment internally](https://supabase.com/blog/supabase-how-we-launch) we landed on something that’s closer to 3 month product cycles. We found that it allows for more ambitious goal setting, accounts for the realities of people being sick or on holiday, and also allows teams to work through different phases.
+
+An intense shipping schedule can lead to an accrual of technical debt. To solve for this during the last year we introduced a series of “Kaizen Weeks” that run within the first month of each 3 month cycle. The three weeks we ran as part of the last one were:
+
+- QA week
+- Docs week
+- Performance week
+
+The goal of each week wasn’t only to make step changes in each area, but to find ways to bake this work into the team’s ongoing workflow. Performance testing for example requires some upfront work to get things up and running, but once this is done it’s fairly straightforward to automate this as part of your CI/CD pipeline.
+
+---
+
+Monday 12th December is Supabase Launch Week 6 where you can find out what we’ve been grinding on for the last 3 months. All the details will be revealed here: [supabase.com/launch-week](https://supabase.com/launch-week)
+
+If you’d like to work with us, [check out our careers page](https://supabase.com/careers).
diff --git a/apps/www/_blog/2022-12-10-postgres-crdt.mdx b/apps/www/_blog/2022-12-10-postgres-crdt.mdx
new file mode 100644
index 00000000000..36dd3a90cbc
--- /dev/null
+++ b/apps/www/_blog/2022-12-10-postgres-crdt.mdx
@@ -0,0 +1,145 @@
+---
+title: 'pg_crdt - an experimental CRDT extension for Postgres'
+description: "Embedding Yjs and Automerge into Postgres for collaborative applications."
+author: paul_copplestone
+image: crdt/crdt-blog.png
+thumb: crdt/crdt-blog.png
+tags:
+ - launch-week
+date: '2022-12-10'
+toc_depth: 2
+---
+
+Today we’re open-sourcing an EXPERIMENTAL extension for CRDTs, `pg_crdt`. The GitHub repo is [here](https://github.com/supabase/pg_crdt). There are [instructions](https://github.com/supabase/pg_crdt#installation) for running it locally in the README.
+
+When we released the new [multiplayer features](/blog/supabase-realtime-multiplayer-general-availability) for our Realtime engine,
+it took 30 minutes for someone to ask if we’d add CRDT support.
+
+> *Anyone from Supabase here, do you have any plans to build in support for CRDT toolkits such as Yjs or AutoMerge for these features? It would make working with them so much easier if there was a plug and play backend.*
+
+@samwillis on [HackerNews](https://news.ycombinator.com/item?id=32510820)
+
+pg_crdt has not been released onto the Supabase platform (and it may never be). We’re considering many options for offline-sync/support and, while CRDTs will undoubtedly factor in, we’re not sure if this is the *right* approach. Hopefully this release generates a healthy discussion about the various ways we can do it at Supabase.
+
+## What’s a CRDT?
+
+A CRDT (Conflict-free Replicated Data Type) is a data structure. More accurately, a family of data structures. They enable collaborative apps like [Figma](https://www.figma.com/blog/how-figmas-multiplayer-technology-works/).
+
+You already know what a “data structure” is: an Array is a good example. CRDTs are a *special* type of data structure designed to solve a specific problem: they can merge changes in a way that the final state of the data will be the same, no matter the order in which the updates were applied.
+
+In simple terms, a CRDT allows multiple users to make changes to the same data without the need for a central authority to coordinate their actions.
+
+Let’s use our Array example to demonstrate the problem they solve. Imagine you have an array (which is not a CRDT):
+
+```jsx
+let fruit = ['Apple', 'Banana', 'Orange']
+```
+
+Now imagine you have 2 developers updating this array on their local computers. They both want to replace the fruit at the start of the array. The first user, let’s call her “Jenny”, does this:
+
+```js
+fruit[0] = 'Grape' // the array is ['Grape', 'Banana', 'Orange']
+```
+
+The developer sitting next to her, let’s call him “Jonny”, does the same:
+
+```js
+fruit[0] = 'Mango' // the array is ['Mango', 'Banana', 'Orange']
+```
+
+And now, since they are both developing on different machines, they push their changes to a remote server. When the updates land on the remote system, what will `fruit[0]` be? “Grape” or “Mango”?
+
+
+
+Since this is one of those *basic* arrays, the answer is "the fruit array that arrives last”.
+
+1. If Jonny’s fruit array arrives first, it will be saved.
+2. After that, Jenny’s fruit arrived and overwrites Jonny’s changes.
+
+But therein lies the problem: Jonny was the last developer to change his array of fruit, shouldn’t his changes be the ones that are saved?
+
+That’s one of the things that CRDTs solve. There is an “array CRDT” which, when merged, will always have the same result. It doesn’t matter if Jonny’s array arrives first, or if Jenny’s arrives first - every time you merge them they would be able to determine that the Jonny’s was the last change the fruit array.
+
+How does it do that? Some sort of algorithm, but you can ask ChatGPT to explain that one.
+
+## Offline philosophy
+
+Collaborative apps are becoming more prolific as legacy software is rebuilt within a browser environment.
+
+Collaboration is largely a data problem - how do we get one user’s changes (data) to merge with another user’s changes (data)? As a database provider it’s natural fit for Supabase to provide the tooling for developers to build collaborative apps.
+
+Before going too far down the “solution” rabbit hole at Supabase, we try to think about the long-term implications of adopting any technology. We’re pretty boring - we don’t make bets on technologies unless we think they will exist in 20 years.
+
+I personally believe CRDTs are the future. For *some things*. If databases were invented today, I’m certain most of the effort would be spent developing CRDT databases or something with “offline algorithms” built-in. But tech is a real-world demonstration of the Lindy effect: the longer something has existed, the longer it’s likely to exist in the future. That’s why we bet on Postgres - with more than 30 years history, it’s here to stay.
+
+Faced with this reality, we should consider how to solve *offline with Postgres* and where CRDTs might fit into that picture.
+
+For a long time I thought there could be a way to shoehorn Postgres row-level data into a CRDT, to give *truly* offline support. This may even still be [possible](https://electric-sql.com/), and it’s one of the ideas we’ll continue to pursue. But Postgres is a rich and evolving ecosystem, and I don’t know whether it will be possible to -
+
+a) find a merge strategy for an entire row, while simultaneously:
+
+b) finding a merge strategy for every data type *within* that row (especially with the number of data types available through extensions)
+
+It’s possible that developers will need to be selective about their “level” of offline support, at least if they plan to use an “incumbent” databases. For the cases, a simple “last write wins” strategy is probably acceptable (see the excellent [Replicache](https://doc.replicache.dev/examples/repliear) and [Watermelon](https://nozbe.github.io/WatermelonDB/) libraries), but there will be important pieces of their application where it is not.
+
+Take this table of blog posts as an example:
+
+```sql
+create table posts (
+ id serial primary key,
+ title text,
+ content text default ''
+);
+```
+
+Perhaps it’s not that important if the `title` has a “last write wins” strategy, because it’s rarely updated and less likely to have a merge conflict. But it is critical to use a smarter algorithm for the `content` of the blog post, especially in a team where multiple users are editing a blog post at the same time.
+
+That’s a lot of background to get to what you probably want to know about:
+
+## Postgres CRDT extension
+
+`pg_crdt` is an extension which adds CRDT support to Postgres as a data type. For example, using our table from above:
+
+```sql
+create table posts (
+ id serial primary key,
+ title text,
+ content crdt.ydoc default crdt.new_ydoc()
+);
+```
+
+The `content` column is now a [Yjs Doc](https://docs.yjs.dev/api/y.doc). Updates to this column are *commutative* and *idempotent*. This means that they can be applied in any order and multiple times, and the result will always be the same. Two people can edit the blog content at the same time, and they won’t have any issues when their changes are saved in the database.
+
+### Support for Yjs, Automerge, and beyond
+
+The Yjs and Automerge teams have done some excellent work to create Rust libraries for their CRDTs implementations. It was relatively trivial to wrap the libraries into a Postgres extension using the [pgx](https://github.com/tcdi/pgx) framework.
+
+At this stage it makes sense to give developers as many choices as possible in one extension. The CRDT space is nascent the algorithms are rapidly changing.
+
+Importantly, both Yjs and Automerge have *JavaScript* and Rust implementations, which means they work natively in both a browser and a Postgres environment. Since collaborative applications are largely a client-side problem, CRDTs are more useful for developers if they have robust JavaScript libraries. In the future, if this extension becomes the approach we take, then Supabase will focus some resources on building Yjs/Automerge libraries for mobile devices too (Swift for iOS, Kotlin for Android).
+
+## Why not build this into Realtime?
+
+An alternative approach for CRDT support in Supabase is to build support directly into [Realtime](https://github.com/supabase/realtime) and use it as an Authoritative server. In this scenario, Realtime would serialize the CRDT and save it to Postgres (probably as a `bytea` data type). Yjs has the concept of [Providers](https://github.com/yjs/yjs#providers) which would facilitate this. You can see the difference between the approaches in the diagram below - on the left, Realtime acts as the “middleman” for saving the CRDT in the database, whereas on the right the CRDT is pushed directly to the database, and Realtime receives updates from Postgres. (Note also that clients can send peer-to-peer updates.)
+
+
+
+This might still be our best option, but it should not be our first attempt. If we make Realtime the authority, it strongly couples developers to the Supabase infrastructure. By placing the CRDT into the database, it simplifies the architecture, enables other tools (like Debezium), and provides the possibility to update the database directly (for semi-realtime events like counters or page-views).
+
+
+
+The Realtime engine seems like a great *compliment* for `pg_crdt`, but before we put it into production we need to solve a few (major) limitations.
+
+## Limitations
+
+These are a few of the *known* limitations:
+
+- Realtime broadcasts database changes from the Postgres write ahead log (WAL). The WAL includes a complete copy of the the underlying data so small updates cause the entire document to broadcast to all collaborators
+- Frequently updated CRDTs produce a lot of WAL and dead tuples
+- Large CRDT types in Postgres generate significant serialization/deserialization overhead on-update.
+
+We’re likely to discover more (no doubt from a few friendly HN comments).
+
+## Next steps
+
+If you want to help with `pg_crdt` the best way is to get involved in the GitHub repo. We have enabled [Discussions](https://github.com/supabase/pg_crdt/discussions) for any and all ideas. If you have experience with CRDTs and you like this approach, don’t hesitate to contact one of the team.
\ No newline at end of file
diff --git a/apps/www/components/LaunchWeek/Ticket/form.tsx b/apps/www/components/LaunchWeek/Ticket/form.tsx
index 3595be0f946..59dbcb902ac 100644
--- a/apps/www/components/LaunchWeek/Ticket/form.tsx
+++ b/apps/www/components/LaunchWeek/Ticket/form.tsx
@@ -169,7 +169,6 @@ export default function Form({ sharePage, align = 'Center' }: Props) {
align === 'Left' ? 'text-center xl:text-left' : 'text-center'
)}
>
-
Coming soon
Register to get your ticket and stay tuned all week for daily announcements
+ The traditional parallel Hackathon is back! Build a new open source project with
+ Supabase and you can win $1500 in GitHub sponsorships and a coveted Supabase Darkmode
+ Keyboard! For more info check the{' '}
+
+ blog post
+
+ .
+
+
+
+
+
+
Prizes
+
+ There are 5 categories to win, with prizes for winners and runner-ups of each
+ category. Each team member gets a prize.
+
+
+
+
Judges
+
+ The Supabase team will judge all the categories except the Best Edge Functions
+ Project, which will be judged by our friends at Deno.
+
+
+
+
+
Community
+
+ If you need help or advice when building, find other people to join your team,
+ or if you just want to chill and watch people build, come and join us!
+
+ Submit your project through{' '}
+
+ madewithsupabase.com
+
+ . All submissions must be open source and publically available. Submissions close
+ Monday 19th Dec 00:01 AM PT.
+
- We worked with +30 content creators from around the world to drop a mountain of
- content simultaneously!
+ We worked with more than 30 content creators from around the world to drop a mountain
+ of content simultaneously!
-
+ Encrypt the column's data with pgsodium's Transparent Column Encryption (TCE).
+ Decrypted values will be stored within the "decrypted_{selectedTable.name}"
+ view.
+
+ {!isPgSodiumInstalled ? (
+
+ You will need to{' '}
+
+ install
+ {' '}
+ the extension pgsodium first before being
+ able to encrypt your column.
+
+ ) : (
+
+ Note: Only columns of text type can be
+ encrypted.
+
+ )}
+
+ }
+ checked={columnFields.isEncrypted}
+ onChange={() => onUpdateField({ isEncrypted: !columnFields.isEncrypted })}
+ />
+ {columnFields.isEncrypted && (
+ onUpdateField({ keyId: id })}
+ onUpdateDescription={(name) => onUpdateField({ keyName: name })}
+ />
+ )}
+
+
+ >
+ )}
+
+ setIsEditingRelation(false)}
+ saveChanges={saveColumnForeignKey}
+ />
)
}
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts
index 299dbfad1d8..c3e33805929 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts
@@ -43,6 +43,7 @@ export const generateColumnField = (field: any = {}): ColumnField => {
isPrimaryKey: false,
isIdentity: false,
isNewColumn: true,
+ isEncrypted: false,
}
}
@@ -69,6 +70,7 @@ export const generateColumnFieldFromPostgresColumn = (
isUnique: column.is_unique,
isNewColumn: false,
+ isEncrypted: false,
isPrimaryKey: primaryKeyColumns.includes(column.name),
}
}
@@ -163,6 +165,9 @@ export const validateFields = (field: ColumnField) => {
if (field.format.length === 0) {
errors['format'] = `Please select a type for your column`
}
+ if (field.keyId === 'create-new' && (field?.keyName ?? '').length === 0) {
+ errors['keyName'] = 'Please provide a name for your new key'
+ }
return errors
}
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx
index cd3a32e6ca0..45a09d5a3cd 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx
@@ -1,6 +1,6 @@
import { FC } from 'react'
import { isUndefined } from 'lodash'
-import { Button, IconArrowRight, IconLink } from 'ui'
+import { Button, IconArrowRight } from 'ui'
import { ColumnField } from '../SidePanelEditor.types'
import InformationBox from 'components/ui/InformationBox'
@@ -86,9 +86,8 @@ const ColumnForeignKeyInformation: FC<{
return (
}
title={
-