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 &&
{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() - }} >
onSaveChanges(e)} className="h-full">
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx index 6fbcb1e4a4d..ff985a5ecb0 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx @@ -192,10 +192,6 @@ const SpreadsheetImport: FC = ({ applyFunction={onConfirm} /> } - onInteractOutside={(event) => { - const isToast = (event.target as Element)?.closest('#toast') - if (isToast) event.preventDefault() - }} >
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx index 160620babbb..d059fe4cec2 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx @@ -183,12 +183,6 @@ const TableEditor: FC = ({ applyFunction={(resolve: () => void) => onSaveChanges(resolve)} /> } - onInteractOutside={(event) => { - const isToast = (event.target as Element)?.closest('#toast') - if (isToast) { - event.preventDefault() - } - }} > <> diff --git a/supabase/migrations/20230421193603_page_version.sql b/supabase/migrations/20230421193603_page_version.sql new file mode 100644 index 00000000000..bc48f4dcb62 --- /dev/null +++ b/supabase/migrations/20230421193603_page_version.sql @@ -0,0 +1,3 @@ +alter table "public"."page" +add "version" uuid, +add "last_refresh" timestamptz;