diff --git a/.github/workflows/og_images.yml b/.github/workflows/og_images.yml
index c3cc6568e73..afec7f34149 100644
--- a/.github/workflows/og_images.yml
+++ b/.github/workflows/og_images.yml
@@ -14,7 +14,7 @@ jobs:
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
- PROJECT_ID: ${{ secrets.PROJECT_ID }}
+ PROJECT_ID: ${{ secrets.OG_IMAGE_SUPABASE_PROJECT_ID }}
steps:
- name: Check out repo
diff --git a/.github/workflows/search.yml b/.github/workflows/search.yml
new file mode 100644
index 00000000000..b3d60d06629
--- /dev/null
+++ b/.github/workflows/search.yml
@@ -0,0 +1,51 @@
+name: Generate Embeddings for Search
+
+on:
+ push:
+ branches:
+ - master
+ paths:
+ - '.github/workflows/search.yml'
+ - 'supabase/migrations/**'
+ - 'apps/docs/**'
+ - 'spec/**'
+ workflow_dispatch: {}
+ schedule:
+ - cron: '0 0 * * *'
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ env:
+ OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
+ SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
+ PROJECT_ID: ${{ secrets.SEARCH_SUPABASE_PROJECT_ID }}
+ NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SEARCH_SUPABASE_URL }}
+ SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SEARCH_SUPABASE_SERVICE_ROLE_KEY }}
+ SUPABASE_DB_PASSWORD: ${{ secrets.SEARCH_SUPABASE_DB_PASSWORD }}
+ SEARCH_GITHUB_APP_ID: ${{ secrets.SEARCH_GITHUB_APP_ID }}
+ SEARCH_GITHUB_APP_INSTALLATION_ID: ${{ secrets.SEARCH_GITHUB_APP_INSTALLATION_ID }}
+ SEARCH_GITHUB_APP_PRIVATE_KEY: ${{ secrets.SEARCH_GITHUB_APP_PRIVATE_KEY }}
+
+ steps:
+ - name: Check out repo
+ uses: actions/checkout@v3
+
+ - name: Setup node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+
+ - name: Download dependencies
+ run: npm ci
+
+ - name: Link Supabase project
+ run: npx supabase link --project-ref $PROJECT_ID
+
+ - name: Run migrations
+ run: npx supabase db push
+
+ - name: Refresh embeddings
+ working-directory: ./apps/docs
+ run: npm run embeddings
diff --git a/apps/docs/.env.example b/apps/docs/.env.example
index 7fa24758cb1..81e97461ea0 100644
--- a/apps/docs/.env.example
+++ b/apps/docs/.env.example
@@ -5,6 +5,9 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=
# PRIVATE
OPENAI_KEY=
SUPABASE_SERVICE_ROLE_KEY=
+SEARCH_GITHUB_APP_ID=
+SEARCH_GITHUB_APP_INSTALLATION_ID=
+SEARCH_GITHUB_APP_PRIVATE_KEY=
# Set IS_PLATFORM to true if you have the api keys and urls
NEXT_PUBLIC_IS_PLATFORM=false
diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore
index fd62e0e6867..ba0b52697ed 100644
--- a/apps/docs/.gitignore
+++ b/apps/docs/.gitignore
@@ -12,6 +12,7 @@
.env.local
.env.development.local
.env.test.local
+.env.staging.local
.env.production.local
npm-debug.log*
@@ -20,4 +21,4 @@ yarn-error.log*
**/*/generated/**/*
!**/*/generated/.gitkeep
-!**/*/generated/**/.gitkeep
\ No newline at end of file
+!**/*/generated/**/.gitkeep
diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index a9a53500320..676be0ee00b 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
@@ -577,7 +577,16 @@ export const functions = {
name: 'Connecting directly to Postgres',
url: '/guides/functions/connect-to-postgres',
},
+ ],
+ },
+ {
+ name: 'Third-Party Tools',
+ url: undefined,
+ items: [
{ name: 'Dart Edge on Supabase', url: '/guides/functions/dart-edge' },
+ { name: 'Browserless.io', url: '/guides/functions/examples/screenshots' },
+ { name: 'OpenAI API', url: '/guides/functions/examples/openai' },
+ { name: 'Upstash Redis', url: '/guides/functions/examples/upstash-redis' },
],
},
{
diff --git a/apps/docs/package.json b/apps/docs/package.json
index ca12c92170b..0eb29301d62 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -47,6 +47,9 @@
"@mdx-js/loader": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@next/mdx": "^12.0.4",
+ "@octokit/auth-app": "^4.0.9",
+ "@octokit/core": "^4.2.0",
+ "@octokit/plugin-paginate-graphql": "^2.0.1",
"@radix-ui/react-accordion": "^1.0.1",
"@supabase/auth-helpers-nextjs": "^0.5.6",
"@supabase/auth-helpers-react": "^0.3.1",
@@ -94,6 +97,7 @@
"ui": "*",
"unist-builder": "^3.0.1",
"unist-util-filter": "^4.0.1",
+ "uuid": "^9.0.0",
"valtio": "^1.7.6"
},
"devDependencies": {
diff --git a/apps/docs/pages/guides/auth/phone-login/twilio.mdx b/apps/docs/pages/guides/auth/phone-login/twilio.mdx
index 11bca2d9bac..2b6fd7f2b18 100644
--- a/apps/docs/pages/guides/auth/phone-login/twilio.mdx
+++ b/apps/docs/pages/guides/auth/phone-login/twilio.mdx
@@ -1,4 +1,5 @@
import Layout from '~/layouts/DefaultGuideLayout'
+
export const meta = {
id: 'auth-twilio',
title: 'Phone Auth with Twilio',
@@ -308,33 +309,35 @@ To make use of WhatsApp OTP, please complete the following steps:
- Submit a template via the [Twilio Guide For Submitting WhatsApp templates](https://www.twilio.com/docs/whatsapp/tutorial/send-whatsapp-notification-messages-templates#creating-message-templates-and-submitting-them-for-approval)
-The message template submitted to Twilio must exactly match the SMS Body entered on the Supabase dashboard.
+ The message template submitted to Twilio must exactly match the SMS Body entered on the Supabase
+ dashboard.
The sign in process with WhatsApp is similar to the sign in process for SMS. Do note the additional `whatsapp` parameter added:
```js
-const {data, error } = await supabase.auth.signInWithOtp({
- phone: '+57336567365',
- options: {
- channel:'whatsapp'
- }
- })
+const { data, error } = await supabase.auth.signInWithOtp({
+ phone: '+57336567365',
+ options: {
+ channel: 'whatsapp',
+ },
+})
```
You can also sign up with `whatsapp` as a channel:
```js
-const {data, error }= await supabase.auth.signUp({
- phone: '+57336567365',
- password: 'testsupabasenow',
- options: {
- channel:'whatsapp',
- }
+const { data, error } = await supabase.auth.signUp({
+ phone: '+57336567365',
+ password: 'testsupabasenow',
+ options: {
+ channel: 'whatsapp',
+ },
})
```
There is no change in the verification process, you should continue to use the `sms` type for verification
+
```js
// After receiving a WhatsApp OTP
let { data, error } = await supabase.auth.verifyOtp({
diff --git a/apps/docs/pages/guides/functions/dart-edge.mdx b/apps/docs/pages/guides/functions/dart-edge.mdx
index 36efc06b31b..b8f3012560f 100644
--- a/apps/docs/pages/guides/functions/dart-edge.mdx
+++ b/apps/docs/pages/guides/functions/dart-edge.mdx
@@ -10,6 +10,15 @@ export const meta = {
> ⚠️ WARNING: Dart Edge is an experimental project - it's probably not ready for production usage unless you're okay with living on the 'edge'. Open issues on [Dart Edge repo](https://github.com/invertase/dart_edge) if you encounter any issues.
+
+
+
+
## **Pre requirements**
Make sure you have [the edge CLI](https://docs.dartedge.dev/#getting-started) installed in addition to [the Supabase CLI](https://supabase.com/docs/guides/cli#installation).
diff --git a/apps/docs/pages/guides/functions/local-development.mdx b/apps/docs/pages/guides/functions/local-development.mdx
index e3e77c95105..9cb55927208 100644
--- a/apps/docs/pages/guides/functions/local-development.mdx
+++ b/apps/docs/pages/guides/functions/local-development.mdx
@@ -6,6 +6,52 @@ export const meta = {
description: 'Run your Edge Functions locally.',
}
+## Setting Up Your Environment
+
+You can follow the [Deno guide](https://deno.com/manual@v1.32.5/getting_started/setup_your_environment) for setting up your development environment with your favorite editor/IDE.
+
+When developing with VS Code inside of an existing application, you can utilize [multi-root workspaces](https://code.visualstudio.com/docs/editor/workspaces#_multiroot-workspaces).
+
+
+
+
+
+For example, see this `edge-functions.code-workspace` configuration for a CRA (create react app) client with Supabase Edge Functions. You can find the complete example on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions).
+
+```json
+{
+ "folders": [
+ {
+ "name": "project-root",
+ "path": "./"
+ },
+ {
+ "name": "client",
+ "path": "app"
+ },
+ {
+ "name": "supabase-functions",
+ "path": "supabase/functions"
+ }
+ ],
+ "settings": {
+ "files.exclude": {
+ "node_modules/": true,
+ "app/": true,
+ "supabase/functions/": true
+ },
+ "deno.importMap": "./supabase/functions/import_map.json"
+ }
+}
+```
+
+## Running Edge Functions locally
+
You can run your Edge Function locally using [`supabase functions serve`](/docs/reference/cli/usage#supabase-functions-serve):
```bash
@@ -15,7 +61,7 @@ supabase functions serve # start the Functions watcher
The `functions serve` command has hot-reloading capabilities. It will watch for any changes to your files and restart the Deno server.
-### Invoking Edge Functions locally
+## Invoking Edge Functions locally
While serving your local Edge Function, you can invoke it using curl:
diff --git a/apps/docs/pages/guides/functions/quickstart.mdx b/apps/docs/pages/guides/functions/quickstart.mdx
index 9563322a715..17f0ca95de5 100644
--- a/apps/docs/pages/guides/functions/quickstart.mdx
+++ b/apps/docs/pages/guides/functions/quickstart.mdx
@@ -88,6 +88,50 @@ const { data, error } = await supabase.functions.invoke('hello-world', {
After invoking your Edge Function you should see the response `{ "message":"Hello Functions!" }`.
+## Setting Up Your Environment
+
+You can follow the [Deno guide](https://deno.com/manual@v1.32.5/getting_started/setup_your_environment) for setting up your development environment with your favorite editor/IDE.
+
+When developing with VS Code inside of an existing application, you can utilize [multi-root workspaces](https://code.visualstudio.com/docs/editor/workspaces#_multiroot-workspaces).
+
+
+
+
+
+For example, see this `edge-functions.code-workspace` configuration for a CRA (create react app) client with Supabase Edge Functions. You can find the complete example on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions).
+
+```json
+{
+ "folders": [
+ {
+ "name": "project-root",
+ "path": "./"
+ },
+ {
+ "name": "client",
+ "path": "app"
+ },
+ {
+ "name": "supabase-functions",
+ "path": "supabase/functions"
+ }
+ ],
+ "settings": {
+ "files.exclude": {
+ "node_modules/": true,
+ "app/": true,
+ "supabase/functions/": true
+ },
+ "deno.importMap": "./supabase/functions/import_map.json"
+ }
+}
+```
+
## Database Functions vs Edge Functions
For data-intensive operations we recommend using [Database Functions](/docs/guides/database/functions), which are executed within your database
diff --git a/apps/docs/pages/guides/platform/migrating-and-upgrading-projects.mdx b/apps/docs/pages/guides/platform/migrating-and-upgrading-projects.mdx
index 10308c95d0e..2dfa8e6bc56 100644
--- a/apps/docs/pages/guides/platform/migrating-and-upgrading-projects.mdx
+++ b/apps/docs/pages/guides/platform/migrating-and-upgrading-projects.mdx
@@ -12,7 +12,7 @@ In some cases, access to new features require upgrading or migrating your Supaba
## Upgrade your project
-This is only available for projects on the Free plan.
+This is only available for projects on the Free plan. For projects on the Pro plan, please contact support for assistance with upgrading.
When you pause and restore a project, the restored database includes the latest features. This method _does_ include downtime, so be aware that your project will be inaccessible for a short period of time.
diff --git a/apps/docs/pages/guides/realtime/rate-limits.mdx b/apps/docs/pages/guides/realtime/rate-limits.mdx
index f1673752944..dd36d805374 100644
--- a/apps/docs/pages/guides/realtime/rate-limits.mdx
+++ b/apps/docs/pages/guides/realtime/rate-limits.mdx
@@ -66,9 +66,11 @@ Use your browser's developer tools to find the WebSocket initiation request and
-## Payload Limits
+## Postgres Changes Payload Limit
-Realtime has a message byte size limit of 1 megabyte.
+Realtime Postgres Changes has a change payload size limit of 1 mebibyte (MiB).
+
+When this limit is reached, the `new` and `old` record payloads only include the fields with a value size of less than or equal to 64 bytes.
## Presence Limits
diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt
index 6407c1bb7a2..db94336b341 100644
--- a/apps/docs/public/humans.txt
+++ b/apps/docs/public/humans.txt
@@ -44,6 +44,7 @@ Michel Pelletier
Monica El Khoury
Nikita Kotlyarov
Oli R
+Pamela Chia
Paul Cioanca
Paul Copplestone
Pavel Borisov
diff --git a/apps/docs/scripts/search/generate-embeddings.ts b/apps/docs/scripts/search/generate-embeddings.ts
index d04765afc67..490861209d2 100644
--- a/apps/docs/scripts/search/generate-embeddings.ts
+++ b/apps/docs/scripts/search/generate-embeddings.ts
@@ -3,6 +3,7 @@ import dotenv from 'dotenv'
import 'openai'
import { Configuration, OpenAIApi } from 'openai'
import { inspect } from 'util'
+import { v4 as uuidv4 } from 'uuid'
import { fetchSources } from './sources'
dotenv.config()
@@ -12,7 +13,14 @@ async function generateEmbeddings() {
const args = process.argv.slice(2)
const shouldRefresh = args.includes('--refresh')
- const requiredEnvVars = ['NEXT_PUBLIC_SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY', 'OPENAI_KEY']
+ const requiredEnvVars = [
+ 'NEXT_PUBLIC_SUPABASE_URL',
+ 'SUPABASE_SERVICE_ROLE_KEY',
+ 'OPENAI_KEY',
+ 'SEARCH_GITHUB_APP_ID',
+ 'SEARCH_GITHUB_APP_INSTALLATION_ID',
+ 'SEARCH_GITHUB_APP_PRIVATE_KEY',
+ ]
if (requiredEnvVars.some((name) => !process.env[name])) {
throw new Error(
@@ -33,6 +41,12 @@ async function generateEmbeddings() {
}
)
+ // Use this version to track which pages to purge
+ // after the refresh
+ const refreshVersion = uuidv4()
+
+ const refreshDate = new Date()
+
const embeddingSources = await fetchSources()
console.log(`Discovered ${embeddingSources.length} pages`)
@@ -92,6 +106,24 @@ async function generateEmbeddings() {
throw updatePageError
}
}
+
+ // No content/embedding update required on this page
+ // Update other meta info
+ const { error: updatePageError } = await supabaseClient
+ .from('page')
+ .update({
+ type,
+ source,
+ meta,
+ version: refreshVersion,
+ last_refresh: refreshDate,
+ })
+ .filter('id', 'eq', existingPage.id)
+
+ if (updatePageError) {
+ throw updatePageError
+ }
+
continue
}
@@ -137,6 +169,8 @@ async function generateEmbeddings() {
source,
meta,
parent_page_id: parentPage?.id,
+ version: refreshVersion,
+ last_refresh: refreshDate,
},
{ onConflict: 'path' }
)
@@ -215,6 +249,18 @@ async function generateEmbeddings() {
}
}
+ console.log(`Removing old pages and their sections`)
+
+ // Delete pages that have been removed (and their sections via cascade)
+ const { error: deletePageError } = await supabaseClient
+ .from('page')
+ .delete()
+ .filter('version', 'neq', refreshVersion)
+
+ if (deletePageError) {
+ throw deletePageError
+ }
+
console.log('Embedding generation complete')
}
@@ -222,4 +268,9 @@ async function main() {
await generateEmbeddings()
}
-main().catch((err) => console.error(err))
+main().catch((err) => {
+ console.error(err)
+
+ // Exit with non-zero code
+ process.exit(1)
+})
diff --git a/apps/docs/scripts/search/sources/github-discussion.ts b/apps/docs/scripts/search/sources/github-discussion.ts
new file mode 100644
index 00000000000..a87012a9780
--- /dev/null
+++ b/apps/docs/scripts/search/sources/github-discussion.ts
@@ -0,0 +1,119 @@
+import { createAppAuth } from '@octokit/auth-app'
+import { Octokit } from '@octokit/core'
+import { paginateGraphql } from '@octokit/plugin-paginate-graphql'
+import { createHash } from 'crypto'
+import { BaseSource } from './base'
+
+export const ExtendedOctokit = Octokit.plugin(paginateGraphql)
+export type ExtendedOctokit = InstanceType
+
+export type Discussion = {
+ id: string
+ updatedAt: string
+ url: string
+ title: string
+ body: string
+ databaseId: number
+}
+
+export type DiscussionsResponse = {
+ repository: {
+ discussions: {
+ totalCount: number
+ nodes: Discussion[]
+ }
+ }
+}
+
+/**
+ * Fetches GitHub discussions for a repository + category
+ */
+export async function fetchDiscussions(owner: string, repo: string, categoryId: string) {
+ const octokit = new ExtendedOctokit({
+ authStrategy: createAppAuth,
+ auth: {
+ appId: process.env.SEARCH_GITHUB_APP_ID,
+ installationId: process.env.SEARCH_GITHUB_APP_INSTALLATION_ID,
+ privateKey: process.env.SEARCH_GITHUB_APP_PRIVATE_KEY,
+ },
+ })
+
+ const {
+ repository: {
+ discussions: { nodes: discussions },
+ },
+ } = await octokit.graphql.paginate(
+ `
+ query troubleshootDiscussions($cursor: String, $owner: String!, $repo: String!, $categoryId: ID!) {
+ repository(owner: $owner, name: $repo) {
+ discussions(first: 100, after: $cursor, categoryId: $categoryId) {
+ totalCount
+ nodes {
+ id
+ updatedAt
+ url
+ title
+ body
+ databaseId
+ }
+ pageInfo {
+ hasNextPage
+ endCursor
+ }
+ }
+ }
+ }
+ `,
+ {
+ owner,
+ repo,
+ categoryId,
+ }
+ )
+
+ return discussions
+}
+
+export class GitHubDiscussionSource extends BaseSource {
+ type = 'github-discussions' as const
+
+ constructor(source: string, public discussion: Discussion) {
+ super(source, discussion.url)
+ }
+
+ async load() {
+ const { id, title, updatedAt, body, databaseId } = this.discussion
+
+ const checksum = createHash('sha256').update(updatedAt).digest('base64')
+
+ const meta = { id, title, updatedAt }
+
+ // Currently the discussion post itself is being considered as the answer
+ // (as opposed to a comment marked as answer)
+ // So we link the slug to the initial discussion post rather than a comment answer
+ const slug = `discussion-${databaseId}`
+
+ // Format the discussion title + body as markdown for better embeddings + LLM response
+ const content = `# ${title}\n${body}`
+
+ // For now, only a single section is created for GH discussions
+ // Consider adding multiple if we want to include comments/answers
+ const sections = [
+ {
+ heading: title,
+ slug,
+ content,
+ },
+ ]
+
+ this.checksum = checksum
+ this.meta = meta
+ this.sections = sections
+
+ return {
+ checksum,
+ meta,
+ sections,
+ }
+ }
+}
diff --git a/apps/docs/scripts/search/sources/index.ts b/apps/docs/scripts/search/sources/index.ts
index 66ff45b2932..57b8314aafc 100644
--- a/apps/docs/scripts/search/sources/index.ts
+++ b/apps/docs/scripts/search/sources/index.ts
@@ -1,3 +1,4 @@
+import { GitHubDiscussionSource, fetchDiscussions } from './github-discussion'
import { MarkdownSource } from './markdown'
import {
CliReferenceSource,
@@ -13,6 +14,7 @@ export type SearchSource =
| OpenApiReferenceSource
| ClientLibReferenceSource
| CliReferenceSource
+ | GitHubDiscussionSource
/**
* Fetches all the sources we want to index for search
@@ -71,6 +73,14 @@ export async function fetchSources() {
.filter(({ path }) => !ignoredFiles.includes(path))
.map((entry) => new MarkdownSource('guide', entry.path))
+ const githubDiscussionSources = (
+ await fetchDiscussions(
+ 'supabase',
+ 'supabase',
+ 'DIC_kwDODMpXOc4CUvEr' // 'Troubleshooting' category
+ )
+ ).map((discussion) => new GitHubDiscussionSource('supabase/supabase', discussion))
+
const sources: SearchSource[] = [
openApiReferenceSource,
jsLibReferenceSource,
@@ -78,6 +88,7 @@ export async function fetchSources() {
pythonLibReferenceSource,
cSharpLibReferenceSource,
cliReferenceSource,
+ ...githubDiscussionSources,
...guideSources,
]
diff --git a/apps/docs/scripts/search/sources/markdown.ts b/apps/docs/scripts/search/sources/markdown.ts
index 1c7b29640c8..851839a772b 100644
--- a/apps/docs/scripts/search/sources/markdown.ts
+++ b/apps/docs/scripts/search/sources/markdown.ts
@@ -110,7 +110,8 @@ export function processMdxForSearch(content: string): ProcessedMdx {
})
const meta = extractMetaExport(mdxTree)
- const serializableMeta: Json = JSON.parse(JSON.stringify(meta))
+
+ const serializableMeta: Json = meta && JSON.parse(JSON.stringify(meta))
// Remove all MDX elements from markdown
const mdTree = filter(
diff --git a/apps/www/_blog/2023-02-03-openai-embeddings-postgres-vector.mdx b/apps/www/_blog/2023-02-03-openai-embeddings-postgres-vector.mdx
index 46001ec1f17..5a689d9de29 100644
--- a/apps/www/_blog/2023-02-03-openai-embeddings-postgres-vector.mdx
+++ b/apps/www/_blog/2023-02-03-openai-embeddings-postgres-vector.mdx
@@ -104,7 +104,7 @@ Soon we're going to need to perform a similarity search over these embeddings. L
```sql
create or replace function match_documents (
query_embedding vector(1536),
- similarity_threshold float,
+ match_threshold float,
match_count int
)
returns table (
@@ -117,12 +117,12 @@ as $$
begin
return query
select
- id,
- content,
+ documents.id,
+ documents.content,
1 - (documents.embedding <=> query_embedding) as similarity
from documents
- where 1 - (documents.embedding <=> query_embedding) > similarity_threshold
- order by documents.embedding <=> query_embedding
+ where 1 - (documents.embedding <=> query_embedding) > match_threshold
+ order by similarity desc
limit match_count;
end;
$$;
diff --git a/apps/www/_blog/2023-04-21-whats-new-in-pg-graphql-v1-2.mdx b/apps/www/_blog/2023-04-21-whats-new-in-pg-graphql-v1-2.mdx
index 539350f8c87..d3cd7fb012c 100644
--- a/apps/www/_blog/2023-04-21-whats-new-in-pg-graphql-v1-2.mdx
+++ b/apps/www/_blog/2023-04-21-whats-new-in-pg-graphql-v1-2.mdx
@@ -15,10 +15,16 @@ thumb: launch-week-6/pggraphql/og-pg-graphql.png
---
It's been 4 months since the [1.0.0 release of pg_graphql](https://supabase.com/blog/pg-graphql-v1).
-Since then, we’ve pushed several features to improve the APIs that `pg_graphql` produces.
+Since then, we’ve pushed several features to improve the APIs that `pg_graphql` produces.
In this article, we’ll walk through those features and show examples of each.
+
+
+📢 These features are only available on projects with Postgres version `15.1.0.63` or higher. For help with upgrading, please review the [migrating and upgrading projects guide](https://supabase.com/docs/guides/platform/migrating-and-upgrading-projects).
+
+
+
### View Support
Prior to `v1.1`, `pg_graphql` would only reflect standard tables. Since then, views, materialized views, and foreign tables are now also reflected in the GraphQL schema.
@@ -26,7 +32,7 @@ Prior to `v1.1`, `pg_graphql` would only reflect standard tables. Since then, vi
For example:
```sql
-create view "ProjectOwner" as
+create view "ProjectOwner" as
select
acc.id,
acc.name
@@ -70,7 +76,7 @@ With associated `Edge` and `Connection` types. That enables querying via:
}
```
-Additionally, simple views automatically support mutation events like inserts and updates. You might use these to migrate underlying tables while maintaining backwards compatibility with previous API versions.
+Additionally, simple views automatically support mutation events like inserts and updates. You might use these to migrate underlying tables while maintaining backwards compatibility with previous API versions.
## Filtering
@@ -109,7 +115,7 @@ to return all `blog`s where the `name` is `null`.
### **`like`**, **`ilike`**, and **`startsWith`**
-Text filtering options in `pg_graphql` have historically been restricted to equality checks. The hesitation was due to concerns about exposing a default filter that is difficult to index. The combination of [citext](https://www.postgresql.org/docs/current/citext.html) and [PGroonga available on the platform](https://supabase.com/docs/guides/database/extensions/pgroonga) solves those scalability risks and enabled us to expand the `StringFilter` with options for `like` `ilike` and `startsWith`.
+Text filtering options in `pg_graphql` have historically been restricted to equality checks. The hesitation was due to concerns about exposing a default filter that is difficult to index. The combination of [citext](https://www.postgresql.org/docs/current/citext.html) and [PGroonga available on the platform](https://supabase.com/docs/guides/database/extensions/pgroonga) solves those scalability risks and enabled us to expand the `StringFilter` with options for `like` `ilike` and `startsWith`.
```graphql
input StringFilter {
@@ -118,7 +124,7 @@ input StringFilter {
startsWith: String
like: String
ilike: String
-}
+}
```
Note that `startsWith` filters should be preferred where appropriate because they can leverage simple B-Tree indexes to improve performance.
@@ -139,7 +145,7 @@ Note that `startsWith` filters should be preferred where appropriate because the
### GraphQL directives `@skip` and `@include`
-The GraphQL spec has evolved over time. Although the spec is clear, it is common for GraphQL servers to selectively omit some chunks of functionality. For example, some frameworks intentionally do not expose an introspection schema as a form of security through obscurity.
+The GraphQL spec has evolved over time. Although the spec is clear, it is common for GraphQL servers to selectively omit some chunks of functionality. For example, some frameworks intentionally do not expose an introspection schema as a form of security through obscurity.
`pg_graphql` aims to be unopinionated and adhere exactly to the spec. The `@skip` and `@include` directives are part of the [GraphQL core specification](https://spec.graphql.org/October2021/#sec--skip) and are now functional.
@@ -198,6 +204,6 @@ The headline features we aim to launch in coming releases of `pg_graphql` includ
## More pg_graphql
-- [Introducing pg_graphql: A GraphQL extension for PostgreSQL](https://supabase.com/blog/pg-graphql)
+- [Introducing pg_graphql: A GraphQL extension for PostgreSQL](https://supabase.com/blog/pg-graphql)
- [GraphQL is now available in Supabase](https://supabase.com/blog/graphql-now-available)
- [pg_graphql v1.0](https://supabase.com/blog/pg-graphql-v1)
diff --git a/apps/www/package.json b/apps/www/package.json
index 895a7187b8c..cadcf99b872 100644
--- a/apps/www/package.json
+++ b/apps/www/package.json
@@ -42,7 +42,6 @@
"parse-numeric-range": "^1.3.0",
"react": "^17.0.2",
"react-copy-to-clipboard": "^5.0.2",
- "react-countdown": "^2.3.5",
"react-dom": "^17.0.2",
"react-ga": "^3.3.0",
"react-markdown": "^8.0.3",
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index c2a5205856d..28a865abbbb 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -57,7 +57,7 @@ services:
auth:
container_name: supabase-auth
- image: supabase/gotrue:v2.47.0
+ image: supabase/gotrue:v2.60.7
depends_on:
db:
# Disable this if you are using an external Postgres database
diff --git a/package-lock.json b/package-lock.json
index 0b6dffd6aa2..ab9bc95cb9e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -42,6 +42,9 @@
"@mdx-js/loader": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@next/mdx": "^12.0.4",
+ "@octokit/auth-app": "^4.0.9",
+ "@octokit/core": "^4.2.0",
+ "@octokit/plugin-paginate-graphql": "^2.0.1",
"@radix-ui/react-accordion": "^1.0.1",
"@supabase/auth-helpers-nextjs": "^0.5.6",
"@supabase/auth-helpers-react": "^0.3.1",
@@ -89,6 +92,7 @@
"ui": "*",
"unist-builder": "^3.0.1",
"unist-util-filter": "^4.0.1",
+ "uuid": "^9.0.0",
"valtio": "^1.7.6"
},
"devDependencies": {
@@ -593,6 +597,14 @@
"url": "https://opencollective.com/unified"
}
},
+ "apps/docs/node_modules/uuid": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+ "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"apps/docs/node_modules/valtio": {
"version": "1.7.6",
"license": "MIT",
@@ -683,7 +695,6 @@
"parse-numeric-range": "^1.3.0",
"react": "^17.0.2",
"react-copy-to-clipboard": "^5.0.2",
- "react-countdown": "^2.3.5",
"react-dom": "^17.0.2",
"react-ga": "^3.3.0",
"react-markdown": "^8.0.3",
@@ -5821,6 +5832,14 @@
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-16.0.0.tgz",
"integrity": "sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA=="
},
+ "node_modules/@octokit/plugin-paginate-graphql": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-2.0.1.tgz",
+ "integrity": "sha512-ZlhrWMlkGeK7qkWMKPKb6b7SP6lowN0lsOm+eyDssTBMoEZKSfwK+mrG4YtUhM66YNrJI22L32Vt+CcjteU0Vw==",
+ "peerDependencies": {
+ "@octokit/core": ">=4"
+ }
+ },
"node_modules/@octokit/plugin-paginate-rest": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.0.0.tgz",
@@ -9547,9 +9566,9 @@
}
},
"node_modules/@supabase/gotrue-js": {
- "version": "2.18.1",
- "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.18.1.tgz",
- "integrity": "sha512-nXTQPbV1t1WRmdAfUeed0uIXiTwNplCGVEnHbbymyx0V7YZShlzgayuB5WZDRZLau5jg616NIRWf6/9VNF3hlg==",
+ "version": "2.24.0",
+ "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.24.0.tgz",
+ "integrity": "sha512-ZsH4K5cbMTjfMytXaDYVYs9l9igmlZFxiwXn7J2IP/CklWR5qmLCma+dvat5rccPLITVkN6oAZbKxDzW+pEgCg==",
"dependencies": {
"cross-fetch": "^3.1.5"
}
@@ -38890,7 +38909,7 @@
"license": "MIT",
"dependencies": {
"@headlessui/react": "^1.5.0",
- "@supabase/gotrue-js": "^2.16.0",
+ "@supabase/gotrue-js": "2.24.0",
"@supabase/ui": "^0.37.0-alpha.50",
"react-use": "^17.4.0"
},
@@ -39005,6 +39024,7 @@
"postcss": "^8.4.5",
"prop-types": "^15.7.2",
"react-copy-to-clipboard": "^5.1.0",
+ "react-countdown": "^2.3.5",
"react-feather": "^2.0.10",
"react-markdown": "^8.0.3",
"react-syntax-highlighter": "^15.5.0",
@@ -39018,6 +39038,7 @@
"@storybook/react": "^6.5.16",
"@types/common-tags": "^1.8.1",
"@types/react": "^17.0.37",
+ "@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^17.0.11",
"sql-formatter": "^12.2.0",
"storybook-dark-mode": "^2.1.1",
@@ -39047,6 +39068,15 @@
"csstype": "^3.0.2"
}
},
+ "packages/ui/node_modules/@types/react-copy-to-clipboard": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz",
+ "integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"packages/ui/node_modules/clsx": {
"version": "1.2.1",
"license": "MIT",
@@ -43523,6 +43553,12 @@
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-16.0.0.tgz",
"integrity": "sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA=="
},
+ "@octokit/plugin-paginate-graphql": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-2.0.1.tgz",
+ "integrity": "sha512-ZlhrWMlkGeK7qkWMKPKb6b7SP6lowN0lsOm+eyDssTBMoEZKSfwK+mrG4YtUhM66YNrJI22L32Vt+CcjteU0Vw==",
+ "requires": {}
+ },
"@octokit/plugin-paginate-rest": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.0.0.tgz",
@@ -46230,9 +46266,9 @@
}
},
"@supabase/gotrue-js": {
- "version": "2.18.1",
- "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.18.1.tgz",
- "integrity": "sha512-nXTQPbV1t1WRmdAfUeed0uIXiTwNplCGVEnHbbymyx0V7YZShlzgayuB5WZDRZLau5jg616NIRWf6/9VNF3hlg==",
+ "version": "2.24.0",
+ "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.24.0.tgz",
+ "integrity": "sha512-ZsH4K5cbMTjfMytXaDYVYs9l9igmlZFxiwXn7J2IP/CklWR5qmLCma+dvat5rccPLITVkN6oAZbKxDzW+pEgCg==",
"requires": {
"cross-fetch": "^3.1.5"
}
@@ -49775,12 +49811,12 @@
"version": "file:packages/common",
"requires": {
"@headlessui/react": "^1.5.0",
- "@supabase/gotrue-js": "^2.16.0",
+ "@supabase/gotrue-js": "2.24.0",
"@supabase/ui": "^0.37.0-alpha.50",
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11",
"config": "*",
- "react-use": "*",
+ "react-use": "^17.4.0",
"tsconfig": "*",
"typescript": "^4.5.3"
},
@@ -51481,6 +51517,9 @@
"@mdx-js/loader": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@next/mdx": "^12.0.4",
+ "@octokit/auth-app": "^4.0.9",
+ "@octokit/core": "^4.2.0",
+ "@octokit/plugin-paginate-graphql": "^2.0.1",
"@radix-ui/react-accordion": "^1.0.1",
"@supabase/auth-helpers-nextjs": "^0.5.6",
"@supabase/auth-helpers-react": "^0.3.1",
@@ -51542,6 +51581,7 @@
"ui": "*",
"unist-builder": "^3.0.1",
"unist-util-filter": "^4.0.1",
+ "uuid": "^9.0.0",
"valtio": "^1.7.6"
},
"dependencies": {
@@ -51877,6 +51917,11 @@
"unist-util-is": "^4.0.0"
}
},
+ "uuid": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+ "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
+ },
"valtio": {
"version": "1.7.6",
"requires": {
@@ -66373,7 +66418,7 @@
"config": "*",
"configcat-js": "^7.0.0",
"dayjs": "^1.11.0",
- "eslint-config-next": "*",
+ "eslint-config-next": "^13.3.0",
"file-saver": "^2.0.5",
"generate-password": "^1.7.0",
"graphql": "^16.6.0",
@@ -68050,6 +68095,7 @@
"@tailwindcss/typography": "^0.5.0",
"@types/common-tags": "^1.8.1",
"@types/react": "^17.0.37",
+ "@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^17.0.11",
"autoprefixer": "^10.4.2",
"clsx": "^1.2.1",
@@ -68063,6 +68109,7 @@
"postcss": "^8.4.5",
"prop-types": "^15.7.2",
"react-copy-to-clipboard": "^5.1.0",
+ "react-countdown": "^2.3.5",
"react-feather": "^2.0.10",
"react-markdown": "^8.0.3",
"react-syntax-highlighter": "^15.5.0",
@@ -68094,6 +68141,15 @@
"csstype": "^3.0.2"
}
},
+ "@types/react-copy-to-clipboard": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz",
+ "integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"clsx": {
"version": "1.2.1"
},
@@ -69822,7 +69878,6 @@
"postcss-preset-env": "^6.7.0",
"react": "^17.0.2",
"react-copy-to-clipboard": "^5.0.2",
- "react-countdown": "^2.3.5",
"react-dom": "^17.0.2",
"react-ga": "^3.3.0",
"react-markdown": "^8.0.3",
diff --git a/packages/common/package.json b/packages/common/package.json
index 7a654b57ef3..b3677fd5333 100644
--- a/packages/common/package.json
+++ b/packages/common/package.json
@@ -6,7 +6,7 @@
"license": "MIT",
"dependencies": {
"@headlessui/react": "^1.5.0",
- "@supabase/gotrue-js": "^2.16.0",
+ "@supabase/gotrue-js": "2.24.0",
"@supabase/ui": "^0.37.0-alpha.50",
"react-use": "^17.4.0"
},
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 97a84f67f4e..5423c0e5df5 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -42,15 +42,17 @@
"sse.js": "^0.6.1",
"tailwind-merge": "^1.10.0",
"tailwindcss": "^3.0.15",
- "tailwindcss-radix": "^1.6.0"
+ "tailwindcss-radix": "^1.6.0",
+ "react-countdown": "^2.3.5"
},
"devDependencies": {
"@storybook/react": "^6.5.16",
- "storybook-dark-mode": "^2.1.1",
"@types/common-tags": "^1.8.1",
"@types/react": "^17.0.37",
+ "@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^17.0.11",
"sql-formatter": "^12.2.0",
+ "storybook-dark-mode": "^2.1.1",
"typescript": "4.3.4"
}
}
diff --git a/packages/ui/src/components/Command/DocsSearch.tsx b/packages/ui/src/components/Command/DocsSearch.tsx
index fb643fde44d..22e1cf54c0b 100644
--- a/packages/ui/src/components/Command/DocsSearch.tsx
+++ b/packages/ui/src/components/Command/DocsSearch.tsx
@@ -7,7 +7,9 @@ import {
IconAlertTriangle,
IconBook,
IconChevronRight,
+ IconGitHub,
IconHash,
+ IconMessageSquare,
IconSearch,
useCommandMenu,
} from 'ui'
@@ -27,6 +29,7 @@ const questions = [
export enum PageType {
Markdown = 'markdown',
Reference = 'reference',
+ GithubDiscussion = 'github-discussions',
}
export interface PageSection {
@@ -163,14 +166,11 @@ const DocsSearch = () => {
value={`${page.meta.title}-item-index-${i}`}
type="block-link"
onSelect={() => {
- // TODO: replace with Next.js router/Link when cross-project link logic solved
- window.location.assign(`/docs/${page.path}`)
+ openLink(page.type, formatPageUrl(page))
}}
>
-
-
-
+
{getPageIcon(page)}
@@ -190,21 +190,14 @@ const DocsSearch = () => {
forceMount
className="ml-3 mb-3"
onSelect={() => {
- // TODO: replace with Next.js router/Link when cross-project link logic solved
- window.location.assign(
- `/docs/${page.path}${page.type === PageType.Reference ? '/' : '#'}${
- section.slug
- }`
- )
+ openLink(page.type, formatSectionUrl(page, section))
}}
key={`${page.meta.title}__${section.heading}-item-index-${i}`}
value={`${page.meta.title}__${section.heading}-item-index-${i}`}
type="block-link"
>
-
-
-
+
{getPageSectionIcon(page)}
{
}
export default DocsSearch
+
+export function formatPageUrl(page: PageResult) {
+ switch (page.type) {
+ case PageType.Markdown:
+ case PageType.Reference:
+ return `/docs/${page.path}`
+ case PageType.GithubDiscussion:
+ return page.path
+ default:
+ throw new Error(`Unknown page type '${page.type}'`)
+ }
+}
+
+export function formatSectionUrl(page: PageResult, section: PageSection) {
+ switch (page.type) {
+ case PageType.Markdown:
+ case PageType.GithubDiscussion:
+ return `${formatPageUrl(page)}#${section.slug}`
+ case PageType.Reference:
+ return `${formatPageUrl(page)}/${section.slug}`
+ default:
+ throw new Error(`Unknown page type '${page.type}'`)
+ }
+}
+
+export function getPageIcon(page: PageResult) {
+ switch (page.type) {
+ case PageType.Markdown:
+ case PageType.Reference:
+ return
+ case PageType.GithubDiscussion:
+ return
+ default:
+ throw new Error(`Unknown page type '${page.type}'`)
+ }
+}
+
+export function getPageSectionIcon(page: PageResult) {
+ switch (page.type) {
+ case PageType.Markdown:
+ case PageType.Reference:
+ return
+ case PageType.GithubDiscussion:
+ return
+ default:
+ throw new Error(`Unknown page type '${page.type}'`)
+ }
+}
+
+export function openLink(pageType: PageType, link: string) {
+ switch (pageType) {
+ case PageType.Markdown:
+ case PageType.Reference:
+ return window.location.assign(link)
+ case PageType.GithubDiscussion:
+ return window.open(link, '_blank')
+ default:
+ throw new Error(`Unknown page type '${pageType}'`)
+ }
+}
diff --git a/packages/ui/src/components/SidePanel/SidePanel.tsx b/packages/ui/src/components/SidePanel/SidePanel.tsx
index 6dc69c307e9..25f4b2214dc 100644
--- a/packages/ui/src/components/SidePanel/SidePanel.tsx
+++ b/packages/ui/src/components/SidePanel/SidePanel.tsx
@@ -109,7 +109,11 @@ const SidePanel = ({
onCloseAutoFocus={props.onCloseAutoFocus}
onEscapeKeyDown={props.onEscapeKeyDown}
onPointerDownOutside={props.onPointerDownOutside}
- onInteractOutside={props.onInteractOutside}
+ onInteractOutside={(event) => {
+ const isToast = (event.target as Element)?.closest('#toast')
+ if (isToast) event.preventDefault()
+ if (props.onInteractOutside) props.onInteractOutside(event)
+ }}
>
{header && }
{children}
diff --git a/spec/cli_v1_commands.yaml b/spec/cli_v1_commands.yaml
index 05f64930410..2a340a9b0b4 100644
--- a/spec/cli_v1_commands.yaml
+++ b/spec/cli_v1_commands.yaml
@@ -1,7 +1,7 @@
-elispec: '001'
+clispec: '001'
info:
id: cli
- version: 1.42.0
+ version: 1.50.13
title: Supabase CLI
language: sh
source: https://github.com/supabase/cli
@@ -405,6 +405,25 @@ commands:
- id: supabase-test-db
title: supabase test db
summary: Tests local database with pgTAP
+ description: |2
+
+ Executes pgTAP tests against the local database.
+
+ Requires the local development stack to be started by running `supabase start`.
+
+ Runs `pg_prove` in a container with unit test files volume mounted from `supabase/tests` directory. The test file can be suffixed by either `.sql` or `.pg` extension.
+
+ Since each test is wrapped in its own transaction, it will be individually rolled back regardless of success or failure.
+ examples:
+ - id: basic-usage
+ name: Basic usage
+ code: supabase test db
+ response: |
+ /tmp/supabase/tests/nested/order_test.pg .. ok
+ /tmp/supabase/tests/pet_test.sql .......... ok
+ All tests successful.
+ Files=2, Tests=2, 6 wallclock secs ( 0.03 usr 0.01 sys + 0.05 cusr 0.02 csys = 0.11 CPU)
+ Result: PASS
tags: []
links: []
usage: supabase test db
@@ -460,7 +479,8 @@ commands:
response: |
supabase local development setup is running.
- API URL: http://localhost:54321
+ API URL: http://localhost:54321
+ GraphQL URL: http://localhost:54321/graphql/v1
DB URL: postgresql://postgres:postgres@localhost:54322/postgres
Studio URL: http://localhost:54323
Inbucket URL: http://localhost:54324
@@ -474,6 +494,7 @@ commands:
ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"
API_URL="http://localhost:54321"
DB_URL="postgresql://postgres:postgres@localhost:54322/postgres"
+ GRAPHQL_URL="http://localhost:54321/graphql/v1"
INBUCKET_URL="http://localhost:54324"
JWT_SECRET="super-secret-jwt-token-with-at-least-32-characters-long"
SERVICE_ROLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU"
@@ -485,6 +506,7 @@ commands:
Stopped services: [supabase_inbucket_cli supabase_rest_cli supabase_studio_cli]
SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"
DB_URL="postgresql://postgres:postgres@localhost:54322/postgres"
+ GRAPHQL_URL="http://localhost:54321/graphql/v1"
JWT_SECRET="super-secret-jwt-token-with-at-least-32-characters-long"
SUPABASE_SERVICE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU"
tags:
@@ -494,25 +516,25 @@ commands:
subcommands: []
flags:
- id: output
- name: -o, --output <[ env | json | pretty | toml | yaml ]>
+ name: -o, --output <[ env | pretty | json | toml | yaml ]>
description: Output format of status variables.
default_value: pretty
accepted_values:
- id: env
name: env
- type: '[ env | json | pretty | toml | yaml ]'
- - id: json
- name: json
- type: '[ env | json | pretty | toml | yaml ]'
+ type: '[ env | pretty | json | toml | yaml ]'
- id: pretty
name: pretty
- type: '[ env | json | pretty | toml | yaml ]'
+ type: '[ env | pretty | json | toml | yaml ]'
+ - id: json
+ name: json
+ type: '[ env | pretty | json | toml | yaml ]'
- id: toml
name: toml
- type: '[ env | json | pretty | toml | yaml ]'
+ type: '[ env | pretty | json | toml | yaml ]'
- id: yaml
name: yaml
- type: '[ env | json | pretty | toml | yaml ]'
+ type: '[ env | pretty | json | toml | yaml ]'
- id: override-name
name: --override-name
description: Override specific variable names.
@@ -536,6 +558,7 @@ commands:
name: Basic usage
code: supabase start
response: |
+ Creating custom roles supabase/roles.sql...
Applying migration 20220810154536_employee.sql...
Seeding data supabase/seed.sql...
Started supabase local development setup.
@@ -545,13 +568,11 @@ commands:
response: |
Excluding container: supabase/studio:20221214-4eecc99
Excluding container: darthsim/imgproxy:v3.8.0
- Seeding data supabase/seed.sql...
Started supabase local development setup.
- id: ignore-health-check
name: Ignore service health checks
code: supabase start --ignore-health-check
response: |
- Seeding data supabase/seed.sql...
service not healthy: [supabase_storage_cli]
Started supabase local development setup.
tags:
@@ -563,7 +584,7 @@ commands:
- id: exclude
name: -x, --exclude
description: |
- Names of containers to not start. [gotrue,realtime,storage-api,imgproxy,kong,inbucket,postgrest,pgadmin-schema-diff,migra,postgres-meta,studio,deno-relay]
+ Names of containers to not start. [gotrue,realtime,storage-api,imgproxy,kong,inbucket,postgrest,pgadmin-schema-diff,migra,postgres-meta,studio,edge-runtime,logflare,vector]
default_value: '[]'
- id: ignore-health-check
name: --ignore-health-check
@@ -759,6 +780,9 @@ commands:
- id: us-west-1
name: us-west-1
type: string
+ - id: us-west-2
+ name: us-west-2
+ type: string
- id: supabase-orgs
title: supabase orgs
summary: Manage Supabase organizations
@@ -1089,19 +1113,19 @@ commands:
name: Basic usage
code: supabase link --project-ref ********************
response: |
- Enter your database password: ********
+ Enter your database password (or leave blank to skip): ********
Finished supabase link.
- id: without-password
name: Link without database password
code: supabase link --project-ref ******************** <<< ""
response: |
- Enter your database password:
+ Enter your database password (or leave blank to skip):
Finished supabase link.
- id: using-alternate-dns
name: Link using DNS-over-HTTPS resolver
code: supabase link --project-ref ******************** --dns-resolver https
response: |
- Enter your database password:
+ Enter your database password (or leave blank to skip):
Finished supabase link.
tags:
- local-dev
@@ -1429,6 +1453,25 @@ commands:
- id: supabase-db-reset
title: supabase db reset
summary: Resets the local database to current migrations
+ description: |2
+
+ Resets the local database to a clean state.
+
+ Requires the local development stack to be started by running `supabase start`.
+
+ Recreates the local Postgres container and applies all local migrations found in `supabase/migrations` directory. If test data is defined in `supabase/seed.sql`, it will be seeded after the migrations are run. Any other data or schema changes made during local development will be discarded.
+
+ Note that since Postgres roles are cluster level entities, those changes will persist between resets. In order to reset custom roles, you need to restart the local development stack.
+ examples:
+ - id: basic-usage
+ name: Basic usage
+ code: supabase db reset
+ response: |
+ Resetting database...
+ Initializing schema...
+ Applying migration 20220810154537_create_employees_table.sql...
+ Seeding data supabase/seed.sql...
+ Finished supabase db reset on branch main.
tags: []
links: []
usage: supabase db reset
@@ -1469,6 +1512,37 @@ commands:
- id: supabase-db-push
title: supabase db push
summary: Push new migrations to the remote database
+ description: |2
+
+ Pushes all local migrations to a remote database.
+
+ Requires your local project to be linked to a remote database by running `supabase link`. For self-hosted databases, you can pass in the connection parameters using `--db-url` flag.
+
+ The first time this command is run, a migration history table will be created under `supabase_migrations.schema_migrations`. After successfully applying a migration, a new row will be inserted into the migration history table with timestamp as its unique id. Subsequent pushes will skip migrations that have already been applied.
+
+ If you need to mutate the migration history table, such as deleting existing entries or inserting new entries without actually running the migration, use the `migration repair` command.
+
+ Use the `--dry-run` flag to view the list of changes before applying.
+ examples:
+ - id: basic-usage
+ name: Basic usage
+ code: supabase db push
+ response: |
+ Linked project is up to date.
+ - id: self-hosted
+ name: Self hosted
+ code: supabase db push --db-url "postgres://user:pass@localhost:5432/postgres"
+ response: |
+ Pushing migration 20230410135622_create_employees_table.sql...
+ Finished supabase db push.
+ - id: dry-run
+ name: Dry run
+ code: supabase db push --dry-run
+ response: |
+ DRY RUN: migrations will *not* be pushed to the database.
+ Would push migration 20230410135622_create_employees_table.sql...
+ Would push migration 20230425064254_my_table.sql...
+ Finished supabase db push.
tags: []
links: []
usage: supabase db push [flags]
@@ -1490,6 +1564,40 @@ commands:
- id: supabase-db-lint
title: supabase db lint
summary: Checks local database for typing error
+ description: |2
+
+ Lints local database for schema errors.
+
+ Requires the local development stack to be running when linting against the local database. To lint against a remote or self-hosted database, specify the `--linked` or `--db-url` flag respectively.
+
+ Runs `plpgsql_check` extension in the local Postgres container to check for errors in all schemas. The default lint level is `warning` and can be raised to error via the `--level` flag.
+
+ To lint against specific schemas only, pass in the `--schema` flag.
+ examples:
+ - id: basic-usage
+ name: Basic usage
+ code: supabase db lint
+ response: |
+ Linting schema: public
+
+ No schema errors found
+ - id: schema-warnings
+ name: Warnings for a specific schema
+ code: supabase db lint --level warning --schema storage
+ response: |
+ Linting schema: storage
+ [
+ {
+ "function": "storage.search",
+ "issues": [
+ {
+ "level": "warning",
+ "message": "unused variable \"_bucketid\"",
+ "sqlState": "00000"
+ }
+ ]
+ }
+ ]
tags: []
links: []
usage: supabase db lint [flags]
@@ -1517,6 +1625,34 @@ commands:
- id: supabase-db-dump
title: supabase db dump
summary: Dumps data or schemas from the remote database
+ description: |2
+
+ Dumps contents from a remote database.
+
+ Requires your local project to be linked to a remote database by running `supabase link`. For self-hosted databases, you can pass in the connection parameters using `--db-url` flag.
+
+ Runs `pg_dump` in a container with additional flags to exclude Supabase managed schemas. The ignored schemas include auth, stroage, and those created by extensions.
+
+ The default dump does not contain any data or custom roles. To dump those contents explicitly, specify either the `--data-only` and `--role-only` flag.
+ examples:
+ - id: basic-usage
+ name: Basic usage
+ code: supabase db dump -f supabase/schema.sql
+ response: |
+ Dumping schemas from remote database...
+ Dumped schema to supabase/schema.sql.
+ - id: role-only
+ name: Role only
+ code: supabase db dump -f supabase/roles.sql --role-only
+ response: |
+ Dumping roles from remote database...
+ Dumped schema to supabase/roles.sql.
+ - id: data-only
+ name: Data only
+ code: supabase db dump -f supabase/seed.sql --data-only
+ response: |
+ Dumping data from remote database...
+ Dumped schema to supabase/seed.sql.
tags: []
links: []
usage: supabase db dump [flags]
@@ -1545,6 +1681,54 @@ commands:
- id: supabase-db-diff
title: supabase db diff
summary: Diffs the local database for schema changes
+ description: |2
+
+ Diffs schema changes made to the local or remote database.
+
+ Requires the local development stack to be running when diffing against the local database. To diff against a remote or self-hosted database, specify the `--linked` or `--db-url` flag respectively.
+
+ Runs [djrobstep/migra](https://github.com/djrobstep/migra) in a container to compare schema differences between the target database and a shadow database. The shadow database is created by applying migrations in local `supabase/migrations` directory in a separate container. Output is written to stdout by default. For convenience, you can also save the schema diff as a new migration file by passing in `-f` flag.
+
+ By default, all schemas in the target database are diffed. Use the `--schema public,extensions` flag to restrict diffing to a subset of schemas.
+
+ While the diff command is able to capture most schema changes, there are cases where it is known to fail. Currently, this could happen if you schema contains:
+
+ - Changes to publication
+ - Changes to storage buckets
+ - Views with `security_invoker` attributes
+ examples:
+ - id: basic-usage
+ name: Basic usage
+ code: supabase db diff -f my_table
+ response: |
+ Connecting to local database...
+ Creating shadow database...
+ Applying migration 20230425064254_remote_commit.sql...
+ Diffing schemas: auth,extensions,public,storage
+ Finished supabase db diff on branch main.
+
+ No schema changes found
+ - id: linked-project
+ name: Against linked project
+ code: supabase db diff -f my_table --linked
+ response: |
+ Connecting to local database...
+ Creating shadow database...
+ Diffing schemas: auth,extensions,public,storage
+ Finished supabase db diff on branch main.
+
+ WARNING: The diff tool is not foolproof, so you may need to manually rearrange and modify the generated migration.
+ Run supabase db reset to verify that the new migration does not generate errors.
+ - id: specific-schema
+ name: For a specific schema
+ code: supabase db diff -f my_table --schema auth
+ response: |
+ Connecting to local database...
+ Creating shadow database...
+ Diffing schemas: auth
+ Finished supabase db diff on branch main.
+
+ No schema changes found
tags: []
links: []
usage: supabase db diff [flags]
@@ -1602,7 +1786,7 @@ commands:
To load completions in your current shell session:
- source <(supabase completion zsh); compdef _supabase supabase
+ source <(supabase completion zsh)
To load completions for every new session, execute once:
diff --git a/studio/components/interfaces/Auth/Policies/PolicyEditorModal/index.tsx b/studio/components/interfaces/Auth/Policies/PolicyEditorModal/index.tsx
index 7e77f5285a0..81bb78295db 100644
--- a/studio/components/interfaces/Auth/Policies/PolicyEditorModal/index.tsx
+++ b/studio/components/interfaces/Auth/Policies/PolicyEditorModal/index.tsx
@@ -183,12 +183,6 @@ const PolicyEditorModal: FC = ({
/>,
]}
onCancel={onSelectCancel}
- onInteractOutside={(event) => {
- const isToast = (event.target as Element)?.closest('#toast')
- if (isToast) {
- event.preventDefault()
- }
- }}
>
{view === POLICY_MODAL_VIEWS.SELECTION ? (
diff --git a/studio/components/interfaces/Functions/FunctionLogsItem.tsx b/studio/components/interfaces/Functions/FunctionLogsItem.tsx
deleted file mode 100644
index 88e17dc9ae9..00000000000
--- a/studio/components/interfaces/Functions/FunctionLogsItem.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import dayjs from 'dayjs'
-import { FC } from 'react'
-import { observer } from 'mobx-react-lite'
-import { Badge, IconClipboard, IconExternalLink, IconGlobe } from 'ui'
-import Table from 'components/to-be-cleaned/Table'
-
-import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock'
-import router, { useRouter } from 'next/router'
-// import logDropdown from './logDropdown'
-
-interface Props {
- log: any
- onClick: () => void
-}
-
-const FunctionLogsItem: FC
= ({ log, onClick }) => {
- const islogConfirmed = log.email_confirmed_at || log.phone_confirmed_at
-
- const router = useRouter()
- const { ref } = router.query
-
- return (
-
-
-
-
{log.function_status}
-
-
-
- {log.method}
-
-
- {log.url}
-
-
-
- {dayjs(log.created_at).format('DD MMM, YYYY HH:mm')}
-
-
-
- )
-}
-
-export default observer(FunctionLogsItem)
diff --git a/studio/components/interfaces/SQLEditor/UtilityPanel/Results.tsx b/studio/components/interfaces/SQLEditor/UtilityPanel/Results.tsx
index 6d5a99c57e1..892aa1d1495 100644
--- a/studio/components/interfaces/SQLEditor/UtilityPanel/Results.tsx
+++ b/studio/components/interfaces/SQLEditor/UtilityPanel/Results.tsx
@@ -75,14 +75,6 @@ const Results = ({ id, rows }: { id: string; rows: readonly any[] }) => {
)
}
- if (rows.length <= 0) {
- return (
-
-
Success. No rows returned
-
- )
- }
-
return (
<>
= ({ item }: Props) => {
- {dayjs(item.inserted_at).format('DD MMM, YYYY HH:mm')}
+ {timestampLocalFormatter(item.inserted_at)}
- {dayjs(item.updated_at).format('DD MMM, YYYY HH:mm')}
+ {timestampLocalFormatter(item.updated_at)}
diff --git a/studio/components/interfaces/Settings/Logs/LogsFormatters.tsx b/studio/components/interfaces/Settings/Logs/LogsFormatters.tsx
index b7ebaa0fdd5..6ea93cd06f8 100644
--- a/studio/components/interfaces/Settings/Logs/LogsFormatters.tsx
+++ b/studio/components/interfaces/Settings/Logs/LogsFormatters.tsx
@@ -235,9 +235,16 @@ export const TimestampLocalFormatter = ({
className?: string
value: string | number
}) => {
+ return {timestampLocalFormatter(value)}
+}
+
+/**
+ * Formats a string to local timestamp display
+ * Accepts unix microsecond or iso timestamp
+ */
+export const timestampLocalFormatter = (value: string | number) => {
const timestamp = isUnixMicro(value) ? unixMicroToIsoTimestamp(value) : value
- const formattedTimestamp = dayjs(timestamp).format('DD MMM, HH:mm:ss')
- return {formattedTimestamp}
+ return dayjs(timestamp).format('DD MMM HH:mm:ss')
}
/*
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx
index bffc2850680..a82e5d41a4d 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx
@@ -198,10 +198,6 @@ const ColumnEditor = ({
applyFunction={(resolve: () => void) => onSaveChanges(resolve)}
/>
}
- onInteractOutside={(event) => {
- const isToast = (event.target as Element)?.closest('#toast')
- if (isToast) event.preventDefault()
- }}
>
General}>
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx
index 5866545dff8..669c8a0a699 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx
@@ -143,10 +143,6 @@ const RowEditor = ({
isEditingJson || isSelectingForeignKey ? ' mr-32' : ''
}`}
onCancel={closePanel}
- onInteractOutside={(event) => {
- const isToast = (event.target as Element)?.closest('#toast')
- if (isToast) event.preventDefault()
- }}
>