diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml deleted file mode 100644 index 0c9a3c46ebd..00000000000 --- a/.github/workflows/integration-tests.yml +++ /dev/null @@ -1,92 +0,0 @@ -name: Tests - -# Controls when the workflow will run -on: - schedule: - - cron: '0 4/6 * * *' - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - autotests: - name: Run tests and generate Allure Report - strategy: - matrix: - node: ['14'] - - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./tests - - steps: - - uses: actions/checkout@v2 - - - name: Set up JDK - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - - name: Set up Node - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node }} - - - name: Install dependencies - run: npm ci - - - uses: supabase/setup-cli@v1 - - run: supabase start - - - name: Run Test - run: npm run test:local - env: - SUPABASE_DB_PORT: 54322 - SUPABASE_DB_PASS: postgres - SUPABASE_DB_HOST: localhost - SUPABASE_GOTRUE: http://localhost:54321 - SUPABASE_URL: http://localhost:54321 - SUPABASE_KEY_ANON: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs - SUPABASE_KEY_ADMIN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU - - - name: Stop infrastructure - if: always() - run: supabase stop - - - name: Get Allure history - uses: actions/checkout@v2 - if: always() - continue-on-error: true - with: - repository: supabase/test-reports - ref: gh-pages - path: gh-pages - - - name: Allure Report action - uses: simple-elf/allure-report-action@master - if: always() - with: - github_repo: test-reports - allure_results: tests/allure-results - allure_history: allure-history - subfolder: supabase-integration - keep_reports: 50 - - - name: Deploy report to Github Pages - if: always() - uses: peaceiris/actions-gh-pages@v2 - env: - EXTERNAL_REPOSITORY: supabase/test-reports - ACTIONS_DEPLOY_KEY: ${{ secrets.DEPLOY_TO_TEST_REPORTS_KEY }} - PUBLISH_BRANCH: gh-pages - PUBLISH_DIR: allure-history - - - name: Post the link to the report - if: always() - uses: Sibz/github-status-action@v1 - with: - authToken: ${{ secrets.GITHUB_TOKEN }} - context: 'Test report' - state: 'success' - sha: ${{ github.event.pull_request.head.sha || github.sha }} - target_url: https://supabase.github.io/test-reports/supabase-integration/${{ github.run_number }} diff --git a/.github/workflows/og_images.yml b/.github/workflows/og_images.yml new file mode 100644 index 00000000000..2e1461461d2 --- /dev/null +++ b/.github/workflows/og_images.yml @@ -0,0 +1,28 @@ +name: Deploy OG Images + +on: + push: + branches: + - main + paths: + - 'supabase/functions/og-images' + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + PROJECT_ID: ${{ secrets.PROJECT_ID }} + + steps: + - name: Check out repo + - uses: actions/checkout@v3 + + - name: Setup the Supabase CLI + - uses: supabase/setup-cli@v1 + with: + version: 1.0.0 + + - run: supabase functions deploy og-images --project-ref $PROJECT_ID diff --git a/.github/workflows/studio-build.yml b/.github/workflows/studio-build.yml index 570baa1aa61..35d1f97b8b1 100644 --- a/.github/workflows/studio-build.yml +++ b/.github/workflows/studio-build.yml @@ -7,6 +7,7 @@ on: - studio paths: - 'studio/**' + - 'package-lock.json' jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/studio-tests.yml b/.github/workflows/studio-tests.yml index 1a99a50182d..72a84549568 100644 --- a/.github/workflows/studio-tests.yml +++ b/.github/workflows/studio-tests.yml @@ -8,10 +8,12 @@ on: branches: [master] paths: - 'studio/**' + - 'package-lock.json' pull_request: branches: [master] paths: - 'studio/**' + - 'package-lock.json' jobs: build: runs-on: ubuntu-latest diff --git a/apps/docs/components/Extensions.js b/apps/docs/components/Extensions.js index 47753d6bfde..d03780463c8 100644 --- a/apps/docs/components/Extensions.js +++ b/apps/docs/components/Extensions.js @@ -1,33 +1,37 @@ +import Link from 'next/link' import React, { useState } from 'react' +import { GlassPanel, IconSearch, Input } from 'ui' import extensions from '../data/extensions.json' export default function Extensions() { const [filter, setFilter] = useState('') return ( <> -
- - + } + value={filter} onChange={(e) => setFilter(e.target.value)} />
-
+
{extensions .filter((x) => x.name.indexOf(filter) >= 0) .map((extension) => ( -
-
-

- {extension.name} -

-

- {extension.comment.charAt(0).toUpperCase() + extension.comment.slice(1)} -

-
-
+ + + {extension.name}} + background={false} + key={extension.name} + > +

+ {extension.comment.charAt(0).toUpperCase() + extension.comment.slice(1)} +

+
+
+ ))}
diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 08cea106b45..7385afd2d3d 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -399,16 +399,41 @@ export const database = { items: [], }, { name: 'http: RESTful Client', url: '/guides/database/extensions/http', items: [] }, + { + name: 'PGAudit: Postgres Auditing', + url: '/guides/database/extensions/pgaudit', + items: [], + }, + { + name: 'pgjwt: JSON Web Tokens', + url: '/guides/database/extensions/pgjwt', + items: [], + }, { name: 'PGroonga: Multilingual Full Text Search', url: '/guides/database/extensions/pgroonga', items: [], }, + { + name: 'pgRouting: Geospatial Routing', + url: '/guides/database/extensions/pgrouting', + items: [], + }, { name: 'pg_cron: Job Scheduling', url: '/guides/database/extensions/pgcron', items: [], }, + { + name: 'pg_graphql: GraphQL Support', + url: '/guides/database/extensions/pg_graphql', + items: [], + }, + { + name: 'pg_hashids: Short UIDs', + url: '/guides/database/extensions/pg_hashids', + items: [], + }, { name: 'pg_jsonschema: JSON Schema Validation', url: '/guides/database/extensions/pg_jsonschema', @@ -419,6 +444,16 @@ export const database = { url: '/guides/database/extensions/pgnet', items: [], }, + { + name: 'pg_plan_filter: Restrict Total Cost', + url: '/guides/database/extensions/pg_plan_filter', + items: [], + }, + { + name: 'pg_stat_monitor: Extended Query Performance Monitoring', + url: '/guides/database/extensions/pg_stat_monitor', + items: [], + }, { name: 'pgvector: Embeddings and vector similarity', url: '/guides/database/extensions/pgvector', @@ -439,7 +474,27 @@ export const database = { url: '/guides/database/extensions/postgis', items: [], }, + { + name: 'pg-safeupdate: Required Where Clauses', + url: '/guides/database/extensions/pg-safeupdate', + items: [], + }, + { + name: 'pgsodium: Encryption Features', + url: '/guides/database/extensions/pgsodium', + items: [], + }, { name: 'pgTAP: Unit Testing', url: '/guides/database/extensions/pgtap', items: [] }, + { + name: 'plpgsql_check: PL/pgSQL Linter', + url: '/guides/database/extensions/plpgsql_check', + items: [], + }, + { + name: 'timescaledb: Time-series data', + url: '/guides/database/extensions/timescaledb', + items: [], + }, { name: 'uuid-ossp: Unique Identifiers', url: '/guides/database/extensions/uuid-ossp', @@ -450,6 +505,11 @@ export const database = { url: '/guides/database/extensions/rum', items: [], }, + { + name: 'wrappers: 3rd Party Integrations', + url: '/guides/database/extensions/wrappers', + items: [], + }, ], }, { diff --git a/apps/docs/data/authProviders.ts b/apps/docs/data/authProviders.ts index 44958476ba6..ea5855061bd 100644 --- a/apps/docs/data/authProviders.ts +++ b/apps/docs/data/authProviders.ts @@ -107,7 +107,7 @@ const authProviders = [ supporter: 'MessageBird', platform: true, selfHosted: true, - authType: 'phone' + authType: 'phone', }, { name: 'Notion', diff --git a/apps/docs/pages/guides/database/extensions/http.mdx b/apps/docs/pages/guides/database/extensions/http.mdx index 11ac8e55d41..7f686ed8e7b 100644 --- a/apps/docs/pages/guides/database/extensions/http.mdx +++ b/apps/docs/pages/guides/database/extensions/http.mdx @@ -63,7 +63,7 @@ drop extension if exists http; Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension". To disable an extension, call `drop extension`. -It's good practice to create the extension within a separate schema (like `extensions`) to keep your database clean. +It's good practice to create the extension within a separate schema (like `extensions`) to keep the `public` schema clean. diff --git a/apps/docs/pages/guides/database/extensions/hypopg.mdx b/apps/docs/pages/guides/database/extensions/hypopg.mdx index c493f9f89cd..18688e276e4 100644 --- a/apps/docs/pages/guides/database/extensions/hypopg.mdx +++ b/apps/docs/pages/guides/database/extensions/hypopg.mdx @@ -7,11 +7,10 @@ export const meta = { } `HypoPG` is PostgreSQL extension for creating hypothetical/virtual indexes. HypoPG allows users to rapidly create hypothetical/virtual indexes that have no resource cost (CPU, disk, memory) that are visible to the PostgreSQL query planner. -That allows users to quickly search for an index to improve a slow query without waiting for them to build. -## Usage +The motivation for HypoPG is to allow users to quickly search for an index to improve a slow query without consuming server resources or waiting for them to build. -### Enable the extension +## Enable the extension ### Speeding up a query -Given the following table and a simple query to select from the table by id: +Given the following table and a simple query to select from the table by `id`: {/* prettier-ignore */} ```sql diff --git a/apps/docs/pages/guides/database/extensions/pg-safeupdate.mdx b/apps/docs/pages/guides/database/extensions/pg-safeupdate.mdx new file mode 100644 index 00000000000..33773977b55 --- /dev/null +++ b/apps/docs/pages/guides/database/extensions/pg-safeupdate.mdx @@ -0,0 +1,60 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'pg-safeupdate', + title: 'pg-safeupdate: Required Where Clauses', + description: 'Requires a where clause for update and delete', +} + +[pg-safeupdate](https://github.com/eradman/pg-safeupdate) is a PostgreSQL extension designed to prevent users from accidentally updating or deleting too many records in a single statement by requiring a "where" clause in all update and delete statements. + +The `pg-safeupdate` extension is a useful tool for protecting data integrity and preventing accidental data loss. Without it, a user could accidentally execute an update or delete statement that affects all records in a table. With `pg-safeupdate`, users are required to be more deliberate in their update and delete statements, which reduces the risk of significant error. + +## Enable the extension + +`pg-safeupdate` can be enabled on a per connection basis: + +{/* prettier-ignore */} +```sql +load 'safeupdate'; +``` + +or for all connections: + +{/* prettier-ignore */} +```sql +alter database some_db set session_preload_libraries = 'safeupdate'; +``` + +## Usage + +Let's take a look at an example to see how pg-safeupdate works. Suppose we have a table called `employees` with the following columns: `id`, `name`, `department`, and `date_of_birth`. We want to update the `date_of_birth` for a specific employee with the `id` of 12345. Here is what the query would look like if we forgot to add a "where" clause: + +{/* prettier-ignore */} +```sql +load 'safeupdate'; + +update employees set date_of_birth = '1987-01-28'; +``` + +This query updates the `date_of_birth` for all employees to 1987-01-28, which is not what we intended. With `pg-safeupdate` enabled, we receive an error message prompting us to add a "where" clause to the query: + +{/* prettier-ignore */} +```sql +ERROR: UPDATE requires a WHERE clause +``` + +We would then update our query to include a "where" clause specifying the employee with the `id` of 12345: + +{/* prettier-ignore */} +```sql +update employees set date_of_birth = '1987-01-28' where id = 12345; +``` + +## Resources + +- Official [pg-safeupdate documentation](https://github.com/eradman/pg-safeupdate) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/extensions/pg_graphql.mdx b/apps/docs/pages/guides/database/extensions/pg_graphql.mdx new file mode 100644 index 00000000000..a63be68c164 --- /dev/null +++ b/apps/docs/pages/guides/database/extensions/pg_graphql.mdx @@ -0,0 +1,116 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'pg_graphql', + title: 'pg_graphql: GraphQL for PostgreSQL', + description: 'A GraphQL Interface for PostgreSQL', +} + +[pg_graphql](https://supabase.github.io/pg_graphql/) is PostgreSQL extension for interacting with the database using [GraphQL](https://graphql.org) instead of SQL. + +The extension reflects a GraphQL schema from the existing SQL schema and exposes it through a SQL function, `graphql.resolve(...)`. This enables any programming language that can connect to PostgreSQL to query the database via GraphQL with no additional servers, processes, or libraries. + +The `pg_graphql` resolve method is designed to interop with [PostgREST](https://postgrest.org/en/stable/index.html), the tool that underpins the Supabase API, such that the `graphql.resolve` function can be called via RPC to safely and performantly expose the GraphQL API over HTTP/S. + +For more information about how the SQL schema is reflected into a GraphQL schema, see the [pg_graphql API docs](https://supabase.github.io/pg_graphql/api/). + +## Enable the extension + + + + +1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard. +2. Click on **Extensions** in the sidebar. +3. Search for "pg_graphql" and enable the extension. + + + + +{/* prettier-ignore */} +```sql +-- Enable the "pg_graphql" extension +create extension pg_graphql; + +-- Disable the "pg_graphql" extension +drop extension if exists pg_graphql; +``` + +Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension". +To disable an extension you can call `drop extension`. + + + + +## Usage + +Given a table + +{/* prettier-ignore */} +```sql +create table "Blog"( + id serial primary key, + name varchar(255) not null, + description varchar(255), +); + +insert into "Blog"(name) +values ('My Blog'); +``` + +The reflected GraphQL schema can be queries immediately as + +{/* prettier-ignore */} +```sql +select + graphql.resolve($$ + { + blogCollection(first: 1) { + edges { + node { + id, + name + } + } + } + } + $$); +``` + +returning the JSON + +{/* prettier-ignore */} +```json +{ + "data": { + "blogCollection": { + "edges": [ + { + "node": { + "id": 1 + "name": "My Blog" + } + } + ] + } + } +} +``` + +Note that `pg_graphql` fully supports schema introspection so you can connect any GraphQL IDE or schema inspection tool to see the full set of fields and arguments available in the API. + +## API + +- [`graphql.resolve`](https://supabase.github.io/pg_graphql/sql_interface/): A SQL function for executing GraphQL queries. + +## Resources + +- Official [`pg_graphql` documentation](https://github.com/supabase/pg_graphql) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/extensions/pg_hashids.mdx b/apps/docs/pages/guides/database/extensions/pg_hashids.mdx new file mode 100644 index 00000000000..ad02c19b1ff --- /dev/null +++ b/apps/docs/pages/guides/database/extensions/pg_hashids.mdx @@ -0,0 +1,81 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'pg_hashids', + title: 'pg_hashids: Short UIDs', + description: 'Generate Short UIDs from Numbers', +} + +[pg_hashids](https://github.com/iCyberon/pg_hashids) provides a secure way to generate short, unique, non-sequential ids from numbers. The hashes are intended to be small, easy-to-remember identifiers that can be used to obfuscate data (optionally) with a password, alphabet, and salt. For example, you may wish to hide data like user IDs, order numbers, or tracking codes in favor of `pg_hashid`'s unique identifers. + +## Enable the Extension + + + + +1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard. +2. Click on **Extensions** in the sidebar. +3. Search for "pg_hashids" and enable the extension. + + + + +{/* prettier-ignore */} +```sql +-- Enable the "pg_hashids" extension +create extension pg_hashids with schema extensions; + +-- Disable the "pg_hashids" extension +drop extension if exists pg_hashids; +``` + +Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension". +To disable an extension you can call `drop extension`. + +It's good practice to create the extension within a separate schema (like `extensions`) to keep your `public` schema clean. + + + + +## Usage + +Suppose we have a table that stores order information, and we want to give customers a unique identifer without exposing the sequential `id` column. To do this, we can use `pg_hashid`'s `id_encode` function. + +```sql +create table orders ( + id serial primary key, + description text, + price_cents bigint +); + +insert into orders (description, price_cents) +values ('a book', 9095); + +select + id, + id_encode(id) as short_id, + description, + price_cents +from + orders; + + id | short_id | description | price_cents +----+----------+-------------+------------- + 1 | jR | a book | 9095 +(1 row) +``` + +To reverse the `short_id` back into an `id`, there is an equivalent function named `id_decode`. + +## Resources + +- Official [pg_hashids documention](https://github.com/iCyberon/pg_hashids) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/extensions/pg_jsonschema.mdx b/apps/docs/pages/guides/database/extensions/pg_jsonschema.mdx index c51a4c3783e..4cf54f21a19 100644 --- a/apps/docs/pages/guides/database/extensions/pg_jsonschema.mdx +++ b/apps/docs/pages/guides/database/extensions/pg_jsonschema.mdx @@ -6,11 +6,9 @@ export const meta = { description: 'Validate json/jsonb with JSON Schema in PostgreSQL.', } -[JSON Schema](https://json-schema.org) is a language for annotating and validating JSON documents. [`pg_jsonschema`](https://json-schema.org) is a PostgreSQL extension that adds the ability to validate PostgreSQL's built-in `json` and `jsonb` data types against a JSON Schema document. +[JSON Schema](https://json-schema.org) is a language for annotating and validating JSON documents. [`pg_jsonschema`](https://github.com/supabase/pg_jsonschema) is a PostgreSQL extension that adds the ability to validate PostgreSQL's built-in `json` and `jsonb` data types against JSON Schema documents. -## Usage - -### Enable the extension +## Enable the extension @@ -49,9 +47,9 @@ It's good practice to create the extension within a separate schema (like `exten - [`json_matches_schema(schema json, instance json)`](https://github.com/supabase/pg_jsonschema#api): Checks if a `json` _instance_ conforms to a JSON Schema _schema_. - [`jsonb_matches_schema(schema json, instance jsonb)`](https://github.com/supabase/pg_jsonschema#api): Checks if a `jsonb` _instance_ conforms to a JSON Schema _schema_. -### Validating a document +## Usage -Since `pg_jsonschema` exposes its utilities as functions, we can test it with a simple select statement: +Since `pg_jsonschema` exposes its utilities as functions, we can execute them with a select statement: {/* prettier-ignore */} ```sql @@ -62,7 +60,7 @@ select ); ``` -`pg_jsonschema` is generally used in tandem with a [check constraint](https://www.postgresql.org/docs/current/ddl-constraints.html) as a way to constrain the contents of a json/b field to match a JSON Schema. +`pg_jsonschema` is generally used in tandem with a [check constraint](https://www.postgresql.org/docs/current/ddl-constraints.html) as a way to constrain the contents of a json/b column to match a JSON Schema. {/* prettier-ignore */} ```sql diff --git a/apps/docs/pages/guides/database/extensions/pg_plan_filter.mdx b/apps/docs/pages/guides/database/extensions/pg_plan_filter.mdx new file mode 100644 index 00000000000..ab46d931ffe --- /dev/null +++ b/apps/docs/pages/guides/database/extensions/pg_plan_filter.mdx @@ -0,0 +1,97 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'pg_plan_filter', + title: 'pg_plan_filter: Restrict Total Cost', + description: 'Block queries over a total cost limit', +} + +[pg_plan_filter](https://github.com/pgexperts/pg_plan_filter) is PostgreSQL extension to block execution of statements where query planner's estimate of the total cost exceeds a threshold. This is intended to give database administrators a way to restrict the contribution an individual query has on database load. + +## Enable the extension + +`pg_plan_filter` can be enabled on a per connection basis: + +{/* prettier-ignore */} +```sql +load 'plan_filter'; +``` + +or for all connections: + +{/* prettier-ignore */} +```sql +alter database some_db set session_preload_libraries = 'plan_filter'; +``` + +## API + +`plan_filter.statement_cost_limit`: restricts the maximum total cost for executed statements +`plan_filter.limit_select_only`: restricts to `select` statements + +Note that `limit_select_only = true` is not the same as read-only because `select` statements may modfiy data e.g. through a function call. + +## Example + +To demonstrate total cost filtering, we'll compare how `plan_filter.statement_cost_limit` treats queries that are under and over its cost limit. First, we set up a table with some data: + +{/* prettier-ignore */} +```sql +create table book( + id int primary key +); +-- CREATE TABLE + +insert into book(id) select * from generate_series(1, 10000); +-- INSERT 0 10000 +``` + +Next, we can review the explain plans for a single record select, and a whole table select. + +{/* prettier-ignore */} +```sql +explain select * from book where id =1; + QUERY PLAN +--------------------------------------------------------------------------- + Index Only Scan using book_pkey on book (cost=0.28..2.49 rows=1 width=4) + Index Cond: (id = 1) +(2 rows) + +explain select * from book; + QUERY PLAN +--------------------------------------------------------- + Seq Scan on book (cost=0.00..135.00 rows=10000 width=4) +(1 row) +``` + +Now we can choose a `statement_cost_filter` value between the total cost for the single select (2.49) and the whole table select (135.0) so one statement will succeed and one will fail. + +{/* prettier-ignore */} +```sql +load 'plan_filter'; +set plan_filter.statement_cost_limit = 50; -- between 2.49 and 135.0 + +select * from book where id = 1; + id +---- + 1 +(1 row) +-- SUCCESS +``` + +{/* prettier-ignore */} +```sql +select * from book; + +ERROR: plan cost limit exceeded +HINT: The plan for your query shows that it would probably have an excessive run time. This may be due to a logic error in the SQL, or it maybe just a very costly query. Rewrite your query or increase the configuration parameter "plan_filter.statement_cost_limit". +-- FAILURE +``` + +## Resources + +- Official [pg_plan_filter documentation](https://github.com/pgexperts/pg_plan_filter) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/extensions/pg_stat_monitor.mdx b/apps/docs/pages/guides/database/extensions/pg_stat_monitor.mdx new file mode 100644 index 00000000000..4c1363b83b9 --- /dev/null +++ b/apps/docs/pages/guides/database/extensions/pg_stat_monitor.mdx @@ -0,0 +1,88 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'pg_stat_monitor', + title: 'pg_stat_monitor: Query Performance Monitoring', + description: 'Query performance monitoring for PostgreSQL', +} + +[pg_stat_monitor](https://github.com/percona/pg_stat_monitor) is query performance monitoring tool that provides query execution statistics in a SQL view named `pg_stat_monitor`. It tracks a superset of statistics available in [`pg_stat_statements`](https://www.postgresql.org/docs/current/pgstatstatements.html). Some of the most useful features are: + +- **Time Interval Grouping**: configurable time buckets to track query usage over time +- **Capture Parameters**: optionally track parameters passed into queries instead of generic placeholders e.g. `$1` +- **Query Plan**: store query plans used for execution + +For more information on query optimization, check out the [query performance guide](/docs/guides/platform/performance#examining-query-performance). + +## Enable the extension + + + + +1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard. +2. Click on **Extensions** in the sidebar. +3. Search for "pg_stat_monitor" and enable the extension. + + + + +{/* prettier-ignore */} +```sql +-- Enable the "pg_stat_monitor" extension +create extension pg_stat_monitor with schema extensions; + +-- Disable the "pg_stat_monitor" extension +drop extension if exists pg_stat_monitor; +``` + +Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension". +To disable an extension you can call `drop extension`. + +It's good practice to create the extension within a separate schema (like `extensions`) to keep the `public` schema clean. + + + + +## Views + +{/* prettier-ignore */} +```sql +select * from extensions.pg_stat_monitor; +``` + +The following table shows a subset of available columns: + +| Column Type | Description | +| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| bucket bigint | Data collection unit. The number shows what bucket in a chain a record belongs to | +| bucket_start_time timestampz | The start time of the bucket | +| userid oid (references pg_authid.oid) | OID of user who executed the statement | +| dbid oid (references pg_database.oid) | OID of database in which the statement was executed | +| toplevel bool | True if the query was executed as a top-level statement (always true if pg_stat_statements.track is set to top) | +| client_ip inet | The IP address of a client that ran the query | +| queryid bigint | Hash code to identify identical normalized queries. | +| planid text | An internally generated ID of a query plan | +| query_plan text | The sequence of steps used to execute a query. This parameter is only available when pgsm_enable_query_plan is enabled | +| query text | Text of a representative statement | +| plans bigint | Number of times the statement was planned (if pg_stat_statements.track_planning is enabled, otherwise zero) | +| total_plan_time double precision | Total time spent planning the statement, in milliseconds (if pg_stat_statements.track_planning is enabled, otherwise zero) | +| min_plan_time double precision | Minimum time spent planning the statement, in milliseconds (if pg_stat_statements.track_planning is enabled, otherwise zero) | + +A full list of statistics is available in the [pg_stat_monitor docs](https://docs.percona.com/pg-stat-monitor/reference.html#postgresql-15). + +## Functions +- [`pg_stat_monitor_reset()`](https://docs.percona.com/pg-stat-monitor/functions.html): Resets the statistics tracked by the `pg_stat_monitor` view and deletes all previous data. +- [`pg_stat_monitor_version()`](https://docs.percona.com/pg-stat-monitor/functions.html): Displays the version of the `pg_stat_monitor` extension. + +## Resources + +- Official [`pg_stat_monitor` documentation](https://github.com/percona/pg_stat_monitor) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/extensions/pg_stat_statements.mdx b/apps/docs/pages/guides/database/extensions/pg_stat_statements.mdx index 711878d78c0..cb64a901b0c 100644 --- a/apps/docs/pages/guides/database/extensions/pg_stat_statements.mdx +++ b/apps/docs/pages/guides/database/extensions/pg_stat_statements.mdx @@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'pg_stat_statements', - title: 'pg_stat_statements: SQL Planning and Execution Statistics', + title: 'pg_stat_statements: Query Performance Monitoring', description: 'Track planning and execution statistics of all SQL statements executed on the database.', } @@ -22,9 +22,9 @@ export const meta = { A full list of statistics is available in the [pg_stat_statements docs](https://www.postgresql.org/docs/current/pgstatstatements.html). -## Usage +For more information on query optimization, check out the [query performance guide](/docs/guides/platform/performance#examining-query-performance). -### Enable the extension +## Enable the extension -### Inspecting activity +## Inspecting activity A common use for `pg_stat_statements` is to track down expensive or slow queries. The `pg_stat_statements` view contains a row for each executed query with statistics inlined. For example, you can leverage the statistics to identify frequently executed and slow queries against a given table. @@ -74,15 +74,15 @@ select from pg_stat_statements where - calls > 1000 -- at least 50 calls - and mean_exec_time > 2.0 -- averaging at least 2ms/call - and total_exec_time > 60000 -- at least one minute total server time spent - and query ilike '%user_in_organization%' -- filter to queries that touch the user_in_organization table + calls > 50 -- at least 50 calls + and mean_exec_time > 2.0 -- averaging at least 2ms/call + and total_exec_time > 60000 -- at least one minute total server time spent + and query ilike '%user_in_organization%' -- filter to queries that touch the user_in_organization table order by calls desc ``` -From the results, an informed decision about which queries to optimize, index, or adjust can be made. +From the results, we can make an informed decision about which queries to optimize or index. ## Resources diff --git a/apps/docs/pages/guides/database/extensions/pgaudit.mdx b/apps/docs/pages/guides/database/extensions/pgaudit.mdx new file mode 100644 index 00000000000..0352fb6d131 --- /dev/null +++ b/apps/docs/pages/guides/database/extensions/pgaudit.mdx @@ -0,0 +1,105 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'pgaudit', + title: 'PGAudit: Postgres Auditing', + description: 'Session and object auditing via PostgreSQL standard logging', +} + +[PGAudit](https://www.pgaudit.org) is a PostgreSQL extension for logging session and object auditing over the standard PostgreSQL logging utility. + +PGAudit grants fine grain control over which statements and objects are emitted to logs. + +## Enable the extension + + + + +1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard. +2. Click on **Extensions** in the sidebar. +3. Search for "pgaudit" and enable the extension. + + + + +{/* prettier-ignore */} +```sql +-- Enable the "pgaudit" extension +create extension pgaudit; + +-- Disable the "pgaudit" extension +drop extension if exists pgaudit; +``` + +Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension". +To disable an extension you can call `drop extension`. + + + + +## Settings + +The `pgaudit.log` setting controls which statements to log. Available values include: + +- **read**: `SELECT` and `COPY` when the source is a relation or a query. +- **write**: `INSERT`, `UPDATE`, `DELETE`, `TRUNCATE`, and `COPY` when the destination is a relation. +- **function**: Function calls and `DO` blocks. +- **role**: Statements related to roles and privileges: `GRANT`, `REVOKE`, `CREATE/ALTER/DROP ROLE`. +- **ddl**: All `DDL` that is not included in the `ROLE` class. +- **misc**: Miscellaneous commands, e.g. `DISCARD`, `FETCH`, `CHECKPOINT`, `VACUUM`, `SET`. +- **misc_set**: Miscellaneous `SET` commands, e.g. `SET ROLE`. + +- **all**: Include all of the above. + +For a full list of available settings see [settings docs](https://github.com/pgaudit/pgaudit/blob/master/README.md#settings). Be aware that the `all` setting will generate a very large volume of logs. + +## Example + +Given a pgaudit setting + +{/* prettier-ignore */} +```sql +set pgaudit.log = 'read, ddl'; +``` + +The following create table, insert and select statements + +{/* prettier-ignore */} +```sql +create table account ( + id int primary key, + name text, + description text +); + +insert into account (id, name, description) +values (1, 'Foo Barsworth', 'Customer account'); + +select * from account; +``` + +Results in the log output + +```text +AUDIT: SESSION,1,1,DDL,CREATE TABLE,TABLE,public.account,create table account( + id int, + name text, + description text +);, +AUDIT: SESSION,2,1,READ,SELECT,,,select * from account,, +``` + +Note that the insert statement is not logged because we did not include the `write` option for `pgaudit.log`. + +## Resources + +- Official [`PGAudit` documentation](https://www.pgaudit.org) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/extensions/pgjwt.mdx b/apps/docs/pages/guides/database/extensions/pgjwt.mdx new file mode 100644 index 00000000000..77c8bcc8dea --- /dev/null +++ b/apps/docs/pages/guides/database/extensions/pgjwt.mdx @@ -0,0 +1,113 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'pgjwt', + title: 'pgjwt: JSON Web Tokens', + description: 'Encode and decode JWTs in PostgreSQL', +} + +The [pgjwt](https://github.com/michelp/pgjwt) (PostgreSQL JSON Web Token) extension allows you to create and parse [JSON Web Tokens (JWTs)](https://en.wikipedia.org/wiki/JSON_Web_Token) within a PostgreSQL database. JWTs are commonly used for authentication and authorization in web applications and services. + +## Enable the extension + + + + +1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard. +2. Click on **Extensions** in the sidebar. +3. Search for "pgjwt" and enable the extension. + + + + +{/* prettier-ignore */} +```sql +-- Enable the "pgjwt" extension +create extension pgjwt schema extensions; + +-- Disable the "pgjwt" extension +drop extension if exists pgjwt; +``` + +Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension". +To disable an extension you can call `drop extension`. + +It's good practice to create the extension within a separate schema (like `extensions`) to keep the `public` schema clean. + + + + +## API + +- [`sign(payload json, secret text, algorithm text default 'HSA256')`](https://github.com/michelp/pgjwt#usage): Signs a JWT containng _payload_ with _secret_ using _algorithm_. +- [`verify(token text, secret text, algorithm text default 'HSA256')`](https://github.com/michelp/pgjwt#usage): Decodes a JWT _token_ that was signed with _secret_ using _algorithm_. + +Where: + +- `payload` is an encrypted JWT represented as a string. +- `secret` is the private/secret passcode which is used to sign the JWT and verify its integrity. +- `algorithm` is the method used to sign the JWT using the secret. +- `token` is an encrypted JWT represented as a string. + +## Usage + +Once the extension is installed, you can use its functions to create and parse JWTs. Here's an example of how you can use the `sign` function to create a JWT: + +{/* prettier-ignore */} +```sql +select + extensions.sign( + payload := '{"sub":"1234567890","name":"John Doe","iat":1516239022}', + secret := 'secret', + algorithm := 'HS256' + ); +``` + +The pgjwt_encode function returns a string that represents the JWT, which can then be safely transmitted between parties. + +{/* prettier-ignore */} +``` + sign +--------------------------------- + eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX + VCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiw + ibmFtZSI6IkpvaG4gRG9lIiwiaWF0Ijo + xNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y9 + 22BhjWgQzWXcXNrz0ogtVhfEd2o +(1 row) +``` + +To parse a JWT and extract its claims, you can use the `verify` function. Here's an example: + +{/* prettier-ignore */} +```sql +select + extensions.verify( + token := 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiRm9vIn0.Q8hKjuadCEhnCPuqIj9bfLhTh_9QSxshTRsA5Aq4IuM', + secret := 'secret', + algorithm := 'HS256' + ); +``` + +Which returns the decoded contents and some associated metadata. + +{/* prettier-ignore */} +```sql + header | payload | valid +-----------------------------+----------------+------- + {"alg":"HS256","typ":"JWT"} | {"name":"Foo"} | t +(1 row) +``` + +## Resources + +- Official [`pgjwt` documentation](https://github.com/michelp/pgjwt) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/extensions/pgnet.mdx b/apps/docs/pages/guides/database/extensions/pgnet.mdx index 18c51a8dd2a..3246fb4f5df 100644 --- a/apps/docs/pages/guides/database/extensions/pgnet.mdx +++ b/apps/docs/pages/guides/database/extensions/pgnet.mdx @@ -16,9 +16,8 @@ The pg_net API is in alpha. Functions signatures may change. It differs from the `http` extension in that it is asynchronous by default. This makes it useful in blocking functions (like triggers). -## Usage -### Enable the extension +## Enable the extension -### Creating a full text search index +## Creating a full text search index Given a table with a `text` column: diff --git a/apps/docs/pages/guides/database/extensions/pgrouting.mdx b/apps/docs/pages/guides/database/extensions/pgrouting.mdx new file mode 100644 index 00000000000..73a047fb362 --- /dev/null +++ b/apps/docs/pages/guides/database/extensions/pgrouting.mdx @@ -0,0 +1,159 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'pgrouting', + title: 'pgrouting: Geospatial Routing', + description: 'Extends PostGIS with Geospatial Routing', +} + +[pgRouting](http://pgrouting.org) is PostgreSQL and [PostGIS](http://postgis.net) extension adding geospatial routing functionality. + +The core functionality of `pgRouting` is a set of path finding algorithms including: + +- All Pairs Shortest Path, Johnson’s Algorithm +- All Pairs Shortest Path, Floyd-Warshall Algorithm +- Shortest Path A\* +- Bi-directional Dijkstra Shortest Path +- Bi-directional A\* Shortest Path +- Shortest Path Dijkstra +- Driving Distance +- K-Shortest Path, Multiple Alternative Paths +- K-Dijkstra, One to Many Shortest Path +- Traveling Sales Person +- Turn Restriction Shortest Path (TRSP) + +## Enable the extension + + + + +1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard. +2. Click on **Extensions** in the sidebar. +3. Search for "pgrouting" and enable the extension. + + + + +{/* prettier-ignore */} +```sql +-- Enable the "pgRouting" extension +create extension pgrouting cascade; + +-- Disable the "pgRouting" extension +drop extension if exists pgRouting; +``` + +Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension". +To disable an extension you can call `drop extension`. + + + + +## Example + +As an example, we'll solve the [traveling salesman probelem](https://en.wikipedia.org/wiki/Travelling_salesman_problem) using the pgRouting's `pgr_TSPeuclidean` function from some PostGIS coodinates. + +A summary of the traveling salesman problem is, given a set of city coordinates, solve for a path that goes through each city and minimizes the total distance traveled. + +First we populate a table with some X, Y coordinates + +{/* prettier-ignore */} +```sql +create table wi29 ( + id bigint, + x float, + y float, + geom geometry +); + +insert into wi29 (id, x, y) +values + (1,20833.3333,17100.0000), + (2,20900.0000,17066.6667), + (3,21300.0000,13016.6667), + (4,21600.0000,14150.0000), + (5,21600.0000,14966.6667), + (6,21600.0000,16500.0000), + (7,22183.3333,13133.3333), + (8,22583.3333,14300.0000), + (9,22683.3333,12716.6667), + (10,23616.6667,15866.6667), + (11,23700.0000,15933.3333), + (12,23883.3333,14533.3333), + (13,24166.6667,13250.0000), + (14,25149.1667,12365.8333), + (15,26133.3333,14500.0000), + (16,26150.0000,10550.0000), + (17,26283.3333,12766.6667), + (18,26433.3333,13433.3333), + (19,26550.0000,13850.0000), + (20,26733.3333,11683.3333), + (21,27026.1111,13051.9444), + (22,27096.1111,13415.8333), + (23,27153.6111,13203.3333), + (24,27166.6667,9833.3333), + (25,27233.3333,10450.0000), + (26,27233.3333,11783.3333), + (27,27266.6667,10383.3333), + (28,27433.3333,12400.0000), + (29,27462.5000,12992.2222); +``` + +Next we use the `pgr_TSPeuclidean` function to find the best path. + +{/* prettier-ignore */} +```sql +select + * +from + pgr_TSPeuclidean($$select * from wi29$$) +``` + +{/* prettier-ignore */} +```sql + seq | node | cost | agg_cost +-----+------+------------------+------------------ + 1 | 1 | 0 | 0 + 2 | 2 | 74.535614157127 | 74.535614157127 + 3 | 6 | 900.617093380362 | 975.152707537489 + 4 | 10 | 2113.77757765045 | 3088.93028518793 + 5 | 11 | 106.718669615254 | 3195.64895480319 + 6 | 12 | 1411.95293791574 | 4607.60189271893 + 7 | 13 | 1314.23824873744 | 5921.84014145637 + 8 | 14 | 1321.76283931305 | 7243.60298076942 + 9 | 17 | 1202.91366735569 | 8446.5166481251 + 10 | 18 | 683.333268292684 | 9129.84991641779 + 11 | 15 | 1108.05137466134 | 10237.9012910791 + 12 | 19 | 772.082339448903 | 11009.983630528 + 13 | 22 | 697.666150054665 | 11707.6497805827 + 14 | 23 | 220.141999627513 | 11927.7917802102 + 15 | 21 | 197.926372783442 | 12125.7181529937 + 16 | 29 | 440.456596290771 | 12566.1747492844 + 17 | 28 | 592.939989005405 | 13159.1147382898 + 18 | 26 | 648.288376333318 | 13807.4031146231 + 19 | 20 | 509.901951359278 | 14317.3050659824 + 20 | 25 | 1330.83095428717 | 15648.1360202696 + 21 | 27 | 74.535658878487 | 15722.6716791481 + 22 | 24 | 559.016994374947 | 16281.688673523 + 23 | 16 | 1243.87392358622 | 17525.5625971092 + 24 | 9 | 4088.0585364911 | 21613.6211336004 + 25 | 7 | 650.85409697993 | 22264.4752305803 + 26 | 3 | 891.004385199336 | 23155.4796157796 + 27 | 4 | 1172.36699411442 | 24327.846609894 + 28 | 8 | 994.708187806297 | 25322.5547977003 + 29 | 5 | 1188.01888359478 | 26510.5736812951 + 30 | 1 | 2266.91173136004 | 28777.4854126552 +``` + +## Resources + +- Official [`pgRouting` documentation](https://docs.pgrouting.org/latest/en/index.html) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/extensions/pgsodium.mdx b/apps/docs/pages/guides/database/extensions/pgsodium.mdx new file mode 100644 index 00000000000..f11c5fc3ec8 --- /dev/null +++ b/apps/docs/pages/guides/database/extensions/pgsodium.mdx @@ -0,0 +1,49 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'pgsodium', + title: 'pgsodium: Encryption Features', + description: 'Encryption library for PostgreSQL', +} + +[pgsodium](https://github.com/michelp/pgsodium) is a PostgreSQL extension which provides SQL access to libsodium's high-level cryptographic algorithms. It also enables some Postgres specific features including: + +- [Server Key Management](https://github.com/michelp/pgsodium#server-key-management) +- [Transparent Column Encryption](https://github.com/michelp/pgsodium#transparent-column-encryption) + +## Enable the extension + + + + +1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard. +2. Click on **Extensions** in the sidebar. +3. Search for "pgsodium" and enable the extension. + + + + +{/* prettier-ignore */} +```sql +-- Enable the "pgsodium" extension +create extension pgsodium; + +-- Disable the "pgsodium" extension +drop extension if exists pgsodium; +``` + + + + +## Resources + +- Official [`pgsodium` documentation](https://github.com/michelp/pgsodium) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/extensions/pgtap.mdx b/apps/docs/pages/guides/database/extensions/pgtap.mdx index fc642b522ab..219eedd11ee 100644 --- a/apps/docs/pages/guides/database/extensions/pgtap.mdx +++ b/apps/docs/pages/guides/database/extensions/pgtap.mdx @@ -15,9 +15,7 @@ Let's cover some basic concepts: - Unit tests: allow you to test small parts of a system (like a database table!). - TAP: stands for [Test Anything Protocol](http://testanything.org/). It is an framework which aims to simplify the error reporting during testing. -## Usage - -### Enable the extension +## Enable the extension -### Testing tables +## Testing tables ```sql begin; @@ -68,7 +66,7 @@ API: - [`has_index()`](https://pgtap.org/documentation.html#has_index): Checks for the existence of a named index associated with the named table. - [`has_relation()`](https://pgtap.org/documentation.html#has_relation): Tests whether or not a relation exists in the database. -### Testing columns +## Testing columns ```sql begin; @@ -86,7 +84,7 @@ API: - [`has_column()`](https://pgtap.org/documentation.html#has_column): Tests whether or not a column exists in a given table, view, materialized view or composite type. - [`col_is_pk()`](https://pgtap.org/documentation.html#col_is_pk): Tests whether the specified column or columns in a table is/are the primary key for that table. -### Testing RLS Policies +## Testing RLS Policies ```sql begin; @@ -133,7 +131,7 @@ API: - [`results_eq()`](https://pgtap.org/documentation.html#results_eq) - [`results_ne()`](https://pgtap.org/documentation.html#results_ne) -### Testing Functions +## Testing Functions ```sql begin; diff --git a/apps/docs/pages/guides/database/extensions/plpgsql_check.mdx b/apps/docs/pages/guides/database/extensions/plpgsql_check.mdx new file mode 100644 index 00000000000..a064f463df6 --- /dev/null +++ b/apps/docs/pages/guides/database/extensions/plpgsql_check.mdx @@ -0,0 +1,105 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'plpgsql_check', + title: 'plpgsql_check: PL/pgSQL Linter', + description: 'Lint PL/pgSQL code', +} + +[plpgsql_check](https://github.com/okbob/plpgsql_check) is a PostgreSQL extension that lints plpgsql for syntax, semantic and other related issues. The tool helps developers to identify and correct errors before executing the code. plpgsql_check is most useful for developers who are working with large or complex SQL codebases, as it can help identify and resolve issues early in the development cycle. + +## Enable the extension + + + + +1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard. +2. Click on **Extensions** in the sidebar. +3. Search for "plpgsql_check" and enable the extension. + + + + +{/* prettier-ignore */} +```sql +-- Enable the "plpgsql_check" extension +create extension plpgsql_check; + +-- Disable the "plpgsql_check" extension +drop extension if exists plpgsql_check; +``` + +Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension". +To disable an extension you can call `drop extension`. + + + + +## API + +- [`plpgsql_check_function( ... )`](https://github.com/okbob/plpgsql_check#active-mode): Scans a function for errors. + +`plpgsql_check_function` is highly customizable. For a complete list of available arguments see [the docs](https://github.com/okbob/plpgsql_check#arguments) + +## Usage + +To demonstrate `plpgsql_check` we can create a function with a known error. In this case we create a function `some_func`, that references a non-existent column `place.created_at`. + +{/* prettier-ignore */} +```sql +create table place( + x float, + y float +); + +create or replace function public.some_func() + returns void + language plpgsql +as $$ +declare + rec record; +begin + for rec in select * from place + loop + -- Bug: There is no column `created_at` on table `place` + raise notice '%', rec.created_at; + end loop; +end; +$$; +``` + +Note that executing the function would not catch the invalid reference error because the `loop` does not execute if no rows are present in the table. + +{/* prettier-ignore */} +```sql +select public.some_func(); + some_func + ─────────── + + (1 row) +``` + +Now we can use plpgsql_check's `plpgsql_check_function` function to identify the known error. + +{/* prettier-ignore */} +```sql +select plpgsql_check_function('public.some_func()'); + + plpgsql_check_function +------------------------------------------------------------ + error:42703:8:RAISE:record "rec" has no field "created_at" + Context: SQL expression "rec.created_at" +``` + +## Resources + +- Official [`plpgsql_check` documentation](https://github.com/okbob/plpgsql_check) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/extensions/plv8.mdx b/apps/docs/pages/guides/database/extensions/plv8.mdx index eb408b2c30f..fdb827f85da 100644 --- a/apps/docs/pages/guides/database/extensions/plv8.mdx +++ b/apps/docs/pages/guides/database/extensions/plv8.mdx @@ -15,9 +15,8 @@ While Postgres natively runs SQL, it can also run other "procedural languages". It can be used for database functions, triggers, queries and more. -## Usage -### Enable the extension +## Enable the extension -### Create `plv8` functions +## Create `plv8` functions Functions written in `plv8` are written just like any other PostgreSQL functions, only with the `language` identifier set to `plv8`. @@ -135,6 +134,8 @@ returns setof messages as $$ return json_result; $$ language plv8; + +select * from get_messages(); ``` ## Resources diff --git a/apps/docs/pages/guides/database/extensions/postgis.mdx b/apps/docs/pages/guides/database/extensions/postgis.mdx index 6f241b0774e..9e9a65098ad 100644 --- a/apps/docs/pages/guides/database/extensions/postgis.mdx +++ b/apps/docs/pages/guides/database/extensions/postgis.mdx @@ -14,9 +14,7 @@ While you may be able to store simple lat/long geographic coordinates as a set o The additional data types that PostGIS provides include [Point](https://postgis.net/docs/using_postgis_dbmanagement.html#Point), [Polygon](https://postgis.net/docs/using_postgis_dbmanagement.html#Polygon), [Linestring](https://postgis.net/docs/using_postgis_dbmanagement.html#LineString), and many more to represent different types of geographical data. In this guide, we will mainly focus on how to interact with `Point` type, which represents a single set of latitude and longitude. If you are interested in digging deeper, you can learn more about different data types on the [data management section of PostGIS docs](https://postgis.net/docs/using_postgis_dbmanagement.html). -## Usage - -### Enable the extension +## Enable the extension You can get started with PostGIS by enabling the PostGIS extension in your Supabase dashboard. diff --git a/apps/docs/pages/guides/database/extensions/timescaledb.mdx b/apps/docs/pages/guides/database/extensions/timescaledb.mdx new file mode 100644 index 00000000000..265e8662ad4 --- /dev/null +++ b/apps/docs/pages/guides/database/extensions/timescaledb.mdx @@ -0,0 +1,99 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'timescaledb', + title: 'timescaledb: Time-Series data', + description: 'Scalable time-series data storage and analysis', +} + +[timescaledb](https://docs.timescale.com/timescaledb/latest/) is a PostgreSQL extension designed for improved handling of time-series data. It provides a scalable, high-performance solution for storing and querying time-series data on top of a standard PostgreSQL database. + +`timescaledb` uses a time-series-aware storage model and indexing techniques to improve performance of PostgreSQL in working with time-series data. The extension divides data into chunks based on time intervals, allowing it to scale efficiently, especially for large data sets. The data is then compressed, optimized for write-heavy workloads, and partitioned for parallel processing. `timescaledb` also includes a set of functions, operators, and indexes that work with time-series data to reduce query times, and make data easier to work with. + +## Enable the extension + + + + +1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard. +2. Click on **Extensions** in the sidebar. +3. Search for "timescaledb" and enable the extension. + + + + +{/* prettier-ignore */} +```sql +-- Enable the "timescaledb" extension +create extension timescaledb; + +-- Disable the "timescaledb" extension +drop extension if exists timescaledb; +``` + + + + +## Usage + +To demonstrate how `timescaledb` works, let's consider a simple example where we have a table that stores temperature data from different sensors. We will create a table named "temperatures" and store data for two sensors. + +First we create a hypertable, which is a virtual table that is partitioned into chunks based on time intervals. The hypertable acts as a proxy for the actual table and makes it easy to query and manage time-series data. + +{/* prettier-ignore */} +```sql +create table temperatures ( + time timestamp not null, + sensor_id int not null, + temperature double precision not null +); + +select create_hypertable('temperatures', 'time'); +``` + +Next, we can populate some values + +{/* prettier-ignore */} +```sql +insert into temperatures (time, sensor_id, temperature) +values + ('2023-02-14 09:00:00', 1, 23.5), + ('2023-02-14 09:00:00', 2, 21.2), + ('2023-02-14 09:05:00', 1, 24.5), + ('2023-02-14 09:05:00', 2, 22.3), + ('2023-02-14 09:10:00', 1, 25.1), + ('2023-02-14 09:10:00', 2, 23.9), + ('2023-02-14 09:15:00', 1, 24.9), + ('2023-02-14 09:15:00', 2, 22.7), + ('2023-02-14 09:20:00', 1, 24.7), + ('2023-02-14 09:20:00', 2, 23.5); +``` + +And finally we can query the table using `timescaledb`'s `time_bucket` function to divide the time-series into intervals of the specified size (in this case, 1 hour) averaging the `temperature` reading within each group. + +{/* prettier-ignore */} +```sql +select + time_bucket('1 hour', time) AS hour, + avg(temperature) AS average_temperature +from + temperatures +where + sensor_id = 1 + and time > NOW() - interval '1 hour' +group by + hour; +``` + +## Resources + +- Official [`timescaledb` documentation](https://docs.timescale.com/timescaledb/latest/) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/extensions/uuid-ossp.mdx b/apps/docs/pages/guides/database/extensions/uuid-ossp.mdx index 167e9b92807..b270fdd581f 100644 --- a/apps/docs/pages/guides/database/extensions/uuid-ossp.mdx +++ b/apps/docs/pages/guides/database/extensions/uuid-ossp.mdx @@ -13,9 +13,7 @@ The `uuid-ossp` extension can be used to generate a `UUID`. A `UUID` is a "Universally Unique Identifier" and it is, for practical purposes, unique. This makes them particularly well suited as Primary Keys. It is occasionally referred to as a `GUID`, which stands for "Globally Unique Identifier". -## Usage - -### Enable the extension +## Enable the extension -### The `uuid` type +## The `uuid` type Once the extension is enabled, you now have access to a `uuid` type. -### `uuid_generate_v1()` +## `uuid_generate_v1()` Creates a UUID value based on the combination of computer’s MAC address, current timestamp, and a random value. @@ -67,7 +65,7 @@ Creates a UUID value based on the combination of computer’s MAC address, curre applications. -### `uuid_generate_v4()` +## `uuid_generate_v4()` Creates UUID values based solely on random numbers. You can also use Postgres's built-in [`gen_random_uuid()`](https://www.postgresql.org/docs/current/functions-uuid.html) function to generate a UUIDv4. diff --git a/apps/docs/pages/guides/database/extensions/wrappers.mdx b/apps/docs/pages/guides/database/extensions/wrappers.mdx new file mode 100644 index 00000000000..943b1dd6391 --- /dev/null +++ b/apps/docs/pages/guides/database/extensions/wrappers.mdx @@ -0,0 +1,75 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'wrappers', + title: 'wrappers: 3rd Party Integrations', + description: '3rd party integrations for PostgreSQL ', +} + +[supabase/wrappers](https://supabase.github.io/wrappers/) is a PostgreSQL extension that provides integrations with external sources so you can interact with third-party data using SQL. + +For example, the [Stripe wrapper](https://supabase.github.io/wrappers/stripe/) connects to [Stripe's API](https://stripe.com/docs/api) and exposes each endpoint as a SQL table. + +{/* prettier-ignore */} +```sql +select + customer_id + currency +from + stripe.customers; + + customer_id | currency +--------------------+----------- + cus_MJiBtCqOF1Bb3F | usd +(1 row) +``` + +## Usage + +### Enable the extension + + + + +1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard. +2. Click on **Extensions** in the sidebar. +3. Search for "wrappers" and enable the extension. + + + + +{/* prettier-ignore */} +```sql +-- Enable the "wrappers" extension +create extension wrappers; + +-- Disable the "wrappers" extension +drop extension if exists wrappers; +``` + + + + +Instructions for setting up connections to each 3rd party service via SQL are available at the links in the integrations section below. +A web interface for connecting to external data is coming to Supabase Studio in 2023. + +## Integrations + +- [Firebase](https://supabase.github.io/wrappers/firebase/) +- [Stripe](https://supabase.github.io/wrappers/stripe/) +- Airtable - Coming Soon +- BigQuery - Coming Soon +- Clickhouse - Coming Soon + +## Resources + +- Official [`supabase/wrappers` documentation](https://supabase.github.io/wrappers/) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/tables.mdx b/apps/docs/pages/guides/database/tables.mdx index 13fd0bfa2bd..9ed2619b47d 100644 --- a/apps/docs/pages/guides/database/tables.mdx +++ b/apps/docs/pages/guides/database/tables.mdx @@ -242,9 +242,18 @@ You would [connect](../../guides/database/connecting-to-postgres#direct-connecti ```bash psql -h DATABASE_URL -p 5432 -d postgres -U postgres \ - -c "COPY movies FROM './movies.csv';" + -c "\COPY movies FROM './movies.csv';" ``` +Additionally use the `DELIMITER`, `HEADER` and `FORMAT` options as defined in the PostgreSQL [COPY](https://www.postgresql.org/docs/current/sql-copy.html) docs. + +```bash +psql -h DATABASE_URL -p 5432 -d postgres -U postgres \ + -c "\COPY movies FROM './movies.csv' WITH DELIMITER ',' CSV HEADER" +``` + +If you receive an error `FATAL: password authentication failed for user "postgres"`, reset your database password in the Database Settings and try again. + ## Joining tables with Foreign Keys Tables can be "joined" together using Foreign Keys. diff --git a/apps/docs/pages/guides/integrations/flutterflow.mdx b/apps/docs/pages/guides/integrations/flutterflow.mdx index b7a25f8d1a5..ed6ddf1f046 100644 --- a/apps/docs/pages/guides/integrations/flutterflow.mdx +++ b/apps/docs/pages/guides/integrations/flutterflow.mdx @@ -8,8 +8,14 @@ export const meta = { canonical: 'https://docs.flutterflow.io/actions/actions/backend-database/supabase', } + + FlutterFlow and Supabase integration is currently in alpha, and supported features may be limited. + + [FlutterFlow](https://flutterflow.io/) is a low-code builder for developing native mobile applications using Flutter. You can use the simple drag-and-drop interface to build your app faster than traditional development. +This guide gives you a quick overview of implementing basic CRUD operations using FlutterFlow and Supabase. You can find the full docs on FlutterFlow and Supabase [here](https://docs.flutterflow.io/actions/actions/backend-database/supabase). +
-This guide gives you a quick overview of implementing basic CRUD operations using FlutterFlow and Supabase. You can find the full docs on FlutterFlow and Supabase [here](https://docs.flutterflow.io/actions/actions/backend-database/supabase). ## Step 1: Connect FlutterFlow to Supabase diff --git a/apps/docs/pages/guides/platform/database-usage.mdx b/apps/docs/pages/guides/platform/database-usage.mdx index c6a734b4fac..6ce6385770d 100644 --- a/apps/docs/pages/guides/platform/database-usage.mdx +++ b/apps/docs/pages/guides/platform/database-usage.mdx @@ -24,7 +24,7 @@ This value is also reported in the [database settings page](https://app.supabase Supabase uses network-attached storage to balance performance with scalability. For Pro and Enterprise projects, disk size expands ~1.5x automatically (e.g., 8GB -> 12GB) when you reach 90% of the disk size. -Automatic disk size expansion can only occur once every six hours. +Disk size expansion can only occur once every six hours. Pro projects can store up to 1024TB. All projects enter read-only mode when you reach 95% of the disk size. In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`. diff --git a/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx b/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx index 636eef09903..00caf731fbc 100644 --- a/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx +++ b/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx @@ -155,6 +155,17 @@ create policy "Only Blizzard staff can update leaderboard" Supabase comes with two built-in helper functions: `auth.uid()` and `auth.jwt()`. +To create your own functions, navigate to the SQL editor and create a a new query. + +```sql +create or replace function user_agent() +returns text +language sql +as $$ + select nullif(current_setting('request.headers', true)::json->>'user-agent', '')::text; +$$; +``` + See the full PostgreSQL policy docs here: [https://www.postgresql.org/docs/12/sql-createpolicy.html](https://www.postgresql.org/docs/12/sql-createpolicy.html) You can get as creative as you like with these policies. diff --git a/apps/docs/public/sitemap.xml b/apps/docs/public/sitemap.xml index 90cd255fc9c..35bf4d7ff55 100644 --- a/apps/docs/public/sitemap.xml +++ b/apps/docs/public/sitemap.xml @@ -3867,4 +3867,4 @@ weekly 0.5 - + \ No newline at end of file diff --git a/apps/www/_blog/2022-06-30-flutter-tutorial-building-a-chat-app.mdx b/apps/www/_blog/2022-06-30-flutter-tutorial-building-a-chat-app.mdx index 8285a717828..cbe86871d1b 100644 --- a/apps/www/_blog/2022-06-30-flutter-tutorial-building-a-chat-app.mdx +++ b/apps/www/_blog/2022-06-30-flutter-tutorial-building-a-chat-app.mdx @@ -890,5 +890,6 @@ If you have any questions please reach out via [Twitter](https://twitter.com/dsh - [Part 2: Flutter Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls) - [Flutter Supabase Quick Starter Guide](https://supabase.com/docs/guides/with-flutter) - [supabase-flutter v1.0 released](https://supabase.com/blog/supabase-flutter-sdk-v1-released) +- [How to build a real-time multiplayer game with Flutter Flame](https://supabase.com/blog/flutter-real-time-multiplayer-game) - [Open source map based Flutter app with CI/CD and scalable statemanagement example](https://github.com/dshukertjr/spot) - [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase) diff --git a/apps/www/_blog/2022-08-02-supabase-flutter-sdk-1-developer-preview.mdx b/apps/www/_blog/2022-08-02-supabase-flutter-sdk-1-developer-preview.mdx index 39f8595377c..cdabad21e70 100644 --- a/apps/www/_blog/2022-08-02-supabase-flutter-sdk-1-developer-preview.mdx +++ b/apps/www/_blog/2022-08-02-supabase-flutter-sdk-1-developer-preview.mdx @@ -160,4 +160,5 @@ If you have any feedbacks, please let us know in the issues of the [supabase-flu - [supabase-flutter 1.0 developer preview](https://pub.dev/packages/supabase_flutter) - [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app) - [Flutter Tutorial - Part 2: Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls) +- [How to build a real-time multiplayer game with Flutter Flame](https://supabase.com/blog/flutter-real-time-multiplayer-game) - [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase) diff --git a/apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx b/apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx index 92aed5cbb3b..8709747976a 100644 --- a/apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx +++ b/apps/www/_blog/2022-10-21-supabase-flutter-sdk-v1-released.mdx @@ -100,4 +100,5 @@ It required massive support from the community to bring the supabase-flutter to - [v0 to v1 migration guide](https://supabase.com/docs/reference/dart/v0/upgrade-guide) - [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app) - [Flutter Tutorial - Part 2: Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls) +- [How to build a real-time multiplayer game with Flutter Flame](https://supabase.com/blog/flutter-real-time-multiplayer-game) - [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase) diff --git a/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx b/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx index e6ecfbe4cd2..fde39a288d0 100644 --- a/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx +++ b/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx @@ -1357,6 +1357,7 @@ We can for example update this to a list of users that are online at the same ti - [Complete set of code of this chat app](https://github.com/supabase-community/flutter-chat/tree/with_auth) - [supabase-flutter docs](https://supabase.com/docs/reference/dart) +- [How to build a real-time multiplayer game with Flutter Flame](https://supabase.com/blog/flutter-real-time-multiplayer-game) - [Flutter Supabase Quick Starter Guide](https://supabase.com/docs/guides/with-flutter) - [supabase-flutter v1.0 released](https://supabase.com/blog/supabase-flutter-sdk-v1-released) - [Build a Flutter app with Very Good CLI and Supabase](https://verygood.ventures/blog/flutter-app-very-good-cli-supabase) 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 c04183c99b0..e6c7aa2fe4f 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 @@ -6,7 +6,7 @@ image: embeddings/og_pgvector.png thumb: embeddings/og_pgvector.png tags: - postgres - - ai + - AI date: '2023-02-06' toc_depth: 3 --- diff --git a/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx b/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx index 6c1b29a9e70..bce1784c0f6 100644 --- a/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx +++ b/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx @@ -5,7 +5,7 @@ author: paul_copplestone image: docsgpt/og_supabase_clippy.png thumb: docsgpt/og_supabase_clippy.png tags: - - ai + - AI - docs date: '2023-02-07' toc_depth: 3 diff --git a/apps/www/_blog/2023-02-14-flutter-real-time-multiplayer-game.mdx b/apps/www/_blog/2023-02-14-flutter-real-time-multiplayer-game.mdx index 321263554ba..796f60340f8 100644 --- a/apps/www/_blog/2023-02-14-flutter-real-time-multiplayer-game.mdx +++ b/apps/www/_blog/2023-02-14-flutter-real-time-multiplayer-game.mdx @@ -14,7 +14,7 @@ toc_depth: 3 Flutter is a UI library to build apps that run on any platform, but it can also build interactive games thanks to an open-source game engine built on top of Flutter called [Flame](https://flame-engine.org/). Flame takes care of things like collision detection or loading image sprites to bring game development to all the Flutter devs. We can take it a step further to introduce real-time communication features so that players can play against each other in real-time. -In this article, we will use Flutter, Flame, and Supabase's real-time features to build a real-time multiplayer shooting game. You can find the complete code of this tutorial [here](https://github.com/supabase/supabase/tree/master/examples/realtime/examples/flutter-multiplayer-shooting-game). +In this article, we will use Flutter, Flame, and Supabase's real-time features to build a real-time multiplayer shooting game. You can find the complete code of this tutorial [here](https://github.com/supabase/supabase/tree/master/examples/realtime/flutter-multiplayer-shooting-game). ## Overview of the final game @@ -833,7 +833,8 @@ We learned how to create an interactive shooting game. We took advantage of Flut ## More Flutter Resources -- [Complete code of this article](https://github.com/supabase/supabase/tree/master/examples/realtime/examples/flutter-multiplayer-shooting-game) -- [Flame Engine website](https://flame-engine.org/) +- [Complete code of this article](https://github.com/supabase/supabase/tree/master/examples/realtime/flutter-multiplayer-shooting-game) +- [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app) +- [Flutter Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls) - [Generate Flame template using Very Good CLI](https://verygood.ventures/blog/generate-a-game-with-our-new-template) - [Supabase Flutter SDK docs](https://supabase.com/docs/reference/dart/start) diff --git a/apps/www/components/AuthWidget/AuthWidgetSection.tsx b/apps/www/components/AuthWidget/AuthWidgetSection.tsx index 047ff5c4ac7..f9ee1bbf25a 100644 --- a/apps/www/components/AuthWidget/AuthWidgetSection.tsx +++ b/apps/www/components/AuthWidget/AuthWidgetSection.tsx @@ -3,10 +3,11 @@ import { Button } from 'ui' import { useState } from 'react' // Import Swiper React components import { useRouter } from 'next/router' +import Image from 'next/image' import { ColorSwatchIcon, MenuIcon } from '@heroicons/react/outline' import { Auth, ThemeSupa } from '@supabase/auth-ui-react' -import { useTheme } from '../Providers' +import { useTheme } from 'common/Providers' const supabase = createClient( 'https://rsnibhkhsbfnncjmwnkj.supabase.co', @@ -120,7 +121,14 @@ function AuthWidgetSection() { match your brand and aesthetic

- +
+ react icon +
React only. Other frameworks coming soon.
@@ -204,14 +212,22 @@ function AuthWidgetSection() { : '', ].join(' ')} > - - +
+ react icon +
+
+ react icon +
diff --git a/apps/www/components/Blog/BlogListItem.tsx b/apps/www/components/Blog/BlogListItem.tsx index bc77f543bf7..c08b5006db7 100644 --- a/apps/www/components/Blog/BlogListItem.tsx +++ b/apps/www/components/Blog/BlogListItem.tsx @@ -58,6 +58,7 @@ const BlogListItem = ({ post }: Props) => { {`${author.author} { const { isDarkMode } = useTheme() diff --git a/apps/www/components/ImageGrid.tsx b/apps/www/components/ImageGrid.tsx index 5a01d9ffd96..fb4782c985b 100644 --- a/apps/www/components/ImageGrid.tsx +++ b/apps/www/components/ImageGrid.tsx @@ -91,15 +91,11 @@ const ImageGrid = ({ ${x.link && 'dark:hover:bg-scale-600 hover:bg-gray-100'} p-8 ${className}`} > -
+
{x.alt} setVideoVisible(false)} + hideFooter + header={ +
+ Compute pricing + setShowComputeModal(false)} + /> +
+ } + > +
+
+
+
+
+
+ +
+
+
+

Choose best compute setup for you

+

+ Every project on the Supabase Platform comes with its own dedicated Postgres + instance running inside a virtual machine (VM). The following table describes the + base instance with additional compute add-ons available if you need extra + performance when scaling up Supabase. +

+
+
+
+
+ + + + {columnNames.map((column) => ( + + ))} + + + + {pricingAddOn.database.rows.map((row, i) => ( + + {i === 0 && ( + + + + )} + + {row.columns.map((column) => ( + + ))} + + + ))} + +
+ {column.title} +
+ Included in Free and Pro tiers +
+ {column.key === 'dedicated' ? ( + column.value ? ( + + ) : ( + + ) + ) : ( + column.value + )} +
+
+ + + + {pricingAddOn.database.rows.map((row, i) => ( + + {row.columns.map((column) => ( + + + + + ))} + + ))} + +
+ {column.title} + + {column.key === 'dedicated' ? ( + + ) : ( + column.value + )} +
+
+
+ + ) +} diff --git a/apps/www/components/PricingAddOnTable.tsx b/apps/www/components/Pricing/PricingAddOnTable.tsx similarity index 99% rename from apps/www/components/PricingAddOnTable.tsx rename to apps/www/components/Pricing/PricingAddOnTable.tsx index dbd7ccf7562..41b9f96698b 100644 --- a/apps/www/components/PricingAddOnTable.tsx +++ b/apps/www/components/Pricing/PricingAddOnTable.tsx @@ -1,7 +1,7 @@ import { Collapsible, IconChevronUp } from 'ui' import classNames from 'classnames' import { Fragment, useMemo, useState } from 'react' -import { Check } from './PricingTableRow' +import { Check } from './PricingIcons' interface PricingAddOnTableProps { pricing: { diff --git a/apps/www/components/Pricing/PricingIcons.tsx b/apps/www/components/Pricing/PricingIcons.tsx new file mode 100644 index 00000000000..5dd4c804921 --- /dev/null +++ b/apps/www/components/Pricing/PricingIcons.tsx @@ -0,0 +1,57 @@ +export const Check = () => ( + + + +) + +export const IconPricingIncludedCheck = (props: any) => ( + + + Included in {props.tier} + +) + +export const IconPricingMinus = (props: any) => ( + <> + + + + Not included in {props.tier} + +) + +export const IconPricingInfo = () => ( + <> + + + + Info + +) diff --git a/apps/www/components/Pricing/PricingTableRow.tsx b/apps/www/components/Pricing/PricingTableRow.tsx new file mode 100644 index 00000000000..3aaacc55236 --- /dev/null +++ b/apps/www/components/Pricing/PricingTableRow.tsx @@ -0,0 +1,149 @@ +import { IconHelpCircle } from 'ui' +import ProductIcon from 'components/ProductIcon' +import React, { Fragment } from 'react' +import ReactTooltip from 'react-tooltip' +import { IconPricingIncludedCheck, IconPricingInfo, IconPricingMinus } from './PricingIcons' + +export const PricingTableRowDesktop = (props: any) => { + const category = props.category + + return ( + <> + + +
+ {props.icon && } +

{category.title}

+
+ + + + + + + + {category.features.map((feat: any, i: number) => { + return ( + + + + {feat.title} + {feat.tooltips?.main && ( + + + + )} + + + {Object.entries(feat.tiers).map((entry: any, i) => { + const tierName = entry[0] + const tierValue = entry[1] + + return ( + + {typeof tierValue === 'boolean' && tierValue === true ? ( + + ) : typeof tierValue === 'boolean' && tierValue === false ? ( +
+ +
+ ) : ( + + {feat.tooltips?.[tierName] && ( + + + + )} + {tierValue} + + )} + + ) + })} + + {i === category.features.length - 1 && ( +
+ )} +
+ ) + })} + + + ) +} + +export const PricingTableRowMobile = (props: any) => { + const category = props.category + const tier = props.tier + + return ( + <> + + + + + + + + + + {category.features.map((feat: any, i: number) => { + return ( + + + + + ) + })} + +
+
+ {category.icon ? : null} + {category.title} +
+
+ Feature + + Included +
+ +

{feat.title}

+
+
+ {typeof feat.tiers[tier] === 'boolean' && feat.tiers[tier] === true ? ( +
+ +
+ ) : typeof feat.tiers[tier] === 'boolean' && feat.tiers[tier] === false ? ( +
+ +
+ ) : ( + {feat.tiers[tier]} + )} +
+ + + ) +} diff --git a/apps/www/components/PricingTableRow.tsx b/apps/www/components/PricingTableRow.tsx deleted file mode 100644 index 2a81769258b..00000000000 --- a/apps/www/components/PricingTableRow.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { IconHelpCircle } from 'ui' -import ProductIcon from 'components/ProductIcon' -import React from 'react' -import ReactTooltip from 'react-tooltip' - -export const Check = () => ( - - - -) - -const IncludedCheck = (props: any) => ( - <> - - Included in {props.tier} - -) - -const Minus = (props: any) => ( - <> - - Not included in {props.tier} - -) - -export const PricingTableRowDesktop = (props: any) => { - const category = props.category - - return ( - <> - - -
- {props.icon && } -

{category.title}

-
- - - - - - - {category.features.map((feat: any, i: number) => { - return ( - - - {feat.title} - {feat.tooltip && ( - - - - )} - - - {Object.values(feat.tiers).map((tier: any, i) => { - return ( - - {typeof tier === 'boolean' && tier === true ? ( - - ) : typeof tier === 'boolean' && tier === false ? ( -
- -
- ) : ( - {tier} - )} - - ) - })} - - ) - })} - - - ) -} - -export const PricingTableRowMobile = (props: any) => { - const category = props.category - const tier = props.tier - - return ( - <> - - - - - - - - - - {category.features.map((feat: any, i: number) => { - return ( - - - - - ) - })} - -
-
- {category.icon ? : null} - {category.title} -
-
- Feature - - Included -
- -

{feat.title}

-
-
- {typeof feat.tiers[tier] === 'boolean' && feat.tiers[tier] === true ? ( -
- -
- ) : typeof feat.tiers[tier] === 'boolean' && feat.tiers[tier] === false ? ( -
- -
- ) : ( - {feat.tiers[tier]} - )} -
- - - ) -} diff --git a/apps/www/components/ProductIcon.tsx b/apps/www/components/ProductIcon.tsx index d7a24709787..894c7ea5117 100644 --- a/apps/www/components/ProductIcon.tsx +++ b/apps/www/components/ProductIcon.tsx @@ -1,12 +1,22 @@ interface ProductIcon { icon: string + color?: 'black' | 'green' } -function ProductIcon({ icon }: ProductIcon) { +function ProductIcon({ icon, color }: ProductIcon) { return ( -
+
void -} - -interface ThemeProviderProps { - children?: any -} - -export const ThemeContext = createContext({ - isDarkMode: true, - toggleTheme: () => {}, -}) - -export const useTheme = () => useContext(ThemeContext) - -export const ThemeProvider = ({ children }: ThemeProviderProps) => { - const [isDarkMode, setIsDarkMode] = useState(false) - - useEffect(() => { - const key = localStorage.getItem('supabaseDarkMode') - // Default to dark mode if no preference config - setIsDarkMode(!key || key === 'true') - }, []) - - const toggleTheme = () => { - setIsDarkMode(!isDarkMode) - } - - return ( - <> - - {children} - - - ) -} diff --git a/apps/www/data/Pricing.json b/apps/www/data/Pricing.json index 08a9cdba95d..395c309cb9f 100644 --- a/apps/www/data/Pricing.json +++ b/apps/www/data/Pricing.json @@ -5,7 +5,9 @@ "features": [ { "title": "Dedicated Postgres Database", - "tooltip": "A Postgres database with no restrictions? You get it. No pseudo limited users, you are the postgres root user. No caveats.", + "tooltips": { + "main": "A Postgres database with no restrictions? You get it. No pseudo limited users, you are the postgres root user. No caveats." + }, "tiers": { "free": true, "pro": true, @@ -22,46 +24,60 @@ }, { "title": "Database size", - "tooltip": "Billing is based on the average daily database size in GB throughout the billing period.", + "tooltips": { + "main": "Billing is based on the average database size in GB throughout the billing period.", + "pro": "8 GB included, then $0.125 per GB" + }, "tiers": { - "free": "Up to 500 MB", - "pro": "8 GB included, then $0.125 per GB", + "free": "500 MB", + "pro": "8 GB", "enterprise": "Unlimited" } }, { "title": "Automatic backups", - "tooltip": "Backups are entire copies of your database that can be restored in the future.", + "tooltips": { + "main": "Backups are entire copies of your database that can be restored in the future.", + "pro": "7 days of backup (if > 1TB, contact for Enterprise pricing)" + }, "tiers": { "free": false, - "pro": "7 days of backup (if > 1TB, contact enterprise)", + "pro": "7 days", "enterprise": "Custom" } }, { "title": "Point in time recovery", - "tooltip": "PITR cannot be applied retroactively, projects can only be rolled back to the point from which PITR has been applied.", + "tooltips": { + "main": "PITR cannot be applied retroactively, projects can only be rolled back to the point from which PITR has been applied.", + "pro": "$100 per 7 days. If > 28 day rollback period, contact enterprise" + }, "tiers": { "free": false, - "pro": "$100 per 7 day rollback period (if > 28 days, contact enterprise)", + "pro": "$100 per 7 days", "enterprise": true } }, { "title": "Pausing", - "tooltip": "Projects that have no activity or API requests will be paused. They can be reactivated via the dashboard", + "tooltips": { + "main": "Projects that have no activity or API requests will be paused. They can be reactivated via the dashboard" + }, "tiers": { - "free": "Paused after 1 week inactivity", + "free": "After 1 inactive week", "pro": "Never", "enterprise": "Never" } }, { "title": "Database egress", - "tooltip": "Billing is based on the total sum of outgoing traffic of your database in GB throughout your billing period.", + "tooltips": { + "main": "Billing is based on the total sum of outgoing traffic of your database in GB throughout your billing period. Only download operations are counted towards the limit", + "pro": "50GB included per month, then $0.09 per GB" + }, "tiers": { - "free": "Up to 2GB", - "pro": "50GB included per month, then $0.09 per GB", + "free": "2GB", + "pro": "50GB", "enterprise": "Unlimited" } } @@ -73,7 +89,7 @@ "features": [ { "title": "Total Users", - "tooltip": "The maximum number of users your project can have", + "tooltips": { "main": "The maximum number of users your project can have" }, "tiers": { "free": "Unlimited", "pro": "Unlimited", @@ -82,10 +98,13 @@ }, { "title": "MAUs", - "tooltip": "Distinct users requesting your API throughout the billing period.", + "tooltips": { + "main": "Distinct users requesting your API throughout the billing period.", + "pro": "100,000 included, then $0.00325 per MAU" + }, "tiers": { - "free": "Up to 50,000 MAUs", - "pro": "100,000 included, then $0.00325 per MAU", + "free": "50,000", + "pro": "100,000", "enterprise": "Unlimited" } }, @@ -131,7 +150,7 @@ }, { "title": "Supabase Auth emails", - "tooltip": "Rate limits do not apply to Custom SMTP", + "tooltips": { "main": "Rate limits do not apply to Custom SMTP" }, "tiers": { "free": "30 / hour", "pro": "100 / hour", @@ -151,22 +170,29 @@ "storage": { "title": "Storage", "icon": "M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4", + "features": [ { "title": "Storage", - "tooltip": "The sum of all objects' size in your storage buckets.", - "tiers": { - "free": "Up to 1 GB", + "tooltips": { "pro": "100 GB included, then $0.021 per GB", + "main": "The sum of all objects' size in your storage buckets." + }, + "tiers": { + "free": "1 GB", + "pro": "100 GB", "enterprise": "Unlimited" } }, { "title": "Storage egress", - "tooltip": "Total sum of outgoing traffic (egress) from your storage buckets, only download operations are counted.", + "tooltips": { + "main": "Total sum of outgoing traffic (egress) from your storage buckets, only download operations are counted.", + "pro": "200 GB included, then $0.09 per GB" + }, "tiers": { - "free": "Up to 2 GB", - "pro": "200 GB included, then $0.09 per GB", + "free": "2 GB", + "pro": "200 GB", "enterprise": "Unlimited" } }, @@ -180,20 +206,22 @@ }, { "title": "Max file upload size", - "tooltip": "You can change the upload size in the dashboard", + "tooltips": { "main": "You can change the upload size in the dashboard" }, "tiers": { - "free": "Up to 50MB", - "pro": "Up to 5GB", + "free": "50MB", + "pro": "5GB", "enterprise": "Unlimited" } }, { "title": "Asset transformations", - "tooltip": "Distinct count of all images that were transformed in the billing period, ignoring any transformations.", + "tooltips": { + "main": "Distinct count of all images that were transformed in the billing period, ignoring any transformations.", + "pro": "Unlimited for 100 origin images. $5 per 1000 origin images after the first 100" + }, "tiers": { "free": false, - "pro": "Unlimited transformations for 100 origin images, then $5 per 1000 origin images", - "enterprise": "Unlimited" + "pro": "Unlimited for 100 origin images" } } ] @@ -204,27 +232,34 @@ "features": [ { "title": "Invocations", - "tooltip": "Billing is based on the sum of all invocations, independent of response status, throughout your billing period.", + "tooltips": { + "main": "Billing is based on the sum of all invocations, independent of response status, throughout your billing period.", + "pro": "2 Million per month, then $2 per 1 million invocations" + }, + "main": "Billing is based on the sum of all invocations, independent of response status, throughout your billing period.", "tiers": { - "free": "500K per month", - "pro": "2 Million per month, then $2 per 1 million invocations", + "free": "500K/month", + "pro": "2 Million/month", "enterprise": "Unlimited" } }, { "title": "Script size", "tiers": { - "free": "2 mb", - "pro": "10 mb", + "free": "2 MB", + "pro": "10 MB", "enterprise": "Unlimited" } }, { "title": "Number of functions", - "tooltip": "Billing is based on the maximum amount of functions at any point in time throughout your billing period.", + "tooltips": { + "main": "Billing is based on the maximum amount of functions at any point in time throughout your billing period.", + "pro": "100, then $10 per additional 100 functions" + }, "tiers": { "free": "10", - "pro": "100, then $10 per additional 100 functions", + "pro": "100", "enterprise": "Unlimited" } } @@ -244,27 +279,34 @@ }, { "title": "Concurrent Peak Connections", - "tooltip": "We only charge for successful connections - not connection attempts!", + "tooltips": { + "main": "We only charge for successful connections - not connection attempts!", + "pro": "Up to 500, then $10 per 1000" + }, "tiers": { - "free": "Up to 200", - "pro": "Up to 500, then $10 per 1000", + "free": "200", + "pro": "500", "enterprise": "Unlimited concurrent connections and volume discount" } }, { "title": "Messages Per Month", - "tooltip": "Billing is based on the total sum of Realtime messages throughout your billing period.", - "tiers": { + "tooltips": { + "main": "Billing is based on the total sum of Realtime messages throughout your billing period.", "free": "2 Million (including database)", - "pro": "5 Million, then $2.50 per million", + "pro": "5 Million, then $2.50 per million" + }, + "tiers": { + "free": "2 Million", + "pro": "5 Million", "enterprise": "Volume discounts on messages" } }, { "title": "Max Message Size", "tiers": { - "free": "250 Kb", - "pro": "3 Mb", + "free": "250 KB", + "pro": "3 MB", "enterprise": "Custom" } } @@ -378,10 +420,13 @@ }, { "title": "Custom Domains", + "tooltips": { + "enterprise": "Volume discounts available." + }, "tiers": { "free": false, "pro": "$10 per domain per month per project add on", - "enterprise": "1 included, additional custom domains $10 per custom domain per month. Volume discounts available." + "enterprise": "1 included, additional $10/domain/month" } } ] diff --git a/apps/www/data/PricingAddOnTable.json b/apps/www/data/PricingAddOnTable.json new file mode 100644 index 00000000000..e20da275960 --- /dev/null +++ b/apps/www/data/PricingAddOnTable.json @@ -0,0 +1,360 @@ +{ + "database": { + "title": "Optimized Compute", + "features": [ + "For projects that need dedicated compute", + "For scaling number of connections", + "Additional CPU & RAM options", + "Additional Disk IO Bandwidth" + ], + "rows": [ + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "Starter" }, + { "key": "pricing", "title": "Pricing", "value": "$0" }, + { "key": "cpu", "title": "CPU", "value": "2-core ARM" }, + { "key": "dedicated", "title": "Dedicated", "value": false }, + { "key": "memory", "title": "Memory", "value": "1 GB" }, + { "key": "maxDiskIO", "title": "Disk IO Bandwidth up to", "value": "2,606 Mbps" }, + { + "key": "baseDiskIO", + "title": "Baseline Disk IO Bandwidth", + "value": "87 Mbps" + }, + { + "key": "directConnections", + "title": "Connections: Direct", + "value": "10" + }, + { + "key": "poolerConnections", + "title": "Connections: Pooler", + "value": "50" + } + ] + }, + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "Small" }, + { "key": "pricing", "title": "Pricing", "value": "$5" }, + { "key": "cpu", "title": "CPU", "value": "2-core ARM" }, + { "key": "dedicated", "title": "Dedicated", "value": false }, + { "key": "memory", "title": "Memory", "value": "2 GB" }, + { "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "2,606 Mbps" }, + { + "key": "baseDiskIO", + "title": "Baseline Disk IO Bandwidth", + "value": "174 Mbps" + }, + { + "key": "directConnections", + "title": "Connections: Direct (recommended)", + "value": "30" + }, + { + "key": "poolerConnections", + "title": "Connections: Pooler (recommended)", + "value": "75" + } + ] + }, + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "Medium" }, + { "key": "pricing", "title": "Pricing", "value": "$50" }, + { "key": "cpu", "title": "CPU", "value": "2-core ARM" }, + { "key": "dedicated", "title": "Dedicated", "value": false }, + { "key": "memory", "title": "Memory", "value": "4 GB" }, + { "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "2,606 Mbps" }, + { + "key": "baseDiskIO", + "title": "Baseline Disk IO Bandwidth", + "value": "347 Mbps" + }, + { + "key": "directConnections", + "title": "Connections: Direct (recommended)", + "value": "50" + }, + { + "key": "poolerConnections", + "title": "Connections: Pooler (recommended)", + "value": "150" + } + ] + }, + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "Large" }, + { "key": "pricing", "title": "Pricing", "value": "$100" }, + { "key": "cpu", "title": "CPU", "value": "2-core ARM" }, + { "key": "dedicated", "title": "Dedicated", "value": true }, + { "key": "memory", "title": "Memory", "value": "8 GB" }, + { "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "4,750 Mbps" }, + { + "key": "baseDiskIO", + "title": "Baseline Disk IO Bandwidth", + "value": "630 Mbps" + }, + { + "key": "directConnections", + "title": "Connections: Direct (recommended)", + "value": "100" + }, + { + "key": "poolerConnections", + "title": "Connections: Pooler (recommended)", + "value": "300" + } + ] + }, + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "XL" }, + { "key": "pricing", "title": "Pricing", "value": "$200" }, + { "key": "cpu", "title": "CPU", "value": "4-core ARM" }, + { "key": "dedicated", "title": "Dedicated", "value": true }, + { "key": "memory", "title": "Memory", "value": "16 GB" }, + { "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "4,750 Mbps" }, + { + "key": "baseDiskIO", + "title": "Baseline Disk IO Bandwidth", + "value": "1,188 Mbps" + }, + { + "key": "directConnections", + "title": "Connections: Direct (recommended)", + "value": "200" + }, + { + "key": "poolerConnections", + "title": "Connections: Pooler (recommended)", + "value": "600" + } + ] + }, + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "2XL" }, + { "key": "pricing", "title": "Pricing", "value": "$400" }, + { "key": "cpu", "title": "CPU", "value": "8-core ARM" }, + { "key": "dedicated", "title": "Dedicated", "value": true }, + { "key": "memory", "title": "Memory", "value": "32 GB" }, + { "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "4,750 Mbps" }, + { + "key": "baseDiskIO", + "title": "Baseline Disk IO Bandwidth", + "value": "2,375 Mbps" + }, + { + "key": "directConnections", + "title": "Connections: Direct (recommended)", + "value": "350" + }, + { + "key": "poolerConnections", + "title": "Connections: Pooler (recommended)", + "value": "1200" + } + ] + }, + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "4XL" }, + { "key": "pricing", "title": "Pricing", "value": "$950" }, + { "key": "cpu", "title": "CPU", "value": "16-core ARM" }, + { "key": "dedicated", "title": "Dedicated", "value": true }, + { "key": "memory", "title": "Memory", "value": "64 GB" }, + { "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "4,750 Mbps" }, + { + "key": "baseDiskIO", + "title": "Baseline Disk IO Bandwidth", + "value": "4,750 Mbps" + }, + { + "key": "directConnections", + "title": "Connections: Direct (recommended)", + "value": "420" + }, + { + "key": "poolerConnections", + "title": "Connections: Pooler (recommended)", + "value": "2800" + } + ] + }, + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "8XL" }, + { "key": "pricing", "title": "Pricing", "value": "$1,860" }, + { "key": "cpu", "title": "CPU", "value": "32-core ARM" }, + { "key": "dedicated", "title": "Dedicated", "value": true }, + { "key": "memory", "title": "Memory", "value": "128 GB" }, + { "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "9,500 Mbps" }, + { + "key": "baseDiskIO", + "title": "Baseline Disk IO Bandwidth", + "value": "9,500 Mbps" + }, + { + "key": "directConnections", + "title": "Connections: Direct (recommended)", + "value": "450" + }, + { + "key": "poolerConnections", + "title": "Connections: Pooler (recommended)", + "value": "5600" + } + ] + }, + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "12XL" }, + { "key": "pricing", "title": "Pricing", "value": "$2,790" }, + { "key": "cpu", "title": "CPU", "value": "48-core ARM" }, + { "key": "dedicated", "title": "Dedicated", "value": true }, + { "key": "memory", "title": "Memory", "value": "192 GB" }, + { + "key": "maxDiskIO", + "title": "Maximum Disk IO Bandwidth", + "value": "14,250 Mbps" + }, + { + "key": "baseDiskIO", + "title": "Baseline Disk IO Bandwidth", + "value": "14,250 Mbps" + }, + { + "key": "directConnections", + "title": "Connections: Direct (recommended)", + "value": "480" + }, + { + "key": "poolerConnections", + "title": "Connections: Pooler (recommended)", + "value": "8600" + } + ] + }, + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "16XL" }, + { "key": "pricing", "title": "Pricing", "value": "$3,720" }, + { "key": "cpu", "title": "CPU", "value": "64-core ARM" }, + { "key": "dedicated", "title": "Dedicated", "value": true }, + { "key": "memory", "title": "Memory", "value": "256 GB" }, + { + "key": "maxDiskIO", + "title": "Maximum Disk IO Bandwidth", + "value": "19,000 Mbps" + }, + { + "key": "baseDiskIO", + "title": "Baseline Disk IO Bandwidth", + "value": "19,000 Mbps" + }, + { + "key": "directConnections", + "title": "Connections: Direct (recommended)", + "value": "500" + }, + { + "key": "poolerConnections", + "title": "Connections: Pooler (recommended)", + "value": "11,600" + } + ] + } + ] + }, + "support": { + "title": "Support", + "features": [ + "Guaranteed Response Times", + "SLAs", + "Designated Support", + "Advanced Support Scope" + ], + "rows": [ + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "Standard" }, + { "key": "pricing", "title": "Pricing", "value": "Contact Us" }, + { + "key": "severityLevelUrgent", + "title": "1. Severity Level: Urgent", + "value": "1 business hour (24/7x365)" + }, + { + "key": "severityLevelHigh", + "title": "2. Severity Level: High", + "value": "4 business hours (Monday - Friday)" + }, + { + "key": "severityLevelNormal", + "title": "3. Severity Level: Normal", + "value": "1 business day (Monday - Friday)" + }, + { + "key": "severityLevelLow", + "title": "4. Severity Level: Low", + "value": "2 business days (Monday - Friday)" + } + ] + }, + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "Priority" }, + { "key": "pricing", "title": "Pricing", "value": "Contact Us" }, + { + "key": "severityLevelUrgent", + "title": "1. Severity Level: Urgent", + "value": "1 business hour (24/7x365)" + }, + { + "key": "severityLevelHigh", + "title": "2. Severity Level: High", + "value": "2 business hours (Monday - Friday)" + }, + { + "key": "severityLevelNormal", + "title": "3. Severity Level: Normal", + "value": "1 business day (Monday - Friday)" + }, + { + "key": "severityLevelLow", + "title": "4. Severity Level: Low", + "value": "2 business days (Monday - Friday)" + } + ] + }, + { + "columns": [ + { "key": "plan", "title": "Plan", "value": "Priority Plus" }, + { "key": "pricing", "title": "Pricing", "value": "Contact Us" }, + { + "key": "severityLevelUrgent", + "title": "1. Severity Level: Urgent", + "value": "1 business hour (24/7x365)" + }, + { + "key": "severityLevelHigh", + "title": "2. Severity Level: High", + "value": "2 business hours (24/7x365)" + }, + { + "key": "severityLevelNormal", + "title": "3. Severity Level: Normal", + "value": "12 business hours (Monday - Friday)" + }, + { + "key": "severityLevelLow", + "title": "4. Severity Level: Low", + "value": "1 business days (Monday - Friday)" + } + ] + } + ] + } +} diff --git a/apps/www/data/PricingAddOn.json b/apps/www/data/PricingAddOns.json similarity index 99% rename from apps/www/data/PricingAddOn.json rename to apps/www/data/PricingAddOns.json index d756fb75d18..63b69e84725 100644 --- a/apps/www/data/PricingAddOn.json +++ b/apps/www/data/PricingAddOns.json @@ -1,5 +1,5 @@ { - "database": { + "optimized_compute": { "title": "Optimized Compute", "features": [ "For projects that need dedicated compute", diff --git a/apps/www/data/PricingFAQ.json b/apps/www/data/PricingFAQ.json index f6d897e8541..44fa8c58784 100644 --- a/apps/www/data/PricingFAQ.json +++ b/apps/www/data/PricingFAQ.json @@ -1,7 +1,7 @@ [ { "question": "Can I cap my usage so my bill doesn't run over?", - "answer": "Yes. Spend caps are on by default on the pro tier. You can turn spend caps off for usage beyond the tier limits to pay as you grow." + "answer": "Yes. Spend caps are on by default on the Pro tier. You can turn spend caps off for usage beyond the tier limits to pay as you grow." }, { "question": "I'm worried I could end up with a huge bill at the end of the month.", @@ -9,7 +9,7 @@ }, { "question": "When will I be billed?", - "answer": "Our Pro plan is charged up front, and billed on a monthly basis. Additional usage costs are also billed at the end of the month." + "answer": "Our Pro tier is charged up front, and billed on a monthly basis. Additional usage costs are also billed at the end of the month." }, { "question": "Are you going to change your pricing in the future?", diff --git a/apps/www/pages/404.tsx b/apps/www/pages/404.tsx index f0d453140e4..f31fe7d4dd3 100644 --- a/apps/www/pages/404.tsx +++ b/apps/www/pages/404.tsx @@ -5,7 +5,7 @@ import { Button } from 'ui' import DefaultLayout from '../components/Layouts/Default' -import { useTheme } from '~/components/Providers' +import { useTheme } from 'common/Providers' import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png' import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png' diff --git a/apps/www/pages/_app.tsx b/apps/www/pages/_app.tsx index afe6bd7a1a1..b4fb7a031ce 100644 --- a/apps/www/pages/_app.tsx +++ b/apps/www/pages/_app.tsx @@ -6,7 +6,7 @@ import { useEffect } from 'react' import Meta from '~/components/Favicons' import '../styles/index.css' import { post } from './../lib/fetchWrapper' -import { ThemeProvider } from '~/components/Providers' +import { ThemeProvider } from 'common/Providers' import Head from 'next/head' export default function MyApp({ Component, pageProps }: AppProps) { diff --git a/apps/www/pages/blog.tsx b/apps/www/pages/blog.tsx index a96ed86cd9a..75537732c6a 100644 --- a/apps/www/pages/blog.tsx +++ b/apps/www/pages/blog.tsx @@ -166,6 +166,7 @@ function FeaturedThumb(blog: PostTypes) { src={`/images/blog/` + (blog.thumb ? blog.thumb : blog.image)} layout="fill" objectFit="cover" + alt="blog thumbnail" />
diff --git a/apps/www/pages/blog/[slug].tsx b/apps/www/pages/blog/[slug].tsx index f5762169a36..1d612ab7b95 100644 --- a/apps/www/pages/blog/[slug].tsx +++ b/apps/www/pages/blog/[slug].tsx @@ -209,6 +209,7 @@ function BlogPostPage(props: any) { {`${author.author} {props.blog.title} diff --git a/apps/www/pages/brand-assets.tsx b/apps/www/pages/brand-assets.tsx index e29ca0ee249..72eb3c161c5 100644 --- a/apps/www/pages/brand-assets.tsx +++ b/apps/www/pages/brand-assets.tsx @@ -49,7 +49,12 @@ const Index = () => {
- + Supabase logo Preview
diff --git a/apps/www/pages/careers/index.tsx b/apps/www/pages/careers/index.tsx index af3b1cff166..e69ad552801 100644 --- a/apps/www/pages/careers/index.tsx +++ b/apps/www/pages/careers/index.tsx @@ -8,7 +8,7 @@ import { Badge, Button, IconCheck } from 'ui' import Globe from '~/components/Globe' import DefaultLayout from '~/components/Layouts/Default' import SectionContainer from '~/components/Layouts/SectionContainer' -import { useTheme } from '~/components/Providers' +import { useTheme } from 'common/Providers' import career from '~/data/career.json' import Styles from './career.module.css' diff --git a/apps/www/pages/launch-week/index.tsx b/apps/www/pages/launch-week/index.tsx index 113e177a1b0..69620a43bea 100644 --- a/apps/www/pages/launch-week/index.tsx +++ b/apps/www/pages/launch-week/index.tsx @@ -1,5 +1,4 @@ // @ts-nocheck - import { NextSeo } from 'next-seo' import _days from '~/components/LaunchWeek/lw6_days.json' @@ -10,7 +9,7 @@ import { createClient, Session } from '@supabase/supabase-js' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import TicketContainer from '~/components/LaunchWeek/Ticket/TicketContainer' -import { useTheme } from '~/components/Providers' +import { useTheme } from 'common/Providers' import classNames from 'classnames' import styleUtils from '~/components/LaunchWeek/Ticket/utils.module.css' import { SITE_ORIGIN } from '~/lib/constants' @@ -19,6 +18,7 @@ import { WeekDayProps } from '~/components/LaunchWeek/types' import styles from './launchWeek.module.css' import Image from 'next/image' +import Link from 'next/link' const days = _days as WeekDayProps[] const constellation = [ @@ -122,33 +122,51 @@ export default function launchweek() { const SectionButtons = ({ blog, docs, video }) => { return (
- -
- Blog post -
- -
-
-
- {docs && ( - + +
- Docs + Blog post
- + blog post icon
+ + {docs && ( + + +
+ Docs +
+ docs icon +
+
+
+ )} {video && ( - - ) @@ -181,12 +199,11 @@ export default function launchweek() { >
- Launch Week logo

Dec 12 – 16 at 6 AM PT | 9 AM ET

@@ -228,9 +245,10 @@ export default function launchweek() { Ant & Copple + />
Who we hire at Supabase @@ -238,22 +256,34 @@ export default function launchweek() {
- - @@ -263,9 +293,10 @@ export default function launchweek() {
Outro icon + />
Wrap Up @@ -273,14 +304,20 @@ export default function launchweek() {
@@ -433,6 +470,7 @@ export default function launchweek() { ? '/images/launchweek/mfa-dark.png' : '/images/launchweek/mfa-light.png' } + alt="MFA" layout="fill" objectFit="cover" quality={100} @@ -446,6 +484,7 @@ export default function launchweek() { ? '/images/launchweek/mfa-dark-hover.png' : '/images/launchweek/mfa-light-hover.png' } + alt="MFA" layout="fill" objectFit="cover" quality={100} @@ -492,6 +531,7 @@ export default function launchweek() { ? '/images/launchweek/wrappers-visual.svg' : '/images/launchweek/wrappers-visual-light.svg' } + alt="Wrappers" layout="fill" objectFit="cover" quality={100} @@ -508,6 +548,7 @@ export default function launchweek() { ? '/images/launchweek/wrappers-visual-hover.svg' : '/images/launchweek/wrappers-visual-hover-light.svg' } + alt="Wrappers" layout="fill" objectFit="cover" quality={100} @@ -559,6 +600,7 @@ export default function launchweek() { ? '/images/launchweek/vault-visual.svg' : '/images/launchweek/vault-visual-light.svg' } + alt="Vault" layout="fill" objectFit="cover" quality={100} @@ -575,6 +617,7 @@ export default function launchweek() { ? '/images/launchweek/vault-visual-hover.svg' : '/images/launchweek/vault-visual-hover-light.svg' } + alt="Vault" layout="fill" objectFit="cover" quality={100} @@ -601,6 +644,7 @@ export default function launchweek() { ? '/images/launchweek/TCE-visual.svg' : '/images/launchweek/TCE-visual-light.svg' } + alt="TCE" layout="fill" objectFit="cover" quality={100} @@ -617,6 +661,7 @@ export default function launchweek() { ? '/images/launchweek/TCE-visual-hover.svg' : '/images/launchweek/TCE-visual-hover-light.svg' } + alt="TCE" layout="fill" objectFit="cover" quality={100} @@ -648,6 +693,7 @@ export default function launchweek() { ? '/images/launchweek/community-visual.svg' : '/images/launchweek/community-visual-light.svg' } + alt="Community" layout="fill" objectFit="cover" quality={100} @@ -664,6 +710,7 @@ export default function launchweek() { ? '/images/launchweek/community-visual-hover.svg' : '/images/launchweek/community-visual-light-hover.svg' } + alt="Community" layout="fill" objectFit="cover" quality={100} @@ -675,68 +722,64 @@ export default function launchweek() {
- -
- Blog post - - - - @@ -755,6 +798,7 @@ export default function launchweek() { ? '/images/launchweek/PgGraphql-visual.svg' : '/images/launchweek/PgGraphql-visual-light.svg' } + alt="PgGraphql" layout="fill" objectFit="cover" quality={100} @@ -771,6 +815,7 @@ export default function launchweek() { ? '/images/launchweek/PgGraphql-visual-hover.svg' : '/images/launchweek/PgGraphql-visual-hover-light.svg' } + alt="PgGraphql" layout="fill" objectFit="cover" quality={100} @@ -800,6 +845,7 @@ export default function launchweek() { ? '/images/launchweek/custom-domains-visual.svg' : '/images/launchweek/custom-domains-visual-light.svg' } + alt="Custom Domains" layout="fill" objectFit="cover" quality={100} @@ -816,6 +862,7 @@ export default function launchweek() { ? '/images/launchweek/custom-domains-visual-hover.svg' : '/images/launchweek/custom-domains-visual-hover-light.svg' } + alt="Custom Domains" layout="fill" objectFit="cover" quality={100} @@ -845,6 +892,7 @@ export default function launchweek() { ? '/images/launchweek/PITR-visual.svg' : '/images/launchweek/PITR-visual-light.svg' } + alt="PITR" layout="fill" objectFit="cover" quality={100} @@ -861,6 +909,7 @@ export default function launchweek() { ? '/images/launchweek/PITR-visual-hover.svg' : '/images/launchweek/PITR-visual-hover-light.svg' } + alt="PITR" layout="fill" objectFit="cover" quality={100} @@ -890,6 +939,7 @@ export default function launchweek() { ? '/images/launchweek/pg_crdt-visual.svg' : '/images/launchweek/pg_crdt-visual-light.svg' } + alt="pg_crdt" layout="fill" objectFit="cover" quality={100} @@ -906,6 +956,7 @@ export default function launchweek() { ? '/images/launchweek/pg_crdt-visual-hover.svg' : '/images/launchweek/pg_crdt-visual-hover-light.svg' } + alt="pg_crdt" layout="fill" objectFit="cover" quality={100} @@ -935,6 +986,7 @@ export default function launchweek() { ? '/images/launchweek/postgres-visual.svg' : '/images/launchweek/postgres-visual-light.svg' } + alt="postgres" layout="fill" objectFit="cover" quality={100} @@ -951,6 +1003,7 @@ export default function launchweek() { ? '/images/launchweek/postgres-visual-hover.svg' : '/images/launchweek/postgres-visual-hover-light.svg' } + alt="postgres" layout="fill" objectFit="cover" quality={100} @@ -980,6 +1033,7 @@ export default function launchweek() { ? '/images/launchweek/PostgREST11-visual.svg' : '/images/launchweek/PostgREST11-visual-light.svg' } + alt="PostgREST11" layout="fill" objectFit="cover" quality={100} @@ -996,6 +1050,7 @@ export default function launchweek() { ? '/images/launchweek/PostgREST11-visual-hover.svg' : '/images/launchweek/PostgREST11-visual-hover-light.svg' } + alt="PostgREST11" layout="fill" objectFit="cover" quality={100} @@ -1039,14 +1094,11 @@ export default function launchweek() { The traditional parallel Hackathon is back! Build a new open source project with Supabase and you can win $1500 in GitHub sponsorships and a coveted Supabase Darkmode Keyboard! For more info check the{' '} - - blog post - + + + blog post + + .

@@ -1074,27 +1126,27 @@ export default function launchweek() { or if you just want to chill and watch people build, come and join us!

- - Join our Discord - + + + Join our Discord{' '} + + +

Submission

Submit your project through{' '} - - madewithsupabase.com - + + + madewithsupabase.com + + . All submissions must be open source and publically available. Submissions close Monday 19th Dec 00:01 AM PT.

@@ -1106,75 +1158,73 @@ export default function launchweek() { 'flex basis-1/3 flex-col px-5' )} > - -
- -

Supabase Cache Helpers

-

Previous Best Overall Project Winner

-
-
- -
- -

Super UI

-

Previous Best Overall Project Runner Up

-
-
- -
- -

Quid Faciam Hodie?

-

Winner Best Flutter Project

-
-
- -
- -

That Hot Dog Game

-

Previous Winner: Most Fun/Interesting

-
-
- -
- -

RepoWatch

-

Previous Winner Best Realtime Project

-
-
+ + +
+ link icon +

Supabase Cache Helpers

+

Previous Best Overall Project Winner

+
+
+ + + +
+ link icon +

Super UI

+

+ Previous Best Overall Project Runner Up +

+
+
+ + + +
+ link icon +

Quid Faciam Hodie?

+

Winner Best Flutter Project

+
+
+ + + +
+ link icon +

That Hot Dog Game

+

Previous Winner: Most Fun/Interesting

+
+
+ + + +
+ link icon +

RepoWatch

+

Previous Winner Best Realtime Project

+
+
+
@@ -1202,12 +1252,15 @@ export default function launchweek() { >
- - - + + + {`${creator.first_name} + +
) @@ -1227,12 +1280,14 @@ export default function launchweek() {

We worked with more than 30 content creators from around the world to drop a mountain of content simultaneously! - -

- + + +
+ See all the content + +
+
+

{activeCreator !== null && (
diff --git a/apps/www/pages/launch-week/tickets/[username].tsx b/apps/www/pages/launch-week/tickets/[username].tsx index 7bd706c81bd..b39313e2c8e 100644 --- a/apps/www/pages/launch-week/tickets/[username].tsx +++ b/apps/www/pages/launch-week/tickets/[username].tsx @@ -8,7 +8,7 @@ import { SITE_URL, SAMPLE_TICKET_NUMBER } from '~/lib/constants' import { createClient } from '@supabase/supabase-js' import { useEffect, useState } from 'react' import { FastGlobOptionsWithoutCwd } from 'globby' -import { useTheme } from '~/components/Providers' +import { useTheme } from 'common/Providers' type Props = { username: string | null diff --git a/apps/www/pages/launch-week/tickets/index.tsx b/apps/www/pages/launch-week/tickets/index.tsx index fba61ea897e..b7476a0475c 100644 --- a/apps/www/pages/launch-week/tickets/index.tsx +++ b/apps/www/pages/launch-week/tickets/index.tsx @@ -6,7 +6,7 @@ import { SITE_URL } from '~/lib/constants' import { useRouter } from 'next/router' import { createClient, Session } from '@supabase/supabase-js' import { useState, useEffect } from 'react' -import { useTheme } from '~/components/Providers' +import { useTheme } from 'common/Providers' export default function TicketHome() { const { isDarkMode } = useTheme() diff --git a/apps/www/pages/partners/[slug].tsx b/apps/www/pages/partners/[slug].tsx index dc498cae9fc..7ce29906765 100644 --- a/apps/www/pages/partners/[slug].tsx +++ b/apps/www/pages/partners/[slug].tsx @@ -115,12 +115,12 @@ function Partner({ partner }: { partner: Partner }) { {partner.video && (