From 797e8c00a183e75c8db5734273e8a315e970e2aa Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 17 May 2023 12:21:44 +0800 Subject: [PATCH 001/108] chore: add build check to studio ci --- .github/workflows/studio-tests.yml | 12 +++++++----- package.json | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/studio-tests.yml b/.github/workflows/studio-tests.yml index 766fddf9226..4a540f652a8 100644 --- a/.github/workflows/studio-tests.yml +++ b/.github/workflows/studio-tests.yml @@ -1,7 +1,7 @@ # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions -name: Studio Unit Tests +name: Studio Unit Tests & Build Check on: push: @@ -15,13 +15,15 @@ on: - 'studio/**' - 'package-lock.json' jobs: - build: + check: runs-on: ubuntu-latest strategy: matrix: node-version: [16.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + cmd: + - npm run test:studio + - npm run build:studio steps: - uses: actions/checkout@v3 @@ -33,6 +35,6 @@ jobs: - name: Install deps run: npm ci working-directory: ./ - - name: Run tests - run: npm run test:studio + - name: Run ${{ matrix.cmd }} + run: ${{ matrix.cmd }} working-directory: ./ diff --git a/package.json b/package.json index ff003cd5799..50145626e34 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ ], "scripts": { "build": "turbo run build", + "build:studio": "turbo run build --filter=studio", "dev": "turbo run dev --parallel", "dev:reference": "turbo run dev --filter=reference --parallel", "dev:studio": "turbo run dev --filter=studio --parallel", From 68dca39bf5f4d35c58dddb65248caaea91e2eac2 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 24 May 2023 15:37:05 +0800 Subject: [PATCH 002/108] feat: add network traffic charting --- .../interfaces/Reports/Reports.constants.ts | 55 ++++++++++++++++--- .../Reports/renderers/ApiRenderers.tsx | 52 ++++++++++++++++++ studio/components/ui/Charts/AreaChart.tsx | 7 ++- studio/components/ui/Charts/BarChart.tsx | 8 +-- studio/components/ui/Charts/ChartHeader.tsx | 16 +++--- studio/components/ui/Charts/Charts.types.tsx | 3 +- .../project/[ref]/reports/api-overview.tsx | 24 +++++++- .../pages/projects/reports/api-report.test.js | 2 + 8 files changed, 139 insertions(+), 28 deletions(-) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 914c8a068ae..91cd9cd0870 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -75,8 +75,8 @@ export const PRESET_CONFIG: Record = { timestamp ASC`, }, topRoutes: { - queryType: "logs", - sql: (filters)=> ` + queryType: 'logs', + sql: (filters) => ` select request.path as path, request.method as method, @@ -95,7 +95,7 @@ export const PRESET_CONFIG: Record = { count desc limit 3 - ` + `, }, errorCounts: { queryType: 'logs', @@ -118,8 +118,8 @@ export const PRESET_CONFIG: Record = { `, }, topErrorRoutes: { - queryType: "logs", - sql: (filters)=> ` + queryType: 'logs', + sql: (filters) => ` select request.path as path, request.method as method, @@ -140,7 +140,7 @@ export const PRESET_CONFIG: Record = { count desc limit 3 - ` + `, }, responseSpeed: { queryType: 'logs', @@ -162,8 +162,8 @@ export const PRESET_CONFIG: Record = { `, }, topSlowRoutes: { - queryType: "logs", - sql: (filters)=> ` + queryType: 'logs', + sql: (filters) => ` select request.path as path, request.method as method, @@ -183,7 +183,44 @@ export const PRESET_CONFIG: Record = { avg desc limit 3 - ` + `, + }, + networkTraffic: { + queryType: 'logs', + sql: (filters) => ` + select + cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp, + coalesce( + safe_divide( + sum( + cast(coalesce(headers.content_length, "0") as int64) + ), + 1000000 + ), + 0 + ) as ingress_mb, + coalesce( + safe_divide( + sum( + cast(coalesce(resp_headers.content_length, "0") as int64) + ), + 1000000 + ), + 0 + ) as egress_mb, + FROM + edge_logs t + cross join unnest(metadata) as m + cross join unnest(m.response) as response + cross join unnest(m.request) as request + cross join unnest(request.headers) as headers + cross join unnest(response.headers) as resp_headers + ${generateRexepWhere(filters)} + GROUP BY + timestamp + ORDER BY + timestamp ASC + `, }, }, }, diff --git a/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx b/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx index d6b97f6c3ab..9ecdf49b13c 100644 --- a/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx @@ -9,7 +9,59 @@ import { Button, Collapsible, IconChevronRight } from 'ui' import { queryParamsToObject } from '../Reports.utils' import { Fragment } from 'react' import useFillTimeseriesSorted from 'hooks/analytics/useFillTimeseriesSorted' +import sumBy from 'lodash/sumBy' +export const networkTrafficRenderer = ( + props: ReportWidgetProps<{ + timestamp: string + ingress: number + egress: number + }> +) => { + const data = useFillTimeseriesSorted( + props.data, + 'timestamp', + ['ingress_mb', 'egress_mb'], + 0, + props.params?.iso_timestamp_start, + props.params?.iso_timestamp_end + ) + const totalIngress = sumBy(props.data, 'ingress_mb') + const totalEgress = sumBy(props.data, 'egress_mb') + + function determinePrecision(valueInMb: number) { + return valueInMb < 0.001 ? 7 : totalIngress > 1 ? 2 : 4 + } + return ( +
+ + + +
+ ) +} export const TotalRequestsChartRenderer = ( props: ReportWidgetProps<{ timestamp: string diff --git a/studio/components/ui/Charts/AreaChart.tsx b/studio/components/ui/Charts/AreaChart.tsx index f83fcf91625..256fef27935 100644 --- a/studio/components/ui/Charts/AreaChart.tsx +++ b/studio/components/ui/Charts/AreaChart.tsx @@ -24,7 +24,7 @@ export interface AreaChartProps extends CommonChartProps { displayDateInUtc?: boolean } -const AreaChart: React.FC = ({ +const AreaChart = ({ data, yAxisKey, xAxisKey, @@ -36,8 +36,9 @@ const AreaChart: React.FC = ({ displayDateInUtc, minimalHeader, className = '', + valuePrecision, size = 'normal', -}) => { +}: AreaChartProps) => { const { Container } = useChartSize(size) const [focusDataIndex, setFocusDataIndex] = useState(null) @@ -62,7 +63,7 @@ const AreaChart: React.FC = ({ customDateFormat={customDateFormat} highlightedValue={ typeof resolvedHighlightedValue === 'number' - ? numberFormatter(resolvedHighlightedValue) + ? numberFormatter(resolvedHighlightedValue, valuePrecision) : resolvedHighlightedValue } highlightedLabel={resolvedHighlightedLabel} diff --git a/studio/components/ui/Charts/BarChart.tsx b/studio/components/ui/Charts/BarChart.tsx index 2da9cf9a76e..cba8bf685e2 100644 --- a/studio/components/ui/Charts/BarChart.tsx +++ b/studio/components/ui/Charts/BarChart.tsx @@ -13,13 +13,12 @@ dayjs.extend(utc) export interface BarChartProps extends CommonChartProps { yAxisKey: string xAxisKey: string - format?: string customDateFormat?: string displayDateInUtc?: boolean onBarClick?: (datum: Datum, tooltipData?: Parameters[0]) => void } -const BarChart: React.FC = ({ +const BarChart = ({ data, yAxisKey, xAxisKey, @@ -30,10 +29,11 @@ const BarChart: React.FC = ({ highlightedLabel, displayDateInUtc, minimalHeader, + valuePrecision, className = '', size = 'normal', onBarClick, -}) => { +}: BarChartProps) => { const { Container } = useChartSize(size) const [focusDataIndex, setFocusDataIndex] = useState(null) @@ -58,7 +58,7 @@ const BarChart: React.FC = ({ customDateFormat={customDateFormat} highlightedValue={ typeof resolvedHighlightedValue === 'number' - ? numberFormatter(resolvedHighlightedValue) + ? numberFormatter(resolvedHighlightedValue, valuePrecision) : resolvedHighlightedValue } highlightedLabel={resolvedHighlightedLabel} diff --git a/studio/components/ui/Charts/ChartHeader.tsx b/studio/components/ui/Charts/ChartHeader.tsx index ee515259815..93ce980b751 100644 --- a/studio/components/ui/Charts/ChartHeader.tsx +++ b/studio/components/ui/Charts/ChartHeader.tsx @@ -1,8 +1,6 @@ -import { DateTimeFormats } from './Charts.constants' - export interface ChartHeaderProps { title?: string - format?: string + format?: string | ((value: unknown) => string) customDateFormat?: string minimalHeader?: boolean displayDateInUtc?: boolean @@ -15,7 +13,7 @@ const ChartHeader: React.FC = ({ highlightedLabel, title, minimalHeader = false, -}) => { +}: ChartHeaderProps) => { const chartTitle = (

{title}

) @@ -24,8 +22,10 @@ const ChartHeader: React.FC = ({
- {highlightedValue} - {format} + {highlightedValue !== undefined && String(highlightedValue)} + + {typeof format === 'function' ? format(highlightedValue) : format} +
) const label =
{highlightedLabel}
@@ -35,7 +35,7 @@ const ChartHeader: React.FC = ({
{title && chartTitle}
- {highlightedValue && highlighted} + {highlightedValue !== undefined && highlighted} {label}
@@ -45,7 +45,7 @@ const ChartHeader: React.FC = ({ return (
{title && chartTitle} - {highlightedValue && highlighted} + {highlightedValue !== undefined && highlighted} {label}
) diff --git a/studio/components/ui/Charts/Charts.types.tsx b/studio/components/ui/Charts/Charts.types.tsx index e02178f9350..bf0d1c0aa97 100644 --- a/studio/components/ui/Charts/Charts.types.tsx +++ b/studio/components/ui/Charts/Charts.types.tsx @@ -13,6 +13,7 @@ export interface CommonChartProps > { title?: string className?: string + valuePrecision?: number size?: 'tiny' | 'small' | 'normal' | 'large' } @@ -27,7 +28,7 @@ export interface StackedChartProps extends CommonChartProps { export type HeaderType = { attribute: string focus: number | null - format?: string + format?: string | ((value: unknown) => string); highlightedValue?: number | string highlightedLabel?: string data: D[] diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api-overview.tsx index 2bf633a05b1..439d413d963 100644 --- a/studio/pages/project/[ref]/reports/api-overview.tsx +++ b/studio/pages/project/[ref]/reports/api-overview.tsx @@ -13,6 +13,7 @@ import { ErrorCountsChartRenderer, ResponseSpeedChartRenderer, TopApiRoutesRenderer, + networkTrafficRenderer, } from 'components/interfaces/Reports/renderers/ApiRenderers' import { useState, useEffect } from 'react' import ReportHeader from 'components/interfaces/Reports/ReportHeader' @@ -88,6 +89,15 @@ export const ApiReport: NextPageWithLayout = () => { appendProps={{ data: report.data.topSlowRoutes || [] }} append={TopApiRoutesRenderer} /> + + ) } @@ -106,6 +116,7 @@ const useApiReport = () => { const topErrorRoutes = queryHooks.topErrorRoutes() const responseSpeed = queryHooks.responseSpeed() const topSlowRoutes = queryHooks.topSlowRoutes() + const networkTraffic = queryHooks.networkTraffic() const activeHooks = [ totalRequests, topRoutes, @@ -113,6 +124,7 @@ const useApiReport = () => { topErrorRoutes, responseSpeed, topSlowRoutes, + networkTraffic, ] const [filters, setFilters] = useState([]) const addFilter = (filter: ReportFilterItem) => { @@ -161,17 +173,21 @@ const useApiReport = () => { if (topSlowRoutes.changeQuery) { topSlowRoutes.changeQuery(PRESET_CONFIG.api.queries.topSlowRoutes.sql(filters)) } + + if (networkTraffic.changeQuery) { + networkTraffic.changeQuery(PRESET_CONFIG.api.queries.networkTraffic.sql(filters)) + } }, [JSON.stringify(filters)]) const handleRefresh = async () => { - activeHooks.forEach(hook => hook.runQuery()) + activeHooks.forEach((hook) => hook.runQuery()) } const handleSetParams = (params: Partial) => { - activeHooks.forEach(hook => { + activeHooks.forEach((hook) => { hook.setParams?.((prev: LogsEndpointParams) => ({ ...prev, ...params })) }) } - const isLoading = activeHooks.some(hook => hook.isLoading) + const isLoading = activeHooks.some((hook) => hook.isLoading) return { data: { totalRequests: totalRequests.logData, @@ -180,6 +196,7 @@ const useApiReport = () => { topRoutes: topRoutes.logData, topErrorRoutes: topErrorRoutes.logData, topSlowRoutes: topSlowRoutes.logData, + networkTraffic: networkTraffic.logData, }, params: { totalRequests: totalRequests.params, @@ -188,6 +205,7 @@ const useApiReport = () => { topRoutes: topRoutes.params, topErrorRoutes: topErrorRoutes.params, topSlowRoutes: topSlowRoutes.params, + networkTraffic: networkTraffic.params, }, mergeParams: handleSetParams, filters, diff --git a/studio/tests/pages/projects/reports/api-report.test.js b/studio/tests/pages/projects/reports/api-report.test.js index be214ef6b73..94e889cc389 100644 --- a/studio/tests/pages/projects/reports/api-report.test.js +++ b/studio/tests/pages/projects/reports/api-report.test.js @@ -15,6 +15,8 @@ test(`static elements`, async () => { await screen.findByText('Total Requests') await screen.findByText('Response Errors') await screen.findByText('Response Speed') + await screen.findByText('Network Ingress') + await screen.findByText('Network Egress') await screen.findByText(/Last 24 hours/) await screen.findByText(/Custom/) }) From de1b0f48d53f0d95d8d6f15a71af30e4b08cfbb7 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 24 May 2023 15:44:08 +0800 Subject: [PATCH 003/108] fix: compilation check --- .../components/interfaces/Reports/renderers/ApiRenderers.tsx | 2 +- studio/pages/project/[ref]/reports/api-overview.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx b/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx index 9ecdf49b13c..b5dec3e26fe 100644 --- a/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx @@ -11,7 +11,7 @@ import { Fragment } from 'react' import useFillTimeseriesSorted from 'hooks/analytics/useFillTimeseriesSorted' import sumBy from 'lodash/sumBy' -export const networkTrafficRenderer = ( +export const NetworkTrafficRenderer = ( props: ReportWidgetProps<{ timestamp: string ingress: number diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api-overview.tsx index 439d413d963..f28ea31df93 100644 --- a/studio/pages/project/[ref]/reports/api-overview.tsx +++ b/studio/pages/project/[ref]/reports/api-overview.tsx @@ -13,7 +13,7 @@ import { ErrorCountsChartRenderer, ResponseSpeedChartRenderer, TopApiRoutesRenderer, - networkTrafficRenderer, + NetworkTrafficRenderer, } from 'components/interfaces/Reports/renderers/ApiRenderers' import { useState, useEffect } from 'react' import ReportHeader from 'components/interfaces/Reports/ReportHeader' @@ -96,7 +96,7 @@ export const ApiReport: NextPageWithLayout = () => { title="Network Traffic" tooltip="Ingress and egress of requests and responses respectively" data={report.data.networkTraffic || []} - renderer={networkTrafficRenderer} + renderer={NetworkTrafficRenderer} /> ) From 8c18ba0b99eb35dd972fb64d579b95c8b0fb94f4 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 24 May 2023 15:52:28 +0800 Subject: [PATCH 004/108] chore: fix failing test --- studio/tests/pages/projects/reports/api-report.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/studio/tests/pages/projects/reports/api-report.test.js b/studio/tests/pages/projects/reports/api-report.test.js index 94e889cc389..bffcc319781 100644 --- a/studio/tests/pages/projects/reports/api-report.test.js +++ b/studio/tests/pages/projects/reports/api-report.test.js @@ -15,8 +15,7 @@ test(`static elements`, async () => { await screen.findByText('Total Requests') await screen.findByText('Response Errors') await screen.findByText('Response Speed') - await screen.findByText('Network Ingress') - await screen.findByText('Network Egress') + await screen.findByText('Network Traffic') await screen.findByText(/Last 24 hours/) await screen.findByText(/Custom/) }) From b8762c509bbbb54ca4515ed46461f2e0e62aa07d Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 24 May 2023 12:23:56 -0230 Subject: [PATCH 005/108] Add snippet for views --- .../SQLEditor/SQLEditor.constants.ts | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts b/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts index bfe5fec01a1..d8f4e224626 100644 --- a/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts +++ b/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts @@ -18,13 +18,23 @@ export const SQL_TEMPLATES: SQLTemplate[] = [ { id: 2, type: 'template', + title: 'Add view', + description: + 'Template to add a view. Make sure to change the table and column names to ones that already exist.', + sql: `CREATE VIEW countries_view AS +SELECT id, continent +FROM countries;`, + }, + { + id: 3, + type: 'template', title: 'Add column', description: 'Template to add a column. Make sure to change the name and type.', sql: `alter table table_name add column new_column_name data_type;`, }, { - id: 3, + id: 4, type: 'template', title: 'Add comments', description: 'Templates to add a comment to either a table or a column.', @@ -32,7 +42,7 @@ add column new_column_name data_type;`, comment on column table_name.column_name is 'Column description';`, }, { - id: 4, + id: 5, type: 'template', title: 'Show extensions', description: 'Get a list of extensions in your database and status.', @@ -44,7 +54,7 @@ order by name asc;`, }, { - id: 5, + id: 6, type: 'template', title: 'Show version', description: 'Get your Postgres version.', @@ -53,7 +63,7 @@ order by (select current_setting('server_version_num')) as version_number;`, }, { - id: 6, + id: 7, type: 'template', title: 'Show active connections', description: 'Get the number of active and max connections.', @@ -62,7 +72,7 @@ order by (select setting as max_connections from pg_settings where name = 'max_connections') max_connections;`, }, { - id: 7, + id: 8, type: 'template', title: 'Automatically update timestamps', description: 'Update a column timestamp on every update.', @@ -79,7 +89,7 @@ for each row execute `.trim(), }, { - id: 8, + id: 9, type: 'template', title: 'Increment field value', description: 'Update a field with incrementing value using stored procedure.', @@ -98,7 +108,7 @@ language sql volatile; `.trim(), }, { - id: 9, + id: 10, type: 'template', title: 'pg_stat_statements report', description: 'Select from pg_stat_statements and view recent queries', @@ -131,7 +141,7 @@ select 100;`, }, { - id: 10, + id: 11, type: 'quickstart', title: 'Countries', description: 'Create a table with all the countries in the world.', @@ -410,7 +420,7 @@ insert into public.countries (name,iso2,iso3,local_name,continent) values `.trim(), }, { - id: 11, + id: 12, type: 'quickstart', title: 'Slack Clone', description: 'Build a basic slack clone with Row Level Security.', @@ -607,7 +617,7 @@ values `.trim(), }, { - id: 12, + id: 13, type: 'quickstart', title: 'Todo List', description: 'Build a basic todo list with Row Level Security.', @@ -637,7 +647,7 @@ create policy "Individuals can delete their own todos." on todos for `.trim(), }, { - id: 13, + id: 14, type: 'quickstart', title: 'Stripe Subscriptions', description: 'Starter template for the Next.js Stripe Subscriptions Starter.', @@ -804,7 +814,7 @@ create publication supabase_realtime `.trim(), }, { - id: 14, + id: 15, type: 'quickstart', title: 'User Management Starter', description: 'Sets up a public Profiles table which you can access with your API.', @@ -862,7 +872,7 @@ create policy "Anyone can upload an avatar." on storage.objects `.trim(), }, { - id: 15, + id: 16, type: 'quickstart', title: 'NextAuth Schema Setup', description: 'Sets up a the Schema and Tables for the NextAuth Supabase Adapter.', @@ -971,7 +981,7 @@ GRANT ALL ON TABLE next_auth.verification_tokens TO service_role; `.trim(), }, { - id: 16, + id: 17, type: 'template', title: 'Most frequently invoked', description: 'Most frequently called queries in your database.', @@ -1003,7 +1013,7 @@ select 100;`, }, { - id: 17, + id: 18, type: 'template', title: 'Most time consuming', description: 'Aggregate time spent on a query type.', @@ -1025,7 +1035,7 @@ select 100;`, }, { - id: 18, + id: 19, type: 'template', title: 'Slowest execution time', description: 'Slowest queries based on max execution time.', @@ -1056,7 +1066,7 @@ select 100;`, }, { - id: 19, + id: 20, type: 'template', title: 'Hit rate', description: 'See your cache and index hit rate.', @@ -1073,7 +1083,7 @@ select from pg_statio_user_tables;`, }, { - id: 20, + id: 21, type: 'quickstart', title: 'OpenAI Vector Search', description: 'Template for the Next.js OpenAI Doc Search Starter.', @@ -1081,7 +1091,7 @@ select -- Enable pg_vector extension create extension if not exists vector with schema public; --- Create tables +-- Create tables create table "public"."nods_page" ( id bigserial primary key, parent_page_id bigint references public.nods_page, @@ -1133,7 +1143,7 @@ begin -- -- For the different syntaxes, see https://github.com/pgvector/pgvector order by nods_page_section.embedding <#> embedding - + limit match_count; end; $$; @@ -1144,14 +1154,14 @@ language sql as $$ with recursive chain as ( select * - from nods_page + from nods_page where id = page_id union all select child.* from nods_page as child - join chain on chain.parent_page_id = child.id + join chain on chain.parent_page_id = child.id ) select id, parent_page_id, path, meta from chain; From 5e0f2fe145152c4052218c3d8a4be9d8a8575155 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 24 May 2023 16:01:12 -0230 Subject: [PATCH 006/108] Still show the message in minimized view --- studio/state/sql-editor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/state/sql-editor.ts b/studio/state/sql-editor.ts index 49833b3d8f1..465225690b8 100644 --- a/studio/state/sql-editor.ts +++ b/studio/state/sql-editor.ts @@ -107,7 +107,7 @@ export const sqlEditorState = proxy({ }, collapseUtilityPanel: (id: string) => { if (sqlEditorState.snippets[id]) { - sqlEditorState.snippets[id].splitSizes = [100, 0] + sqlEditorState.snippets[id].splitSizes = [88, 12] } }, restoreUtilityPanel: (id: string) => { From 217793bf31fdb259054ed778eb822290c35ea3df Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Thu, 25 May 2023 13:25:40 +0800 Subject: [PATCH 007/108] chore: fix typo in function name --- .../interfaces/Reports/Reports.constants.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 91cd9cd0870..fa528f9f5a2 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -33,7 +33,7 @@ export const DEFAULT_QUERY_PARAMS = { iso_timestamp_end: REPORTS_DATEPICKER_HELPERS[0].calcTo(), } -const generateRexepWhere = (filters: ReportFilterItem[], prepend = true) => { +const generateRegexpWhere = (filters: ReportFilterItem[], prepend = true) => { if (filters.length === 0) return '' const conditions = filters .map((filter) => { @@ -68,7 +68,7 @@ export const PRESET_CONFIG: Record = { cross join unnest(m.response) as response cross join unnest(m.request) as request cross join unnest(request.headers) as headers - ${generateRexepWhere(filters)} + ${generateRegexpWhere(filters)} GROUP BY timestamp ORDER BY @@ -88,7 +88,7 @@ export const PRESET_CONFIG: Record = { cross join unnest(m.response) as response cross join unnest(m.request) as request cross join unnest(request.headers) as headers - ${generateRexepWhere(filters)} + ${generateRegexpWhere(filters)} group by request.path, request.method, request.search, response.status_code order by @@ -110,7 +110,7 @@ export const PRESET_CONFIG: Record = { cross join unnest(request.headers) as headers WHERE response.status_code >= 400 - ${generateRexepWhere(filters, false)} + ${generateRegexpWhere(filters, false)} GROUP BY timestamp ORDER BY @@ -133,7 +133,7 @@ export const PRESET_CONFIG: Record = { cross join unnest(request.headers) as headers where response.status_code >= 400 - ${generateRexepWhere(filters, false)} + ${generateRegexpWhere(filters, false)} group by request.path, request.method, request.search, response.status_code order by @@ -154,7 +154,7 @@ export const PRESET_CONFIG: Record = { cross join unnest(m.response) as response cross join unnest(m.request) as request cross join unnest(request.headers) as headers - ${generateRexepWhere(filters)} + ${generateRegexpWhere(filters)} GROUP BY timestamp ORDER BY @@ -176,7 +176,7 @@ export const PRESET_CONFIG: Record = { cross join unnest(m.response) as response cross join unnest(m.request) as request cross join unnest(request.headers) as headers - ${generateRexepWhere(filters)} + ${generateRegexpWhere(filters)} group by request.path, request.method, request.search, response.status_code order by From 7cb613e0fd0c983a61c4e19ceee747cf11b81da4 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 26 May 2023 03:17:52 +0800 Subject: [PATCH 008/108] fix: missed out replace --- studio/components/interfaces/Reports/Reports.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index fa528f9f5a2..71b5f65324f 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -215,7 +215,7 @@ export const PRESET_CONFIG: Record = { cross join unnest(m.request) as request cross join unnest(request.headers) as headers cross join unnest(response.headers) as resp_headers - ${generateRexepWhere(filters)} + ${generateRegexpWhere(filters)} GROUP BY timestamp ORDER BY From 2756f3505f8d39a2c2513f1dc6dfc68242616457 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 29 May 2023 17:20:07 +0800 Subject: [PATCH 009/108] fix: prevent accidental filing of large timeseries datasets --- studio/components/interfaces/Settings/Logs/Logs.utils.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/studio/components/interfaces/Settings/Logs/Logs.utils.ts b/studio/components/interfaces/Settings/Logs/Logs.utils.ts index d9ad8fdef3d..db2a0b48642 100644 --- a/studio/components/interfaces/Settings/Logs/Logs.utils.ts +++ b/studio/components/interfaces/Settings/Logs/Logs.utils.ts @@ -397,6 +397,11 @@ export const fillTimeseries = ( }) const diff = maxDate.diff(minDate, truncation as dayjs.UnitType) + if (diff > 10000) { + throw new Error( + 'Data error, filling timeseries dynamically with more than 10k data points degrades performance.' + ) + } for (let i = 0; i <= diff; i++) { const dateToMaybeAdd = minDate.add(i, truncation as dayjs.ManipulateType) From 90c64f6cf5a6ae0d3e8870a60b6c696e82135e5e Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 30 May 2023 16:42:01 -0230 Subject: [PATCH 010/108] Save SQL Editor split size to localStorage --- studio/components/interfaces/SQLEditor/SQLEditor.tsx | 11 ++++++++++- studio/state/sql-editor.ts | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/studio/components/interfaces/SQLEditor/SQLEditor.tsx b/studio/components/interfaces/SQLEditor/SQLEditor.tsx index f9e9d0c71b7..a5485343771 100644 --- a/studio/components/interfaces/SQLEditor/SQLEditor.tsx +++ b/studio/components/interfaces/SQLEditor/SQLEditor.tsx @@ -7,12 +7,20 @@ import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation' import useLatest from 'hooks/misc/useLatest' import MonacoEditor, { IStandaloneCodeEditor } from './MonacoEditor' import UtilityPanel from './UtilityPanel/UtilityPanel' +import { useLocalStorage } from 'hooks' const SQLEditor = () => { const { ref, id } = useParams() const { project } = useProjectContext() const snap = useSqlEditorStateSnapshot() + const [savedSplitSize, setSavedSplitSize] = useLocalStorage( + 'supabase_sql-editor-split-size', + `[50, 50]` + ) + + const splitSize = savedSplitSize ? JSON.parse(savedSplitSize) : undefined + const { mutate: execute, isLoading: isExecuting } = useExecuteSqlMutation({ onSuccess(data) { if (id) snap.addResult(id, data.result) @@ -34,6 +42,7 @@ const SQLEditor = () => { const onDragEnd = useCallback((sizes: number[]) => { const id = idRef.current if (id) snap.setSplitSizes(id, sizes) + setSavedSplitSize(JSON.stringify(sizes)) }, []) const editorRef = useRef(null) @@ -63,7 +72,7 @@ const SQLEditor = () => { style={{ height: '100%' }} direction="vertical" gutterSize={2} - sizes={(snippet?.splitSizes as number[] | undefined) ?? [50, 50]} + sizes={(splitSize ? splitSize : (snippet?.splitSizes as number[] | undefined)) ?? [50, 50]} minSize={minSize} snapOffset={snapOffset} expandToMin={true} diff --git a/studio/state/sql-editor.ts b/studio/state/sql-editor.ts index 465225690b8..49833b3d8f1 100644 --- a/studio/state/sql-editor.ts +++ b/studio/state/sql-editor.ts @@ -107,7 +107,7 @@ export const sqlEditorState = proxy({ }, collapseUtilityPanel: (id: string) => { if (sqlEditorState.snippets[id]) { - sqlEditorState.snippets[id].splitSizes = [88, 12] + sqlEditorState.snippets[id].splitSizes = [100, 0] } }, restoreUtilityPanel: (id: string) => { From 6199c56372b7c0613d9f9571b2d39180762dc212 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 30 May 2023 22:04:28 -0230 Subject: [PATCH 011/108] Show dropdown caret on SQL Snippets on hover instead of having to click on them first --- .../layouts/SQLEditorLayout/QueryItem.tsx | 31 +++++++++++++------ studio/styles/main.scss | 12 +++++++ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/studio/components/layouts/SQLEditorLayout/QueryItem.tsx b/studio/components/layouts/SQLEditorLayout/QueryItem.tsx index a655aff998a..c8539c5e9b3 100644 --- a/studio/components/layouts/SQLEditorLayout/QueryItem.tsx +++ b/studio/components/layouts/SQLEditorLayout/QueryItem.tsx @@ -38,7 +38,7 @@ const QueryItem = ({ tabInfo }: QueryItemProps) => { key={id} className={clsx( 'flex items-center justify-between rounded-md group', - isActive && 'text-scale-1200 bg-scale-400 dark:bg-scale-300 -active' + isActive && 'text-scale-1200 bg-scale-400 dark:bg-scale-600 -active' )} ref={isActive ? (activeItemRef as React.RefObject) : null} > @@ -52,14 +52,20 @@ const QueryItem = ({ tabInfo }: QueryItemProps) => {

-
{isActive && }
+
{}
) } export default QueryItem -const QueryItemActions = observer(({ tabInfo }: { tabInfo: SqlSnippet }) => { +interface QueryItemActionsProps { + tabInfo: SqlSnippet + activeId: string | undefined +} + +const QueryItemActions = observer(({ tabInfo, activeId }: QueryItemActionsProps) => { + console.log('activeId', activeId) const { ui } = useStore() const { ref } = useParams() const router = useRouter() @@ -75,6 +81,7 @@ const QueryItemActions = observer(({ tabInfo }: { tabInfo: SqlSnippet }) => { const { id, name } = tabInfo || {} const [renameModalOpen, setRenameModalOpen] = useState(false) const [deleteModalOpen, setDeleteModalOpen] = useState(false) + const isActive = id === activeId const onCloseRenameModal = () => { setRenameModalOpen(false) @@ -112,7 +119,7 @@ const QueryItemActions = observer(({ tabInfo }: { tabInfo: SqlSnippet }) => { } return ( -
+
{IS_PLATFORM ? ( { } > -
void @@ -16,7 +16,7 @@ interface BucketRowProps { } const BucketRow = ({ - bucket = {}, + bucket, projectRef = '', isSelected = false, onSelectDeleteBucket = noop, @@ -46,9 +46,10 @@ const BucketRow = ({
- {bucket.status === STORAGE_ROW_STATUS.LOADING ? ( + {/* [JOSHEN TODO] need to change this */} + {false ? ( - ) : canUpdateBuckets && bucket.status === STORAGE_ROW_STATUS.READY && isSelected ? ( + ) : canUpdateBuckets && isSelected ? ( = ({ title, children }) => { - const { ui, meta } = useStore() +const StorageLayout = ({ title, children }: StorageLayoutProps) => { + const { ui } = useStore() const { ref: projectRef } = useParams() - const storageExplorerStore = useStorageStore() - const { selectedBucketToEdit, closeDeleteBucketModal, showDeleteBucketModal, deleteBucket } = - storageExplorerStore || {} - const { data } = useBucketsQuery({ projectRef }) - const buckets = data ?? [] + const storageExplorerStore = useStorageStore() + const { selectedBucketToEdit, closeDeleteBucketModal, showDeleteBucketModal } = + storageExplorerStore || {} const { data: settings, isLoading } = useProjectApiQuery({ projectRef }) const apiService = settings?.autoApiService @@ -47,7 +42,6 @@ const StorageLayout: FC = ({ title, children }) => { apiService.serviceApiKey, apiService.protocol ) - await storageExplorerStore.fetchBuckets() } else { ui.setNotification({ category: 'error', @@ -58,38 +52,13 @@ const StorageLayout: FC = ({ title, children }) => { storageExplorerStore.setLoaded(true) } - const onSelectDeleteBucket = async (bucket: any) => { - const res = await deleteBucket(bucket) - // Ideally this should be within deleteBucket as its a necessary side effect - // but we'll do so once we refactor to remove the StorageExplorerStore - if (res) { - const policies = meta.policies.list() - const storageObjectsPolicies = filter(policies, { table: 'objects' }) - const formattedStorageObjectPolicies = formatPoliciesForStorage( - buckets, - storageObjectsPolicies - ) - const bucketPolicies = _get( - find(formattedStorageObjectPolicies, { name: bucket.name }), - ['policies'], - [] - ) - await Promise.all( - bucketPolicies.map((policy: any) => { - meta.policies.del(policy.id) - }) - ) - } - } - return ( }> {children} ) diff --git a/studio/components/layouts/StorageLayout/StorageMenu.tsx b/studio/components/layouts/StorageLayout/StorageMenu.tsx index 218b9a947b6..afd1454f03a 100644 --- a/studio/components/layouts/StorageLayout/StorageMenu.tsx +++ b/studio/components/layouts/StorageLayout/StorageMenu.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from 'react' +import { useState } from 'react' import Link from 'next/link' import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' @@ -16,9 +16,7 @@ import CreateBucketModal from 'components/interfaces/Storage/CreateBucketModal' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useBucketsQuery } from 'data/storage/buckets-query' -interface Props {} - -const StorageMenu: FC = () => { +const StorageMenu = () => { const router = useRouter() const { ref, bucketId } = useParams() const [showCreateBucketModal, setShowCreateBucketModal] = useState(false) @@ -128,7 +126,10 @@ const StorageMenu: FC = () => { setShowCreateBucketModal(false)} + onClose={() => { + console.log('onClose') + setShowCreateBucketModal(false) + }} /> void - onSelectDelete: (bucket: any) => void + bucket: Bucket + onClose: () => void } -const DeleteBucketModal: FC = ({ - visible = false, - bucket = {}, - onSelectCancel, - onSelectDelete, -}) => { +const DeleteBucketModal = ({ visible = false, bucket, onClose }: DeleteBucketModalProps) => { + const router = useRouter() + const { ui, meta } = useStore() + const { ref: projectRef } = useParams() + const [deleting, setDeleting] = useState(false) - const [validationInput, setValidationInput] = useState('') + const { data } = useBucketsQuery({ projectRef }) + const { mutateAsync: deleteBucket } = useBucketDeleteMutation() + + const buckets = data ?? [] useEffect(() => { - setValidationInput('') setDeleting(false) }, [visible]) - const onConfirmDelete = () => { + const onDeleteBucket = async () => { + if (!projectRef) return console.error('Project ref is required') + setDeleting(true) - onSelectDelete(bucket) + try { + await deleteBucket({ projectRef, id: bucket.id }) + + // Clean up policies from the corresponding bucket that was deleted + await meta.policies.loadBySchema('storage') + const policies = meta.policies.list() + const storageObjectsPolicies = policies.filter((policy) => policy.table === 'objects') + const formattedStorageObjectPolicies = formatPoliciesForStorage( + buckets, + storageObjectsPolicies + ) + const bucketPolicies = _get( + find(formattedStorageObjectPolicies, { name: bucket.name }), + ['policies'], + [] + ) + await Promise.all( + bucketPolicies.map((policy: any) => { + meta.policies.del(policy.id) + }) + ) + + ui.setNotification({ + category: 'success', + message: `Successfully deleted bucket ${bucket.name}`, + }) + router.push(`/project/${projectRef}/storage/buckets`) + onClose() + } catch (error: any) { + setDeleting(false) + ui.setNotification({ + category: 'error', + message: `Failed to delete bucket: ${error.message}`, + }) + } } return ( @@ -32,8 +76,8 @@ const DeleteBucketModal: FC = ({ visible={visible} title={`Confirm deletion of ${bucket.name}`} confirmPlaceholder="Type in name of bucket" - onConfirm={onConfirmDelete} - onCancel={onSelectCancel} + onConfirm={onDeleteBucket} + onCancel={onClose} confirmString={bucket.name} loading={deleting} text={ diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js index 8d5d4ac1150..0793f877423 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js @@ -184,7 +184,7 @@ const FileExplorerColumn = ({
diff --git a/studio/data/storage/bucket-create-mutation.ts b/studio/data/storage/bucket-create-mutation.ts new file mode 100644 index 00000000000..593e8ad4833 --- /dev/null +++ b/studio/data/storage/bucket-create-mutation.ts @@ -0,0 +1,56 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { post } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' +import { storageKeys } from './keys' + +export type BucketCreateVariables = { + projectRef: string + id: string + isPublic: boolean + file_size_limit: number | null + allowed_mime_types: string[] | null +} + +export async function createBucket({ + projectRef, + id, + isPublic, + file_size_limit, + allowed_mime_types, +}: BucketCreateVariables) { + if (!projectRef) throw new Error('projectRef is required') + if (!id) throw new Error('Bucket name is requried') + + const response = await post(`${API_URL}/storage/${projectRef}/buckets`, { + id, + public: isPublic, + file_size_limit, + allowed_mime_types, + }) + if (response.error) throw response.error + return response +} + +type BucketCreateData = Awaited> + +export const useBucketCreateMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => createBucket(vars), + { + async onSuccess(data, variables, context) { + const { projectRef } = variables + await queryClient.invalidateQueries(storageKeys.buckets(projectRef), { refetchType: 'all' }) + await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/data/storage/bucket-delete-mutationl.ts b/studio/data/storage/bucket-delete-mutationl.ts new file mode 100644 index 00000000000..1f6e053341c --- /dev/null +++ b/studio/data/storage/bucket-delete-mutationl.ts @@ -0,0 +1,45 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { delete_, post } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' +import { storageKeys } from './keys' + +export type BucketDeleteVariables = { + projectRef: string + id: string +} + +export async function deleteBucket({ projectRef, id }: BucketDeleteVariables) { + if (!projectRef) throw new Error('projectRef is required') + if (!id) throw new Error('Bucket name is requried') + + const emptyBucketRes = await post(`${API_URL}/storage/${projectRef}/buckets/${id}/empty`, {}) + if (emptyBucketRes.error) throw emptyBucketRes.error + + const response = await delete_(`${API_URL}/storage/${projectRef}/buckets/${id}`) + if (response.error) throw response.error + return response +} + +type BucketDeleteData = Awaited> + +export const useBucketDeleteMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => deleteBucket(vars), + { + async onSuccess(data, variables, context) { + const { projectRef } = variables + await queryClient.invalidateQueries(storageKeys.buckets(projectRef), { refetchType: 'all' }) + await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/data/storage/bucket-update-mutation.ts b/studio/data/storage/bucket-update-mutation.ts new file mode 100644 index 00000000000..40a748e3c7e --- /dev/null +++ b/studio/data/storage/bucket-update-mutation.ts @@ -0,0 +1,55 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { patch } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' +import { storageKeys } from './keys' + +export type BucketUpdateVariables = { + projectRef: string + id: string + isPublic: boolean + file_size_limit: number | null + allowed_mime_types: string[] | null +} + +export async function updateBucket({ + projectRef, + id, + isPublic, + file_size_limit, + allowed_mime_types, +}: BucketUpdateVariables) { + if (!projectRef) throw new Error('projectRef is required') + if (!id) throw new Error('Bucket name is requried') + + const response = await patch(`${API_URL}/storage/${projectRef}/buckets/${id}`, { + public: isPublic, + file_size_limit, + allowed_mime_types, + }) + if (response.error) throw response.error + return response +} + +type BucketUpdateData = Awaited> + +export const useBucketUpdateMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => updateBucket(vars), + { + async onSuccess(data, variables, context) { + const { projectRef } = variables + await queryClient.invalidateQueries(storageKeys.buckets(projectRef), { refetchType: 'all' }) + await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/data/storage/buckets-query.ts b/studio/data/storage/buckets-query.ts index 012f2149969..530c257a52b 100644 --- a/studio/data/storage/buckets-query.ts +++ b/studio/data/storage/buckets-query.ts @@ -6,7 +6,7 @@ import { storageKeys } from './keys' export type BucketsVariables = { projectRef?: string } -export type BucketsResponse = { +export type Bucket = { id: string name: string owner: string @@ -22,7 +22,7 @@ export async function getBuckets({ projectRef }: BucketsVariables, signal?: Abor const response = await get(`${API_URL}/storage/${projectRef}/buckets`, { signal }) if (response.error) throw response.error - return response as BucketsResponse[] + return response as Bucket[] } export type BucketsData = Awaited> diff --git a/studio/localStores/storageExplorer/StorageExplorerStore.js b/studio/localStores/storageExplorer/StorageExplorerStore.js index a7b8fa094e3..37641c47ac6 100644 --- a/studio/localStores/storageExplorer/StorageExplorerStore.js +++ b/studio/localStores/storageExplorer/StorageExplorerStore.js @@ -440,18 +440,6 @@ class StorageExplorerStore { /* Methods that involve the storage client library */ /* Bucket CRUD */ - - createBucket = async (payload) => { - const res = await post(`${this.endpoint}/buckets`, payload) - if (res.error) { - this.ui.setNotification({ category: 'error', message: res.error.message }) - return res - } else { - await this.fetchBuckets() - return res - } - } - openBucket = async (bucket) => { const { id, name } = bucket const columnIndex = -1 @@ -461,59 +449,6 @@ class StorageExplorerStore { } } - fetchBuckets = async () => { - const res = await get(`${this.endpoint}/buckets`) - if (res.error) return this.ui.setNotification({ category: 'error', message: res.error.message }) - - const formattedBuckets = res.map((bucket) => { - return { ...bucket, type: STORAGE_ROW_TYPES.BUCKET, status: STORAGE_ROW_STATUS.READY } - }) - this.buckets = formattedBuckets - return formattedBuckets - } - - deleteBucket = async (bucket) => { - // Deleting a bucket requires the bucket to be empty first - // hence delete bucket and empty bucket are coupled tightly here - const { id, name: bucketName } = bucket - - const emptyBucketRes = await post(`${this.endpoint}/buckets/${id}/empty`, {}) - if (emptyBucketRes.error) { - this.ui.setNotification({ category: 'error', message: emptyBucketRes.error.message }) - return false - } - - const deleteBucketRes = await delete_(`${this.endpoint}/buckets/${id}`) - if (deleteBucketRes.error) { - this.ui.setNotification({ category: 'error', message: deleteBucketRes.error.message }) - return false - } - - await this.fetchBuckets() - if (bucketName === this.selectedBucket.name) { - this.setSelectedBucket({}) - this.clearColumns() - this.clearOpenedFolders() - } - this.clearSelectedItemsToDelete() - this.closeDeleteBucketModal() - return true - } - - editBucket = async (bucket, payload) => { - const res = await patch(`${this.endpoint}/buckets/${bucket.id}`, payload) - if (res.error) { - this.ui.setNotification({ category: 'error', message: res.error.message }) - return res - } - - this.openBucket({ ...bucket, ...payload }) - this.fetchBuckets() - this.clearFilePreviewCache() - - return res - } - /* Files CRUD */ getFile = async (fileEntry) => { From a8893cb834c214fad4fd4295f5b6f0676a83e8e7 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 20 Jun 2023 18:31:26 +0800 Subject: [PATCH 020/108] Small UI fixes + fix switching projects while on a bucket, doesnt direct to the buckets page --- .../LayoutHeader/ProjectDropdown.tsx | 10 ++++----- .../NavigationBar/NavigationIconButton.tsx | 6 ++++- .../layouts/StorageLayout/StorageMenu.tsx | 17 ++++++-------- .../StorageExplorer/FileExplorerColumn.js | 2 +- .../tests/components/ProjectDropdown.test.ts | 22 +++++++++++++++++++ 5 files changed, 40 insertions(+), 17 deletions(-) create mode 100644 studio/tests/components/ProjectDropdown.test.ts diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/ProjectDropdown.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/ProjectDropdown.tsx index 1f685d4ca6f..6de692d35f7 100644 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/ProjectDropdown.tsx +++ b/studio/components/layouts/ProjectLayout/LayoutHeader/ProjectDropdown.tsx @@ -14,16 +14,16 @@ import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' // is a unique project id/marker so we'll redirect the user to the // highest common route with just projectRef in the router queries. -const sanitizeRoute = (route: string, routerQueries: ParsedUrlQuery) => { +export const sanitizeRoute = (route: string, routerQueries: ParsedUrlQuery) => { const queryArray = Object.entries(routerQueries) if (queryArray.length > 1) { - // account for query string, if exists (example: /logs/explorer?q=select...) - const hasQueryString = queryArray.some(([key]) => key === 'q') - + // [Joshen] Ideally we shouldn't use hard coded numbers, but temp workaround + // for storage bucket route since its longer + const isStorageBucketRoute = 'bucketId' in routerQueries return route .split('/') - .slice(0, hasQueryString ? 5 : 4) + .slice(0, isStorageBucketRoute ? 5 : 4) .join('/') } else { return route diff --git a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconButton.tsx b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconButton.tsx index fa2f9c3e883..9f7494094c3 100644 --- a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconButton.tsx +++ b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconButton.tsx @@ -16,7 +16,11 @@ const NavigationIconButton: FC = ({ route, isActive = false }) => { {children}} + wrap={(children) => ( + + {children} + + )} > { | 'logs' const storageExplorerStore = useStorageStore() - const { data } = useBucketsQuery({ projectRef: ref }) - const { loaded, openDeleteBucketModal } = storageExplorerStore || {} + const { data, isLoading } = useBucketsQuery({ projectRef: ref }) + const { openDeleteBucketModal } = storageExplorerStore || {} const buckets = data ?? [] @@ -80,11 +80,11 @@ const StorageMenu = () => {
- {!loaded ? ( -
+ {isLoading ? ( +
+ + - -
) : ( <> @@ -126,10 +126,7 @@ const StorageMenu = () => { { - console.log('onClose') - setShowCreateBucketModal(false) - }} + onClose={() => setShowCreateBucketModal(false)} /> diff --git a/studio/tests/components/ProjectDropdown.test.ts b/studio/tests/components/ProjectDropdown.test.ts new file mode 100644 index 00000000000..36142b2e1e7 --- /dev/null +++ b/studio/tests/components/ProjectDropdown.test.ts @@ -0,0 +1,22 @@ +import { sanitizeRoute } from 'components/layouts/ProjectLayout/LayoutHeader/ProjectDropdown' + +test('Should sanitize project routes correctly when switching projects by removing project specific parameters', () => { + expect(sanitizeRoute('/project/[ref]', { ref: 'abc' })).toBe('/project/[ref]') + expect(sanitizeRoute('/project/[ref]/editor', { ref: 'abc' })).toBe('/project/[ref]/editor') + expect(sanitizeRoute('/project/[ref]/storage/buckets', { ref: 'abc' })).toBe( + '/project/[ref]/storage/buckets' + ) + expect(sanitizeRoute('/project/[ref]/settings/billing/subscription', { ref: 'abc' })).toBe( + '/project/[ref]/settings/billing/subscription' + ) + + expect(sanitizeRoute('/project/[ref]/editor/[tableId]', { ref: 'abc', tableId: '10' })).toBe( + '/project/[ref]/editor' + ) + expect( + sanitizeRoute('/project/[ref]/storage/buckets/[bucketId]', { ref: 'abc', bucketId: 'bucket-1' }) + ).toBe('/project/[ref]/storage/buckets') + expect(sanitizeRoute('/project/[ref]/logs/explorer?q=select', { ref: 'abc' })).toBe( + '/project/[ref]/logs/explorer?q=select' + ) +}) From 0ae4a50f38b0b778415e5b07b3ae2ddefd2fbd79 Mon Sep 17 00:00:00 2001 From: Long Hoang <1732217+loong@users.noreply.github.com> Date: Tue, 20 Jun 2023 18:38:33 +0800 Subject: [PATCH 021/108] Add swap info to memory exhaust --- apps/docs/pages/guides/platform/exhaust-ram.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/docs/pages/guides/platform/exhaust-ram.mdx b/apps/docs/pages/guides/platform/exhaust-ram.mdx index 1c1649351d2..f8af80d83ce 100644 --- a/apps/docs/pages/guides/platform/exhaust-ram.mdx +++ b/apps/docs/pages/guides/platform/exhaust-ram.mdx @@ -23,6 +23,8 @@ You may observe elevated memory usage even when your database has little to no l Every Supabase project runs in its own dedicated virtual machine. Your instance will have a different set of hardware provisioned depending on your [compute add-on](https://supabase.com/docs/guides/platform/compute-add-ons). Depending on your workload, your compute hardware may not be suitable and can result in high RAM usage. +A good proxy for unhealthy memory usage is swap usage. If you run out of RAM, your system will offload memory to your disk's much slower swap partition. If your swap is above 40%, chances are high that your compute hardware is not suitable for your workload. Head over to your project's [Database Health](https://supabase.com/dashboard/project/_/reports/database) to see your swap usage. + High RAM usage could come with a range of issues: - degraded performance overall when your instance has to use swap memory @@ -31,7 +33,7 @@ High RAM usage could come with a range of issues: ## Monitor your RAM -To check your RAM usage on the Supabase Platform, head over to [Database health in the Reports section](https://app.supabase.com/project/_/reports/database). +To check your RAM usage on the Supabase Platform, head over to [Database Health in the Reports section](https://supabase.com/dashboard/project/_/reports/database). It is also possible to monitor your resources and set up alerts using Prometheus/Grafana. With Grafana you will be able to see how much of your RAM is used for caching and you can track other metrics such as your Swap usage. Read the [Metrics Guide](https://supabase.com/docs/guides/platform/metrics) to learn more. @@ -41,11 +43,11 @@ Everything you do with your Supabase project requires memory in some form. Hence - **Query performance:** Queries that take a long time to complete (>1 second) could be using your RAM inefficiently. Check our guide on [examining query performance](https://supabase.com/docs/guides/platform/performance#examining-query-performance). - **Too many connections:** Every connection to your database consumes memory. You can check the number of active connections under [Database Roles](https://supabase.com/dashboard/project/_/database/roles) after you select your project. Read our guide on [too many open connections](https://supabase.com/docs/guides/platform/troubleshooting#too-many-open-connections). -- **Extensions:** Some extensions such as `timescaledb` or `pg_cron` can use a lot of memory. It can also add up when you have too many extensions running. You can manage your database extensions in the dashboard under [Extensions](https://app.supabase.com/project/_/database/extensions). +- **Extensions:** Some extensions such as `timescaledb` or `pg_cron` can use a lot of memory. It can also add up when you have too many extensions running. You can manage your database extensions in the dashboard under [Extensions](https://supabase.com/dashboard/project/_/database/extensions). ## How to fix your memory issues -1. **Upgrade your compute:** You can get a Compute Add-on for your project. See your [upgrade options](https://app.supabase.com/project/_/settings/billing/subscription?panel=computeInstance) by selecting your project. +1. **Upgrade your compute:** You can get a Compute Add-on for your project. See your [upgrade options](https://supabase.com/dashboard/project/_/settings/billing/subscription?panel=computeInstance) by selecting your project. 2. **Optimize performance:** Get more out of your instance's resources by optimizing your usage. Have a look at our [performance tuning guide](https://supabase.com/docs/guides/platform/performance#examining-query-performance) and our [production readiness guide](https://supabase.com/docs/guides/platform/going-into-prod#performance). export const Page = ({ children }) => From 2c10d3df283fdcf6adbdc8a2b1a630d7a6584ece Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Tue, 20 Jun 2023 14:16:04 +0200 Subject: [PATCH 022/108] feat: bump gotrue-js to v2.31.0 --- package-lock.json | 8 ++++---- packages/common/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4fdc32b163d..2291fa1b4f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42805,7 +42805,7 @@ "license": "MIT", "dependencies": { "@headlessui/react": "^1.7.14", - "@supabase/gotrue-js": "2.24.0", + "@supabase/gotrue-js": "^2.31.0", "@supabase/ui": "^0.37.0-alpha.50", "react-use": "^17.4.0" }, @@ -42818,9 +42818,9 @@ } }, "packages/common/node_modules/@supabase/gotrue-js": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.24.0.tgz", - "integrity": "sha512-ZsH4K5cbMTjfMytXaDYVYs9l9igmlZFxiwXn7J2IP/CklWR5qmLCma+dvat5rccPLITVkN6oAZbKxDzW+pEgCg==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.31.0.tgz", + "integrity": "sha512-YcwlbbNfedlue/HVIXtYBb4fuOrs29gNOTl6AmyxPp4zryRxzFvslVN9kmLDBRUAVU9fnPJh2bgOR3chRjJX5w==", "dependencies": { "cross-fetch": "^3.1.5" } diff --git a/packages/common/package.json b/packages/common/package.json index 5a6f59a186c..eba24a4faeb 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -6,7 +6,7 @@ "license": "MIT", "dependencies": { "@headlessui/react": "^1.7.14", - "@supabase/gotrue-js": "2.24.0", + "@supabase/gotrue-js": "^2.31.0", "@supabase/ui": "^0.37.0-alpha.50", "react-use": "^17.4.0" }, From 9f9612d7626747e9fda12d2b4a17375c81e01a50 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Tue, 20 Jun 2023 20:53:06 +0800 Subject: [PATCH 023/108] fix text button. it had a border. --- packages/ui/src/components/Button/Button.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/Button/Button.tsx b/packages/ui/src/components/Button/Button.tsx index b553b769c86..efb20c93b81 100644 --- a/packages/ui/src/components/Button/Button.tsx +++ b/packages/ui/src/components/Button/Button.tsx @@ -85,7 +85,8 @@ const buttonVariants = cva( text-scale-1200 hover:bg-scale-500 shadow-none - focus-visible:outline-scale-700`, + focus-visible:outline-scale-700 + border-transparent`, danger: ` text-red-1100 bg-red-200 From 5a1194aa918a989b613adb8ad224f59ca223d9c1 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 20 Jun 2023 20:54:37 +0800 Subject: [PATCH 024/108] Fix spelling --- studio/components/to-be-cleaned/Storage/DeleteBucketModal.tsx | 2 +- .../{bucket-delete-mutationl.ts => bucket-delete-mutation.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename studio/data/storage/{bucket-delete-mutationl.ts => bucket-delete-mutation.ts} (100%) diff --git a/studio/components/to-be-cleaned/Storage/DeleteBucketModal.tsx b/studio/components/to-be-cleaned/Storage/DeleteBucketModal.tsx index 312a88a9df1..bf17b863bbd 100644 --- a/studio/components/to-be-cleaned/Storage/DeleteBucketModal.tsx +++ b/studio/components/to-be-cleaned/Storage/DeleteBucketModal.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react' import { useParams } from 'common' import TextConfirmModal from 'components/ui/Modals/TextConfirmModal' -import { useBucketDeleteMutation } from 'data/storage/bucket-delete-mutationl' +import { useBucketDeleteMutation } from 'data/storage/bucket-delete-mutation' import { Bucket, useBucketsQuery } from 'data/storage/buckets-query' import { useStore } from 'hooks' import { formatPoliciesForStorage } from './Storage.utils' diff --git a/studio/data/storage/bucket-delete-mutationl.ts b/studio/data/storage/bucket-delete-mutation.ts similarity index 100% rename from studio/data/storage/bucket-delete-mutationl.ts rename to studio/data/storage/bucket-delete-mutation.ts From a290eff1bbbc188bf2d4c3cdb398b0dfde48277f Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 20 Jun 2023 21:01:40 +0800 Subject: [PATCH 025/108] Remove refetchType all from bucket queries --- studio/data/storage/bucket-create-mutation.ts | 2 +- studio/data/storage/bucket-delete-mutation.ts | 2 +- studio/data/storage/bucket-update-mutation.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/studio/data/storage/bucket-create-mutation.ts b/studio/data/storage/bucket-create-mutation.ts index 593e8ad4833..3a70d4b0d73 100644 --- a/studio/data/storage/bucket-create-mutation.ts +++ b/studio/data/storage/bucket-create-mutation.ts @@ -47,7 +47,7 @@ export const useBucketCreateMutation = ({ { async onSuccess(data, variables, context) { const { projectRef } = variables - await queryClient.invalidateQueries(storageKeys.buckets(projectRef), { refetchType: 'all' }) + await queryClient.invalidateQueries(storageKeys.buckets(projectRef)) await onSuccess?.(data, variables, context) }, ...options, diff --git a/studio/data/storage/bucket-delete-mutation.ts b/studio/data/storage/bucket-delete-mutation.ts index 1f6e053341c..f7b6fa701d0 100644 --- a/studio/data/storage/bucket-delete-mutation.ts +++ b/studio/data/storage/bucket-delete-mutation.ts @@ -36,7 +36,7 @@ export const useBucketDeleteMutation = ({ { async onSuccess(data, variables, context) { const { projectRef } = variables - await queryClient.invalidateQueries(storageKeys.buckets(projectRef), { refetchType: 'all' }) + await queryClient.invalidateQueries(storageKeys.buckets(projectRef)) await onSuccess?.(data, variables, context) }, ...options, diff --git a/studio/data/storage/bucket-update-mutation.ts b/studio/data/storage/bucket-update-mutation.ts index 40a748e3c7e..7c14acac315 100644 --- a/studio/data/storage/bucket-update-mutation.ts +++ b/studio/data/storage/bucket-update-mutation.ts @@ -46,7 +46,7 @@ export const useBucketUpdateMutation = ({ { async onSuccess(data, variables, context) { const { projectRef } = variables - await queryClient.invalidateQueries(storageKeys.buckets(projectRef), { refetchType: 'all' }) + await queryClient.invalidateQueries(storageKeys.buckets(projectRef)) await onSuccess?.(data, variables, context) }, ...options, From cd6d6d2b594a4c1607943495495f55ea2845d79a Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 20 Jun 2023 21:13:28 +0800 Subject: [PATCH 026/108] Fix small styling issue in help popver --- .../layouts/ProjectLayout/LayoutHeader/HelpPopover.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPopover.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPopover.tsx index 6189ebb211e..6b6add879bd 100644 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPopover.tsx +++ b/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPopover.tsx @@ -28,7 +28,7 @@ const HelpPopover: FC = () => { side="bottom" sideOffset={8} overlay={ -
+
Need help with your project?

From ea1e3792ee67554e3b0566769ecac2a352ad90e0 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 20 Jun 2023 23:22:38 +1000 Subject: [PATCH 027/108] add boilerplate for migrations page --- .../DatabaseLayout/DatabaseMenu.utils.ts | 6 ++ studio/data/database/migrations-query.ts | 57 +++++++++++++++++++ .../project/[ref]/database/migrations.tsx | 37 ++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 studio/data/database/migrations-query.ts create mode 100644 studio/pages/project/[ref]/database/migrations.tsx diff --git a/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts b/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts index 064d1e70c42..bc9fdeda485 100644 --- a/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts +++ b/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts @@ -70,6 +70,12 @@ export const generateDatabaseMenu = ( }, ] : []), + { + name: 'Migrations', + key: 'migrations', + url: `/project/${ref}/database/migrations`, + items: [], + }, ], }, ] diff --git a/studio/data/database/migrations-query.ts b/studio/data/database/migrations-query.ts new file mode 100644 index 00000000000..00e24118c3c --- /dev/null +++ b/studio/data/database/migrations-query.ts @@ -0,0 +1,57 @@ +import { useCallback } from 'react' +import { UseQueryOptions } from '@tanstack/react-query' +import { ExecuteSqlData, useExecuteSqlPrefetch, useExecuteSqlQuery } from '../sql/execute-sql-query' + +export type DatabaseMigration = { + version: string + statements?: string[] +} + +export const getMigrationsQuery = () => { + const sql = /* SQL */ ` + select + * + from supabase_migrations.schema_migrations sm + order by sm.version desc + `.trim() + + return sql +} + +export type MigrationsVariables = { + projectRef?: string + connectionString?: string +} + +export type MigrationsData = { result: DatabaseMigration[] } +export type MigrationsError = unknown + +export const useMigrationsQuery = ( + { projectRef, connectionString }: MigrationsVariables, + options: UseQueryOptions = {} +) => { + return useExecuteSqlQuery( + { + projectRef, + connectionString, + sql: getMigrationsQuery(), + queryKey: ['migrations'], + }, + options + ) +} + +export const useMigrationsPrefetch = () => { + const prefetch = useExecuteSqlPrefetch() + + return useCallback( + ({ projectRef, connectionString }: MigrationsVariables) => + prefetch({ + projectRef, + connectionString, + sql: getMigrationsQuery(), + queryKey: ['migrations'], + }), + [prefetch] + ) +} diff --git a/studio/pages/project/[ref]/database/migrations.tsx b/studio/pages/project/[ref]/database/migrations.tsx new file mode 100644 index 00000000000..8103e6a8da4 --- /dev/null +++ b/studio/pages/project/[ref]/database/migrations.tsx @@ -0,0 +1,37 @@ +import { DatabaseLayout } from 'components/layouts' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { useMigrationsQuery } from 'data/database/migrations-query' +import { NextPageWithLayout } from 'types' + +const MigrationsPage: NextPageWithLayout = () => { + const { project } = useProjectContext() + const { data, isLoading, isSuccess, isError } = useMigrationsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + + return ( +

+ {isLoading &&
Loading state...
} + {isError &&
Error state...
} + {isSuccess && ( +
+ {data.result.map((migration) => ( +
+
{migration.version}
+
+ {migration.statements?.map((statement, i) => ( +
{statement}
+ ))} +
+
+ ))} +
+ )} +
+ ) +} + +MigrationsPage.getLayout = (page) => {page} + +export default MigrationsPage From 79336f0237c9f847f9d4779c68aad50f4b9426e4 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 20 Jun 2023 23:24:21 +1000 Subject: [PATCH 028/108] add no migrations state --- .../project/[ref]/database/migrations.tsx | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/studio/pages/project/[ref]/database/migrations.tsx b/studio/pages/project/[ref]/database/migrations.tsx index 8103e6a8da4..0cfb2e8f266 100644 --- a/studio/pages/project/[ref]/database/migrations.tsx +++ b/studio/pages/project/[ref]/database/migrations.tsx @@ -16,16 +16,19 @@ const MigrationsPage: NextPageWithLayout = () => { {isError &&
Error state...
} {isSuccess && (
- {data.result.map((migration) => ( -
-
{migration.version}
-
- {migration.statements?.map((statement, i) => ( -
{statement}
- ))} + {data.result.length <= 0 &&
No migrations state...
} + + {data.result.length > 0 && + data.result.map((migration) => ( +
+
{migration.version}
+
+ {migration.statements?.map((statement, i) => ( +
{statement}
+ ))} +
-
- ))} + ))}
)}
From 6522682f66173af709123c8a3f097b4bb45cb245 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 20 Jun 2023 13:04:10 -0230 Subject: [PATCH 029/108] Use tailwind --- .../components/layouts/SQLEditorLayout/QueryItem.tsx | 4 ++-- studio/styles/main.scss | 12 ------------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/studio/components/layouts/SQLEditorLayout/QueryItem.tsx b/studio/components/layouts/SQLEditorLayout/QueryItem.tsx index a9a24cb7bfa..aa6af40dda3 100644 --- a/studio/components/layouts/SQLEditorLayout/QueryItem.tsx +++ b/studio/components/layouts/SQLEditorLayout/QueryItem.tsx @@ -119,7 +119,7 @@ const QueryItemActions = observer(({ tabInfo, activeId }: QueryItemActionsProps) } return ( -
+
{IS_PLATFORM ? ( Date: Wed, 21 Jun 2023 02:06:35 +0800 Subject: [PATCH 030/108] Update studio/components/interfaces/Settings/Logs/Logs.utils.ts Co-authored-by: Joshen Lim --- studio/components/interfaces/Settings/Logs/Logs.utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/studio/components/interfaces/Settings/Logs/Logs.utils.ts b/studio/components/interfaces/Settings/Logs/Logs.utils.ts index db2a0b48642..3a0b9b8e53b 100644 --- a/studio/components/interfaces/Settings/Logs/Logs.utils.ts +++ b/studio/components/interfaces/Settings/Logs/Logs.utils.ts @@ -397,6 +397,7 @@ export const fillTimeseries = ( }) const diff = maxDate.diff(minDate, truncation as dayjs.UnitType) + // Intentional throwing of error here to be caught by Sentry, as this would indicate a bug since charts shouldn't be rendering more than 10k data points if (diff > 10000) { throw new Error( 'Data error, filling timeseries dynamically with more than 10k data points degrades performance.' From 15bb1f24dbfa4237ffdd87b3419e2fc343aecec8 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Tue, 20 Jun 2023 17:00:46 -0400 Subject: [PATCH 031/108] remove apps/new-docs from workspaces --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 2f5284b8fa1..b1c3c271fff 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "workspaces": [ "apps/www", "apps/docs", - "apps/new-docs", "studio", "tests", "packages/*" From f42b28f99df01084eaf91e307d1386f76581f6bb Mon Sep 17 00:00:00 2001 From: Long Hoang <1732217+loong@users.noreply.github.com> Date: Wed, 21 Jun 2023 11:31:44 +0800 Subject: [PATCH 032/108] Update critical swap usage threshold --- apps/docs/pages/guides/platform/exhaust-ram.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/platform/exhaust-ram.mdx b/apps/docs/pages/guides/platform/exhaust-ram.mdx index f8af80d83ce..2e5a68e3c96 100644 --- a/apps/docs/pages/guides/platform/exhaust-ram.mdx +++ b/apps/docs/pages/guides/platform/exhaust-ram.mdx @@ -23,7 +23,7 @@ You may observe elevated memory usage even when your database has little to no l Every Supabase project runs in its own dedicated virtual machine. Your instance will have a different set of hardware provisioned depending on your [compute add-on](https://supabase.com/docs/guides/platform/compute-add-ons). Depending on your workload, your compute hardware may not be suitable and can result in high RAM usage. -A good proxy for unhealthy memory usage is swap usage. If you run out of RAM, your system will offload memory to your disk's much slower swap partition. If your swap is above 40%, chances are high that your compute hardware is not suitable for your workload. Head over to your project's [Database Health](https://supabase.com/dashboard/project/_/reports/database) to see your swap usage. +A good proxy for unhealthy memory usage is swap usage. If you run out of RAM, your system will offload memory to your disk's much slower swap partition. If your swap is above 70%, chances are high that your compute hardware is not suitable for your workload. Head over to your project's [Database Health](https://supabase.com/dashboard/project/_/reports/database) to see your swap usage. High RAM usage could come with a range of issues: From d99b672b8d199f4dd3a1f0765c802ce898d4dd02 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Jun 2023 12:17:14 +0800 Subject: [PATCH 033/108] Prettify migrations page --- .../Database/Migrations/Migrations.tsx | 162 ++++++++++++++++++ studio/components/to-be-cleaned/Table.tsx | 1 + .../components/ui/CodeEditor/CodeEditor.tsx | 8 +- .../project/[ref]/database/migrations.tsx | 27 +-- 4 files changed, 175 insertions(+), 23 deletions(-) create mode 100644 studio/components/interfaces/Database/Migrations/Migrations.tsx diff --git a/studio/components/interfaces/Database/Migrations/Migrations.tsx b/studio/components/interfaces/Database/Migrations/Migrations.tsx new file mode 100644 index 00000000000..fcf2f7649e9 --- /dev/null +++ b/studio/components/interfaces/Database/Migrations/Migrations.tsx @@ -0,0 +1,162 @@ +import { observer } from 'mobx-react-lite' +import Link from 'next/link' +import { useState } from 'react' +import { Alert, Button, IconExternalLink, IconSearch, Input, SidePanel } from 'ui' + +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import Table from 'components/to-be-cleaned/Table' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { DatabaseMigration, useMigrationsQuery } from 'data/database/migrations-query' +import { Editor } from '@monaco-editor/react' +import CodeEditor from 'components/ui/CodeEditor' + +const Wrappers = () => { + const [search, setSearch] = useState('') + const [selectedMigration, setSelectedMigration] = useState() + + const { project } = useProjectContext() + const { data, isLoading, isSuccess, isError, error } = useMigrationsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + const migrations = + search.length === 0 + ? data?.result ?? [] + : data?.result.filter((migration) => migration.version.includes(search)) ?? [] + + return ( + <> +
+
+
+

Migrations

+
+ History of migrations that have been run on your database +
+
+ +
+ + {isLoading ? ( +
+ + + +
+ ) : ( +
+ {isError && ( + + + + + , + ]} + > +

+ Try refreshing your browser, but if the issue persists, please reach out to us via + support. +

+

Error: {(error as any)?.message ?? 'Unknown'}

+
+ )} + {isSuccess && ( +
+
+ setSearch(e.target.value)} + icon={} + /> +
+ + {data.result.length <= 0 &&
No migrations state...
} + + {data.result.length > 0 && ( + Version, + , + ]} + body={ + migrations.length > 0 ? ( + migrations.map((migration) => ( + + {migration.version} + + + + + )) + ) : ( + + +

No results found

+

+ Your search for "{search}" did not return any results +

+
+
+ ) + } + /> + )} + + )} + + )} + + setSelectedMigration(undefined)} + customFooter={ +
+ +
+ } + > +
+
+ +
+
+
+ + ) +} + +export default observer(Wrappers) diff --git a/studio/components/to-be-cleaned/Table.tsx b/studio/components/to-be-cleaned/Table.tsx index dc3c37f83ce..3b25fbcf4da 100644 --- a/studio/components/to-be-cleaned/Table.tsx +++ b/studio/components/to-be-cleaned/Table.tsx @@ -79,6 +79,7 @@ type TdProps = { colSpan?: number className?: string style?: React.CSSProperties + align?: 'left' | 'center' | 'right' | 'justify' | 'char' | undefined } & React.HTMLProps const Td: React.FC = ({ children, colSpan, className, style, ...rest }) => { return ( diff --git a/studio/components/ui/CodeEditor/CodeEditor.tsx b/studio/components/ui/CodeEditor/CodeEditor.tsx index 253e2ad4775..7f291e08e3a 100644 --- a/studio/components/ui/CodeEditor/CodeEditor.tsx +++ b/studio/components/ui/CodeEditor/CodeEditor.tsx @@ -4,14 +4,14 @@ import Editor, { EditorProps } from '@monaco-editor/react' import { timeout } from 'lib/helpers' import Connecting from '../Loading' import { alignEditor } from './CodeEditor.utils' -import { merge } from 'lodash' +import { merge, noop } from 'lodash' interface Props { id: string language: 'pgsql' | 'json' | 'html' defaultValue?: string isReadOnly?: boolean - onInputChange: (value?: string) => void + onInputChange?: (value?: string) => void onInputRun?: (value: string) => void hideLineNumbers?: boolean className?: string @@ -26,8 +26,8 @@ const CodeEditor: FC = ({ defaultValue, isReadOnly = false, hideLineNumbers = false, - onInputChange = () => {}, - onInputRun = () => {}, + onInputChange = noop, + onInputRun = noop, className, loading, options, diff --git a/studio/pages/project/[ref]/database/migrations.tsx b/studio/pages/project/[ref]/database/migrations.tsx index 0cfb2e8f266..16a3cd2500e 100644 --- a/studio/pages/project/[ref]/database/migrations.tsx +++ b/studio/pages/project/[ref]/database/migrations.tsx @@ -1,3 +1,5 @@ +import clsx from 'clsx' +import Migrations from 'components/interfaces/Database/Migrations/Migrations' import { DatabaseLayout } from 'components/layouts' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { useMigrationsQuery } from 'data/database/migrations-query' @@ -11,26 +13,13 @@ const MigrationsPage: NextPageWithLayout = () => { }) return ( -
- {isLoading &&
Loading state...
} - {isError &&
Error state...
} - {isSuccess && ( -
- {data.result.length <= 0 &&
No migrations state...
} - - {data.result.length > 0 && - data.result.map((migration) => ( -
-
{migration.version}
-
- {migration.statements?.map((statement, i) => ( -
{statement}
- ))} -
-
- ))} -
+
+
) } From b7c1a6329bb454f177fd4fff5508003e3e2cc075 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 21 Jun 2023 15:03:26 +0800 Subject: [PATCH 034/108] revert xml removal --- .gitignore | 4 +- apps/www/public/customers-rss.xml | 68 + .../planetpg-angelico_de_los_reyes-rss.xml | 40 + apps/www/public/planetpg-ant_wilson-rss.xml | 19 + apps/www/public/planetpg-bo_lu-rss.xml | 19 + apps/www/public/planetpg-burggraf-rss.xml | 26 + apps/www/public/planetpg-gregnr-rss.xml | 19 + apps/www/public/planetpg-michel-rss.xml | 26 + apps/www/public/planetpg-oli_rice-rss.xml | 54 + .../public/planetpg-paul_copplestone-rss.xml | 54 + apps/www/public/planetpg-pavel-rss.xml | 26 + apps/www/public/planetpg-steve_chavez-rss.xml | 26 + apps/www/public/planetpg-victor-rss.xml | 33 + apps/www/public/rss.xml | 1307 +++++++++++ apps/www/public/sitemap.xml | 9 + apps/www/public/sitemap_www.xml | 2062 +++++++++++++++++ 16 files changed, 3790 insertions(+), 2 deletions(-) create mode 100644 apps/www/public/customers-rss.xml create mode 100644 apps/www/public/planetpg-angelico_de_los_reyes-rss.xml create mode 100644 apps/www/public/planetpg-ant_wilson-rss.xml create mode 100644 apps/www/public/planetpg-bo_lu-rss.xml create mode 100644 apps/www/public/planetpg-burggraf-rss.xml create mode 100644 apps/www/public/planetpg-gregnr-rss.xml create mode 100644 apps/www/public/planetpg-michel-rss.xml create mode 100644 apps/www/public/planetpg-oli_rice-rss.xml create mode 100644 apps/www/public/planetpg-paul_copplestone-rss.xml create mode 100644 apps/www/public/planetpg-pavel-rss.xml create mode 100644 apps/www/public/planetpg-steve_chavez-rss.xml create mode 100644 apps/www/public/planetpg-victor-rss.xml create mode 100644 apps/www/public/rss.xml create mode 100644 apps/www/public/sitemap.xml create mode 100644 apps/www/public/sitemap_www.xml diff --git a/.gitignore b/.gitignore index 0b50b45e703..88297ef381c 100644 --- a/.gitignore +++ b/.gitignore @@ -129,5 +129,5 @@ packages/ui/tokens/**/*.json gcloud.json # sitemaps -apps/www/public/*.xml -apps/docs/public/*.xml \ No newline at end of file +# apps/www/public/*.xml +# apps/docs/public/*.xml \ No newline at end of file diff --git a/apps/www/public/customers-rss.xml b/apps/www/public/customers-rss.xml new file mode 100644 index 00000000000..ada43cd244f --- /dev/null +++ b/apps/www/public/customers-rss.xml @@ -0,0 +1,68 @@ + + + + Blog - Supabase + https://supabase.com + Latest news from Supabase + en + Mon, 05 Jun 2023 16:00:00 GMT + + + https://supabase.com/customers/berriai + Berri AI Boosts Productivity by Migrating from AWS RDS to Supabase with pgvector + https://supabase.com/customers/berriai + Learn how Berri AI overcame challenges with self-hosting their vector database on AWS RDS and successfully migrated to Supabase. + Mon, 05 Jun 2023 16:00:00 GMT + + + https://supabase.com/customers/markprompt + Markprompt and Supabase - GDPR-Compliant AI Chatbots for Docs and Websites. + https://supabase.com/customers/markprompt + AI-powered chatbot platform, Markprompt, empowers developers to deliver efficient and GDPR-compliant prompt experiences on top of their content, by leveraging Supabase's secure and privacy-focused database and authentication solutions. + Tue, 16 May 2023 16:00:00 GMT + + + https://supabase.com/customers/mendableai + Mendable switches from Pinecone to Supabase for PostgreSQL vector embeddings. + https://supabase.com/customers/mendableai + How Mendable boosts efficiency and accuracy of chat powered search for documentation using Supabase with pgvector. + Thu, 04 May 2023 16:00:00 GMT + + + https://supabase.com/customers/happyteams + HappyTeams unlocks better performance and reduces cost with Supabase. + https://supabase.com/customers/happyteams + How a bootstrapped startup migrated from Heroku to Supabase in 30 minutes and never looked back. + Wed, 15 Feb 2023 16:00:00 GMT + + + https://supabase.com/customers/xendit + Xendit use Supabase and create a full solution shipped to production in less than one week. + https://supabase.com/customers/xendit + As a payment processor, Xendit are responsible for verifying that all transactions are legal. + Mon, 13 Feb 2023 16:00:00 GMT + + + https://supabase.com/customers/replenysh + Replenysh uses Supabase to implement OTP in less than 24 hours. + https://supabase.com/customers/replenysh + With Supabase, Replenysh gets a slick auth experience, reduces DevOps overhead, and continues to scale with Postgres. + Mon, 13 Feb 2023 16:00:00 GMT + + + https://supabase.com/customers/mobbin + How Mobbin migrated 200,000 users from Firebase for a better authentication experience. + https://supabase.com/customers/mobbin + Mobbin helps over 200,000 creators globally search and view the latest design patterns from well-known apps. + Mon, 13 Feb 2023 16:00:00 GMT + + + https://supabase.com/customers/epsilon3 + Epsilon3 digitize paper-based procedures in the space industry using telemetry data, simplifying testing and operations. + https://supabase.com/customers/epsilon3 + Epsilon3 uses Supabase to help teams execute secure and reliable operations in an industry where project spend runs into the billions. + Mon, 13 Feb 2023 16:00:00 GMT + + + + diff --git a/apps/www/public/planetpg-angelico_de_los_reyes-rss.xml b/apps/www/public/planetpg-angelico_de_los_reyes-rss.xml new file mode 100644 index 00000000000..1085e7ff935 --- /dev/null +++ b/apps/www/public/planetpg-angelico_de_los_reyes-rss.xml @@ -0,0 +1,40 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Angelico de los Reyes at Supabase + en + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-point-in-time-recovery + Point in Time Recovery is now available for Pro projects + https://supabase.com/blog/postgres-point-in-time-recovery + We're making PITR available for more projects, with a new Dashboard UI that makes it simple to use. + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/continuous-postgresql-backup-walg + Continuous PostgreSQL Backups using WAL-G + https://supabase.com/blog/continuous-postgresql-backup-walg + Have you ever wanted to restore your database's state to a particular moment in time? This post explains how, using WAL-G. + Sat, 01 Aug 2020 16:00:00 GMT + + + https://supabase.com/blog/postgresql-templates + What are PostgreSQL Templates? + https://supabase.com/blog/postgresql-templates + What are PostgreSQL templates and what are they used for? + Wed, 08 Jul 2020 16:00:00 GMT + + + https://supabase.com/blog/postgresql-physical-logical-backups + Physical vs Logical Backups in PostgreSQL + https://supabase.com/blog/postgresql-physical-logical-backups + What are physical and logical backups in Postgres? + Mon, 06 Jul 2020 16:00:00 GMT + + + + diff --git a/apps/www/public/planetpg-ant_wilson-rss.xml b/apps/www/public/planetpg-ant_wilson-rss.xml new file mode 100644 index 00000000000..8ce4387e596 --- /dev/null +++ b/apps/www/public/planetpg-ant_wilson-rss.xml @@ -0,0 +1,19 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Ant Wilson at Supabase + en + Fri, 26 Feb 2021 16:00:00 GMT + + + https://supabase.com/blog/cracking-postgres-interview + Cracking PostgreSQL Interview Questions + https://supabase.com/blog/cracking-postgres-interview + Understand the top PostgreSQL Interview Questions + Fri, 26 Feb 2021 16:00:00 GMT + + + + diff --git a/apps/www/public/planetpg-bo_lu-rss.xml b/apps/www/public/planetpg-bo_lu-rss.xml new file mode 100644 index 00000000000..34edf154931 --- /dev/null +++ b/apps/www/public/planetpg-bo_lu-rss.xml @@ -0,0 +1,19 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Bo Lu at Supabase + en + Wed, 14 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-foreign-data-wrappers-rust + Supabase Wrappers, a Postgres FDW framework written in Rust + https://supabase.com/blog/postgres-foreign-data-wrappers-rust + A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more. + Wed, 14 Dec 2022 16:00:00 GMT + + + + diff --git a/apps/www/public/planetpg-burggraf-rss.xml b/apps/www/public/planetpg-burggraf-rss.xml new file mode 100644 index 00000000000..3bec1f37cf3 --- /dev/null +++ b/apps/www/public/planetpg-burggraf-rss.xml @@ -0,0 +1,26 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Mark Burggraf at Supabase + en + Wed, 23 Nov 2022 16:00:00 GMT + + + https://supabase.com/blog/sql-or-nosql-both-with-postgresql + SQL or NoSQL? Why not use both (with PostgreSQL)? + https://supabase.com/blog/sql-or-nosql-both-with-postgresql + How to turn Postgres into an easy-to-use NoSQL database that retains all the power of SQL + Wed, 23 Nov 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-wasm + Postgres WASM by Snaplet and Supabase + https://supabase.com/blog/postgres-wasm + We're open sourcing postgres-wasm, a PostgresQL server that runs inside a browser, with our friends at Snaplet. + Sun, 02 Oct 2022 16:00:00 GMT + + + + diff --git a/apps/www/public/planetpg-gregnr-rss.xml b/apps/www/public/planetpg-gregnr-rss.xml new file mode 100644 index 00000000000..7ff629a885d --- /dev/null +++ b/apps/www/public/planetpg-gregnr-rss.xml @@ -0,0 +1,19 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Greg Richardson at Supabase + en + Sun, 05 Feb 2023 16:00:00 GMT + + + https://supabase.com/blog/openai-embeddings-postgres-vector + Storing OpenAI embeddings in Postgres with pgvector + https://supabase.com/blog/openai-embeddings-postgres-vector + An example of how to build an AI-powered search engine using OpenAI's embeddings and PostgreSQL. + Sun, 05 Feb 2023 16:00:00 GMT + + + + diff --git a/apps/www/public/planetpg-michel-rss.xml b/apps/www/public/planetpg-michel-rss.xml new file mode 100644 index 00000000000..7c2cd380ecf --- /dev/null +++ b/apps/www/public/planetpg-michel-rss.xml @@ -0,0 +1,26 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Michel Pelletier at Supabase + en + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/vault-now-in-beta + Supabase Vault is now in Beta + https://supabase.com/blog/vault-now-in-beta + A Postgres extension to store encrypted secrets and encrypt data. + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/transparent-column-encryption-with-postgres + Transparent Column Encryption with Postgres + https://supabase.com/blog/transparent-column-encryption-with-postgres + Using pgsodium's Transparent Column Encryption to encrypt data and provide your users with row-level encryption. + Wed, 30 Nov 2022 16:00:00 GMT + + + + diff --git a/apps/www/public/planetpg-oli_rice-rss.xml b/apps/www/public/planetpg-oli_rice-rss.xml new file mode 100644 index 00000000000..7b7fc946cd2 --- /dev/null +++ b/apps/www/public/planetpg-oli_rice-rss.xml @@ -0,0 +1,54 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Oliver Rice at Supabase + en + Thu, 20 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/whats-new-in-pg-graphql-v1-2 + What's New in pg_graphql v1.2 + https://supabase.com/blog/whats-new-in-pg-graphql-v1-2 + New Features in the v1.2 release of pg_graphql + Thu, 20 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/type-constraints-in-65-lines-of-sql + Type Constraints in 65 lines of SQL + https://supabase.com/blog/type-constraints-in-65-lines-of-sql + Creating validated data types in Postgres + Thu, 16 Feb 2023 16:00:00 GMT + + + https://supabase.com/blog/pg-graphql-v1 + pg_graphql v1.0 + https://supabase.com/blog/pg-graphql-v1 + Announcing the v1.0 release of pg_graphql + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-foreign-data-wrappers-rust + Supabase Wrappers, a Postgres FDW framework written in Rust + https://supabase.com/blog/postgres-foreign-data-wrappers-rust + A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more. + Wed, 14 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation + pg_jsonschema: JSON Schema support for Postgres + https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation + Today we're releasing pg_jsonschema, a Postgres extension for JSON validation. + Thu, 18 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-audit + Postgres Auditing in 150 lines of SQL + https://supabase.com/blog/postgres-audit + PostgreSQL has a robust set of features which we can leverage to create a generic auditing solution in 150 lines of SQL. + Mon, 07 Mar 2022 16:00:00 GMT + + + + diff --git a/apps/www/public/planetpg-paul_copplestone-rss.xml b/apps/www/public/planetpg-paul_copplestone-rss.xml new file mode 100644 index 00000000000..e4aa87e7a4c --- /dev/null +++ b/apps/www/public/planetpg-paul_copplestone-rss.xml @@ -0,0 +1,54 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Paul Copplestone at Supabase + en + Sun, 30 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/postgres-pluggable-strorage + Next steps for Postgres pluggable storage + https://supabase.com/blog/postgres-pluggable-strorage + Exploring history of Postgres pluggable storage and the possibility of landing it in the Postgres core. + Sun, 30 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/postgres-foreign-data-wrappers-rust + Supabase Wrappers, a Postgres FDW framework written in Rust + https://supabase.com/blog/postgres-foreign-data-wrappers-rust + A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more. + Wed, 14 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-crdt + pg_crdt - an experimental CRDT extension for Postgres + https://supabase.com/blog/postgres-crdt + Embedding Yjs and Automerge into Postgres for collaborative applications. + Fri, 09 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/partial-postgresql-data-dumps-with-rls + Partial data dumps using Postgres Row Level Security + https://supabase.com/blog/partial-postgresql-data-dumps-with-rls + Using RLS to create seed files for local PostgreSQL testing. + Mon, 27 Jun 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-as-a-cron-server + Postgres as a CRON Server + https://supabase.com/blog/postgres-as-a-cron-server + Running repetitive tasks with your Postgres database. + Thu, 04 Mar 2021 16:00:00 GMT + + + https://supabase.com/blog/postgresql-views + Postgres Views + https://supabase.com/blog/postgresql-views + Creating and using a view in PostgreSQL. + Tue, 17 Nov 2020 16:00:00 GMT + + + + diff --git a/apps/www/public/planetpg-pavel-rss.xml b/apps/www/public/planetpg-pavel-rss.xml new file mode 100644 index 00000000000..65fe235548f --- /dev/null +++ b/apps/www/public/planetpg-pavel-rss.xml @@ -0,0 +1,26 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Pavel Borisov at Supabase + en + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/new-in-postgres-15 + What's new in Postgres 15? + https://supabase.com/blog/new-in-postgres-15 + Describes the release of Postgres 15, new features and reasons to use it + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/postgresql-commitfest + What is PostgreSQL commitfest and how to contribute + https://supabase.com/blog/postgresql-commitfest + A time-tested method for contributing to the core Postgres code + Wed, 26 Oct 2022 16:00:00 GMT + + + + diff --git a/apps/www/public/planetpg-steve_chavez-rss.xml b/apps/www/public/planetpg-steve_chavez-rss.xml new file mode 100644 index 00000000000..3fa9a2969f4 --- /dev/null +++ b/apps/www/public/planetpg-steve_chavez-rss.xml @@ -0,0 +1,26 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Steve Chavez at Supabase + en + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/postgrest-11-prerelease + PostgREST 11 pre-release + https://supabase.com/blog/postgrest-11-prerelease + Describes new features of PostgREST 11 pre-release + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/roles-postgres-hooks + Protecting reserved roles with PostgreSQL Hooks + https://supabase.com/blog/roles-postgres-hooks + Using Postgres Hooks to protect functionality in your Postgres database. + Thu, 01 Jul 2021 16:00:00 GMT + + + + diff --git a/apps/www/public/planetpg-victor-rss.xml b/apps/www/public/planetpg-victor-rss.xml new file mode 100644 index 00000000000..a5d33f9feaf --- /dev/null +++ b/apps/www/public/planetpg-victor-rss.xml @@ -0,0 +1,33 @@ + + + + 'Postgres | Supabase Blog + https://supabase.com/blog + Latest Postgres news from Victor at Supabase + en + Thu, 13 Oct 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-full-text-search-vs-the-rest + Postgres Full Text Search vs the rest + https://supabase.com/blog/postgres-full-text-search-vs-the-rest + Comparing one of the most popular Postgres features against alternatives + Thu, 13 Oct 2022 16:00:00 GMT + + + https://supabase.com/blog/choosing-a-postgres-primary-key + Choosing a Postgres Primary Key + https://supabase.com/blog/choosing-a-postgres-primary-key + Turns out the question of which identifier to use as a Primary Key is complicated -- we're going to dive into some of the complexity and inherent trade-offs, and figure things out + Wed, 07 Sep 2022 16:00:00 GMT + + + https://supabase.com/blog/seen-by-in-postgresql + Implementing "seen by" functionality with Postgres + https://supabase.com/blog/seen-by-in-postgresql + Different approaches for tracking visitor counts with PostgreSQL. + Sun, 17 Jul 2022 16:00:00 GMT + + + + diff --git a/apps/www/public/rss.xml b/apps/www/public/rss.xml new file mode 100644 index 00000000000..e365f1a0ef6 --- /dev/null +++ b/apps/www/public/rss.xml @@ -0,0 +1,1307 @@ + + + + Blog - Supabase + https://supabase.com + Latest news from Supabase + en + Thu, 08 Jun 2023 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-update-may-2023 + Supabase Beta May 2023 + https://supabase.com/blog/supabase-beta-update-may-2023 + Learn about the great things we shipped last month. Spoiler alert... lots of AI. + Thu, 08 Jun 2023 16:00:00 GMT + + + https://supabase.com/blog/vecs + Supabase Vecs: a vector client for Postgres + https://supabase.com/blog/vecs + Introducing Supabase Vecs, a PostgreSQL vector client + Sun, 28 May 2023 16:00:00 GMT + + + https://supabase.com/blog/flutter-hackathon-winners + Flutter Hackathon Winners + https://supabase.com/blog/flutter-hackathon-winners + Announcing the winners of the Flutter Hackathon! + Sun, 28 May 2023 16:00:00 GMT + + + https://supabase.com/blog/chatgpt-plugins-support-postgres + ChatGPT plugins now support Postgres & Supabase + https://supabase.com/blog/chatgpt-plugins-support-postgres + Supabase recently contributed to the OpenAI Retrieval Plugin repo with a Postgres and a Supabase implementation to help developers build ChatGPT plugins using pgvector. + Wed, 24 May 2023 16:00:00 GMT + + + https://supabase.com/blog/building-chatgpt-plugins-template + Building ChatGPT Plugins with Supabase Edge Runtime + https://supabase.com/blog/building-chatgpt-plugins-template + We're releasing a ChatGPT plugin template written in TypeScript and running on Deno! + Sun, 14 May 2023 16:00:00 GMT + + + https://supabase.com/blog/flutter-hackathon + Flutter Hackathon + https://supabase.com/blog/flutter-hackathon + Build Flutter apps and win limited edition swag. + Thu, 11 May 2023 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-update-april-2023 + Supabase Beta April 2023 + https://supabase.com/blog/supabase-beta-update-april-2023 + A review of Launch Week 7 and more exciting updates from last month. + Mon, 08 May 2023 16:00:00 GMT + + + https://supabase.com/blog/flutter-multi-factor-authentication + Securing your Flutter apps with Multi-Factor Authentication + https://supabase.com/blog/flutter-multi-factor-authentication + Build a Flutter app where the user is required to authenticate using Multi-Factor Authentication. + Wed, 03 May 2023 16:00:00 GMT + + + https://supabase.com/blog/postgres-pluggable-strorage + Next steps for Postgres pluggable storage + https://supabase.com/blog/postgres-pluggable-strorage + Exploring history of Postgres pluggable storage and the possibility of landing it in the Postgres core. + Sun, 30 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/launch-week-7-hackathon-winners + Launch Week 7 Hackathon Winners + https://supabase.com/blog/launch-week-7-hackathon-winners + Announcing the winners of the Launch Week 7 Hackathon! + Sun, 23 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/whats-new-in-pg-graphql-v1-2 + What's New in pg_graphql v1.2 + https://supabase.com/blog/whats-new-in-pg-graphql-v1-2 + New Features in the v1.2 release of pg_graphql + Thu, 20 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/supabase-studio-2.0 + Supabase Studio 2.0: help when you need it most + https://supabase.com/blog/supabase-studio-2.0 + Supabase Studio now comes with ChatGPT, and GraphiQL built in, Cascade Deletes, and Foreign Key Selectors, and much more. + Thu, 13 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/dbdev + dbdev: PostgreSQL Package Manager + https://supabase.com/blog/dbdev + We're publicly previewing dbdev, a PostgreSQL package manager. + Thu, 13 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/pg-tle + Trusted Language Extensions for Postgres + https://supabase.com/blog/pg-tle + We're collaborating with AWS to bring Trusted Language Extensions to Postgres. + Thu, 13 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/launch-week-7-community-highlights + Launch Week 7 Community Highlights + https://supabase.com/blog/launch-week-7-community-highlights + We're honored to work with, sponsor, and support incredible people and tools. Here is a highlight of the last 3 months. + Thu, 13 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/supabase-auth-sso-pkce + Supabase Auth: SSO, Mobile, and Server-side support + https://supabase.com/blog/supabase-auth-sso-pkce + Supacharging Supabase Auth with Sign in with Apple on iOS, Single-Sign-On support with SAML 2.0, and PKCE for server-side rendering and mobile auth. + Wed, 12 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/storage-v3-resumable-uploads + Supabase Storage v3: Resumable Uploads with support for 50GB files + https://supabase.com/blog/storage-v3-resumable-uploads + Storage V3 with lots of new features including resumable uploads, more image transformationsm a Next.js image loader and more. + Tue, 11 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/edge-runtime-self-hosted-deno-functions + Supabase Edge Runtime: Self-hosted Deno Functions + https://supabase.com/blog/edge-runtime-self-hosted-deno-functions + We are open-sourcing Supabase Edge Runtime allowing you to host your Edge Functions anywhere. + Mon, 10 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/supabase-logs-self-hosted + Supabase Logs: open source logging server + https://supabase.com/blog/supabase-logs-self-hosted + We're releasing Supabase Logs for both self-hosted users and CLI development. + Sun, 09 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-update-march-2023 + Supabase Beta March 2023 + https://supabase.com/blog/supabase-beta-update-march-2023 + We are in full shipping mode 🛥️… Launch Week 7 can’t come quickly enough! + Fri, 07 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/launch-week-7-hackathon + The Supabase AI Hackathon + https://supabase.com/blog/launch-week-7-hackathon + Build an Open Source Project over 10 days. 5 prize categories. + Thu, 06 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/designing-with-ai-midjourney + Designing with AI: Generating unique artwork for every user + https://supabase.com/blog/designing-with-ai-midjourney + Using MidJourney to generative artwork and serving ticket images with Edge Functions + Thu, 06 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/infinite-scroll-with-nextjs-framer-motion + Infinite scroll with Next.js, Framer Motion, and Supabase + https://supabase.com/blog/infinite-scroll-with-nextjs-framer-motion + Lazy load and paginate data on scroll with Next.js and a sprinkle of Framer Motion magic ✨ + Mon, 03 Apr 2023 16:00:00 GMT + + + https://supabase.com/blog/supaclub + SupaClub + https://supabase.com/blog/supaclub + The Worlds First Software Engineering Nightclub. + Fri, 31 Mar 2023 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-update-february-2023 + Supabase Beta February 2023 + https://supabase.com/blog/supabase-beta-update-february-2023 + There is something for everybody this month - AI, Auth, Database, Edge Functions, GraphQL … you name it! + Wed, 08 Mar 2023 16:00:00 GMT + + + https://supabase.com/blog/geo-queries-with-postgis-in-ionic-angular + Geo Queries with PostGIS in Ionic Angular + https://supabase.com/blog/geo-queries-with-postgis-in-ionic-angular + Using the PostGIS extension to build a cross-platform application with Ionic Angular. + Tue, 28 Feb 2023 16:00:00 GMT + + + https://supabase.com/blog/type-constraints-in-65-lines-of-sql + Type Constraints in 65 lines of SQL + https://supabase.com/blog/type-constraints-in-65-lines-of-sql + Creating validated data types in Postgres + Thu, 16 Feb 2023 16:00:00 GMT + + + https://supabase.com/blog/case-study-happyteams + HappyTeams unlocks better performance and reduces cost with Supabase + https://supabase.com/blog/case-study-happyteams + How a bootstrapped startup migrated from Heroku to Supabase in 30 minutes and never looked back + Wed, 15 Feb 2023 16:00:00 GMT + + + https://supabase.com/blog/flutter-real-time-multiplayer-game + How to build a real-time multiplayer game with Flutter Flame + https://supabase.com/blog/flutter-real-time-multiplayer-game + Build a real-time multiplayer game using Flutter, Flame, and Supabase realtime. + Mon, 13 Feb 2023 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-january-2023 + Supabase Beta January 2023 + https://supabase.com/blog/supabase-beta-january-2023 + New Postgres extensions, pg_graphql updates, changes to Edge Functions, and more! + Tue, 07 Feb 2023 16:00:00 GMT + + + https://supabase.com/blog/chatgpt-supabase-docs + Supabase Clippy: ChatGPT for Supabase Docs + https://supabase.com/blog/chatgpt-supabase-docs + Creating a ChatGPT interface for the Supabase documentation. + Mon, 06 Feb 2023 16:00:00 GMT + + + https://supabase.com/blog/openai-embeddings-postgres-vector + Storing OpenAI embeddings in Postgres with pgvector + https://supabase.com/blog/openai-embeddings-postgres-vector + An example of how to build an AI-powered search engine using OpenAI's embeddings and PostgreSQL. + Sun, 05 Feb 2023 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-december-2022 + Supabase Beta December 2022 + https://supabase.com/blog/supabase-beta-december-2022 + This month the Beta Update is a Launch Week 6 Special, where we review the cascade of announcements. + Wed, 04 Jan 2023 16:00:00 GMT + + + https://supabase.com/blog/launch-week-6-hackathon-winners + Launch Week 6 Hackathon Winners + https://supabase.com/blog/launch-week-6-hackathon-winners + Announcing the winners of the Launch Week 6 Hackathon! + Mon, 02 Jan 2023 16:00:00 GMT + + + https://supabase.com/blog/vault-now-in-beta + Supabase Vault is now in Beta + https://supabase.com/blog/vault-now-in-beta + A Postgres extension to store encrypted secrets and encrypt data. + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/postgrest-11-prerelease + PostgREST 11 pre-release + https://supabase.com/blog/postgrest-11-prerelease + Describes new features of PostgREST 11 pre-release + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-point-in-time-recovery + Point in Time Recovery is now available for Pro projects + https://supabase.com/blog/postgres-point-in-time-recovery + We're making PITR available for more projects, with a new Dashboard UI that makes it simple to use. + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/pg-graphql-v1 + pg_graphql v1.0 + https://supabase.com/blog/pg-graphql-v1 + Announcing the v1.0 release of pg_graphql + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/new-in-postgres-15 + What's new in Postgres 15? + https://supabase.com/blog/new-in-postgres-15 + Describes the release of Postgres 15, new features and reasons to use it + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/launch-week-6-wrap-up + Launch Week 6: Wrap Up + https://supabase.com/blog/launch-week-6-wrap-up + That's a wrap on Supabase Launch Week Day 6. Here's everything we shipped in one long blog post. + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/launch-week-6-community-day + Community Day + https://supabase.com/blog/launch-week-6-community-day + Wrapping up Launch Week 6 with contributors, partners, and friends. + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/custom-domain-names + Custom Domain Names + https://supabase.com/blog/custom-domain-names + Change your Supabase project's domain name to your own domain. + Thu, 15 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-foreign-data-wrappers-rust + Supabase Wrappers, a Postgres FDW framework written in Rust + https://supabase.com/blog/postgres-foreign-data-wrappers-rust + A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more. + Wed, 14 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/mfa-auth-via-rls + Multi-factor Authentication via Row Level Security Enforcement + https://supabase.com/blog/mfa-auth-via-rls + MFA Auth with enforcement via RLS + Tue, 13 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/storage-image-resizing-smart-cdn + Supabase Storage v2: Image resizing and Smart CDN + https://supabase.com/blog/storage-image-resizing-smart-cdn + We're introducing new features for Supabase Storage: Image resizing and a Smart CDN. + Mon, 12 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/new-supabase-docs-built-with-nextjs + New Supabase Docs, built with Next.js + https://supabase.com/blog/new-supabase-docs-built-with-nextjs + We've redesigned our Docs and migrated to Next.js + Sun, 11 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-crdt + pg_crdt - an experimental CRDT extension for Postgres + https://supabase.com/blog/postgres-crdt + Embedding Yjs and Automerge into Postgres for collaborative applications. + Fri, 09 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/who-we-hire + Who We Hire at Supabase + https://supabase.com/blog/who-we-hire + Traits we look for to maintain a culture of shipping fast and often + Thu, 08 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/launch-week-6-hackathon + Launch Week 6 Hackathon + https://supabase.com/blog/launch-week-6-hackathon + Build an Open Source Project, Win $1500 and the Supabase Darkmode Keyboard + Thu, 08 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-november-2022 + Supabase Beta November 2022 + https://supabase.com/blog/supabase-beta-november-2022 + We are preparing everything for Launch Week 6, but we still had time to ship some goodies this month! + Tue, 06 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/the-supabase-content-storm + The Supabase Content Storm + https://supabase.com/blog/the-supabase-content-storm + We worked with +30 content creators to drop a mountain of content simultaneously. + Mon, 05 Dec 2022 16:00:00 GMT + + + https://supabase.com/blog/transparent-column-encryption-with-postgres + Transparent Column Encryption with Postgres + https://supabase.com/blog/transparent-column-encryption-with-postgres + Using pgsodium's Transparent Column Encryption to encrypt data and provide your users with row-level encryption. + Wed, 30 Nov 2022 16:00:00 GMT + + + https://supabase.com/blog/sql-or-nosql-both-with-postgresql + SQL or NoSQL? Why not use both (with PostgreSQL)? + https://supabase.com/blog/sql-or-nosql-both-with-postgresql + How to turn Postgres into an easy-to-use NoSQL database that retains all the power of SQL + Wed, 23 Nov 2022 16:00:00 GMT + + + https://supabase.com/blog/flutter-authentication-and-authorization-with-rls + Flutter Authentication and Authorization with RLS + https://supabase.com/blog/flutter-authentication-and-authorization-with-rls + Learn how you can secure your Flutter app using Supabase Row Level Security. + Mon, 21 Nov 2022 16:00:00 GMT + + + https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components + Fetching and caching Supabase data in Next.js 13 Server Components + https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components + Next.js 13 introduces new data fetching and caching methods to enable React Server Components and Suspense. + Wed, 16 Nov 2022 16:00:00 GMT + + + https://supabase.com/blog/authentication-in-ionic-angular + Authentication in Ionic Angular with Supabase + https://supabase.com/blog/authentication-in-ionic-angular + Learn how to build an Ionic Angular app with authentication, Row Level Security, and Magic Link auth. + Mon, 07 Nov 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-update-october-2022 + Supabase Beta October 2022 + https://supabase.com/blog/supabase-beta-update-october-2022 + New SDKs, quickstarts, Functions tricks, and more. But, more importantly, Launch Week 6️ has a date! + Tue, 01 Nov 2022 16:00:00 GMT + + + https://supabase.com/blog/postgresql-commitfest + What is PostgreSQL commitfest and how to contribute + https://supabase.com/blog/postgresql-commitfest + A time-tested method for contributing to the core Postgres code + Wed, 26 Oct 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-flutter-sdk-v1-released + supabase-flutter v1 Released + https://supabase.com/blog/supabase-flutter-sdk-v1-released + We've released supabase-flutter v1. More intuitive way of accessing Supabase from your Flutter application. + Thu, 20 Oct 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-js-v2-released + supabase-js v2 Released + https://supabase.com/blog/supabase-js-v2-released + We've released supabase-js v2. Updated examples, quickstarts, and an improved experience. + Wed, 19 Oct 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-full-text-search-vs-the-rest + Postgres Full Text Search vs the rest + https://supabase.com/blog/postgres-full-text-search-vs-the-rest + Comparing one of the most popular Postgres features against alternatives + Thu, 13 Oct 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-update-september-2022 + Supabase Beta September 2022 + https://supabase.com/blog/supabase-beta-update-september-2022 + We were too focused on clearing out the backlog so we didn't ship anything new last month... or did we?! + Tue, 04 Oct 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-wasm + Postgres WASM by Snaplet and Supabase + https://supabase.com/blog/postgres-wasm + We're open sourcing postgres-wasm, a PostgresQL server that runs inside a browser, with our friends at Snaplet. + Sun, 02 Oct 2022 16:00:00 GMT + + + https://supabase.com/blog/choosing-a-postgres-primary-key + Choosing a Postgres Primary Key + https://supabase.com/blog/choosing-a-postgres-primary-key + Turns out the question of which identifier to use as a Primary Key is complicated -- we're going to dive into some of the complexity and inherent trade-offs, and figure things out + Wed, 07 Sep 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-update-august-2022 + Supabase Beta August 2022 + https://supabase.com/blog/supabase-beta-update-august-2022 + Launch Week Special. See everything we shipped, plus winners of the Hackathon and the extended Community Highlights + Tue, 06 Sep 2022 16:00:00 GMT + + + https://supabase.com/blog/launch-week-5-hackathon-winners + Launch Week 5 Hackathon Winners + https://supabase.com/blog/launch-week-5-hackathon-winners + Announcing the winners of the Launch Week 5 Hackathon! + Wed, 24 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/building-a-realtime-trello-board-with-supabase-and-angular + Building a Realtime Trello Board with Supabase and Angular + https://supabase.com/blog/building-a-realtime-trello-board-with-supabase-and-angular + Go beyond the hello world example with this real world project. + Tue, 23 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-vault + Supabase Vault + https://supabase.com/blog/supabase-vault + Today we're announcing Vault, a Postgres extension for managing secrets and encryption inside your database. + Thu, 18 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/postgrest-v10 + PostgREST v10: EXPLAIN and Improved Relationship Detection + https://supabase.com/blog/postgrest-v10 + Today, PostgREST 10 was released. Let's take a look at some of the new features that go hand in hand with supabase-js v2. + Thu, 18 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation + pg_jsonschema: JSON Schema support for Postgres + https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation + Today we're releasing pg_jsonschema, a Postgres extension for JSON validation. + Thu, 18 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/launch-week-5-one-more-thing + One more thing + https://supabase.com/blog/launch-week-5-one-more-thing + Let's be honest, it's never just one more thing. + Thu, 18 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/launch-week-5-community-day + Community Day + https://supabase.com/blog/launch-week-5-community-day + Wrapping up Launch Week 5 with contributors, partners, and friends. + Thu, 18 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-realtime-multiplayer-general-availability + Realtime: Multiplayer Edition + https://supabase.com/blog/supabase-realtime-multiplayer-general-availability + Announcing the general availability of Realtime's Broadcast and Presence. + Wed, 17 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-soc2 + Supabase is SOC2 compliant + https://supabase.com/blog/supabase-soc2 + Supabase is now SOC2 compliant. Learn how we got here and what it means for our customers. + Tue, 16 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-js-v2 + supabase-js v2 + https://supabase.com/blog/supabase-js-v2 + A look at supabase-js v2, which brings type support and focuses on quality-of-life improvements for developers. + Mon, 15 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta + Supabase CLI v1 and Management API Beta + https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta + We are moving Supabase CLI v1 out of beta, and releasing Management API beta. + Sun, 14 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-series-b + Supabase Series B + https://supabase.com/blog/supabase-series-b + Supabase raised $80M in May, bringing our total funding to $116M. + Thu, 11 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/launch-week-5-hackathon + Launch Week 5 Hackathon + https://supabase.com/blog/launch-week-5-hackathon + Build to win $1500 - Friday 12th to Monday 21st August 2022 + Tue, 09 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/slack-consolidate-slackbot-to-consolidate-messages + Slack Consolidate: a slackbot built with Python and Supabase + https://supabase.com/blog/slack-consolidate-slackbot-to-consolidate-messages + A slackbot to consolidate messages from different channels using Supabase, Slack SDK and Python + Mon, 08 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-update-july-2022 + Supabase Beta July 2022 + https://supabase.com/blog/supabase-beta-update-july-2022 + Launch Week Golden Tickets, Flutter SDK 1.0 Developer Preview and more... + Tue, 02 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview + Supabase Flutter SDK 1.0 Developer Preview + https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview + Supabase Flutter SDK is getting a major update and we need your help making it better. + Mon, 01 Aug 2022 16:00:00 GMT + + + https://supabase.com/blog/seen-by-in-postgresql + Implementing "seen by" functionality with Postgres + https://supabase.com/blog/seen-by-in-postgresql + Different approaches for tracking visitor counts with PostgreSQL. + Sun, 17 Jul 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support + Revamped Auth Helpers for Supabase (with SvelteKit support) + https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support + Supabase Auth Helpers now have improved developer experience, Sveltekit support, and more. + Tue, 12 Jul 2022 16:00:00 GMT + + + https://supabase.com/blog/beta-update-june-2022 + Supabase Beta June 2022 + https://supabase.com/blog/beta-update-june-2022 + Auth Helpers, Unlimited Free Projects, CLI and more... + Tue, 05 Jul 2022 16:00:00 GMT + + + https://supabase.com/blog/flutter-tutorial-building-a-chat-app + Flutter Tutorial: building a Flutter chat app + https://supabase.com/blog/flutter-tutorial-building-a-chat-app + Learn how to build a Flutter chat app with open source and scalable backend (inc. auth, realtime, database, and more). + Wed, 29 Jun 2022 16:00:00 GMT + + + https://supabase.com/blog/visualizing-supabase-data-using-metabase + Visualizing Supabase Data using Metabase + https://supabase.com/blog/visualizing-supabase-data-using-metabase + How to create different kinds of charts out of data stored in Supabase using Metabase. + Tue, 28 Jun 2022 16:00:00 GMT + + + https://supabase.com/blog/partial-postgresql-data-dumps-with-rls + Partial data dumps using Postgres Row Level Security + https://supabase.com/blog/partial-postgresql-data-dumps-with-rls + Using RLS to create seed files for local PostgreSQL testing. + Mon, 27 Jun 2022 16:00:00 GMT + + + https://supabase.com/blog/loading-data-supabase-python + Python data loading with Supabase + https://supabase.com/blog/loading-data-supabase-python + An example of how to load data into Supabase using supabase-py + Thu, 16 Jun 2022 16:00:00 GMT + + + https://supabase.com/blog/beta-update-may-2022 + Supabase Beta May 2022 + https://supabase.com/blog/beta-update-may-2022 + Product and community updates including wildcard auth redirects, edge functions with webhooks, and Prometheus endpoints for everybody. + Tue, 31 May 2022 16:00:00 GMT + + + https://supabase.com/blog/how-supabase-accelerates-development-of-all-pull-together + How Mike Lyndon is using Supabase to accelerate development of AllPullTogether + https://supabase.com/blog/how-supabase-accelerates-development-of-all-pull-together + Mike Lyndon is learning web development as he builds AllPullTogether, and Supabase is helping him accelerate what he can accomplish. + Wed, 25 May 2022 16:00:00 GMT + + + https://supabase.com/blog/partner-gallery-works-with-supabase + Works With Supabase - announcing our Partner Gallery + https://supabase.com/blog/partner-gallery-works-with-supabase + Introducing our Partner Gallery - open source and made with Supabase. + Tue, 19 Apr 2022 16:00:00 GMT + + + https://supabase.com/blog/bring-the-func-hackathon-winners + Bring the Func Hackathon Winners 2022 + https://supabase.com/blog/bring-the-func-hackathon-winners + Celebrating many amazing OSS Hackathon projects using GraphQL and Edge Functions. + Sun, 17 Apr 2022 16:00:00 GMT + + + https://supabase.com/blog/beta-update-march-2022 + Supabase Beta March 2022 + https://supabase.com/blog/beta-update-march-2022 + Functions, GraphQL, and much more. + Thu, 14 Apr 2022 16:00:00 GMT + + + https://supabase.com/blog/supabrew + Supabrew - Never Code Thirsty + https://supabase.com/blog/supabrew + A light and refreshing non-alcoholic beer for devs. + Thu, 31 Mar 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-realtime-with-multiplayer-features + Supabase Realtime, with Multiplayer Features + https://supabase.com/blog/supabase-realtime-with-multiplayer-features + Today we're announced Realtime, with multiplayer features. Realtime enables broadcast, presence, and listening to database changes delivered over WebSockets. + Thu, 31 Mar 2022 16:00:00 GMT + + + https://supabase.com/blog/hackathon-bring-the-func + Hackathon: Bring the Func(🕺) + https://supabase.com/blog/hackathon-bring-the-func + Build open-source projects with our latest features, win limited edition swag and plant a tree! + Thu, 31 Mar 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-edge-functions + Edge Functions are now available in Supabase + https://supabase.com/blog/supabase-edge-functions + Today we're launching Edge Functions. Edge Functions let you execute Typescript code close to your users, no matter where they're located. + Wed, 30 Mar 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-enterprise + Introducing Supabase Enterprise + https://supabase.com/blog/supabase-enterprise + Today we are releasing Supabase Enterprise, a suite of features to scale your project. + Tue, 29 Mar 2022 16:00:00 GMT + + + https://supabase.com/blog/graphql-now-available + GraphQL is now available in Supabase + https://supabase.com/blog/graphql-now-available + GraphQL support is now in general availability on the Supabase platform via our open source PostgreSQL extension, pg_graphql. + Mon, 28 Mar 2022 16:00:00 GMT + + + https://supabase.com/blog/community-day-lw4 + Community Day + https://supabase.com/blog/community-day-lw4 + Kicking off Launch Week 4 with contributors, partners, and friends. + Sun, 27 Mar 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-launch-week-four + Supabase Launch Week 4 + https://supabase.com/blog/supabase-launch-week-four + Launch Week 4: One new feature every day for an entire week. Starting Monday 28th March. + Thu, 24 Mar 2022 16:00:00 GMT + + + https://supabase.com/blog/should-i-open-source-my-company + Should I Open Source my Company? + https://supabase.com/blog/should-i-open-source-my-company + The unexpected upsides of building in public + Thu, 24 Mar 2022 16:00:00 GMT + + + https://supabase.com/blog/postgres-audit + Postgres Auditing in 150 lines of SQL + https://supabase.com/blog/postgres-audit + PostgreSQL has a robust set of features which we can leverage to create a generic auditing solution in 150 lines of SQL. + Mon, 07 Mar 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-january-2022 + Supabase Beta January 2022 + https://supabase.com/blog/supabase-beta-january-2022 + New auth providers, SMS providers, and new videos. + Mon, 21 Feb 2022 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-december-2021 + Supabase Beta December 2021 + https://supabase.com/blog/supabase-beta-december-2021 + New crypto extension, Postgres videos, and a bunch of cool integrations. + Wed, 19 Jan 2022 16:00:00 GMT + + + https://supabase.com/blog/product-hunt-golden-kitty-awards-2021 + Golden Kitty Awards Ceremony Watch Party with Supabase + https://supabase.com/blog/product-hunt-golden-kitty-awards-2021 + Hang out with us while watching the Product Hunt Golden Kitty Awards Ceremony + Wed, 19 Jan 2022 16:00:00 GMT + + + https://supabase.com/blog/holiday-hackdays-winners-2021 + Holiday Hackdays Winners 2021 + https://supabase.com/blog/holiday-hackdays-winners-2021 + Celebrating many amazing projects submitted to our Holiday Hackdays Hackathon. + Thu, 16 Dec 2021 16:00:00 GMT + + + https://supabase.com/blog/beta-november-2021-launch-week-recap + Supabase Beta November 2021: Launch Week Recap + https://supabase.com/blog/beta-november-2021-launch-week-recap + We wrapped up November with Supabase's third Launch Week. Here's all the awesome stuff that got shipped ... + Tue, 14 Dec 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-holiday-hackdays-hackathon + Kicking off the Holiday Hackdays + https://supabase.com/blog/supabase-holiday-hackdays-hackathon + Build cool stuff and celebrate open-source software with us during the Holiday Hackdays! + Thu, 02 Dec 2021 16:00:00 GMT + + + https://supabase.com/blog/pg-graphql + pg_graphql: A GraphQL extension for PostgreSQL + https://supabase.com/blog/pg-graphql + GraphQL support is in development for PostgreSQL + Supabase. + Thu, 02 Dec 2021 16:00:00 GMT + + + https://supabase.com/blog/launch-week-three-friday-five-more-things + Five more things + https://supabase.com/blog/launch-week-three-friday-five-more-things + It's never just one more thing! + Thu, 02 Dec 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-acquires-logflare + Supabase acquires Logflare + https://supabase.com/blog/supabase-acquires-logflare + Today, we're ecstatic to announce that Logflare is joining Supabase. + Wed, 01 Dec 2021 16:00:00 GMT + + + https://supabase.com/blog/realtime-row-level-security-in-postgresql + Realtime Postgres RLS now available on Supabase + https://supabase.com/blog/realtime-row-level-security-in-postgresql + Realtime database changes are now broadcast to authenticated users, respecting the same PostgreSQL policies that you use for Row Level Security. + Tue, 30 Nov 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-studio + Supabase Studio + https://supabase.com/blog/supabase-studio + The same Dashboard that you're using on our Platform is now available for local development and Self-Hosting. + Mon, 29 Nov 2021 16:00:00 GMT + + + https://supabase.com/blog/community-day-lw3 + Community Day + https://supabase.com/blog/community-day-lw3 + Kicking off launch week by highlighting the communities around Supabase. + Sun, 28 Nov 2021 16:00:00 GMT + + + https://supabase.com/blog/whats-new-in-postgres-14 + New in PostgreSQL 14: What every developer should know + https://supabase.com/blog/whats-new-in-postgres-14 + A quick look at some new features and functionality in PostgreSQL 14. + Sat, 27 Nov 2021 16:00:00 GMT + + + https://supabase.com/blog/postgrest-9 + PostgREST 9 + https://supabase.com/blog/postgrest-9 + New features and updates in PostgREST version 9. + Fri, 26 Nov 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-launch-week-the-trilogy + Supabase Launch Week III: Holiday Special + https://supabase.com/blog/supabase-launch-week-the-trilogy + Tis the season to be shipping. + Thu, 25 Nov 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-how-we-launch + How we launch at Supabase + https://supabase.com/blog/supabase-how-we-launch + The history and methodology of Supabase Launch Week. + Thu, 25 Nov 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-october-2021 + Supabase Beta October 2021 + https://supabase.com/blog/supabase-beta-october-2021 + Three new Auth providers, multi-schema support, and we're gearing up for another Launch Week. + Sat, 06 Nov 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-series-a + Supabase $30m Series A + https://supabase.com/blog/supabase-series-a + Supabase just raised $30M, bringing our total funding to $36M. + Wed, 27 Oct 2021 16:00:00 GMT + + + https://supabase.com/blog/replenysh-time-to-value-in-less-than-24-hours + Replenysh uses Supabase to implement OTP in less than 24-hours + https://supabase.com/blog/replenysh-time-to-value-in-less-than-24-hours + Learn how Replenysh uses Supabase to power the circular economy, redefining how brands interact with their customers and products. + Mon, 18 Oct 2021 16:00:00 GMT + + + https://supabase.com/blog/hacktoberfest-hackathon-winners-2021 + Hacktoberfest Hackathon Winners 2021 + https://supabase.com/blog/hacktoberfest-hackathon-winners-2021 + Celebrating many amazing projects submitted to our Hacktoberfest Hackathon. + Wed, 13 Oct 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-sept-2021 + Supabase Beta Sept 2021 + https://supabase.com/blog/supabase-beta-sept-2021 + Hackathon, Aborting request, UI updates, and now Hiring. + Sun, 03 Oct 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-hacktoberfest-hackathon-2021 + Supabase Hacktoberfest Hackathon 2021 + https://supabase.com/blog/supabase-hacktoberfest-hackathon-2021 + We're running another Supabase Hackathon during Hacktoberfest! + Mon, 27 Sep 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-august-2021 + Supabase Beta August 2021 + https://supabase.com/blog/supabase-beta-august-2021 + Fundraising, Realtime Security, custom SMS templates, and deployments in South Korea. + Thu, 09 Sep 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-july-2021 + Supabase Beta July 2021 + https://supabase.com/blog/supabase-beta-july-2021 + Discord Logins, Vercel Integration, Full text search, and OAuth guides. + Wed, 11 Aug 2021 16:00:00 GMT + + + https://supabase.com/blog/hackathon-winners + Open Source Hackathon Winners + https://supabase.com/blog/hackathon-winners + Let the medal ceremony begin for the best projects submitted during the Supabase Hackathon. + Sun, 08 Aug 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-swag-store + Supabase Swag Store + https://supabase.com/blog/supabase-swag-store + Today we are officially launching the Supabase Swag Store. + Thu, 29 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-functions-updates + Updates for Supabase Functions + https://supabase.com/blog/supabase-functions-updates + The question on everyone's mind - are we launching Supabase Functions? Well, it's complicated. + Thu, 29 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/1-the-supabase-hackathon + The Supabase Hackathon + https://supabase.com/blog/1-the-supabase-hackathon + A whole week of Hacking for Fun and Prizes. + Thu, 29 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-reports-and-metrics + Supabase Reports and Metrics + https://supabase.com/blog/supabase-reports-and-metrics + We're exposing a full set of metrics in your projects, so that you can build better (and faster) products for your users. + Wed, 28 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-auth-passwordless-sms-login + Supabase Auth v2: Phone Auth now available + https://supabase.com/blog/supabase-auth-passwordless-sms-login + Phone Auth is available today on all new and existing Supabase projects. + Tue, 27 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/mobbin-supabase-200000-users + Mobbin uses Supabase to authenticate 200,000 users + https://supabase.com/blog/mobbin-supabase-200000-users + Learn how Mobbin migrated 200,000 users from Firebase for a better authentication experience. + Tue, 27 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/storage-beta + Supabase Storage now in Beta + https://supabase.com/blog/storage-beta + Supabase Storage moves into Beta. + Mon, 26 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/spot-flutter-with-postgres + Spot: a video sharing app built with Flutter + https://supabase.com/blog/spot-flutter-with-postgres + Spot is a geolocation-based video-sharing app with some social networking features. + Mon, 26 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-postgres-13 + Supabase is now on Postgres 13.3 + https://supabase.com/blog/supabase-postgres-13 + From today, new Supabase projects will be on a version of Supabase Postgres that runs on Postgres 13.3. + Sun, 25 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-community-day + Supabase Community Day + https://supabase.com/blog/supabase-community-day + Community Day + Sun, 25 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/epsilon3-self-hosting + Epsilon3 Self-Host Supabase To Revolutionize Space Operations + https://supabase.com/blog/epsilon3-self-hosting + Learn how the team at Epsilon3 use Supabase to help teams execute secure and reliable operations in an industry that project spend runs into the billions. + Sun, 25 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-launch-week-sql + Supabase Launch Week II: The SQL + https://supabase.com/blog/supabase-launch-week-sql + Five days of Supabase. Again. + Wed, 21 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/roles-postgres-hooks + Protecting reserved roles with PostgreSQL Hooks + https://supabase.com/blog/roles-postgres-hooks + Using Postgres Hooks to protect functionality in your Postgres database. + Thu, 01 Jul 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-june-2021 + Supabase Beta June 2021 + https://supabase.com/blog/supabase-beta-june-2021 + Discord Logins, Vercel Integration, Full text search, and OAuth guides. + Tue, 01 Jun 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-may-2021 + Supabase Beta May 2021 + https://supabase.com/blog/supabase-beta-may-2021 + Apple &amp; Twitter Logins, Supabase Grid, Go &amp; Swift Libraries. + Tue, 01 Jun 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-april-2021 + Supabase Beta April 2021 + https://supabase.com/blog/supabase-beta-april-2021 + Supabase "gardening" - stability, security, and community support. + Tue, 04 May 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-march-2021 + Supabase Beta March 2021 + https://supabase.com/blog/supabase-beta-march-2021 + Launch week, Storage, Supabase CLI, Connection Pooling, Supabase UI, and Pricing. + Mon, 05 Apr 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-workflows + Workflows are coming to Supabase + https://supabase.com/blog/supabase-workflows + Functions are great, but you know what's better? + Thu, 01 Apr 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-pgbouncer + PgBouncer is now available in Supabase + https://supabase.com/blog/supabase-pgbouncer + Better support for Serverless and Postgres. + Thu, 01 Apr 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-dot-com + Supabase Dot Com + https://supabase.com/blog/supabase-dot-com + The Supabase Domain name is changing. + Thu, 01 Apr 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-nft-marketplace + Supabase Launches NFT Marketplace + https://supabase.com/blog/supabase-nft-marketplace + A fully encrypted NFT platform to protect and transact your digital assets + Wed, 31 Mar 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-cli + Supabase CLI + https://supabase.com/blog/supabase-cli + Local development, database migrations, and self-hosting. + Tue, 30 Mar 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-storage + Storage is now available in Supabase + https://supabase.com/blog/supabase-storage + Launching Supabase Storage and how you can use it in your apps + Mon, 29 Mar 2021 16:00:00 GMT + + + https://supabase.com/blog/pricing + Supabase Beta Pricing + https://supabase.com/blog/pricing + Supabase launches Beta pricing structure + Sun, 28 Mar 2021 16:00:00 GMT + + + https://supabase.com/blog/launch-week + Launch week + https://supabase.com/blog/launch-week + Five days of Supabase. + Wed, 24 Mar 2021 16:00:00 GMT + + + https://supabase.com/blog/angels-of-supabase + Angels of Supabase + https://supabase.com/blog/angels-of-supabase + Meet the investors of Supabase. + Wed, 24 Mar 2021 16:00:00 GMT + + + https://supabase.com/blog/In-The-Loop + Developers stay up to date with intheloop.dev + https://supabase.com/blog/In-The-Loop + Learn why Kevin is building intheloop.dev with Supabase + Sun, 21 Mar 2021 16:00:00 GMT + + + https://supabase.com/blog/using-supabase-replit + Using Supabase in Replit + https://supabase.com/blog/using-supabase-replit + Free hosted relational database from within your node.js repl + Wed, 10 Mar 2021 16:00:00 GMT + + + https://supabase.com/blog/toad-a-link-shortener-with-simple-apis-for-low-coders + Toad, a link shortener with simple APIs for low-coders + https://supabase.com/blog/toad-a-link-shortener-with-simple-apis-for-low-coders + An easy-to-use link shortening tool with simple APIs + Sun, 07 Mar 2021 16:00:00 GMT + + + https://supabase.com/blog/postgres-as-a-cron-server + Postgres as a CRON Server + https://supabase.com/blog/postgres-as-a-cron-server + Running repetitive tasks with your Postgres database. + Thu, 04 Mar 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-february-2021 + Supabase Beta February 2021 + https://supabase.com/blog/supabase-beta-february-2021 + One year of building. + Mon, 01 Mar 2021 16:00:00 GMT + + + https://supabase.com/blog/cracking-postgres-interview + Cracking PostgreSQL Interview Questions + https://supabase.com/blog/cracking-postgres-interview + Understand the top PostgreSQL Interview Questions + Fri, 26 Feb 2021 16:00:00 GMT + + + https://supabase.com/blog/case-study-roboflow + Roboflow.com choose Supabase to power Paint.wtf leaderboard + https://supabase.com/blog/case-study-roboflow + Learn how Roboflow.com used Supabase to build their Paint.wtf leaderboard + Mon, 08 Feb 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-january-2021 + Supabase Beta January 2021 + https://supabase.com/blog/supabase-beta-january-2021 + Eleven months of building. + Mon, 01 Feb 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-beta-december-2020 + Supabase Beta December 2020 + https://supabase.com/blog/supabase-beta-december-2020 + Ten months of building. + Fri, 01 Jan 2021 16:00:00 GMT + + + https://supabase.com/blog/supabase-dashboard-performance + Making the Supabase Dashboard Supa-fast + https://supabase.com/blog/supabase-dashboard-performance + Improving the performance of the Supabase dashboard + Sat, 12 Dec 2020 16:00:00 GMT + + + https://supabase.com/blog/supabase-striveschool + Supabase Partners With Strive School To Help Teach Open Source + https://supabase.com/blog/supabase-striveschool + Supabase Partners With Strive School To Help Teach Open Source To The Next Generation Of Developers + Tue, 01 Dec 2020 16:00:00 GMT + + + https://supabase.com/blog/case-study-xendit + Xendit Built a Counter-Fraud Watchlist for the Fintech Industry + https://supabase.com/blog/case-study-xendit + See how Xendit use Supabase to build a full-text search engine. + Tue, 01 Dec 2020 16:00:00 GMT + + + https://supabase.com/blog/case-study-tayfa + TAYFA Built a No-Code Website Builder in Seven Days + https://supabase.com/blog/case-study-tayfa + See how Tayfa went from idea to paying customer in less than 30 days. + Tue, 01 Dec 2020 16:00:00 GMT + + + https://supabase.com/blog/case-study-monitoro + Monitoro Built a Web Crawler Handling Millions of API Requests + https://supabase.com/blog/case-study-monitoro + See how Monitoro built an automated scraping platform using Supabase. + Tue, 01 Dec 2020 16:00:00 GMT + + + https://supabase.com/blog/supabase-alpha-november-2020 + Supabase Alpha November 2020 + https://supabase.com/blog/supabase-alpha-november-2020 + Nine months of building. + Mon, 30 Nov 2020 16:00:00 GMT + + + https://supabase.com/blog/postgresql-views + Postgres Views + https://supabase.com/blog/postgresql-views + Creating and using a view in PostgreSQL. + Tue, 17 Nov 2020 16:00:00 GMT + + + https://supabase.com/blog/supabase-alpha-october-2020 + Supabase Alpha October 2020 + https://supabase.com/blog/supabase-alpha-october-2020 + Eight months of building. + Sun, 01 Nov 2020 16:00:00 GMT + + + https://supabase.com/blog/improved-dx + Supabase.js 1.0 + https://supabase.com/blog/improved-dx + We're releasing a new version of our Supabase client with some awesome new improvements. + Thu, 29 Oct 2020 16:00:00 GMT + + + https://supabase.com/blog/supabase-alpha-september-2020 + Supabase Alpha September 2020 + https://supabase.com/blog/supabase-alpha-september-2020 + Seven months of building. + Fri, 02 Oct 2020 16:00:00 GMT + + + https://supabase.com/blog/supabase-hacktoberfest-2020 + Supabase Hacktoberfest 2020 + https://supabase.com/blog/supabase-hacktoberfest-2020 + Join us for a celebration of open source software and learn how to contribute to Supabase. + Thu, 10 Sep 2020 16:00:00 GMT + + + https://supabase.com/blog/supabase-alpha-august-2020 + Supabase Alpha August 2020 + https://supabase.com/blog/supabase-alpha-august-2020 + Six months of building + Wed, 02 Sep 2020 16:00:00 GMT + + + https://supabase.com/blog/supabase-auth + Supabase Auth + https://supabase.com/blog/supabase-auth + Authenticate and authorize your users with Supabase Auth + Tue, 04 Aug 2020 16:00:00 GMT + + + https://supabase.com/blog/supabase-alpha-july-2020 + Supabase Alpha July 2020 + https://supabase.com/blog/supabase-alpha-july-2020 + Five months of building + Sat, 01 Aug 2020 16:00:00 GMT + + + https://supabase.com/blog/continuous-postgresql-backup-walg + Continuous PostgreSQL Backups using WAL-G + https://supabase.com/blog/continuous-postgresql-backup-walg + Have you ever wanted to restore your database's state to a particular moment in time? This post explains how, using WAL-G. + Sat, 01 Aug 2020 16:00:00 GMT + + + https://supabase.com/blog/alpha-launch-postmortem + Alpha Launch Postmortem + https://supabase.com/blog/alpha-launch-postmortem + Everything that went wrong with Supabase's launch + Thu, 09 Jul 2020 16:00:00 GMT + + + https://supabase.com/blog/postgresql-templates + What are PostgreSQL Templates? + https://supabase.com/blog/postgresql-templates + What are PostgreSQL templates and what are they used for? + Wed, 08 Jul 2020 16:00:00 GMT + + + https://supabase.com/blog/postgresql-physical-logical-backups + Physical vs Logical Backups in PostgreSQL + https://supabase.com/blog/postgresql-physical-logical-backups + What are physical and logical backups in Postgres? + Mon, 06 Jul 2020 16:00:00 GMT + + + https://supabase.com/blog/supabase-alpha-june-2020 + Supabase Alpha June 2020 + https://supabase.com/blog/supabase-alpha-june-2020 + Four months of building + Tue, 30 Jun 2020 16:00:00 GMT + + + https://supabase.com/blog/supabase-steve-chavez + Steve Chavez has joined Supabase + https://supabase.com/blog/supabase-steve-chavez + Steve joins Supabase to help build Auth. + Sun, 14 Jun 2020 16:00:00 GMT + + + https://supabase.com/blog/supabase-alpha-may-2020 + Supabase Alpha May 2020 + https://supabase.com/blog/supabase-alpha-may-2020 + Three months of building + Sun, 31 May 2020 16:00:00 GMT + + + https://supabase.com/blog/supabase-alpha-april-2020 + Supabase Alpha April 2020 + https://supabase.com/blog/supabase-alpha-april-2020 + Two months of building + Sun, 31 May 2020 16:00:00 GMT + + + + diff --git a/apps/www/public/sitemap.xml b/apps/www/public/sitemap.xml new file mode 100644 index 00000000000..205acb71845 --- /dev/null +++ b/apps/www/public/sitemap.xml @@ -0,0 +1,9 @@ + + + + https://supabase.com/sitemap_www.xml + + + https://supabase.com/docs/sitemap.xml + + diff --git a/apps/www/public/sitemap_www.xml b/apps/www/public/sitemap_www.xml new file mode 100644 index 00000000000..893d4788e28 --- /dev/null +++ b/apps/www/public/sitemap_www.xml @@ -0,0 +1,2062 @@ + + + + https://supabase.com/404 + weekly + 0.5 + + + + https://supabase.com/_app + weekly + 0.5 + + + + https://supabase.com/beta + weekly + 0.5 + + + + https://supabase.com/blog + weekly + 0.5 + + + + https://supabase.com/brand-assets + weekly + 0.5 + + + + https://supabase.com/changelog + weekly + 0.5 + + + + https://supabase.com/company + weekly + 0.5 + + + + https://supabase.com/support + weekly + 0.5 + + + + https://supabase.com/blog/ + weekly + 0.5 + + + + https://supabase.com/auth + weekly + 0.5 + + + + https://supabase.com/contact/mfa + weekly + 0.5 + + + + https://supabase.com/database + weekly + 0.5 + + + + https://supabase.com/edge-functions + weekly + 0.5 + + + + https://supabase.com/legal/dpa + weekly + 0.5 + + + + https://supabase.com/nextjs + weekly + 0.5 + + + + https://supabase.com/storage + weekly + 0.5 + + + + https://supabase.com/realtime + weekly + 0.5 + + + + https://supabase.com/vector/Vector + weekly + 0.5 + + + + https://supabase.com/blog/supabase-alpha-april-2020 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-alpha-may-2020 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-steve-chavez + weekly + 0.5 + + + + https://supabase.com/blog/supabase-alpha-june-2020 + weekly + 0.5 + + + + https://supabase.com/blog/postgresql-templates + weekly + 0.5 + + + + https://supabase.com/blog/alpha-launch-postmortem + weekly + 0.5 + + + + https://supabase.com/blog/postgresql-physical-logical-backups + weekly + 0.5 + + + + https://supabase.com/blog/continuous-postgresql-backup-walg + weekly + 0.5 + + + + https://supabase.com/blog/supabase-alpha-july-2020 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-auth + weekly + 0.5 + + + + https://supabase.com/blog/supabase-alpha-august-2020 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-hacktoberfest-2020 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-alpha-september-2020 + weekly + 0.5 + + + + https://supabase.com/blog/improved-dx + weekly + 0.5 + + + + https://supabase.com/blog/supabase-alpha-october-2020 + weekly + 0.5 + + + + https://supabase.com/blog/postgresql-views + weekly + 0.5 + + + + https://supabase.com/blog/supabase-alpha-november-2020 + weekly + 0.5 + + + + https://supabase.com/blog/case-study-monitoro + weekly + 0.5 + + + + https://supabase.com/blog/case-study-tayfa + weekly + 0.5 + + + + https://supabase.com/blog/case-study-xendit + weekly + 0.5 + + + + https://supabase.com/blog/supabase-striveschool + weekly + 0.5 + + + + https://supabase.com/blog/supabase-dashboard-performance + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-december-2020 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-january-2021 + weekly + 0.5 + + + + https://supabase.com/blog/case-study-roboflow + weekly + 0.5 + + + + https://supabase.com/blog/cracking-postgres-interview + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-february-2021 + weekly + 0.5 + + + + https://supabase.com/blog/postgres-as-a-cron-server + weekly + 0.5 + + + + https://supabase.com/blog/toad-a-link-shortener-with-simple-apis-for-low-coders + weekly + 0.5 + + + + https://supabase.com/blog/using-supabase-replit + weekly + 0.5 + + + + https://supabase.com/blog/In-The-Loop + weekly + 0.5 + + + + https://supabase.com/blog/angels-of-supabase + weekly + 0.5 + + + + https://supabase.com/blog/launch-week + weekly + 0.5 + + + + https://supabase.com/blog/pricing + weekly + 0.5 + + + + https://supabase.com/blog/supabase-storage + weekly + 0.5 + + + + https://supabase.com/blog/supabase-cli + weekly + 0.5 + + + + https://supabase.com/blog/supabase-nft-marketplace + weekly + 0.5 + + + + https://supabase.com/blog/supabase-dot-com + weekly + 0.5 + + + + https://supabase.com/blog/supabase-pgbouncer + weekly + 0.5 + + + + https://supabase.com/blog/supabase-workflows + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-march-2021 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-april-2021 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-may-2021 + weekly + 0.5 + + + + https://supabase.com/blog/roles-postgres-hooks + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-june-2021 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-launch-week-sql + weekly + 0.5 + + + + https://supabase.com/blog/epsilon3-self-hosting + weekly + 0.5 + + + + https://supabase.com/blog/supabase-community-day + weekly + 0.5 + + + + https://supabase.com/blog/supabase-postgres-13 + weekly + 0.5 + + + + https://supabase.com/blog/spot-flutter-with-postgres + weekly + 0.5 + + + + https://supabase.com/blog/storage-beta + weekly + 0.5 + + + + https://supabase.com/blog/mobbin-supabase-200000-users + weekly + 0.5 + + + + https://supabase.com/blog/supabase-auth-passwordless-sms-login + weekly + 0.5 + + + + https://supabase.com/blog/supabase-reports-and-metrics + weekly + 0.5 + + + + https://supabase.com/blog/1-the-supabase-hackathon + weekly + 0.5 + + + + https://supabase.com/blog/supabase-functions-updates + weekly + 0.5 + + + + https://supabase.com/blog/supabase-swag-store + weekly + 0.5 + + + + https://supabase.com/blog/hackathon-winners + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-july-2021 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-august-2021 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-hacktoberfest-hackathon-2021 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-sept-2021 + weekly + 0.5 + + + + https://supabase.com/blog/hacktoberfest-hackathon-winners-2021 + weekly + 0.5 + + + + https://supabase.com/blog/replenysh-time-to-value-in-less-than-24-hours + weekly + 0.5 + + + + https://supabase.com/blog/supabase-series-a + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-october-2021 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-how-we-launch + weekly + 0.5 + + + + https://supabase.com/blog/supabase-launch-week-the-trilogy + weekly + 0.5 + + + + https://supabase.com/blog/postgrest-9 + weekly + 0.5 + + + + https://supabase.com/blog/whats-new-in-postgres-14 + weekly + 0.5 + + + + https://supabase.com/blog/community-day-lw3 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-studio + weekly + 0.5 + + + + https://supabase.com/blog/realtime-row-level-security-in-postgresql + weekly + 0.5 + + + + https://supabase.com/blog/supabase-acquires-logflare + weekly + 0.5 + + + + https://supabase.com/blog/launch-week-three-friday-five-more-things + weekly + 0.5 + + + + https://supabase.com/blog/pg-graphql + weekly + 0.5 + + + + https://supabase.com/blog/supabase-holiday-hackdays-hackathon + weekly + 0.5 + + + + https://supabase.com/blog/beta-november-2021-launch-week-recap + weekly + 0.5 + + + + https://supabase.com/blog/holiday-hackdays-winners-2021 + weekly + 0.5 + + + + https://supabase.com/blog/product-hunt-golden-kitty-awards-2021 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-december-2021 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-january-2022 + weekly + 0.5 + + + + https://supabase.com/blog/postgres-audit + weekly + 0.5 + + + + https://supabase.com/blog/should-i-open-source-my-company + weekly + 0.5 + + + + https://supabase.com/blog/supabase-launch-week-four + weekly + 0.5 + + + + https://supabase.com/blog/community-day-lw4 + weekly + 0.5 + + + + https://supabase.com/blog/graphql-now-available + weekly + 0.5 + + + + https://supabase.com/blog/supabase-enterprise + weekly + 0.5 + + + + https://supabase.com/blog/supabase-edge-functions + weekly + 0.5 + + + + https://supabase.com/blog/hackathon-bring-the-func + weekly + 0.5 + + + + https://supabase.com/blog/supabase-realtime-with-multiplayer-features + weekly + 0.5 + + + + https://supabase.com/blog/supabrew + weekly + 0.5 + + + + https://supabase.com/blog/beta-update-march-2022 + weekly + 0.5 + + + + https://supabase.com/blog/bring-the-func-hackathon-winners + weekly + 0.5 + + + + https://supabase.com/blog/partner-gallery-works-with-supabase + weekly + 0.5 + + + + https://supabase.com/blog/how-supabase-accelerates-development-of-all-pull-together + weekly + 0.5 + + + + https://supabase.com/blog/beta-update-may-2022 + weekly + 0.5 + + + + https://supabase.com/blog/loading-data-supabase-python + weekly + 0.5 + + + + https://supabase.com/blog/partial-postgresql-data-dumps-with-rls + weekly + 0.5 + + + + https://supabase.com/blog/visualizing-supabase-data-using-metabase + weekly + 0.5 + + + + https://supabase.com/blog/flutter-tutorial-building-a-chat-app + weekly + 0.5 + + + + https://supabase.com/blog/beta-update-june-2022 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support + weekly + 0.5 + + + + https://supabase.com/blog/seen-by-in-postgresql + weekly + 0.5 + + + + https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-update-july-2022 + weekly + 0.5 + + + + https://supabase.com/blog/slack-consolidate-slackbot-to-consolidate-messages + weekly + 0.5 + + + + https://supabase.com/blog/launch-week-5-hackathon + weekly + 0.5 + + + + https://supabase.com/blog/supabase-series-b + weekly + 0.5 + + + + https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta + weekly + 0.5 + + + + https://supabase.com/blog/supabase-js-v2 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-soc2 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-realtime-multiplayer-general-availability + weekly + 0.5 + + + + https://supabase.com/blog/launch-week-5-community-day + weekly + 0.5 + + + + https://supabase.com/blog/launch-week-5-one-more-thing + weekly + 0.5 + + + + https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation + weekly + 0.5 + + + + https://supabase.com/blog/postgrest-v10 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-vault + weekly + 0.5 + + + + https://supabase.com/blog/building-a-realtime-trello-board-with-supabase-and-angular + weekly + 0.5 + + + + https://supabase.com/blog/launch-week-5-hackathon-winners + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-update-august-2022 + weekly + 0.5 + + + + https://supabase.com/blog/choosing-a-postgres-primary-key + weekly + 0.5 + + + + https://supabase.com/blog/postgres-wasm + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-update-september-2022 + weekly + 0.5 + + + + https://supabase.com/blog/postgres-full-text-search-vs-the-rest + weekly + 0.5 + + + + https://supabase.com/blog/supabase-js-v2-released + weekly + 0.5 + + + + https://supabase.com/blog/supabase-flutter-sdk-v1-released + weekly + 0.5 + + + + https://supabase.com/blog/postgresql-commitfest + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-update-october-2022 + weekly + 0.5 + + + + https://supabase.com/blog/authentication-in-ionic-angular + weekly + 0.5 + + + + https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components + weekly + 0.5 + + + + https://supabase.com/blog/flutter-authentication-and-authorization-with-rls + weekly + 0.5 + + + + https://supabase.com/blog/sql-or-nosql-both-with-postgresql + weekly + 0.5 + + + + https://supabase.com/blog/transparent-column-encryption-with-postgres + weekly + 0.5 + + + + https://supabase.com/blog/the-supabase-content-storm + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-november-2022 + weekly + 0.5 + + + + https://supabase.com/blog/launch-week-6-hackathon + weekly + 0.5 + + + + https://supabase.com/blog/who-we-hire + weekly + 0.5 + + + + https://supabase.com/blog/postgres-crdt + weekly + 0.5 + + + + https://supabase.com/blog/new-supabase-docs-built-with-nextjs + weekly + 0.5 + + + + https://supabase.com/blog/storage-image-resizing-smart-cdn + weekly + 0.5 + + + + https://supabase.com/blog/mfa-auth-via-rls + weekly + 0.5 + + + + https://supabase.com/blog/postgres-foreign-data-wrappers-rust + weekly + 0.5 + + + + https://supabase.com/blog/custom-domain-names + weekly + 0.5 + + + + https://supabase.com/blog/launch-week-6-community-day + weekly + 0.5 + + + + https://supabase.com/blog/launch-week-6-wrap-up + weekly + 0.5 + + + + https://supabase.com/blog/new-in-postgres-15 + weekly + 0.5 + + + + https://supabase.com/blog/pg-graphql-v1 + weekly + 0.5 + + + + https://supabase.com/blog/postgres-point-in-time-recovery + weekly + 0.5 + + + + https://supabase.com/blog/postgrest-11-prerelease + weekly + 0.5 + + + + https://supabase.com/blog/vault-now-in-beta + weekly + 0.5 + + + + https://supabase.com/blog/launch-week-6-hackathon-winners + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-december-2022 + weekly + 0.5 + + + + https://supabase.com/blog/openai-embeddings-postgres-vector + weekly + 0.5 + + + + https://supabase.com/blog/chatgpt-supabase-docs + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-january-2023 + weekly + 0.5 + + + + https://supabase.com/blog/flutter-real-time-multiplayer-game + weekly + 0.5 + + + + https://supabase.com/blog/case-study-happyteams + weekly + 0.5 + + + + https://supabase.com/blog/type-constraints-in-65-lines-of-sql + weekly + 0.5 + + + + https://supabase.com/blog/geo-queries-with-postgis-in-ionic-angular + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-update-february-2023 + weekly + 0.5 + + + + https://supabase.com/blog/supaclub + weekly + 0.5 + + + + https://supabase.com/blog/infinite-scroll-with-nextjs-framer-motion + weekly + 0.5 + + + + https://supabase.com/blog/designing-with-ai-midjourney + weekly + 0.5 + + + + https://supabase.com/blog/launch-week-7-hackathon + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-update-march-2023 + weekly + 0.5 + + + + https://supabase.com/blog/supabase-logs-self-hosted + weekly + 0.5 + + + + https://supabase.com/blog/edge-runtime-self-hosted-deno-functions + weekly + 0.5 + + + + https://supabase.com/blog/storage-v3-resumable-uploads + weekly + 0.5 + + + + https://supabase.com/blog/supabase-auth-sso-pkce + weekly + 0.5 + + + + https://supabase.com/blog/dbdev + weekly + 0.5 + + + + https://supabase.com/blog/launch-week-7-community-highlights + weekly + 0.5 + + + + https://supabase.com/blog/pg-tle + weekly + 0.5 + + + + https://supabase.com/blog/supabase-studio-2.0 + weekly + 0.5 + + + + https://supabase.com/blog/whats-new-in-pg-graphql-v1-2 + weekly + 0.5 + + + + https://supabase.com/blog/launch-week-7-hackathon-winners + weekly + 0.5 + + + + https://supabase.com/blog/postgres-pluggable-strorage + weekly + 0.5 + + + + https://supabase.com/blog/flutter-multi-factor-authentication + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-update-april-2023 + weekly + 0.5 + + + + https://supabase.com/blog/flutter-hackathon + weekly + 0.5 + + + + https://supabase.com/blog/building-chatgpt-plugins-template + weekly + 0.5 + + + + https://supabase.com/blog/chatgpt-plugins-support-postgres + weekly + 0.5 + + + + https://supabase.com/blog/flutter-hackathon-winners + weekly + 0.5 + + + + https://supabase.com/blog/vecs + weekly + 0.5 + + + + https://supabase.com/blog/supabase-beta-update-may-2023 + weekly + 0.5 + + + + https://supabase.com/customers/berriai + weekly + 0.5 + + + + https://supabase.com/customers/epsilon3 + weekly + 0.5 + + + + https://supabase.com/customers/happyteams + weekly + 0.5 + + + + https://supabase.com/customers/markprompt + weekly + 0.5 + + + + https://supabase.com/customers/mendableai + weekly + 0.5 + + + + https://supabase.com/customers/mobbin + weekly + 0.5 + + + + https://supabase.com/customers/replenysh + weekly + 0.5 + + + + https://supabase.com/customers/xendit + weekly + 0.5 + + + + https://supabase.com/alternatives/supabase-vs-firebase + weekly + 0.5 + + + + https://supabase.com/alternatives/supabase-vs-heroku-postgres + weekly + 0.5 + + + + https://supabase.com/partners/integrations/antstack + weekly + 0.5 + + + + https://supabase.com/partners/integrations/appsmith + weekly + 0.5 + + + + https://supabase.com/partners/integrations/arceptive + weekly + 0.5 + + + + https://supabase.com/partners/integrations/arengu + weekly + 0.5 + + + + https://supabase.com/partners/integrations/authsignal + weekly + 0.5 + + + + https://supabase.com/partners/integrations/basedash + weekly + 0.5 + + + + https://supabase.com/partners/integrations/buildlab + weekly + 0.5 + + + + https://supabase.com/partners/integrations/clerk + weekly + 0.5 + + + + https://supabase.com/partners/integrations/clutch + weekly + 0.5 + + + + https://supabase.com/partners/integrations/codesandbox + weekly + 0.5 + + + + https://supabase.com/partners/integrations/dashibase + weekly + 0.5 + + + + https://supabase.com/partners/integrations/deepnote + weekly + 0.5 + + + + https://supabase.com/partners/integrations/dhiwise + weekly + 0.5 + + + + https://supabase.com/partners/integrations/directus + weekly + 0.5 + + + + https://supabase.com/partners/integrations/doppler + weekly + 0.5 + + + + https://supabase.com/partners/integrations/draftbit + weekly + 0.5 + + + + https://supabase.com/partners/integrations/drapcode + weekly + 0.5 + + + + https://supabase.com/partners/integrations/dronahq + weekly + 0.5 + + + + https://supabase.com/partners/integrations/enginedigital + weekly + 0.5 + + + + https://supabase.com/partners/integrations/estuary + weekly + 0.5 + + + + https://supabase.com/partners/integrations/feelantera + weekly + 0.5 + + + + https://supabase.com/partners/integrations/fezto + weekly + 0.5 + + + + https://supabase.com/partners/integrations/flutterflow + weekly + 0.5 + + + + https://supabase.com/partners/integrations/forestadmin + weekly + 0.5 + + + + https://supabase.com/partners/integrations/getstream_io + weekly + 0.5 + + + + https://supabase.com/partners/integrations/illa + weekly + 0.5 + + + + https://supabase.com/partners/integrations/infisical + weekly + 0.5 + + + + https://supabase.com/partners/integrations/internalio + weekly + 0.5 + + + + https://supabase.com/partners/integrations/jetadmin + weekly + 0.5 + + + + https://supabase.com/partners/integrations/keyri + weekly + 0.5 + + + + https://supabase.com/partners/integrations/morrow + weekly + 0.5 + + + + https://supabase.com/partners/integrations/n8n + weekly + 0.5 + + + + https://supabase.com/partners/integrations/nextauth + weekly + 0.5 + + + + https://supabase.com/partners/integrations/nuxt + weekly + 0.5 + + + + https://supabase.com/partners/integrations/onesignal + weekly + 0.5 + + + + https://supabase.com/partners/integrations/ory + weekly + 0.5 + + + + https://supabase.com/partners/integrations/passageidentity + weekly + 0.5 + + + + https://supabase.com/partners/integrations/pgmustard + weekly + 0.5 + + + + https://supabase.com/partners/integrations/picket + weekly + 0.5 + + + + https://supabase.com/partners/integrations/plasmic + weekly + 0.5 + + + + https://supabase.com/partners/integrations/polyscale + weekly + 0.5 + + + + https://supabase.com/partners/integrations/prisma + weekly + 0.5 + + + + https://supabase.com/partners/integrations/quinence + weekly + 0.5 + + + + https://supabase.com/partners/integrations/readyset + weekly + 0.5 + + + + https://supabase.com/partners/integrations/refine_dev + weekly + 0.5 + + + + https://supabase.com/partners/integrations/replibyte + weekly + 0.5 + + + + https://supabase.com/partners/integrations/replicache + weekly + 0.5 + + + + https://supabase.com/partners/integrations/retool + weekly + 0.5 + + + + https://supabase.com/partners/integrations/sequin_io + weekly + 0.5 + + + + https://supabase.com/partners/integrations/snaplet + weekly + 0.5 + + + + https://supabase.com/partners/integrations/solid_software + weekly + 0.5 + + + + https://supabase.com/partners/integrations/streamlit + weekly + 0.5 + + + + https://supabase.com/partners/integrations/stytch + weekly + 0.5 + + + + https://supabase.com/partners/integrations/supertokens + weekly + 0.5 + + + + https://supabase.com/partners/integrations/teta + weekly + 0.5 + + + + https://supabase.com/partners/integrations/tinloof + weekly + 0.5 + + + + https://supabase.com/partners/integrations/trevor_io + weekly + 0.5 + + + + https://supabase.com/partners/integrations/uncommonpurpose + weekly + 0.5 + + + + https://supabase.com/partners/integrations/vercel + weekly + 0.5 + + + + https://supabase.com/partners/integrations/voltapp + weekly + 0.5 + + + + https://supabase.com/partners/integrations/voypost + weekly + 0.5 + + + + https://supabase.com/partners/integrations/weweb + weekly + 0.5 + + + + https://supabase.com/partners/integrations/yepcode + weekly + 0.5 + + + + https://supabase.com/partners/integrations/zapp_run + weekly + 0.5 + + + + https://supabase.com/partners/integrations/zuplo + weekly + 0.5 + + + + https://supabase.com/partners/experts/antstack + weekly + 0.5 + + + + https://supabase.com/partners/experts/appsmith + weekly + 0.5 + + + + https://supabase.com/partners/experts/arceptive + weekly + 0.5 + + + + https://supabase.com/partners/experts/arengu + weekly + 0.5 + + + + https://supabase.com/partners/experts/authsignal + weekly + 0.5 + + + + https://supabase.com/partners/experts/basedash + weekly + 0.5 + + + + https://supabase.com/partners/experts/buildlab + weekly + 0.5 + + + + https://supabase.com/partners/experts/clerk + weekly + 0.5 + + + + https://supabase.com/partners/experts/clutch + weekly + 0.5 + + + + https://supabase.com/partners/experts/codesandbox + weekly + 0.5 + + + + https://supabase.com/partners/experts/dashibase + weekly + 0.5 + + + + https://supabase.com/partners/experts/deepnote + weekly + 0.5 + + + + https://supabase.com/partners/experts/dhiwise + weekly + 0.5 + + + + https://supabase.com/partners/experts/directus + weekly + 0.5 + + + + https://supabase.com/partners/experts/doppler + weekly + 0.5 + + + + https://supabase.com/partners/experts/draftbit + weekly + 0.5 + + + + https://supabase.com/partners/experts/drapcode + weekly + 0.5 + + + + https://supabase.com/partners/experts/dronahq + weekly + 0.5 + + + + https://supabase.com/partners/experts/enginedigital + weekly + 0.5 + + + + https://supabase.com/partners/experts/estuary + weekly + 0.5 + + + + https://supabase.com/partners/experts/feelantera + weekly + 0.5 + + + + https://supabase.com/partners/experts/fezto + weekly + 0.5 + + + + https://supabase.com/partners/experts/flutterflow + weekly + 0.5 + + + + https://supabase.com/partners/experts/forestadmin + weekly + 0.5 + + + + https://supabase.com/partners/experts/getstream_io + weekly + 0.5 + + + + https://supabase.com/partners/experts/illa + weekly + 0.5 + + + + https://supabase.com/partners/experts/infisical + weekly + 0.5 + + + + https://supabase.com/partners/experts/internalio + weekly + 0.5 + + + + https://supabase.com/partners/experts/jetadmin + weekly + 0.5 + + + + https://supabase.com/partners/experts/keyri + weekly + 0.5 + + + + https://supabase.com/partners/experts/morrow + weekly + 0.5 + + + + https://supabase.com/partners/experts/n8n + weekly + 0.5 + + + + https://supabase.com/partners/experts/nextauth + weekly + 0.5 + + + + https://supabase.com/partners/experts/nuxt + weekly + 0.5 + + + + https://supabase.com/partners/experts/onesignal + weekly + 0.5 + + + + https://supabase.com/partners/experts/ory + weekly + 0.5 + + + + https://supabase.com/partners/experts/passageidentity + weekly + 0.5 + + + + https://supabase.com/partners/experts/pgmustard + weekly + 0.5 + + + + https://supabase.com/partners/experts/picket + weekly + 0.5 + + + + https://supabase.com/partners/experts/plasmic + weekly + 0.5 + + + + https://supabase.com/partners/experts/polyscale + weekly + 0.5 + + + + https://supabase.com/partners/experts/prisma + weekly + 0.5 + + + + https://supabase.com/partners/experts/quinence + weekly + 0.5 + + + + https://supabase.com/partners/experts/readyset + weekly + 0.5 + + + + https://supabase.com/partners/experts/refine_dev + weekly + 0.5 + + + + https://supabase.com/partners/experts/replibyte + weekly + 0.5 + + + + https://supabase.com/partners/experts/replicache + weekly + 0.5 + + + + https://supabase.com/partners/experts/retool + weekly + 0.5 + + + + https://supabase.com/partners/experts/sequin_io + weekly + 0.5 + + + + https://supabase.com/partners/experts/snaplet + weekly + 0.5 + + + + https://supabase.com/partners/experts/solid_software + weekly + 0.5 + + + + https://supabase.com/partners/experts/streamlit + weekly + 0.5 + + + + https://supabase.com/partners/experts/stytch + weekly + 0.5 + + + + https://supabase.com/partners/experts/supertokens + weekly + 0.5 + + + + https://supabase.com/partners/experts/teta + weekly + 0.5 + + + + https://supabase.com/partners/experts/tinloof + weekly + 0.5 + + + + https://supabase.com/partners/experts/trevor_io + weekly + 0.5 + + + + https://supabase.com/partners/experts/uncommonpurpose + weekly + 0.5 + + + + https://supabase.com/partners/experts/vercel + weekly + 0.5 + + + + https://supabase.com/partners/experts/voltapp + weekly + 0.5 + + + + https://supabase.com/partners/experts/voypost + weekly + 0.5 + + + + https://supabase.com/partners/experts/weweb + weekly + 0.5 + + + + https://supabase.com/partners/experts/yepcode + weekly + 0.5 + + + + https://supabase.com/partners/experts/zapp_run + weekly + 0.5 + + + + https://supabase.com/partners/experts/zuplo + weekly + 0.5 + + From c5e57fc1c94be06e1fac27b88f70866c05b223bf Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 21 Jun 2023 15:03:32 +0800 Subject: [PATCH 035/108] Create sitemap.xml --- apps/docs/public/sitemap.xml | 4722 ++++++++++++++++++++++++++++++++++ 1 file changed, 4722 insertions(+) create mode 100644 apps/docs/public/sitemap.xml diff --git a/apps/docs/public/sitemap.xml b/apps/docs/public/sitemap.xml new file mode 100644 index 00000000000..22dfa04b3c4 --- /dev/null +++ b/apps/docs/public/sitemap.xml @@ -0,0 +1,4722 @@ + + + + https://supabase.com/docs/faq + weekly + 0.5 + + + + https://supabase.com/docs/index + weekly + 0.5 + + + + https://supabase.com/docs/handbook/contributing + weekly + 0.5 + + + + https://supabase.com/docs/handbook/introduction + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai + weekly + 0.5 + + + + https://supabase.com/docs/guides/api + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth + weekly + 0.5 + + + + https://supabase.com/docs/guides/cli + weekly + 0.5 + + + + https://supabase.com/docs/guides/database + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage + weekly + 0.5 + + + + https://supabase.com/docs/reference/index + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/choosing-instance-type + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/concepts + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/engineering-for-scale + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/google-colab + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/hugging-face + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/langchain + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/managing-collections + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/managing-indexes + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/structured-unstructured + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/vecs-python-client + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/vector-columns + weekly + 0.5 + + + + https://supabase.com/docs/guides/api/api-keys + weekly + 0.5 + + + + https://supabase.com/docs/guides/api/creating-routes + weekly + 0.5 + + + + https://supabase.com/docs/guides/api/joins-and-nesting + weekly + 0.5 + + + + https://supabase.com/docs/guides/api/quickstart + weekly + 0.5 + + + + https://supabase.com/docs/guides/api/securing-your-api + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-captcha + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-email-templates + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-email + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-helpers + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-magic-link + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-mfa + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-password-reset + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/enterprise-sso + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/managing-user-data + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/phone-login + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/row-level-security + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/server-side-rendering + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login + weekly + 0.5 + + + + https://supabase.com/docs/guides/cli/managing-environments + weekly + 0.5 + + + + https://supabase.com/docs/guides/cli/using-environment-variables-in-config + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/auth + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/cicd-workflow + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/connect-to-postgres + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/cors + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/dart-edge + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/debugging + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/global-deployments + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/import-maps + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/kysely-postgres + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/local-development + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/quickstart + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/schedule-functions + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/secrets + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/storage-caching + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/troubleshooting + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/typescript-support + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/architecture + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/features + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/local-development + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/arrays + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/column-encryption + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/connecting-to-postgres + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/full-text-search + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/functions + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/json + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/managing-passwords + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/managing-timezones + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/overview + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/partitions + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/replication + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/sql-to-api + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/tables + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/testing + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/timeouts + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/vault + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/webhooks + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/appsmith + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/auth0 + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/authsignal + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/bracket + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/clerk + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/cloudflare-workers + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/dhiwise + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/directus + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/draftbit + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/estuary + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/fezto + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/flutterflow + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/forestadmin + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/illa + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/integrations + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/keyri + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/onesignal + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/passage + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/pgmustard + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/picket + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/plasmic + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/polyscale + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/prisma + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/sequin + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/snaplet + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/stytch + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/supertokens + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/vercel + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/weweb + weekly + 0.5 + + + + https://supabase.com/docs/guides/integrations/zuplo + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/access-control + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/backups + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/compute-add-ons + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/custom-domains + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/custom-postgres-config + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/database-size + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/exhaust-cpu + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/exhaust-ram + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/going-into-prod + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/http-status-codes + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/logs + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/maturity-model + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/metrics + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/migrating-and-upgrading-projects + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/network-restrictions + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/performance + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/permissions + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/shared-responsibility-model + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/spend-cap + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/ssl-enforcement + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/sso + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/troubleshooting + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/architecture + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/bring-your-own-database + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/broadcast + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/concepts + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/postgres-changes + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/presence + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/protocol + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/quickstart + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/quotas + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/realtime-with-nextjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/subscribing-to-database-changes + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/examples + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/glossary + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting/docker + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/access-control + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/cdn + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/image-transformations + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/quickstart + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/storage-sample + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/uploads + weekly + 0.5 + + + + https://supabase.com/docs/learn/auth-deep-dive/auth-deep-dive-jwts + weekly + 0.5 + + + + https://supabase.com/docs/learn/auth-deep-dive/auth-google-oauth + weekly + 0.5 + + + + https://supabase.com/docs/learn/auth-deep-dive/auth-gotrue + weekly + 0.5 + + + + https://supabase.com/docs/learn/auth-deep-dive/auth-policies + weekly + 0.5 + + + + https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/integrations/llamaindex + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/quickstarts/face-similarity + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/quickstarts/hello-world + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/quickstarts/text-deduplication + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/examples/building-chatgpt-plugins + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/examples/headless-vector-search + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/examples/huggingface-image-captioning + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/examples/image-search-openai-clip + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/examples/nextjs-vector-search + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/examples/openai + weekly + 0.5 + + + + https://supabase.com/docs/guides/api/graphql/graphiql + weekly + 0.5 + + + + https://supabase.com/docs/guides/api/rest/auto-generated-docs + weekly + 0.5 + + + + https://supabase.com/docs/guides/api/rest/client-libs + weekly + 0.5 + + + + https://supabase.com/docs/guides/api/rest/generating-types + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-helpers/auth-ui + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-helpers/flutter-auth-ui + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-helpers/nextjs-pages + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-helpers/nextjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-helpers/remix + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/auth-helpers/sveltekit + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/quickstarts/react + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-apple + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-azure + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-bitbucket + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-discord + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-facebook + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-github + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-gitlab + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-google + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-keycloak + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-linkedin + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-notion + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-slack + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-spotify + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-twitch + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-twitter + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-workos + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/social-login/auth-zoom + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/sso/auth-sso-saml + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/phone-login/messagebird + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/phone-login/twilio + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/phone-login/vonage + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/cloudflare-turnstile + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/discord-bot + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/github-actions + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/og-image + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/rate-limiting + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/screenshots + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/send-emails + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/stripe-webhooks + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/telegram-bot + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/upstash-redis + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/flutter + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/nextjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/nuxtjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/reactjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/redwoodjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/solidjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/sveltekit + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/vue + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/cascade-deletes + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/dropping-all-tables-in-schema + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/first-row-in-group + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/indexes + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/which-version-of-postgres + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/http + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/hypopg + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg-safeupdate + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg_graphql + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg_hashids + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg_jsonschema + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg_plan_filter + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg_stat_monitor + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg_stat_statements + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgaudit + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgcron + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgjwt + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgnet + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgrepack + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgroonga + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgrouting + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgsodium + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgtap + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgvector + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/plpgsql_check + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/plv8 + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/postgis + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/rum + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/timescaledb + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/uuid-ossp + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/wrappers + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/sso/azure + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/sso/gsuite + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/sso/okta + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/amazon-rds + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-auth + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-storage + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/firestore-data + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/heroku + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/render + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-angular + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-expo + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-flutter + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-angular + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-react + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-vue + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-nextjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-nuxt-3 + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-react + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-redwoodjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-solidjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-svelte + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-sveltekit + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-vue-3 + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting/analytics/config + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting/realtime/config + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting/auth/config + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting/storage/config + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/initializing + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-api + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-signup + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-signinwithpassword + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-signinwithotp + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-signinwithoauth + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-signinwithsso + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-signout + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-verifyotp + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-getsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-getuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-updateuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-reauthentication + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-resend + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-setsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-refreshsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-onauthstatechange + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-exchangecodeforsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-mfa-api + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-mfa-enroll + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-mfa-challenge + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-mfa-verify + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-mfa-challengeandverify + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-mfa-unenroll + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-mfa-getauthenticatorassurancelevel + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/admin-api + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-admin-getuserbyid + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-admin-listusers + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-admin-createuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-admin-deleteuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-admin-inviteuserbyemail + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-resetpasswordforemail + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-admin-generatelink + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-admin-updateuserbyid + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-admin-mfa-listfactors + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/auth-admin-mfa-deletefactor + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/select + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/insert + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/update + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/upsert + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/delete + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/rpc + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/using-filters + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/eq + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/neq + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/gt + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/gte + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/lt + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/lte + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/like + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/ilike + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/is + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/in + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/contains + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/containedby + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/rangegt + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/rangegte + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/rangelt + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/rangelte + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/rangeadjacent + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/overlaps + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/textsearch + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/match + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/not + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/or + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/filter + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/using-modifiers + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/db-modifiers-select + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/order + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/limit + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/range + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/db-abortsignal + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/single + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/maybesingle + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/db-csv + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/db-returns + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/functions-invoke + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/subscribe + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/getchannels + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/removechannel + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/removeallchannels + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-listbuckets + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-getbucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-createbucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-emptybucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-updatebucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-deletebucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-from-upload + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-from-update + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-from-move + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-from-copy + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-from-createsignedurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-from-createsignedurls + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-from-createsigneduploadurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-from-uploadtosignedurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-from-getpublicurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-from-download + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-from-remove + weekly + 0.5 + + + + https://supabase.com/docs/reference/javascript/storage-from-list + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/initializing + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/auth-signup + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/auth-signinwithpassword + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/auth-signinwithotp + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/sign-in-with-apple + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/auth-signinwithoauth + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/auth-signout + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/auth-verifyotp + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/auth-getsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/auth-getuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/auth-updateuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/auth-onauthstatechange + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/undefined + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/functions-invoke + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/select + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/insert + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/update + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/upsert + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/delete + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/rpc + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/subscribe + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/removechannel + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/removeallchannels + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/getchannels + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/stream + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-listbuckets + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-getbucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-createbucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-emptybucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-updatebucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-deletebucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-from-upload + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-from-update + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-from-move + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-from-createsignedurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-from-getpublicurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-from-download + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-from-remove + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/storage-from-list + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/using-modifiers + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/limit + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/order + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/range + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/single + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/using-filters + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/or + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/not + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/match + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/eq + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/neq + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/gt + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/gte + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/lt + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/lte + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/like + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/ilike + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/is + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/in + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/contains + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/containedby + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/rangelt + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/rangegt + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/rangegte + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/rangelte + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/rangeadjacent + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/overlaps + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/textsearch + weekly + 0.5 + + + + https://supabase.com/docs/reference/dart/filter + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/initializing + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/auth-signup + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/auth-signinwithpassword + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/auth-signinwithotp + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/auth-signinwithoauth + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/auth-signout + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/auth-verifyotp + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/auth-getuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/auth-getsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/auth-setsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/auth-refreshsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/select + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/insert + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/functions-invoke + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/storage-listbuckets + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/storage-getbucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/storage-createbucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/storage-emptybucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/storage-deletebucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/storage-from-upload + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/storage-from-move + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/storage-from-createsignedurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/storage-from-getpublicurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/storage-from-download + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/storage-from-remove + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/storage-from-list + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/subscribe + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/initializing + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/auth-signup + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/auth-signinwithpassword + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/auth-signinwithotp + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/auth-signinwithoauth + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/auth-signout + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/auth-verifyotp + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/auth-getsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/auth-getuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/auth-updateuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/auth-onauthstatechange + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/undefined + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/functions-invoke + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/select + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/insert + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/update + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/upsert + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/delete + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/rpc + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/subscribe + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/removechannel + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/getchannels + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-listbuckets + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-getbucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-createbucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-emptybucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-updatebucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-deletebucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-from-upload + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-from-update + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-from-move + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-from-createsignedurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-from-getpublicurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-from-download + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-from-remove + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/storage-from-list + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/using-modifiers + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/limit + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/order + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/range + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/undefined + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/single + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/using-filters + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/or + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/not + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/match + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/eq + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/neq + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/gt + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/gte + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/lt + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/lte + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/like + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/ilike + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/is + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/in + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/contains + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/containedby + weekly + 0.5 + + + + https://supabase.com/docs/reference/csharp/textsearch + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/initializing + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/select + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/insert + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/update + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/upsert + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/delete + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/rpc + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/using-filters + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/or + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/not + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/match + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/eq + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/neq + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/gt + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/gte + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/lt + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/lte + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/like + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/ilike + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/is + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/in + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/contains + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/rangelt + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/rangegt + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/rangegte + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/rangelte + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/rangeadjacent + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/overlaps + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/textsearch + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/filter + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/using-modifiers + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/db-modifiers-select + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/order + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/limit + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/range + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-api + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-signup + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-signinwithpassword + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-signinwithotp + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-signinwithoauth + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-signout + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-verifyotp + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-getsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-getuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-updateuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-setsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-refreshsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/auth-onauthstatechange + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/functions-invoke + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/subscribe + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/removechannel + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/storage-listbuckets + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/storage-getbucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/storage-createbucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/storage-emptybucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/storage-deletebucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/storage-from-upload + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/storage-from-update + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/storage-from-move + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/storage-from-createsignedurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/storage-from-download + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/storage-from-remove + weekly + 0.5 + + + + https://supabase.com/docs/reference/swift/storage-from-list + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/initializing + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/select + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/insert + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/update + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/upsert + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/delete + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/rpc + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/using-filters + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/or + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/not + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/eq + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/neq + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/gt + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/gte + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/lt + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/lte + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/like + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/ilike + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/is + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/in + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/contains + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/rangelt + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/rangegt + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/rangegte + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/rangelte + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/rangeadjacent + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/overlaps + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/textsearch + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/filter + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/order + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/limit + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/range + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-api + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-signup + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-signinwithpassword + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-signinwithotp + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-signinwithoauth + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-signinwithsso + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-signout + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-verifyotp + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-reauthentication + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-resend + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-getsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-getuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-updateuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-setsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-refreshsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-onauthstatechange + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-resetpasswordforemail + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-exchangecodeforsession + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-mfa-api + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-mfa-enroll + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-mfa-challenge + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-mfa-verify + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-mfa-challengeandverify + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-mfa-unenroll + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-mfa-getauthenticatorassurancelevel + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/admin-api + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-admin-getuserbyid + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-admin-listusers + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-admin-createuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-admin-deleteuser + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-admin-inviteuserbyemail + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-admin-generatelink + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-admin-updateuserbyid + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-admin-mfa-listfactors + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/auth-admin-mfa-deletefactor + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/functions-invoke + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/subscribe + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/removechannel + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/removeallchannels + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-listbuckets + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-getbucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-createbucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-updatebucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-emptybucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-deletebucket + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-from-upload + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-from-update + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-from-move + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-from-copy + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-from-createsignedurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-from-createsignedurls + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-from-createsigneduploadurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-from-uploadtosignedurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-from-getpublicurl + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-from-download + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-from-remove + weekly + 0.5 + + + + https://supabase.com/docs/reference/kotlin/storage-from-list + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-sso + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-sso-info + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-sso-add + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-sso-remove + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-sso-update + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-sso-list + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-sso-show + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-vanity-subdomains + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-vanity-subdomains-get + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-vanity-subdomains-delete + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-vanity-subdomains-check-availability + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-vanity-subdomains-activate + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-test + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-test-new + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-test-db + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-stop + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-status + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-start + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-ssl-enforcement + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-ssl-enforcement-update + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-ssl-enforcement-get + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-secrets + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-secrets-unset + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-secrets-set + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-secrets-list + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-projects + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-projects-list + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-projects-create + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-orgs + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-orgs-list + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-network-restrictions + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-network-restrictions-update + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-network-restrictions-get + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-network-bans + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-network-bans-remove + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-network-bans-get + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-migration + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-migration-up + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-migration-repair + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-migration-new + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-migration-list + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-login + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-link + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-init + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-gen + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-gen-types + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-gen-types-typescript + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-gen-keys + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-functions + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-functions-serve + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-functions-new + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-functions-download + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-functions-deploy + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-functions-delete + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-domains + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-domains-reverify + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-domains-get + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-domains-delete + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-domains-create + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-domains-activate + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-db + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-db-reset + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-db-remote + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-db-remote-commit + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-db-push + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-db-lint + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-db-dump + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-db-diff + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-completion + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-completion-zsh + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-completion-powershell + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-completion-fish + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-completion-bash + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/introduction + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/list-all-projects + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/create-a-project + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/list-all-organizations + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/create-an-organization + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/list-all-secrets + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/bulk-create-secrets + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/bulk-delete-secrets + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/generate-typescript-types + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/create-a-function + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/list-all-functions + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/retrieve-a-function + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/update-a-function + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/delete-a-function + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/retrieve-a-function-body + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/gets-projects-custom-hostname-config + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/deletes-a-projects-custom-hostname-configuration + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/updates-projects-custom-hostname-configuration + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/attempts-to-verify-the-dns-configuration-for-projects-custom-hostname-configuration + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/activates-a-custom-hostname-for-a-project + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/gets-projects-pgsodium-config + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/updates-projects-pgsodium-config-updating-the-root_key-can-cause-all-data-encrypted-with-the-older-key-to-become-inaccessible + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/gets-projects-network-bans + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/remove-network-bans + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/gets-projects-network-restrictions + weekly + 0.5 + + + + https://supabase.com/docs/reference/api/updates-projects-network-restrictions + weekly + 0.5 + + From 424d6ec2acce7992ab4c2aa097007e6180ba265d Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Jun 2023 15:11:45 +0800 Subject: [PATCH 036/108] Improve empty state --- .../Database/Migrations/Migrations.tsx | 86 +++++++------- .../Migrations/MigrationsEmptyState.tsx | 111 ++++++++++++++++++ studio/data/database/migrations-query.ts | 10 ++ studio/data/sql/execute-sql-query.ts | 29 +++-- 4 files changed, 188 insertions(+), 48 deletions(-) create mode 100644 studio/components/interfaces/Database/Migrations/MigrationsEmptyState.tsx diff --git a/studio/components/interfaces/Database/Migrations/Migrations.tsx b/studio/components/interfaces/Database/Migrations/Migrations.tsx index fcf2f7649e9..b3e2172d214 100644 --- a/studio/components/interfaces/Database/Migrations/Migrations.tsx +++ b/studio/components/interfaces/Database/Migrations/Migrations.tsx @@ -9,6 +9,7 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { DatabaseMigration, useMigrationsQuery } from 'data/database/migrations-query' import { Editor } from '@monaco-editor/react' import CodeEditor from 'components/ui/CodeEditor' +import MigrationsEmptyState from './MigrationsEmptyState' const Wrappers = () => { const [search, setSearch] = useState('') @@ -24,6 +25,8 @@ const Wrappers = () => { ? data?.result ?? [] : data?.result.filter((migration) => migration.version.includes(search)) ?? [] + console.log({ data }) + return ( <>
@@ -80,51 +83,52 @@ const Wrappers = () => { )} {isSuccess && (
-
- setSearch(e.target.value)} - icon={} - /> -
- - {data.result.length <= 0 &&
No migrations state...
} + {data.result.length <= 0 && } {data.result.length > 0 && ( -
Version, - , - ]} - body={ - migrations.length > 0 ? ( - migrations.map((migration) => ( - - {migration.version} - - + <> +
+ setSearch(e.target.value)} + icon={} + /> +
+
Version, + , + ]} + body={ + migrations.length > 0 ? ( + migrations.map((migration) => ( + + {migration.version} + + + + + )) + ) : ( + + +

No results found

+

+ Your search for "{search}" did not return any results +

- )) - ) : ( - - -

No results found

-

- Your search for "{search}" did not return any results -

-
-
- ) - } - /> + ) + } + /> + )} )} diff --git a/studio/components/interfaces/Database/Migrations/MigrationsEmptyState.tsx b/studio/components/interfaces/Database/Migrations/MigrationsEmptyState.tsx new file mode 100644 index 00000000000..55ef19b3ea7 --- /dev/null +++ b/studio/components/interfaces/Database/Migrations/MigrationsEmptyState.tsx @@ -0,0 +1,111 @@ +import clsx from 'clsx' +import Link from 'next/link' +import { useState } from 'react' +import { + Button, + Collapsible, + IconExternalLink, + IconMaximize2, + IconMinimize2, + IconTerminal, +} from 'ui' + +import CommandRender from 'components/interfaces/Functions/CommandRender' +import { useParams } from 'common' + +const MigrationsEmptyState = () => { + const { ref } = useParams() + const [showInstructions, setShowInstructions] = useState(false) + + const commands = [ + { + comment: 'Link your project', + command: `supabase link --project-ref ${ref}`, + jsx: () => { + return ( + <> + supabase link --project-ref {ref} + + ) + }, + }, + { + comment: 'Create a new migration called "new-migration"', + command: `supabase migration new new-migration`, + jsx: () => { + return ( + <> + supabase migration new new-migration + + ) + }, + }, + { + comment: 'Run all migrations against this project', + command: `supabase db push`, + jsx: () => { + return ( + <> + supabase db push + + ) + }, + }, + ] + + return ( +
+
+
+

Database migrations

+

+ Database changes are managed through "migrations" and they are a common way of tracking + changes to your database over time. You can run migrations against your project's + database via the Supabase CLI. +

+
+ + + + + + +
+ +
+
+
+
+
+ ) +} + +export default MigrationsEmptyState diff --git a/studio/data/database/migrations-query.ts b/studio/data/database/migrations-query.ts index 00e24118c3c..f9a25136e8b 100644 --- a/studio/data/database/migrations-query.ts +++ b/studio/data/database/migrations-query.ts @@ -36,6 +36,16 @@ export const useMigrationsQuery = { + if ( + error.code === 400 && + error.message.includes('relation "supabase_migrations.schema_migrations" does not exist') + ) { + return { result: [] } + } else { + throw error + } + }, }, options ) diff --git a/studio/data/sql/execute-sql-query.ts b/studio/data/sql/execute-sql-query.ts index 1eb86b56373..76ef0f665aa 100644 --- a/studio/data/sql/execute-sql-query.ts +++ b/studio/data/sql/execute-sql-query.ts @@ -10,12 +10,16 @@ import { post } from 'lib/common/fetch' import { API_URL } from 'lib/constants' import { useCallback } from 'react' import { sqlKeys } from './keys' +import { noop } from 'lodash' + +export type Error = { code: number; message: string; requestId: string } export type ExecuteSqlVariables = { projectRef?: string connectionString?: string sql: string queryKey?: QueryKey + handleError?: (error: { code: number; message: string; requestId: string }) => any } export async function executeSql( @@ -24,7 +28,11 @@ export async function executeSql( connectionString, sql, queryKey, - }: Pick, + handleError, + }: Pick< + ExecuteSqlVariables, + 'projectRef' | 'connectionString' | 'sql' | 'queryKey' | 'handleError' + >, signal?: AbortSignal ) { if (!projectRef) { @@ -44,8 +52,13 @@ export async function executeSql( { query: sql }, { headers: Object.fromEntries(headers), signal } ) + if (response.error) { - throw response.error + if (handleError !== undefined) { + return handleError(response.error) + } else { + throw response.error + } } return { result: response } @@ -55,21 +68,22 @@ export type ExecuteSqlData = Awaited> export type ExecuteSqlError = unknown export const useExecuteSqlQuery = ( - { projectRef, connectionString, sql, queryKey }: ExecuteSqlVariables, + { projectRef, connectionString, sql, queryKey, handleError }: ExecuteSqlVariables, { enabled = true, ...options }: UseQueryOptions = {} ) => useQuery( sqlKeys.query(projectRef, queryKey ?? [md5(sql)]), - ({ signal }) => executeSql({ projectRef, connectionString, sql, queryKey }, signal), + ({ signal }) => + executeSql({ projectRef, connectionString, sql, queryKey, handleError }, signal), { enabled: enabled && typeof projectRef !== 'undefined', ...options } ) export const prefetchExecuteSql = ( client: QueryClient, - { projectRef, connectionString, sql, queryKey }: ExecuteSqlVariables + { projectRef, connectionString, sql, queryKey, handleError }: ExecuteSqlVariables ) => { return client.prefetchQuery(sqlKeys.query(projectRef, queryKey ?? [md5(sql)]), ({ signal }) => - executeSql({ projectRef, connectionString, sql, queryKey }, signal) + executeSql({ projectRef, connectionString, sql, queryKey, handleError }, signal) ) } @@ -89,13 +103,14 @@ export const useExecuteSqlPrefetch = () => { const client = useQueryClient() return useCallback( - ({ projectRef, connectionString, sql, queryKey }: ExecuteSqlVariables) => { + ({ projectRef, connectionString, sql, queryKey, handleError }: ExecuteSqlVariables) => { if (projectRef) { return prefetchExecuteSql(client, { projectRef, connectionString, sql, queryKey, + handleError, }) } From e158286e444f2310a3465da7f6d7b1a8a366f8a1 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Jun 2023 15:12:49 +0800 Subject: [PATCH 037/108] Small refactor --- .../Database/Migrations/Migrations.tsx | 157 +++++++++--------- 1 file changed, 79 insertions(+), 78 deletions(-) diff --git a/studio/components/interfaces/Database/Migrations/Migrations.tsx b/studio/components/interfaces/Database/Migrations/Migrations.tsx index b3e2172d214..763eef98619 100644 --- a/studio/components/interfaces/Database/Migrations/Migrations.tsx +++ b/studio/components/interfaces/Database/Migrations/Migrations.tsx @@ -48,93 +48,94 @@ const Wrappers = () => { - {isLoading ? ( + {isLoading && (
- ) : ( -
- {isError && ( - - - - - , - ]} - > -

- Try refreshing your browser, but if the issue persists, please reach out to us via - support. -

-

Error: {(error as any)?.message ?? 'Unknown'}

-
- )} - {isSuccess && ( -
- {data.result.length <= 0 && } + )} - {data.result.length > 0 && ( - <> -
- setSearch(e.target.value)} - icon={} - /> -
-
Version, - , - ]} - body={ - migrations.length > 0 ? ( - migrations.map((migration) => ( - - {migration.version} - - - - - )) - ) : ( - - -

No results found

-

- Your search for "{search}" did not return any results -

+
+ {isError && ( + + + + + , + ]} + > +

+ Try refreshing your browser, but if the issue persists, please reach out to us via + support. +

+

Error: {(error as any)?.message ?? 'Unknown'}

+
+ )} + {isSuccess && ( +
+ {data.result.length <= 0 && } + + {data.result.length > 0 && ( + <> +
+ setSearch(e.target.value)} + icon={} + /> +
+
Version, + , + ]} + body={ + migrations.length > 0 ? ( + migrations.map((migration) => ( + + {migration.version} + + - ) - } - /> - - )} - - )} - - )} + )) + ) : ( + + +

No results found

+

+ Your search for "{search}" did not return any results +

+
+
+ ) + } + /> + + )} + + )} + + Date: Wed, 21 Jun 2023 15:14:02 +0800 Subject: [PATCH 038/108] Clean up unused improts --- .../interfaces/Database/Migrations/Migrations.tsx | 3 +-- .../Database/Migrations/MigrationsEmptyState.tsx | 12 ++---------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/studio/components/interfaces/Database/Migrations/Migrations.tsx b/studio/components/interfaces/Database/Migrations/Migrations.tsx index 763eef98619..1f5eca1e7d8 100644 --- a/studio/components/interfaces/Database/Migrations/Migrations.tsx +++ b/studio/components/interfaces/Database/Migrations/Migrations.tsx @@ -5,10 +5,9 @@ import { Alert, Button, IconExternalLink, IconSearch, Input, SidePanel } from 'u import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import Table from 'components/to-be-cleaned/Table' +import CodeEditor from 'components/ui/CodeEditor' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { DatabaseMigration, useMigrationsQuery } from 'data/database/migrations-query' -import { Editor } from '@monaco-editor/react' -import CodeEditor from 'components/ui/CodeEditor' import MigrationsEmptyState from './MigrationsEmptyState' const Wrappers = () => { diff --git a/studio/components/interfaces/Database/Migrations/MigrationsEmptyState.tsx b/studio/components/interfaces/Database/Migrations/MigrationsEmptyState.tsx index 55ef19b3ea7..b4f2153339c 100644 --- a/studio/components/interfaces/Database/Migrations/MigrationsEmptyState.tsx +++ b/studio/components/interfaces/Database/Migrations/MigrationsEmptyState.tsx @@ -1,17 +1,9 @@ import clsx from 'clsx' -import Link from 'next/link' import { useState } from 'react' -import { - Button, - Collapsible, - IconExternalLink, - IconMaximize2, - IconMinimize2, - IconTerminal, -} from 'ui' +import { Collapsible, IconMaximize2, IconMinimize2, IconTerminal } from 'ui' -import CommandRender from 'components/interfaces/Functions/CommandRender' import { useParams } from 'common' +import CommandRender from 'components/interfaces/Functions/CommandRender' const MigrationsEmptyState = () => { const { ref } = useParams() From 67901abe87d7d6c8916eeebadf380a5a5e5b7e46 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 21 Jun 2023 15:52:14 +0800 Subject: [PATCH 039/108] Update studio/components/layouts/SQLEditorLayout/QueryItem.tsx Co-authored-by: Joshen Lim --- studio/components/layouts/SQLEditorLayout/QueryItem.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/studio/components/layouts/SQLEditorLayout/QueryItem.tsx b/studio/components/layouts/SQLEditorLayout/QueryItem.tsx index aa6af40dda3..7912a4f6b09 100644 --- a/studio/components/layouts/SQLEditorLayout/QueryItem.tsx +++ b/studio/components/layouts/SQLEditorLayout/QueryItem.tsx @@ -65,7 +65,6 @@ interface QueryItemActionsProps { } const QueryItemActions = observer(({ tabInfo, activeId }: QueryItemActionsProps) => { - console.log('activeId', activeId) const { ui } = useStore() const { ref } = useParams() const router = useRouter() From cc244a9a1533434bd593e23e07dd1afff0c0e044 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Jun 2023 15:53:00 +0800 Subject: [PATCH 040/108] Update studio/components/layouts/SQLEditorLayout/QueryItem.tsx --- studio/components/layouts/SQLEditorLayout/QueryItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/layouts/SQLEditorLayout/QueryItem.tsx b/studio/components/layouts/SQLEditorLayout/QueryItem.tsx index 7912a4f6b09..d7cb1ad653e 100644 --- a/studio/components/layouts/SQLEditorLayout/QueryItem.tsx +++ b/studio/components/layouts/SQLEditorLayout/QueryItem.tsx @@ -45,7 +45,7 @@ const QueryItem = ({ tabInfo }: QueryItemProps) => {

{name} From 15e08017836e5f5dfc1f8157e9e596384798a982 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Jun 2023 16:14:10 +0800 Subject: [PATCH 041/108] Add security invoker label for encrypt column --- studio/stores/project/VaultStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/stores/project/VaultStore.ts b/studio/stores/project/VaultStore.ts index a803ff64dbc..03aa9f72ab3 100644 --- a/studio/stores/project/VaultStore.ts +++ b/studio/stores/project/VaultStore.ts @@ -133,7 +133,7 @@ export default class VaultStore implements IVaultStore { } async encryptColumn(column: PostgresColumn, keyId: string) { - const query = `security label for pgsodium on column "${column.table}"."${column.name}" is 'ENCRYPT WITH KEY ID ${keyId}';` + const query = `security label for pgsodium on column "${column.table}"."${column.name}" is 'ENCRYPT WITH KEY ID ${keyId} SECURITY INVOKER';` return await this.rootStore.meta.query(query) } From a6fd450c046279165e72b24fd37b8e045ce75302 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Jun 2023 16:41:54 +0800 Subject: [PATCH 042/108] Fix copying url in storage explorer returns object Promise --- .../storageExplorer/StorageExplorerStore.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/studio/localStores/storageExplorer/StorageExplorerStore.js b/studio/localStores/storageExplorer/StorageExplorerStore.js index 37641c47ac6..2c30d660945 100644 --- a/studio/localStores/storageExplorer/StorageExplorerStore.js +++ b/studio/localStores/storageExplorer/StorageExplorerStore.js @@ -8,7 +8,7 @@ import { createClient } from '@supabase/supabase-js' import { useStore } from 'hooks' import { copyToClipboard } from 'lib/helpers' import { API_URL, IS_PLATFORM } from 'lib/constants' -import { get, patch, post, delete_ } from 'lib/common/fetch' +import { post, delete_ } from 'lib/common/fetch' import { PROJECT_ENDPOINT_PROTOCOL } from 'pages/api/constants' import { STORAGE_VIEWS, @@ -407,15 +407,12 @@ class StorageExplorerStore { }) } else { // Need to generate signed URL, and might as well save it to cache as well - const signedUrlAsync = this.fetchFilePreview(file.name, expiresIn).then((signedUrl) => { - const formattedUrl = new URL(signedUrl) - formattedUrl.searchParams.set('t', new Date().toISOString()) - - return formattedUrl.toString() - }) + const signedUrl = await this.fetchFilePreview(file.name, expiresIn) + const formattedUrl = new URL(signedUrl) + formattedUrl.searchParams.set('t', new Date().toISOString()) try { - copyToClipboard(signedUrlAsync, () => { + copyToClipboard(formattedUrl.toString(), () => { this.ui.setNotification({ category: 'success', message: `Copied URL for ${file.name} to clipboard.`, @@ -424,7 +421,7 @@ class StorageExplorerStore { }) const fileCache = { id: file.id, - url: await signedUrlAsync, + url: formattedUrl.toString(), expiresIn: DEFAULT_EXPIRY, fetchedAt: Date.now(), } From 3899e7d407787b585e204715ed57dba84c7bd336 Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Wed, 21 Jun 2023 17:21:34 +0800 Subject: [PATCH 043/108] Added sendActivity() to telemetry --- studio/lib/telemetry.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/studio/lib/telemetry.ts b/studio/lib/telemetry.ts index 9cf951fc0c6..bb4df3610ba 100644 --- a/studio/lib/telemetry.ts +++ b/studio/lib/telemetry.ts @@ -53,7 +53,45 @@ const sendIdentify = (user: User, gaProps?: TelemetryProps) => { }) } +const sendActivity = ( + event: { + activity: string + source: string + projectRef?: string + orgId?: string + data?: object + }, + router: NextRouter +) => { + if (!IS_PLATFORM) return + + const { activity, source, projectRef, orgId, data } = event + + const properties = { + activity, + source, + page: { + path: router.route, + location: router.asPath, + referrer: document?.referrer || '', + title: document?.title || '', + }, + ...(data && { data }), + // add if included, else estimate from path + ...(projectRef && { projectRef }), + ...(router.route.includes('/project/') && + !projectRef && { + projectRef: router.route.split('/project/')[1].split('/')[0], + }), + ...(orgId && { orgId }), + ...(router.route.includes('/org/') && + !orgId && { orgId: router.route.split('/org/')[1].split('/')[0] }), + } + return post(`${API_URL}/telemetry/activity`, properties) +} + export default { sendEvent, sendIdentify, + sendActivity, } From fd0abadc6da16bb28851a2dc996285da71688e36 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Jun 2023 17:44:10 +0800 Subject: [PATCH 044/108] Deprecate onCopyFileUrlToClipboard in StorageExplorerStore and add a new function at the UI level to consider custom domains --- .../layouts/StorageLayout/StorageLayout.tsx | 2 +- .../to-be-cleaned/Storage/Storage.types.ts | 30 +++++++++++ .../StorageExplorer/CustomExpiryModal.tsx | 15 +++--- .../{FileExplorer.js => FileExplorer.tsx} | 32 ++++++++--- ...plorerColumn.js => FileExplorerColumn.tsx} | 53 +++++++++++-------- .../StorageExplorer/FileExplorerRow.tsx | 30 ++++++----- .../StorageExplorer/ItemContextMenu.tsx | 11 ++-- .../Storage/StorageExplorer/PreviewPane.tsx | 16 +++--- .../StorageExplorer/StorageExplorer.js | 43 ++++++++++++--- .../storageExplorer/StorageExplorerStore.js | 42 +++++---------- 10 files changed, 178 insertions(+), 96 deletions(-) create mode 100644 studio/components/to-be-cleaned/Storage/Storage.types.ts rename studio/components/to-be-cleaned/Storage/StorageExplorer/{FileExplorer.js => FileExplorer.tsx} (74%) rename studio/components/to-be-cleaned/Storage/StorageExplorer/{FileExplorerColumn.js => FileExplorerColumn.tsx} (88%) diff --git a/studio/components/layouts/StorageLayout/StorageLayout.tsx b/studio/components/layouts/StorageLayout/StorageLayout.tsx index 797b327ad60..4993b2c015f 100644 --- a/studio/components/layouts/StorageLayout/StorageLayout.tsx +++ b/studio/components/layouts/StorageLayout/StorageLayout.tsx @@ -30,7 +30,7 @@ const StorageLayout = ({ title, children }: StorageLayoutProps) => { useEffect(() => { if (!isLoading && apiService) initializeStorageStore(apiService) - }, [isLoading]) + }, [isLoading, projectRef]) const initializeStorageStore = async (apiService: AutoApiService) => { if (isPaused) return diff --git a/studio/components/to-be-cleaned/Storage/Storage.types.ts b/studio/components/to-be-cleaned/Storage/Storage.types.ts new file mode 100644 index 00000000000..fcb2ac2e405 --- /dev/null +++ b/studio/components/to-be-cleaned/Storage/Storage.types.ts @@ -0,0 +1,30 @@ +export interface StorageColumn { + id: string + name: string + status: string + items: StorageItem[] + hasMoreItems: boolean + isLoadingMoreItems: boolean +} + +export interface StorageItem { + id: string | null + name: string + type: string + status: string + metadata: StorageItemMetadata | null + isCorrupted: boolean + created_at: string | null + updated_at: string | null + last_accessed_at: string | null +} + +export interface StorageItemMetadata { + cacheControl: string + contentLength: number + size: number + httpStatusCode: number + eTag: string + lastModified: string + mimetype: string +} diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx index f702ba38bfa..d20d137cdbc 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx @@ -1,14 +1,14 @@ -import { FC } from 'react' import { Form, Modal, Input, Button } from 'ui' import { observer } from 'mobx-react-lite' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' -interface Props {} +export interface CustomExpiryModalProps { + onCopyUrl: (name: string, url: string) => void +} -const CustomExpiryModal: FC = () => { +const CustomExpiryModal = ({ onCopyUrl }: CustomExpiryModalProps) => { const storageExplorerStore = useStorageStore() - const { selectedFileCustomExpiry, setSelectedFileCustomExpiry, copyFileURLToClipboard } = - storageExplorerStore + const { getFileUrl, selectedFileCustomExpiry, setSelectedFileCustomExpiry } = storageExplorerStore const visible = selectedFileCustomExpiry !== undefined const onClose = () => setSelectedFileCustomExpiry(undefined) @@ -28,7 +28,10 @@ const CustomExpiryModal: FC = () => { initialValues={{ expiresIn: '' }} onSubmit={async (values: any, { setSubmitting }: any) => { setSubmitting(true) - await copyFileURLToClipboard(selectedFileCustomExpiry, values.expiresIn) + onCopyUrl( + selectedFileCustomExpiry.name, + await getFileUrl(selectedFileCustomExpiry, values.expiresIn) + ) setSubmitting(false) onClose() }} diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.tsx similarity index 74% rename from studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js rename to studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.tsx index 2b41ffda51a..09783f3f591 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.tsx @@ -6,6 +6,21 @@ import ItemContextMenu from './ItemContextMenu' import FolderContextMenu from './FolderContextMenu' import ColumnContextMenu from './ColumnContextMenu' import FileExplorerColumn from './FileExplorerColumn' +import { noop } from 'lodash' +import { StorageColumn } from '../Storage.types' + +export interface FileExplorerProps { + view: string + columns: any[] + openedFolders: any[] + selectedItems: any[] + selectedFilePreview: any + onFilesUpload: (event: any, index: number) => void + onSelectAllItemsInColumn: (index: number) => void + onSelectColumnEmptySpace: (index: number) => void + onColumnLoadMore: (index: number, column: StorageColumn) => void + onCopyUrl: (name: string, url: string) => void +} const FileExplorer = ({ view = STORAGE_VIEWS.COLUMNS, @@ -13,12 +28,13 @@ const FileExplorer = ({ openedFolders = [], selectedItems = [], selectedFilePreview = {}, - onFilesUpload = () => {}, - onSelectAllItemsInColumn = () => {}, - onSelectColumnEmptySpace = () => {}, - onColumnLoadMore = () => {}, -}) => { - const fileExplorerRef = useRef(null) + onFilesUpload = noop, + onSelectAllItemsInColumn = noop, + onSelectColumnEmptySpace = noop, + onColumnLoadMore = noop, + onCopyUrl = noop, +}: FileExplorerProps) => { + const fileExplorerRef = useRef(null) useEffect(() => { if (fileExplorerRef) { @@ -35,7 +51,7 @@ const FileExplorer = ({ className="file-explorer flex flex-grow overflow-x-auto justify-between h-full w-full" > - + {view === STORAGE_VIEWS.COLUMNS ? (

@@ -52,6 +68,7 @@ const FileExplorer = ({ onSelectAllItemsInColumn={onSelectAllItemsInColumn} onSelectColumnEmptySpace={onSelectColumnEmptySpace} onColumnLoadMore={onColumnLoadMore} + onCopyUrl={onCopyUrl} /> ))}
@@ -69,6 +86,7 @@ const FileExplorer = ({ onSelectAllItemsInColumn={onSelectAllItemsInColumn} onSelectColumnEmptySpace={onSelectColumnEmptySpace} onColumnLoadMore={onColumnLoadMore} + onCopyUrl={onCopyUrl} /> )} diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.tsx similarity index 88% rename from studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js rename to studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.tsx index 3bf029e8f5f..264deef55a7 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerColumn.tsx @@ -1,5 +1,5 @@ import { useState, useRef, useEffect } from 'react' -import { get, sum } from 'lodash' +import { get, noop, sum } from 'lodash' import { Checkbox, IconUpload } from 'ui' import { Transition } from '@headlessui/react' import { useContextMenu } from 'react-contexify' @@ -15,8 +15,9 @@ import { } from '../Storage.constants' import { formatBytes } from 'lib/helpers' import { BASE_PATH } from 'lib/constants' +import { StorageColumn } from '../Storage.types' -const DragOverOverlay = ({ isOpen, onDragLeave, onDrop, folderIsEmpty }) => { +const DragOverOverlay = ({ isOpen, onDragLeave, onDrop, folderIsEmpty }: any) => { return ( { ) } +export interface FileExplorerColumnProps { + index: number + view: string + column: StorageColumn + fullWidth?: boolean + openedFolders?: any[] + selectedItems: any[] + selectedFilePreview: any + onFilesUpload: (event: any, index: number) => void + onSelectAllItemsInColumn: (index: number) => void + onSelectColumnEmptySpace: (index: number) => void + onColumnLoadMore: (index: number, column: StorageColumn) => void + onCopyUrl: (name: string, url: string) => void +} + const FileExplorerColumn = ({ index = 0, view = STORAGE_VIEWS.COLUMNS, - column = {}, + column, fullWidth = false, openedFolders = [], selectedItems = [], selectedFilePreview = {}, - onFilesUpload = () => {}, - onSelectAllItemsInColumn = () => {}, - onSelectColumnEmptySpace = () => {}, - onColumnLoadMore = () => {}, -}) => { + onFilesUpload = noop, + onSelectAllItemsInColumn = noop, + onSelectColumnEmptySpace = noop, + onColumnLoadMore = noop, + onCopyUrl = noop, +}: FileExplorerColumnProps) => { const [isDraggedOver, setIsDraggedOver] = useState(false) - const fileExplorerColumnRef = useRef(null) + const fileExplorerColumnRef = useRef(null) useEffect(() => { if (fileExplorerColumnRef) { @@ -87,14 +104,14 @@ const FileExplorerColumn = ({ const columnItemsSize = sum(columnItems.map((item) => get(item, ['metadata', 'size'], 0))) const { show } = useContextMenu() - const displayMenu = (event) => { + const displayMenu = (event: any) => { show(event, { id: CONTEXT_MENU_KEYS.STORAGE_COLUMN, props: { index }, }) } - const onDragOver = (event) => { + const onDragOver = (event: any) => { if (event) { event.stopPropagation() event.preventDefault() @@ -104,7 +121,7 @@ const FileExplorerColumn = ({ } } - const onDrop = (event) => { + const onDrop = (event: any) => { onDragOver(event) onFilesUpload(event, index) } @@ -144,7 +161,7 @@ const FileExplorerColumn = ({ {view === STORAGE_VIEWS.COLUMNS && (
0 ? 'h-10 py-3 opacity-100' : 'h-0 py-0 opacity-0' + haveSelectedItems ? 'h-10 py-3 opacity-100' : 'h-0 py-0 opacity-0' } transition-all duration-200`} onClick={(event) => event.stopPropagation()} > @@ -202,15 +219,7 @@ const FileExplorerColumn = ({ selectedItems, openedFolders, selectedFilePreview, - // onCheckItem, - // onRenameFile, - // onCopyFileURL, - // onDownloadFile, - // onRenameFolder, - // onCreateFolder, - // onSelectItemDelete, - // onSelectItemRename, - // onSelectItemMove, + onCopyUrl, }} ItemComponent={FileExplorerRow} getItemSize={(index) => (index !== 0 && index === columnItems.length ? 85 : 37)} diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx index 95fb39f36ca..19567f7484b 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx @@ -71,7 +71,7 @@ export const RowIcon = ({ view, status, fileType, mimeType }: any) => { return } -interface FileExplorerRowProps { +export interface FileExplorerRowProps { index: number item: any view: string @@ -79,6 +79,7 @@ interface FileExplorerRowProps { selectedItems: any[] openedFolders: any[] selectedFilePreview: any + onCopyUrl: (name: string, url: string) => void } const FileExplorerRow = ({ @@ -89,9 +90,11 @@ const FileExplorerRow = ({ selectedItems = [], openedFolders = [], selectedFilePreview = {}, + onCopyUrl, }: FileExplorerRowProps) => { const storageExplorerStore = useStorageStore() const { + getFileUrl, popColumnAtIndex, pushOpenedFolderAtIndex, popOpenedFoldersAtIndex, @@ -107,7 +110,6 @@ const FileExplorerRow = ({ fetchFolderContents, downloadFile, downloadFolder, - copyFileURLToClipboard, selectRangeItems, } = storageExplorerStore @@ -188,7 +190,11 @@ const FileExplorerRow = ({ { name: 'Get URL', icon: , - onClick: async () => await copyFileURLToClipboard(itemWithColumnIndex), + onClick: async () => + onCopyUrl( + itemWithColumnIndex.name, + await getFileUrl(itemWithColumnIndex) + ), }, ] : [ @@ -199,25 +205,25 @@ const FileExplorerRow = ({ { name: 'Expire in 1 week', onClick: async () => - await copyFileURLToClipboard( - itemWithColumnIndex, - URL_EXPIRY_DURATION.WEEK + onCopyUrl( + itemWithColumnIndex.name, + await getFileUrl(itemWithColumnIndex, URL_EXPIRY_DURATION.WEEK) ), }, { name: 'Expire in 1 month', onClick: async () => - await copyFileURLToClipboard( - itemWithColumnIndex, - URL_EXPIRY_DURATION.MONTH + onCopyUrl( + itemWithColumnIndex.name, + await getFileUrl(itemWithColumnIndex, URL_EXPIRY_DURATION.MONTH) ), }, { name: 'Expire in 1 year', onClick: async () => - await copyFileURLToClipboard( - itemWithColumnIndex, - URL_EXPIRY_DURATION.YEAR + onCopyUrl( + itemWithColumnIndex.name, + await getFileUrl(itemWithColumnIndex, URL_EXPIRY_DURATION.YEAR) ), }, { diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.tsx index bd0ad8fec92..e04d39f146c 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.tsx @@ -1,4 +1,3 @@ -import { FC } from 'react' import { observer } from 'mobx-react-lite' import { Menu, Item, Separator, Submenu } from 'react-contexify' import 'react-contexify/dist/ReactContexify.css' @@ -8,21 +7,23 @@ import { IconClipboard, IconEdit, IconMove, IconDownload, IconTrash2, IconChevro import { checkPermissions } from 'hooks' import { URL_EXPIRY_DURATION } from '../Storage.constants' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' +import { noop } from 'lodash' -interface Props { +interface ItemContextMenuProps { id: string + onCopyUrl: (name: string, url: string) => void } -const ItemContextMenu: FC = ({ id = '' }) => { +const ItemContextMenu = ({ id = '', onCopyUrl = noop }: ItemContextMenuProps) => { const storageExplorerStore = useStorageStore() const { + getFileUrl, downloadFile, selectedBucket, setSelectedItemsToDelete, setSelectedItemToRename, setSelectedItemsToMove, setSelectedFileCustomExpiry, - copyFileURLToClipboard, } = storageExplorerStore const isPublic = selectedBucket.public const canUpdateFiles = checkPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*') @@ -32,7 +33,7 @@ const ItemContextMenu: FC = ({ id = '' }) => { switch (event) { case 'copy': if (expiresIn !== undefined && expiresIn < 0) return setSelectedFileCustomExpiry(item) - else return await copyFileURLToClipboard(item, expiresIn) + else return onCopyUrl(item.name, await getFileUrl(item, expiresIn)) case 'rename': return setSelectedItemToRename(item) case 'move': diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx index 453825ddeec..cb4deb1bea3 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx @@ -94,13 +94,17 @@ const PreviewFile = ({ mimeType, previewUrl }: { mimeType: string; previewUrl: s ) } -const PreviewPane = () => { +export interface PreviewPaneProps { + onCopyUrl: (name: string, url: string) => void +} + +const PreviewPane = ({ onCopyUrl }: PreviewPaneProps) => { const storageExplorerStore = useStorageStore() const { + getFileUrl, downloadFile, selectedBucket, selectedFilePreview: file, - copyFileURLToClipboard, closeFilePreview, setSelectedItemsToDelete, setSelectedFileCustomExpiry, @@ -194,7 +198,7 @@ const PreviewPane = () => {
) }) diff --git a/studio/localStores/storageExplorer/StorageExplorerStore.js b/studio/localStores/storageExplorer/StorageExplorerStore.js index 2c30d660945..f0148c926f2 100644 --- a/studio/localStores/storageExplorer/StorageExplorerStore.js +++ b/studio/localStores/storageExplorer/StorageExplorerStore.js @@ -394,44 +394,26 @@ class StorageExplorerStore { this.selectedFilePreview = {} } - copyFileURLToClipboard = async (file, expiresIn = 0) => { + getFileUrl = async (file, expiresIn = 0) => { const filePreview = find(this.filePreviewCache, { id: file.id }) if (filePreview !== undefined && expiresIn === 0) { - // Already generated signed URL - copyToClipboard(filePreview.url, () => { - this.ui.setNotification({ - category: 'success', - message: `Copied URL for ${file.name} to clipboard.`, - duration: 4000, - }) - }) + return filePreview.url } else { - // Need to generate signed URL, and might as well save it to cache as well const signedUrl = await this.fetchFilePreview(file.name, expiresIn) const formattedUrl = new URL(signedUrl) formattedUrl.searchParams.set('t', new Date().toISOString()) + const fileUrl = formattedUrl.toString() - try { - copyToClipboard(formattedUrl.toString(), () => { - this.ui.setNotification({ - category: 'success', - message: `Copied URL for ${file.name} to clipboard.`, - duration: 4000, - }) - }) - const fileCache = { - id: file.id, - url: formattedUrl.toString(), - expiresIn: DEFAULT_EXPIRY, - fetchedAt: Date.now(), - } - this.addFileToPreviewCache(fileCache) - } catch (error) { - this.ui.setNotification({ - category: 'error', - message: `Failed to copy URL: ${error}`, - }) + // Also save it to cache + const fileCache = { + id: file.id, + url: fileUrl, + expiresIn: DEFAULT_EXPIRY, + fetchedAt: Date.now(), } + this.addFileToPreviewCache(fileCache) + + return fileUrl } } From 2ee9e411fbf71c01563170299ba234b6905a3423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Jun 2023 11:33:55 +0100 Subject: [PATCH 045/108] Add image and change og --- apps/www/pages/partners/integrations/index.tsx | 2 +- apps/www/public/images/og/integrations.png | Bin 0 -> 189336 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 apps/www/public/images/og/integrations.png diff --git a/apps/www/pages/partners/integrations/index.tsx b/apps/www/pages/partners/integrations/index.tsx index e18c63166b1..d8587f2d29d 100644 --- a/apps/www/pages/partners/integrations/index.tsx +++ b/apps/www/pages/partners/integrations/index.tsx @@ -101,7 +101,7 @@ function IntegrationPartnersPage(props: Props) { url: `https://supabase.com/partners`, images: [ { - url: `https://supabase.com${router.basePath}/images/product/database/database-og.jpg`, // TODO + url: `https://supabase.com${router.basePath}/images/og/integrations.png` , // TODO }, ], }} diff --git a/apps/www/public/images/og/integrations.png b/apps/www/public/images/og/integrations.png new file mode 100644 index 0000000000000000000000000000000000000000..9f065beafb52ce53963eacc6d10fb0c19be14c2e GIT binary patch literal 189336 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%0A#wckof^lF_G;u1`67 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 bejlO_a?Z{4IR__b9oQ=*!oiXY_lo%&F!iVg literal 0 HcmV?d00001 From 690112c7bfba65e32a4191aa75256c68ae31658b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Jun 2023 11:39:19 +0100 Subject: [PATCH 046/108] SEO Updates. --- ...finite-scroll-with-nextjs-framer-motion.mdx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx b/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx index a8c453cf3db..342621c48bc 100644 --- a/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx +++ b/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx @@ -13,13 +13,23 @@ date: '2023-04-04' toc_depth: 6 --- -Every Launch Week, we strive to approach things differently as part of our Kaizen mindset. For [Launch Week 7](https://www.supabase.com/launch-week), we decided to add some twists to our popular ticket system. While we made several changes, this post will focus on one specific modification (we'll share more about the rest this week!). +Imagine you generated a bunch of tickets for the Supabase [Launch Week](https://www.supabase.com/launch-week) and you wanted to display them all on [one page](https://supabase.com/launch-week/tickets). It might take a while to load them all in one go, so a great solution to this would be to lazy load smaller subsets while the user scrolls, with a technique called infinite scrolling. -Our goal was to feature all the amazing people who generated tickets on a [single page](https://supabase.com/launch-week/tickets). However, displaying them all at once could take a toll on the loading time and negatively affect the user experience. That's where lazy loading smaller subsets via infinite scrolling came in as the solution. +An infinite scroll allows you to scroll through your content endlessly while only loading the data you need as you need it. This improves performance while also creating a smooth and delightful user experience. -With infinite scrolling, users can effortlessly scroll through content while only loading the necessary data as they go. This not only enhances performance but also creates a seamless and enjoyable user experience. +In this post, we'll go through the steps of how to create an infinite scroll using NextJs, Supabase, and Framer Motion. -In this post, we'll detail our process of implementing infinite scrolling using Next.js, Framer Motion, and Supabase. +## What is infinite scroll? + +Infinite scroll is a web design technique that automatically loads and displays new content as a user scrolls down a web page. This creates an endless scrolling experience that eliminates the need for pagination and provides a seamless and uninterrupted browsing experience. + +Infinite scroll can enhance user experience and engagement by providing a fluid and intuitive browsing experience. It also allows for faster content delivery. By dynamically loading and displaying content as needed, infinite scroll can reduce the time and effort required to load pages and deliver content, improving overall performance and user satisfaction. + +Next.js is a great choice for implementing infinite scroll in a web application. It provides server-side rendering and automatic code splitting, which can improve performance and optimize the loading of content as the user scrolls down the page. It also includes built-in support for dynamic imports and lazy loading, which can be useful for fetching and displaying content as needed. + +Plus, incremental static regeneration (ISR) and pre-fetching of data can further improve performance by enabling the server to generate and cache pages in advance, and to pre-fetch data as the user scrolls down the page. + +Now, onto the code! ## Step 0. Load the first batch From 3b2179a1a760764fdd6503ab1cf086870bde1e88 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Wed, 21 Jun 2023 12:46:35 +0200 Subject: [PATCH 047/108] format --- apps/www/pages/partners/integrations/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/pages/partners/integrations/index.tsx b/apps/www/pages/partners/integrations/index.tsx index d8587f2d29d..50236ca7598 100644 --- a/apps/www/pages/partners/integrations/index.tsx +++ b/apps/www/pages/partners/integrations/index.tsx @@ -101,7 +101,7 @@ function IntegrationPartnersPage(props: Props) { url: `https://supabase.com/partners`, images: [ { - url: `https://supabase.com${router.basePath}/images/og/integrations.png` , // TODO + url: `https://supabase.com${router.basePath}/images/og/integrations.png`, // TODO }, ], }} From 92e67688f3351245e72939f77e9569d3d9bfff5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Wed, 21 Jun 2023 14:34:11 +0200 Subject: [PATCH 048/108] feat: free plan for org level billing --- .../src/lib/Layout/FormLayout/FormLayout.tsx | 4 +- .../Organization/NewOrg/NewOrgForm.tsx | 78 +++++++------- studio/components/ui/PasswordStrengthBar.tsx | 6 +- studio/data/subscriptions/keys.ts | 2 + .../subscriptions/org-subscription-query.ts | 90 ++++++++++++++++ studio/lib/constants/infrastructure.ts | 1 + studio/pages/new/[slug].tsx | 100 ++++++++++-------- 7 files changed, 192 insertions(+), 89 deletions(-) create mode 100644 studio/data/subscriptions/org-subscription-query.ts diff --git a/packages/ui/src/lib/Layout/FormLayout/FormLayout.tsx b/packages/ui/src/lib/Layout/FormLayout/FormLayout.tsx index 9715d795b1d..7a8fe25e9ae 100644 --- a/packages/ui/src/lib/Layout/FormLayout/FormLayout.tsx +++ b/packages/ui/src/lib/Layout/FormLayout/FormLayout.tsx @@ -125,12 +125,12 @@ export function FormLayout({ ) const renderDescription = descriptionText && ( -

{descriptionText} -

+ ) return ( diff --git a/studio/components/interfaces/Organization/NewOrg/NewOrgForm.tsx b/studio/components/interfaces/Organization/NewOrg/NewOrgForm.tsx index 12bcbc13e2a..021c17371be 100644 --- a/studio/components/interfaces/Organization/NewOrg/NewOrgForm.tsx +++ b/studio/components/interfaces/Organization/NewOrg/NewOrgForm.tsx @@ -49,7 +49,7 @@ const NewOrgForm = ({ onPaymentMethodReset }: NewOrgFormProps) => { const [newOrgLoading, setNewOrgLoading] = useState(false) const [paymentMethod, setPaymentMethod] = useState() - const [dbPricingTierKey, setDbPricingTierKey] = useState('PRO') + const [dbPricingTierKey, setDbPricingTierKey] = useState('FREE') function validateOrgName(name: any) { const value = name ? name.trim() : '' @@ -72,7 +72,7 @@ const NewOrgForm = ({ onPaymentMethodReset }: NewOrgFormProps) => { setDbPricingTierKey(value) } - async function createOrg(paymentMethodId: string) { + async function createOrg(paymentMethodId?: string) { const response = await post( `${API_URL}/organizations`, { @@ -118,7 +118,9 @@ const NewOrgForm = ({ onPaymentMethodReset }: NewOrgFormProps) => { } setNewOrgLoading(true) - if (!paymentMethod) { + if (dbPricingTierKey === 'FREE') { + await createOrg() + } else if (!paymentMethod) { const { error, setupIntent } = await stripe.confirmSetup({ elements, redirect: 'if_required', @@ -290,41 +292,43 @@ const NewOrgForm = ({ onPaymentMethodReset }: NewOrgFormProps) => { - - {paymentMethod?.card !== undefined ? ( -
-
- Card - -

- Expires: {paymentMethod.card.exp_month}/{paymentMethod.card.exp_year} -

+ {dbPricingTierKey !== 'FREE' && ( + + {paymentMethod?.card !== undefined ? ( +
+
+ Card + +

+ Expires: {paymentMethod.card.exp_month}/{paymentMethod.card.exp_year} +

+
+
+
-
-
-
- ) : ( - - )} - + ) : ( + + )} + + )} diff --git a/studio/components/ui/PasswordStrengthBar.tsx b/studio/components/ui/PasswordStrengthBar.tsx index 2c472fad53a..4bd35b7b473 100644 --- a/studio/components/ui/PasswordStrengthBar.tsx +++ b/studio/components/ui/PasswordStrengthBar.tsx @@ -1,8 +1,4 @@ -import { - DEFAULT_MINIMUM_PASSWORD_STRENGTH, - PASSWORD_STRENGTH_COLOR, - PASSWORD_STRENGTH_PERCENTAGE, -} from 'lib/constants' +import { PASSWORD_STRENGTH_COLOR, PASSWORD_STRENGTH_PERCENTAGE } from 'lib/constants' interface Props { passwordStrengthScore: number diff --git a/studio/data/subscriptions/keys.ts b/studio/data/subscriptions/keys.ts index c8f1fddaead..16b4998d66d 100644 --- a/studio/data/subscriptions/keys.ts +++ b/studio/data/subscriptions/keys.ts @@ -3,6 +3,8 @@ export const subscriptionKeys = { subscriptionV2: (projectRef: string | undefined) => ['projects', projectRef, 'subscriptionV2'] as const, + orgSubscription: (orgSlug: string | undefined) => + ['organizations', orgSlug, 'subscription'] as const, addons: (projectRef: string | undefined) => ['projects', projectRef, 'addons'] as const, plans: (projectRef: string | undefined) => ['projects', projectRef, 'plans'] as const, preview: (projectRef: string | undefined) => diff --git a/studio/data/subscriptions/org-subscription-query.ts b/studio/data/subscriptions/org-subscription-query.ts new file mode 100644 index 00000000000..3429664c4ae --- /dev/null +++ b/studio/data/subscriptions/org-subscription-query.ts @@ -0,0 +1,90 @@ +import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' +import { get } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' +import { useCallback } from 'react' +import { subscriptionKeys } from './keys' + +export type OrgSubscriptionVariables = { + orgSlug?: string +} + +export type PlanId = 'free' | 'pro' | 'team' | 'enterprise' + +export type OrgSubscriptionResponse = { + billing_cycle_anchor: number + current_period_start: number + current_period_end: number + next_invoice_at: number + usage_billing_enabled: boolean + plan: { + id: PlanId + name: string + price: number + } + addons: { + name: string + supabase_prod_id: string + price: number + }[] + usage_fees: { + metric: string + pricingStrategy: 'UNIT' | 'PACKAGE' + pricingOptions: { + perUnitPrice?: number + packagePrice?: number + packageSize?: number + freeUnits: number + } + }[] + payment_method_id?: string + payment_method_type: 'invoice' | 'card' | 'none' + payment_method_card_details?: { + last_4_digits: string + brand: string + expiry_month: number + expiry_year: number + } +} + +export async function getOrgSubscription( + { orgSlug }: OrgSubscriptionVariables, + signal?: AbortSignal +) { + if (!orgSlug) throw new Error('orgSlug is required') + + const response = await get(`${API_URL}/organizations/${orgSlug}/billing/subscription`, { signal }) + if (response.error) throw response.error + + return response as OrgSubscriptionResponse +} + +export type OrgSubscriptionData = Awaited> +export type OrgSubscriptionError = unknown + +export const useOrgSubscriptionQuery = ( + { orgSlug }: OrgSubscriptionVariables, + { + enabled = true, + ...options + }: UseQueryOptions = {} +) => + useQuery( + subscriptionKeys.orgSubscription(orgSlug), + ({ signal }) => getOrgSubscription({ orgSlug }, signal), + { + enabled: enabled && typeof orgSlug !== 'undefined', + ...options, + } + ) + +export const useOrgSubscriptionPrefetch = ({ orgSlug }: OrgSubscriptionVariables) => { + const client = useQueryClient() + + return useCallback(() => { + if (orgSlug) { + client.prefetchQuery(subscriptionKeys.orgSubscription(orgSlug), ({ signal }) => + getOrgSubscription({ orgSlug }, signal) + ) + } + }, [orgSlug]) +} diff --git a/studio/lib/constants/infrastructure.ts b/studio/lib/constants/infrastructure.ts index eed060ab177..e283bf68e19 100644 --- a/studio/lib/constants/infrastructure.ts +++ b/studio/lib/constants/infrastructure.ts @@ -38,6 +38,7 @@ export const PRICING_TIER_LABELS = { } export const PRICING_TIER_LABELS_ORG = { + FREE: 'Free - $0/month', PRO: 'Pro - $25/month', TEAM: 'Team - $599/month', } diff --git a/studio/pages/new/[slug].tsx b/studio/pages/new/[slug].tsx index fe03cf0ddc3..4b61f61c060 100644 --- a/studio/pages/new/[slug].tsx +++ b/studio/pages/new/[slug].tsx @@ -37,6 +37,7 @@ import { } from 'components/interfaces/Organization/NewProject' import SpendCapModal from 'components/interfaces/Billing/SpendCapModal' import InformationBox from 'components/ui/InformationBox' +import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' const Wizard: NextPageWithLayout = () => { const router = useRouter() @@ -70,6 +71,11 @@ const Wizard: NextPageWithLayout = () => { const currentOrg = organizations.find((o: any) => o.slug === slug) const billedViaOrg = Boolean(currentOrg?.subscription_id) + const { data: orgSubscription } = useOrgSubscriptionQuery( + { orgSlug: slug }, + { enabled: billedViaOrg } + ) + const [availableRegions, setAvailableRegions] = useState( getAvailableRegions(PROVIDERS[cloudProvider].id) ) @@ -83,8 +89,10 @@ const Wizard: NextPageWithLayout = () => { const showNonProdFields = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' - const canCreateProject = - isAdmin && (!isSelectFreeTier || (isSelectFreeTier && !hasMembersExceedingFreeTierLimit)) + const freePlanWithExceedingLimits = + (isSelectFreeTier || orgSubscription?.plan?.id === 'free') && hasMembersExceedingFreeTierLimit + + const canCreateProject = isAdmin && !freePlanWithExceedingLimits const canSubmit = projectName !== '' && @@ -465,55 +473,57 @@ const Wizard: NextPageWithLayout = () => { )} - {isAdmin && !billedViaOrg && ( + {isAdmin && ( - - Select a plan that suits your needs.  -
- More details - - {!isSelectFreeTier && !isEmptyPaymentMethod && ( - + Select a plan that suits your needs.  + -

- By creating a new Pro Project, there will be an immediate charge of $25 - once the project has been created. -

-
- )} - - } - > - {Object.entries(PRICING_TIER_LABELS).map(([k, v]) => { - const label = `${v}${k === 'PRO' ? ' - $25/month' : ' - $0/month'}` - return ( - - {label} - - ) - })} - + More details + + {!isSelectFreeTier && !isEmptyPaymentMethod && ( + +

+ By creating a new Pro Project, there will be an immediate charge of + $25 once the project has been created. +

+
+ )} + + } + > + {Object.entries(PRICING_TIER_LABELS).map(([k, v]) => { + const label = `${v}${k === 'PRO' ? ' - $25/month' : ' - $0/month'}` + return ( + + {label} + + ) + })} + + )} - {isSelectFreeTier && hasMembersExceedingFreeTierLimit && ( + {freePlanWithExceedingLimits && ( )} - {!isSelectFreeTier && isEmptyPaymentMethod && ( + {!billedViaOrg && !isSelectFreeTier && isEmptyPaymentMethod && ( )} From 0815397dff87e62324b18f9778a55a74dd7dfb74 Mon Sep 17 00:00:00 2001 From: ehmicky Date: Tue, 20 Jun 2023 23:00:51 +0100 Subject: [PATCH 049/108] chore: add new contributor to `humans.txt` --- apps/docs/public/humans.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index 50aea950567..d4668694ba3 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -42,6 +42,7 @@ Margarita Sandomirskaia Marijana Šimag Mark Burggraf Michel Pelletier +Mickael Hebert Monica El Khoury Nikita Kotlyarov Oli R From 7bae34e1760c79ecc8420d0e1eacf1ee4f07470c Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Jun 2023 14:38:40 -0230 Subject: [PATCH 050/108] Fix copy to clipboard --- studio/lib/helpers.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/studio/lib/helpers.ts b/studio/lib/helpers.ts index cc98c0d2b48..65c39154eda 100644 --- a/studio/lib/helpers.ts +++ b/studio/lib/helpers.ts @@ -160,22 +160,19 @@ export const snakeToCamel = (str: string) => * Safari doesn't support write text into clipboard async, so if you need to load * text content async before coping, please use Promise for the 1st arg. */ -export const copyToClipboard = async ( - str: string, - callback: () => void = () => {} -): Promise => { +export const copyToClipboard = async (str: string | Promise, callback = () => {}) => { const focused = window.document.hasFocus() if (focused) { if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { - try { - await navigator.clipboard.writeText(str) - callback() - } catch (error) { - console.warn('Unable to copy to clipboard') - } - } else { - console.warn('Clipboard writeText method not supported') + const text = await Promise.resolve(str) + Promise.resolve(window.navigator?.clipboard?.writeText(text)).then(callback) + + return } + + Promise.resolve(str) + .then((text) => window.navigator?.clipboard?.writeText(text)) + .then(callback) } else { console.warn('Unable to copy to clipboard') } From 1038b4143aa58ae1d6daaf6d48249b49d98873b7 Mon Sep 17 00:00:00 2001 From: Greg Richardson Date: Wed, 21 Jun 2023 11:33:30 -0600 Subject: [PATCH 051/108] feat: shared tab state between matching tab groups --- apps/docs/pages/_app.tsx | 11 +- packages/ui/src/components/Tabs/Tabs.tsx | 75 +++++-------- .../ui/src/components/Tabs/TabsContext.tsx | 7 -- .../ui/src/components/Tabs/TabsProvider.tsx | 103 ++++++++++++++++++ packages/ui/src/components/Tabs/index.tsx | 1 + 5 files changed, 140 insertions(+), 57 deletions(-) delete mode 100644 packages/ui/src/components/Tabs/TabsContext.tsx create mode 100644 packages/ui/src/components/Tabs/TabsProvider.tsx diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx index 1ddc56fd1fa..aeb409b0910 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -1,5 +1,5 @@ -import '../../../packages/ui/build/css/themes/light.css' import '../../../packages/ui/build/css/themes/dark.css' +import '../../../packages/ui/build/css/themes/light.css' import 'config/code-hike.scss' import '../styles/main.scss?v=1.0.0' @@ -13,6 +13,7 @@ import { useRouter } from 'next/router' import { useCallback, useEffect, useState } from 'react' import { AppPropsWithLayout } from 'types' import { CommandMenuProvider } from 'ui' +import { TabsProvider } from 'ui/src/components/Tabs' import Favicons from '~/components/Favicons' import SiteLayout from '~/layouts/SiteLayout' import { API_URL, IS_PLATFORM, LOCAL_SUPABASE } from '~/lib/constants' @@ -150,9 +151,11 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { - - - + + + + + diff --git a/packages/ui/src/components/Tabs/Tabs.tsx b/packages/ui/src/components/Tabs/Tabs.tsx index aba66a6dfea..67d389baecd 100644 --- a/packages/ui/src/components/Tabs/Tabs.tsx +++ b/packages/ui/src/components/Tabs/Tabs.tsx @@ -1,13 +1,8 @@ -import * as React from 'react' -import { TabsContext } from './TabsContext' - import * as TabsPrimitive from '@radix-ui/react-tabs' import { useRouter } from 'next/router' - -// @ts-ignore -// import TabsStyles from './Tabs.module.css' - +import * as React from 'react' import styleHandler from '../../lib/theme/styleHandler' +import { useTabGroup } from './TabsProvider' interface TabsProps { type?: 'pills' | 'underlined' | 'cards' | 'rounded-pills' @@ -45,12 +40,9 @@ const Tabs: React.FC & TabsSubComponents = ({ children, }) => { const [activeTab, setActiveTab] = React.useState( - defaultActiveId - ? defaultActiveId - : // if no defaultActiveId is set use the first panel - children && children[0].props - ? children[0].props.id - : '' + defaultActiveId ?? + // if no defaultActiveId is set use the first panel + children?.[0]?.props?.id ) const router = useRouter() @@ -58,40 +50,39 @@ const Tabs: React.FC & TabsSubComponents = ({ let __styles = styleHandler('tabs') - // activeId state can be overriden externally with `active` - // defaults to the first panelif we have one or url hash if not - const active = activeId ? activeId : activeTab ? activeTab : hash - - function onTabClick(id: string) { - const newTabSelected = id !== active - setActiveTab(id) - if (onClick) onClick(id) - if (onChange && newTabSelected) onChange(id) + if (children && !Array.isArray(children)) { + children = [children] } - // for styling the tabs for underline style - const underlined = type === 'underlined' - // for styling the tabs for cards style + const tabIds = children.map((tab) => tab.props.id) + + const { groupActiveId, setGroupActiveId } = useTabGroup(tabIds) + + const active = activeId ?? groupActiveId ?? activeTab ?? hash + + function onTabClick(id: string) { + setActiveTab(id) + setGroupActiveId?.(id) + + onClick?.(id) + if (id !== active) { + onChange?.(id) + } + } const listClasses = [__styles[type].list] if (scrollable) listClasses.push(__styles.scrollable) if (listClassNames) listClasses.push(listClassNames) - // if only 1 react child, it needs to be converted to a list/array - // this is so 1 tab can be displayed - if (children && !Array.isArray(children)) { - children = [children] - } - return ( - + {addOnBefore} {children.map((tab) => { - const activeMatch = active === tab.props.id + const isActive = active === tab.props.id const triggerClasses = [__styles[type].base, __styles.size[size]] - if (activeMatch) { + if (isActive) { triggerClasses.push(__styles[type].active) } else { triggerClasses.push(__styles[type].inactive) @@ -118,10 +109,9 @@ const Tabs: React.FC & TabsSubComponents = ({ ) })} - {/* */} {addOnAfter} - {children} + {children} ) } @@ -142,16 +132,9 @@ export const Panel: React.FC = ({ children, id, className }) => { let __styles = styleHandler('tabs') return ( - - {({ activeId }) => { - const active = activeId === id - return ( - - {children} - - ) - }} - + + {children} + ) } diff --git a/packages/ui/src/components/Tabs/TabsContext.tsx b/packages/ui/src/components/Tabs/TabsContext.tsx deleted file mode 100644 index b4a3471f16b..00000000000 --- a/packages/ui/src/components/Tabs/TabsContext.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { createContext } from 'react' - -// Make sure the shape of the default value passed to -// createContext matches the shape that the consumers expect! -export const TabsContext = createContext({ - activeId: '', -}) diff --git a/packages/ui/src/components/Tabs/TabsProvider.tsx b/packages/ui/src/components/Tabs/TabsProvider.tsx new file mode 100644 index 00000000000..feb6a74941b --- /dev/null +++ b/packages/ui/src/components/Tabs/TabsProvider.tsx @@ -0,0 +1,103 @@ +import { xor } from 'lodash' +import { + Dispatch, + PropsWithChildren, + SetStateAction, + createContext, + useCallback, + useContext, + useMemo, + useState, +} from 'react' + +export interface TabGroup { + tabIds: string[] + activeId: string +} + +interface TabsContextValue { + tabGroups: TabGroup[] + setTabGroups: Dispatch> +} + +const TabsContext = createContext({ + tabGroups: [], + setTabGroups: () => {}, +}) + +/** + * Tracks active Tab IDs across the site so that tabs + * with the same ID stay in sync (eg. JS vs TS tabs). + */ +const TabsProvider = ({ children }: PropsWithChildren<{}>) => { + const [tabGroups, setTabGroups] = useState([]) + + return {children} +} + +export interface UseTabGroupValue { + /** + * The active tab ID for the tab group. + * This value is shared with all matching tab + * groups within the context. + */ + groupActiveId?: string + + /** + * Set the active tab ID for the tab group. + * Value will be shared with all matching tab + * groups within the context. + */ + setGroupActiveId?(id: string): void +} + +/** + * Hook to retrieve and set the active tab ID for + * the current tab group. + * The value will be shared with all matching tab + * groups within the context. + * + * Silently fails if no `TabsProvider` is set. + */ +export const useTabGroup = (tabIds: string[]): UseTabGroupValue => { + const tabsContext = useContext(TabsContext) + + if (!tabsContext) { + return {} + } + + const { tabGroups, setTabGroups } = tabsContext + + const groupActiveId = useMemo( + () => tabGroups.find((group) => xor(group.tabIds, tabIds).length === 0)?.activeId, + [tabGroups] + ) + + const setGroupActiveId = useCallback((id: string) => { + setTabGroups((groups) => { + // Clone the array + const newGroups = groups.concat() + + const existingGroupIndex = groups.findIndex((group) => xor(group.tabIds, tabIds).length === 0) + + const tabGroup: TabGroup = { + tabIds, + activeId: id, + } + + // If this group already exists, replace it + // Otherwise add to the end + if (existingGroupIndex !== -1) { + newGroups.splice(existingGroupIndex, 1, tabGroup) + } else { + newGroups.push(tabGroup) + } + + return newGroups + }) + }, []) + + return { groupActiveId, setGroupActiveId } +} + +export default TabsProvider diff --git a/packages/ui/src/components/Tabs/index.tsx b/packages/ui/src/components/Tabs/index.tsx index 91e13405f93..14400819be6 100644 --- a/packages/ui/src/components/Tabs/index.tsx +++ b/packages/ui/src/components/Tabs/index.tsx @@ -1 +1,2 @@ export { default as Tabs } from './Tabs' +export { default as TabsProvider } from './TabsProvider' From 0c0afabb529714f20fb317e5aa000b91cf2c188c Mon Sep 17 00:00:00 2001 From: Kenley Jean Date: Wed, 21 Jun 2023 15:40:03 -0400 Subject: [PATCH 052/108] Fix typo in documentation: change "usert()" to "upsert()" command in "docs" add embeddings section --- apps/docs/pages/guides/ai/vecs-python-client.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/ai/vecs-python-client.mdx b/apps/docs/pages/guides/ai/vecs-python-client.mdx index 0be65c7c744..bdcecb77c43 100644 --- a/apps/docs/pages/guides/ai/vecs-python-client.mdx +++ b/apps/docs/pages/guides/ai/vecs-python-client.mdx @@ -42,7 +42,7 @@ docs = vx.create_collection(name="docs", dimension=3) ### Add embeddings -Now we can insert some embeddings into our "docs" collection using the `usert()` command: +Now we can insert some embeddings into our "docs" collection using the `upsert()` command: ```py import vecs From e211968f35be56dcb7e20ba57a829ebff104f1d3 Mon Sep 17 00:00:00 2001 From: Greg Richardson Date: Wed, 21 Jun 2023 14:31:56 -0600 Subject: [PATCH 053/108] fix: remove crawlers from canonical path --- apps/docs/components/reference/RefSectionHandler.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/docs/components/reference/RefSectionHandler.tsx b/apps/docs/components/reference/RefSectionHandler.tsx index 26b3f0350a4..c3f1ab7642f 100644 --- a/apps/docs/components/reference/RefSectionHandler.tsx +++ b/apps/docs/components/reference/RefSectionHandler.tsx @@ -60,6 +60,10 @@ const RefSectionHandler = (props: RefSectionHandlerProps) => { const pageTitle = getPageTitle() const section = props.sections.find((section) => section.slug === slug) const fullTitle = `${pageTitle}${section ? ` - ${section.title}` : ''}` + const path = router.asPath + .split('/') + .filter((segment) => segment !== 'crawlers') + .join('/') return ( <> @@ -72,7 +76,7 @@ const RefSectionHandler = (props: RefSectionHandlerProps) => { content={`https://supabase.com/docs/img/supabase-og-image.png`} /> - + {props.isOldVersion && } From 0b67f38f94e621cd1a613b829e92cd20baeafee2 Mon Sep 17 00:00:00 2001 From: Kenley Jean Date: Wed, 21 Jun 2023 17:14:07 -0400 Subject: [PATCH 054/108] Update face-similarity.mdx subtitle and quick description. --- apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx b/apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx index 9bc0be7c9c2..22d3ceedb85 100644 --- a/apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx +++ b/apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx @@ -3,11 +3,11 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'ai-vecs-python-client', title: 'Face similarity search', - subtitle: 'Identify the celebrities you looks most similar to using Supabase Vecs.', + subtitle: 'Identify the celebrities who look most similar to you using Supabase Vecs.', breadcrumb: 'AI Quickstarts', } -This guide will walk you through a ["Face Similarity Search"](https://github.com/supabase/supabase/blob/master/examples/ai/face_similarity.ipynb) example using Colab and Supabase Vecs. You'll identify the celebrities you (or any other person) looks most similar to. You will: +This guide will walk you through a ["Face Similarity Search"](https://github.com/supabase/supabase/blob/master/examples/ai/face_similarity.ipynb) example using Colab and Supabase Vecs. You will be able to identify the celebrities who look most similar to you (or any other person). You will: 1. Launch a Postgres database that uses pgvector to store embeddings 1. Launch a notebook that connects to your database From b024c964cdf01795c779ef49386ab87445e02db1 Mon Sep 17 00:00:00 2001 From: Greg Richardson Date: Wed, 21 Jun 2023 15:15:37 -0600 Subject: [PATCH 055/108] fix: prettier --- .../pages/guides/getting-started/tutorials/with-nextjs.mdx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx index 2b05db833b5..e2e84524416 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx @@ -454,11 +454,7 @@ export default function AccountForm({ session }) { getProfile() }, [user, getProfile]) - async function updateProfile({ - username, - website, - avatar_url, - }) { + async function updateProfile({ username, website, avatar_url }) { try { setLoading(true) From 9491e442c3d506d521d49a2123b266321caf2e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Wed, 21 Jun 2023 23:25:25 +0200 Subject: [PATCH 056/108] fix: compute instance specs on subscription page --- .../BillingV2/Subscription/AddOns/AddOns.tsx | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Subscription/AddOns/AddOns.tsx b/studio/components/interfaces/BillingV2/Subscription/AddOns/AddOns.tsx index 037943496ac..ca737136495 100644 --- a/studio/components/interfaces/BillingV2/Subscription/AddOns/AddOns.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/AddOns/AddOns.tsx @@ -38,16 +38,8 @@ const AddOns = ({}: AddOnsProps) => { const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef }) const selectedAddons = addons?.selected_addons ?? [] - const availableAddons = addons?.available_addons ?? [] const { computeInstance, pitr, customDomain } = getAddons(selectedAddons) - const computeInstanceSpecs = - computeInstance !== undefined - ? availableAddons - .find((addon) => addon.type === 'compute_instance') - ?.variants.find((variant) => variant.identifier === computeInstance.variant.identifier) - ?.meta - : undefined return ( <> @@ -157,8 +149,9 @@ const AddOns = ({}: AddOnsProps) => { >

Your workload is currently running at the baseline disk IO bandwidth at{' '} - {computeInstanceSpecs?.baseline_disk_io_mbs?.toLocaleString() ?? 87} Mbps - and may suffer degradation in performance. + {computeInstance?.variant?.meta?.baseline_disk_io_mbs?.toLocaleString() ?? + 87}{' '} + Mbps and may suffer degradation in performance.

Consider upgrading to a larger compute instance for a higher baseline @@ -175,8 +168,9 @@ const AddOns = ({}: AddOnsProps) => {

If the disk IO budget drops to zero, your workload will run at the baseline disk IO bandwidth at{' '} - {computeInstanceSpecs?.baseline_disk_io_mbs?.toLocaleString() ?? 87} Mbps - and may suffer degradation in performance. + {computeInstance?.variant?.meta?.baseline_disk_io_mbs?.toLocaleString() ?? + 87}{' '} + Mbps and may suffer degradation in performance.

Consider upgrading to a larger compute instance for a higher baseline @@ -200,7 +194,7 @@ const AddOns = ({}: AddOnsProps) => {

-

{computeInstanceSpecs?.memory_gb ?? 1} GB

+

{computeInstance?.variant?.meta?.memory_gb ?? 1} GB

@@ -218,17 +212,21 @@ const AddOns = ({}: AddOnsProps) => {

- {computeInstanceSpecs?.cpu_cores ?? 2}-core ARM{' '} - {computeInstanceSpecs?.cpu_dedicated ? '(Dedicated)' : '(Shared)'} + {computeInstance?.variant?.meta?.cpu_cores ?? 2}-core ARM{' '} + {computeInstance?.variant?.meta?.cpu_dedicated ? '(Dedicated)' : '(Shared)'}

No. of direct connections

-

{computeInstanceSpecs?.connections_direct ?? 60}

+

+ {computeInstance?.variant?.meta?.connections_direct ?? 60} +

No. of pooler connections

-

{computeInstanceSpecs?.connections_pooler ?? 200}

+

+ {computeInstance?.variant?.meta?.connections_pooler ?? 200} +

@@ -246,7 +244,8 @@ const AddOns = ({}: AddOnsProps) => {

- {computeInstanceSpecs?.max_disk_io_mbs?.toLocaleString() ?? '2,085'} Mbps + {computeInstance?.variant?.meta?.max_disk_io_mbs?.toLocaleString() ?? '2,085'}{' '} + Mbps

@@ -265,7 +264,8 @@ const AddOns = ({}: AddOnsProps) => {

- {computeInstanceSpecs?.baseline_disk_io_mbs?.toLocaleString() ?? 87} Mbps + {computeInstance?.variant?.meta?.baseline_disk_io_mbs?.toLocaleString() ?? 87}{' '} + Mbps

@@ -293,9 +293,7 @@ const AddOns = ({}: AddOnsProps) => {

Point in time recovery

{pitr !== undefined - ? `Point in time recovery of ${ - pitr.variant.identifier.split('_')[1] - } days is enabled` + ? `Point in time recovery of ${pitr.variant.meta?.backup_duration_days} days is enabled` : 'Point in time recovery is not enabled'}

From 5ba658e2b288a1a08c4097799fe6378899de6a0f Mon Sep 17 00:00:00 2001 From: Greg Richardson Date: Wed, 21 Jun 2023 15:50:15 -0600 Subject: [PATCH 057/108] docs: remove extra space --- apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx b/apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx index 22d3ceedb85..6996f4c1046 100644 --- a/apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx +++ b/apps/docs/pages/guides/ai/quickstarts/face-similarity.mdx @@ -3,7 +3,7 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'ai-vecs-python-client', title: 'Face similarity search', - subtitle: 'Identify the celebrities who look most similar to you using Supabase Vecs.', + subtitle: 'Identify the celebrities who look most similar to you using Supabase Vecs.', breadcrumb: 'AI Quickstarts', } From eb4fe2e2791439eaa84a6cd4871c717db187cf9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Wed, 21 Jun 2023 23:53:23 +0200 Subject: [PATCH 058/108] fix: compute instance specs on usage page --- .../AddOns/ComputeInstanceSidePanel.tsx | 4 +- .../BillingV2/Usage/Infrastructure.tsx | 50 ++++++++------ .../BillingV2/Usage/Usage.constants.tsx | 66 ------------------- 3 files changed, 33 insertions(+), 87 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Subscription/AddOns/ComputeInstanceSidePanel.tsx b/studio/components/interfaces/BillingV2/Subscription/AddOns/ComputeInstanceSidePanel.tsx index 43d224f320d..35a3241b55c 100644 --- a/studio/components/interfaces/BillingV2/Subscription/AddOns/ComputeInstanceSidePanel.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/AddOns/ComputeInstanceSidePanel.tsx @@ -98,7 +98,9 @@ const ComputeInstanceSidePanel = () => { ui.setNotification({ duration: 8000, category: 'success', - message: `Successfully updated compute instance to ${selectedCompute?.name}. Your project is currently being restarted to update its instance`, + message: `Successfully updated compute instance to ${ + selectedCompute?.name || 'Micro' + }. Your project is currently being restarted to update its instance`, }) app.onProjectStatusUpdated(projectId, PROJECT_STATUS.RESTORING) onClose() diff --git a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx index 90298f7e9fe..1681ee1cdce 100644 --- a/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Infrastructure.tsx @@ -6,11 +6,13 @@ import Link from 'next/link' import { Alert, Button, IconBarChart2 } from 'ui' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' -import { COMPUTE_INSTANCE_SPECS, USAGE_CATEGORIES } from './Usage.constants' +import { USAGE_CATEGORIES } from './Usage.constants' import { getUpgradeUrl } from './Usage.utils' import UsageBarChart from './UsageBarChart' import Panel from 'components/ui/Panel' import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' +import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' +import { getAddons } from '../Subscription/Subscription.utils' export interface InfrastructureProps { projectRef: string @@ -31,11 +33,18 @@ const Infrastructure = ({ const upgradeUrl = getUpgradeUrl(projectRef, subscription) const isFreeTier = subscription?.plan?.id === 'free' - const currentComputeInstance = subscription?.addons.find((addon) => - addon.supabase_prod_id.includes('_instance_') - ) - const currentComputeInstanceSpecs = - COMPUTE_INSTANCE_SPECS[currentComputeInstance?.supabase_prod_id ?? 'addon_instance_micro'] + + const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef }) + const selectedAddons = addons?.selected_addons ?? [] + + const { computeInstance } = getAddons(selectedAddons) + const currentComputeInstanceSpecs = computeInstance?.variant?.meta ?? { + baseline_disk_io_mbs: 87, + max_disk_io_mbs: 2085, + cpu_cores: 2, + cpu_dedicated: true, + memory_gb: 1, + } // Switch to hourly interval, if the timeframe is <48 hours let interval: '1d' | '1h' = '1d' @@ -144,18 +153,18 @@ const Infrastructure = ({

Disk IO Bandwidth

- {currentComputeInstanceSpecs.maxBandwidth === - currentComputeInstanceSpecs.baseBandwidth ? ( + {currentComputeInstanceSpecs.baseline_disk_io_mbs === + currentComputeInstanceSpecs.max_disk_io_mbs ? (

- Your current compute can has a baseline and maximum disk throughput of - {currentComputeInstanceSpecs.maxBandwidth.toLocaleString()} Mbps. + Your current compute can has a baseline and maximum disk throughput of{' '} + {currentComputeInstanceSpecs.max_disk_io_mbs?.toLocaleString()} Mbps.

) : (

Your current compute can burst up to{' '} - {currentComputeInstanceSpecs.maxBandwidth.toLocaleString()} Mbps for 30 + {currentComputeInstanceSpecs.max_disk_io_mbs?.toLocaleString()} Mbps for 30 minutes a day and reverts to the baseline performance of{' '} - {currentComputeInstanceSpecs.baseBandwidth.toLocaleString()} Mbps. + {currentComputeInstanceSpecs.baseline_disk_io_mbs?.toLocaleString()} Mbps.

)}
@@ -163,22 +172,22 @@ const Infrastructure = ({

Overview

Current compute instance

-

{currentComputeInstance?.name ?? 'Micro'}

+

{computeInstance?.variant?.name ?? 'Micro'}

Maximum IO Bandwidth (burst limit)

- {currentComputeInstanceSpecs.maxBandwidth.toLocaleString()} Mbps + {currentComputeInstanceSpecs.max_disk_io_mbs?.toLocaleString()} Mbps

Baseline IO Bandwidth

- {currentComputeInstanceSpecs.baseBandwidth.toLocaleString()} Mbps + {currentComputeInstanceSpecs.baseline_disk_io_mbs?.toLocaleString()} Mbps

- {currentComputeInstanceSpecs.maxBandwidth !== - currentComputeInstanceSpecs.baseBandwidth && ( + {currentComputeInstanceSpecs.max_disk_io_mbs !== + currentComputeInstanceSpecs?.baseline_disk_io_mbs && (

Daily burst time limit

30 mins

@@ -205,9 +214,10 @@ const Infrastructure = ({ {attribute.key === 'ram_usage' && (

- Your compute instance has {currentComputeInstanceSpecs.memoryGb} GB of memory. + Your compute instance has {currentComputeInstanceSpecs.memory_gb} GB of + memory.

- {currentComputeInstanceSpecs.memoryGb === 1 && ( + {currentComputeInstanceSpecs.memory_gb === 1 && (

As your project is running on the smallest compute instance, it is not unusual for your project to have a base memory usage of ~50%. @@ -218,7 +228,7 @@ const Infrastructure = ({ {attribute.key === 'cpu_usage' && (

- Your compute instance has {currentComputeInstanceSpecs.cpuCores} CPU cores. + Your compute instance has {currentComputeInstanceSpecs.cpu_cores} CPU cores.

)} diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.constants.tsx b/studio/components/interfaces/BillingV2/Usage/Usage.constants.tsx index 35fad4af14b..c4d2b2c1386 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.constants.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Usage.constants.tsx @@ -362,69 +362,3 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [ ], }, ] - -// [Joshen] Ideally live in common package for www as well -export const COMPUTE_INSTANCE_SPECS: { - [key: string]: { maxBandwidth: number; baseBandwidth: number; memoryGb: number; cpuCores: number } -} = { - addon_instance_micro: { - memoryGb: 1, - cpuCores: 2, - maxBandwidth: 2085, - baseBandwidth: 87, - }, - addon_instance_small: { - memoryGb: 2, - cpuCores: 2, - maxBandwidth: 2085, - baseBandwidth: 174, - }, - addon_instance_medium: { - memoryGb: 4, - cpuCores: 2, - maxBandwidth: 2085, - baseBandwidth: 347, - }, - addon_instance_large: { - memoryGb: 8, - cpuCores: 2, - maxBandwidth: 4750, - baseBandwidth: 630, - }, - addon_instance_xlarge: { - memoryGb: 16, - cpuCores: 4, - maxBandwidth: 4750, - baseBandwidth: 1188, - }, - addon_instance_xxlarge: { - memoryGb: 32, - cpuCores: 8, - maxBandwidth: 4750, - baseBandwidth: 2375, - }, - addon_instance_4xlarge: { - memoryGb: 64, - cpuCores: 16, - maxBandwidth: 4750, - baseBandwidth: 4750, - }, - addon_instance_8xlarge: { - memoryGb: 128, - cpuCores: 32, - maxBandwidth: 9500, - baseBandwidth: 9500, - }, - addon_instance_12xlarge: { - memoryGb: 192, - cpuCores: 48, - maxBandwidth: 14250, - baseBandwidth: 14250, - }, - addon_instance_16xlarge: { - memoryGb: 256, - cpuCores: 64, - maxBandwidth: 19000, - baseBandwidth: 19000, - }, -} From a14f507908a0822cc141703b2468f8766266e36e Mon Sep 17 00:00:00 2001 From: Greg Richardson Date: Wed, 21 Jun 2023 19:59:56 -0600 Subject: [PATCH 059/108] fix(blog): broken links in pgvector post --- .../2023-02-03-openai-embeddings-postgres-vector.mdx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 18c1b7a7473..d9899ee5152 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 @@ -12,11 +12,17 @@ date: '2023-02-06' toc_depth: 3 --- -A new PostgreSQL extension is now available in Supabase: [`pgvector`](https://supabase.com/docs/guides/getting-started/openai/vector-search), an open-source vector similarity search. +A new PostgreSQL extension is now available in Supabase: [`pgvector`](https://github.com/pgvector/pgvector), an open-source vector similarity search. The exponential progress of AI functionality over the past year has inspired many new real world applications. One specific challenge has been the ability to store and query _embeddings_ at scale. In this post we'll explain what embeddings are, why we might want to use them, and how we can store and query them in PostgreSQL using `pgvector`. +
+ +🆕 Supabase has now released an open source toolkit for developing AI applications using Postgres and pgvector. Learn more in the [AI & Vectors docs](https://supabase.com/docs/guides/ai). + +
+ ## What are embeddings? Embeddings capture the “relatedness” of text, images, video, or other types of information. This relatedness is most commonly used for: @@ -370,7 +376,7 @@ The OpenAI API supports [completion streaming](https://platform.openai.com/docs/ Storing embeddings in Postgres opens a world of possibilities. You can combine your search function with telemetry functions, add an user-provided feedback (thumbs up/down), and make your search feel more integrated with your products. -The [pgvector extension](https://supabase.com/docs/guides/getting-started/openai/vector-search) is available on all new Supabase projects today. If you want to try it out, launch a new Postgres database today: [database.new](https://database.new) +The [pgvector extension](https://supabase.com/docs/guides/ai/vector-columns) is available on all new Supabase projects today. If you want to try it out, launch a new Postgres database today: [database.new](https://database.new) ## More pgvector and ChatGPT resources From 658691eb6399025bf79dada0cad9227edc13a7e1 Mon Sep 17 00:00:00 2001 From: Kenley Jean Date: Wed, 21 Jun 2023 22:02:58 -0400 Subject: [PATCH 060/108] Update managing-collections.mdx to correct typos and improve clarity in documentation. --- apps/docs/pages/guides/ai/managing-collections.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/pages/guides/ai/managing-collections.mdx b/apps/docs/pages/guides/ai/managing-collections.mdx index 5849f6aa526..58e700e2927 100644 --- a/apps/docs/pages/guides/ai/managing-collections.mdx +++ b/apps/docs/pages/guides/ai/managing-collections.mdx @@ -7,7 +7,7 @@ export const meta = { sidebar_label: 'Managing collections', } -A collection is an group of vector records managed by the `vecs` Python library. Records can be added to or updated in a collection. Collections can be queried at any time, but should be indexed for scalable query performance. +A collection is a group of vector records managed by the `vecs` Python library. Records can be added to or updated in a collection. Collections can be queried at any time, but should be indexed for scalable query performance. Supabase provides a [Python client](/docs/guides/ai/vecs-python-client) called `vecs` for managing unstructured vector stores in Postgres. If you come from a data science background, this unstructured data approach will feel familiar. If you are more interested in a structured data approach, see [Vector columns](/docs/guides/ai/vector-columns) or read our guide on [Structured & Unstructured Embeddings](/docs/guides/ai/structured-unstructured-embeddings). @@ -32,7 +32,7 @@ vx = vecs.create_client(DB_CONNECTION) ### Create collection -You can create a collection to store vectors specifying the collections name and the number of dimensions in the vectors you intend to store. +You can create a collection to store vectors specifying the collection's name and the number of dimensions in the vectors you intend to store. ```python docs = vx.create_collection(name="docs", dimension=3) @@ -75,7 +75,7 @@ docs.upsert( Collections can be queried immediately after being created. However, for good performance, the collection should be indexed after records have been upserted. -Indexes should be created **after** the collection has been populated with records. Building an index on an empty collection will result in significantly reduced recall. Once the index has been created you can still upsert new documents into the collection but you should rebuild the index if the size of the collection more than doubles. +Indexes should be created **after** the collection has been populated with records. Building an index on an empty collection will significantly reduce recall. Once the index has been created you can still upsert new documents into the collection but you should rebuild the index if the size of the collection more than doubles. Only one index may exist per-collection. By default, creating an index will replace any existing index. From 61fac557dc7bc49aec758035e67410df3e82db66 Mon Sep 17 00:00:00 2001 From: Kenley Jean Date: Wed, 21 Jun 2023 22:25:50 -0400 Subject: [PATCH 061/108] Update engineering-for-scale.mdx --- apps/docs/pages/guides/ai/engineering-for-scale.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/docs/pages/guides/ai/engineering-for-scale.mdx b/apps/docs/pages/guides/ai/engineering-for-scale.mdx index 663550fc4a8..3214495a918 100644 --- a/apps/docs/pages/guides/ai/engineering-for-scale.mdx +++ b/apps/docs/pages/guides/ai/engineering-for-scale.mdx @@ -12,7 +12,7 @@ Content sources for vectors can be extremely large. As you grow you should run y ## Simple workloads -For small workloads it's typical to store your data in a single database. +For small workloads, it's typical to store your data in a single database. If you've used [Vecs](/docs/guides/ai/vecs-python-client) to create 3 different collections, you can expose collections to your web or mobile application using [views](/docs/guides/database/tables#views): @@ -93,9 +93,9 @@ docs.query( ### Accessing external collections using Wrappers -Supabase supports [Foreign Data Wrappers](/blog/postgres-foreign-data-wrappers-rust). Wrappers allow you connect two databases together so that you can query them over the network. +Supabase supports [Foreign Data Wrappers](/blog/postgres-foreign-data-wrappers-rust). Wrappers allow you to connect two databases together so that you can query them over the network. -This involves 2 steps: connecting to your remote database from the primary, and creating a Foreign Table. +This involves 2 steps: connecting to your remote database from the primary and creating a Foreign Table. #### Connecting your remote database @@ -140,7 +140,7 @@ const { data, error } = await supabase ### Enterprise architecture -This diagram provides an example architecture, allowing you to access the collections either with our client libraries or using Vecs. You can add as many secondary databases as you need, in this example we show one only: +This diagram provides an example architecture, allowing you to access the collections either with our client libraries or using Vecs. You can add as many secondary databases as you need, in this example, we show one only:
Date: Thu, 22 Jun 2023 11:03:10 +0800 Subject: [PATCH 062/108] Remove odd linebreaks --- apps/docs/pages/guides/platform/database-size.mdx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/docs/pages/guides/platform/database-size.mdx b/apps/docs/pages/guides/platform/database-size.mdx index bf2a6f8c526..df4515ee663 100644 --- a/apps/docs/pages/guides/platform/database-size.mdx +++ b/apps/docs/pages/guides/platform/database-size.mdx @@ -44,9 +44,7 @@ Vacuum operations can temporarily increase resource utilization, which may adver Supabase projects have automatic vacuuming enabled, which ensures that these operations are performed regularly to keep the database healthy and performant. -It is possible to [fine-tune](https://www.percona.com/blog/2018/08/10/tuning-autovacuum-in-postgresql-and-autovacuum-internals/) -the [autovacuum parameters](https://www.enterprisedb.com/blog/postgresql-vacuum-and-analyze-best-practice-tips), -or [manually initiate](https://www.postgresql.org/docs/current/sql-vacuum.html) vacuum operations. +It is possible to [fine-tune](https://www.percona.com/blog/2018/08/10/tuning-autovacuum-in-postgresql-and-autovacuum-internals/) the [autovacuum parameters](https://www.enterprisedb.com/blog/postgresql-vacuum-and-analyze-best-practice-tips), or [manually initiate](https://www.postgresql.org/docs/current/sql-vacuum.html) vacuum operations. Running a manual vacuum after deleting large amounts of data from your DB could help reduce the database size reported by Postgres. ### Preoccupied Space From c84971b5af591e924d5758b23526bb85109e62b1 Mon Sep 17 00:00:00 2001 From: Long Hoang <1732217+loong@users.noreply.github.com> Date: Thu, 22 Jun 2023 11:24:42 +0800 Subject: [PATCH 063/108] Add guide for high Disk IO consumption --- .../NavigationMenu.constants.ts | 4 ++ .../pages/guides/platform/exhaust-disk-io.mdx | 48 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 apps/docs/pages/guides/platform/exhaust-disk-io.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index aa1116135d7..8a6ae7c33b9 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -1058,6 +1058,10 @@ export const platform: NavMenuConstant = { name: 'HTTP and Project Issues', url: '/guides/platform/troubleshooting', }, + { + name: 'High Disk IO Consumption', + url: '/guides/platform/exhaust-disk-io', + }, { name: 'High CPU Usage', url: '/guides/platform/exhaust-cpu', diff --git a/apps/docs/pages/guides/platform/exhaust-disk-io.mdx b/apps/docs/pages/guides/platform/exhaust-disk-io.mdx new file mode 100644 index 00000000000..23ff66a844d --- /dev/null +++ b/apps/docs/pages/guides/platform/exhaust-disk-io.mdx @@ -0,0 +1,48 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'exhaust-disk-io', + title: 'High Disk IO Consumption', + description: + 'Understand Disk IO, what can deplete your Disk Budget and what you can do about it.', +} + +## Understanding Disk IO and Disk Budget + +Disk IO refers to two metrics: throughput in Megabits per Second and IOPS which are Input/Output Operations per Second. Depending on the compute add-on of your instance you will have [different baseline performances](https://supabase.com/docs/guides/platform/compute-add-ons#disk-io). + +Smaller compute instances can burst and exceed their baseline performance for a short quota of time every day. This quota is represented as your Disk Budget and once your Disk Budget is consumed, your instance reverts back to its baseline performance. You can read more about this under [Bursting and Disk Budget](https://supabase.com/docs/guides/platform/compute-add-ons#bursting-and-disk-budget). + +## Depleting your Disk Budget + +Running out of Disk Budget means that your instance is using more disk than its compute add-on can handle and essentially gets throttled. This could have a wide range of implications: + +- Response times on requests can increase noticeably +- CPU usage rises noticeably due to IO wait +- Disruption of [daily backup](https://supabase.com/docs/guides/platform/backups#daily-backups) routines +- Disruption of internal Postgres processes such as [autovacuuming](https://supabase.com/docs/guides/platform/database-size#vacuum-operations) +- Your instance may become unresponsive + +## Monitor your Disk Budget + +To check your Disk Budget on the Supabase Platform, head over to [Database Health in the Reports section](https://supabase.com/dashboard/project/_/reports/database). + +It is also possible to monitor your resources and set up alerts using Prometheus/Grafana. With Grafana you will be able to see how much of your RAM is used for caching and you can track other metrics such as your Swap usage. Read the [Metrics Guide](https://supabase.com/docs/guides/platform/metrics) to learn more. + +## Common reasons for high disk IO usage + +Most operations on your Supabase project require the disk in some form. Hence, there can be many reasons for high disk usage. Here are some common ones: + +- **High Memory Usage:** Every Supabase project has 1GB of disk allocated for swapping. When your memory usage is high, the operating system might frequently move parts of the memory back and forth of the swap space on the disk. +- **Low Cache Usage:** If your cache hit rate is low, many of your database requests might go straight to the disk. Go to the [Cache Hit Rate Guide](https://supabase.com/docs/guides/platform/performance#hit-rate) to learn more. +- **Query performance:** Queries that take a long time to complete (>1 second) could be using your disk inefficiently. Check our guide on [examining query performance](https://supabase.com/docs/guides/platform/performance#examining-query-performance). +- **High popularity:** Congrats! Your side project turned out to be a real success and is getting more requests than it can handle. + +## How to fix + +1. **Upgrade your compute:** You can get a Compute Add-on for your project. See your [upgrade options](https://supabase.com/dashboard/project/_/settings/billing/subscription?panel=computeInstance) by selecting your project. Do reference the [different baseline performances](https://supabase.com/docs/guides/platform/compute-add-ons#disk-io) that come with larger Compute Add-ons. +2. **Optimize performance:** Get more out of your instance's resources by optimizing your usage. Have a look at our [performance tuning guide](https://supabase.com/docs/guides/platform/performance#examining-query-performance) and our [production readiness guide](https://supabase.com/docs/guides/platform/going-into-prod#performance). + +export const Page = ({ children }) => + +export default Page From e27678834d18f793df070c2c88adad4711f4767e Mon Sep 17 00:00:00 2001 From: Long Hoang <1732217+loong@users.noreply.github.com> Date: Thu, 22 Jun 2023 11:34:12 +0800 Subject: [PATCH 064/108] Change url to support tickets to new /dashboard format --- apps/docs/data/footer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/data/footer.ts b/apps/docs/data/footer.ts index 15228f0e532..05004527be4 100644 --- a/apps/docs/data/footer.ts +++ b/apps/docs/data/footer.ts @@ -5,7 +5,7 @@ export const primaryLinks = [ featherIcon: IconLifeBuoy, text: 'Need some help?', ctaLabel: 'Contact support', - url: 'https://app.supabase.com/support/new', + url: 'https://supabase.com/dashboard/support/new', }, { icon: 'M12.9521 10.5073C12.766 10.3212 12.5289 10.1943 12.2708 10.1427L10.6794 9.82467C9.80719 9.65024 8.90169 9.77152 8.10609 10.1693L7.89409 10.2747C7.0985 10.6725 6.193 10.7938 5.32076 10.6193L4.03343 10.362C3.81823 10.319 3.59574 10.3298 3.38571 10.3934C3.17569 10.457 2.9846 10.5715 2.82943 10.7267M5.33343 2.88867H10.6668L10.0001 3.55534V7.00334C10.0002 7.35693 10.1407 7.69602 10.3908 7.94601L13.7241 11.2793C14.5641 12.1193 13.9688 13.5553 12.7808 13.5553H3.21876C2.03076 13.5553 1.43609 12.1193 2.27609 11.2793L5.60943 7.94601C5.85949 7.69602 6.00002 7.35693 6.00009 7.00334V3.55534L5.33343 2.88867Z', From 55a018d07c70415212998e69e34e76f0a98bcaa1 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 22 Jun 2023 12:25:03 +0800 Subject: [PATCH 065/108] Remove console log --- studio/components/interfaces/Database/Migrations/Migrations.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/studio/components/interfaces/Database/Migrations/Migrations.tsx b/studio/components/interfaces/Database/Migrations/Migrations.tsx index 1f5eca1e7d8..d9cd18177ef 100644 --- a/studio/components/interfaces/Database/Migrations/Migrations.tsx +++ b/studio/components/interfaces/Database/Migrations/Migrations.tsx @@ -24,8 +24,6 @@ const Wrappers = () => { ? data?.result ?? [] : data?.result.filter((migration) => migration.version.includes(search)) ?? [] - console.log({ data }) - return ( <>
From e734db1cc2f3ae8e1b609f8606b1faca0de9071e Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 22 Jun 2023 13:28:09 +0800 Subject: [PATCH 066/108] Fix custom domain url to be used only if active --- .../interfaces/Auth/AuthProvidersForm/ProviderForm.tsx | 2 +- studio/components/layouts/StorageLayout/StorageMenu.tsx | 4 +++- .../Storage/StorageExplorer/StorageExplorer.js | 7 ++++--- studio/data/storage/buckets-query.ts | 2 ++ 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx b/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx index 673fdaebd80..0b41fdfacef 100644 --- a/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx +++ b/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx @@ -158,7 +158,7 @@ const ProviderForm: FC = ({ provider }) => { disabled label="Redirect URL" value={ - customDomainData?.customDomain + customDomainData?.customDomain?.status === 'active' ? `https://${customDomainData.customDomain?.hostname}/auth/v1/callback` : `https://${ui.selectedProjectRef}.supabase.co/auth/v1/callback` } diff --git a/studio/components/layouts/StorageLayout/StorageMenu.tsx b/studio/components/layouts/StorageLayout/StorageMenu.tsx index 1d85c34ee92..5ccbbc42bc4 100644 --- a/studio/components/layouts/StorageLayout/StorageMenu.tsx +++ b/studio/components/layouts/StorageLayout/StorageMenu.tsx @@ -31,11 +31,13 @@ const StorageMenu = () => { | 'logs' const storageExplorerStore = useStorageStore() - const { data, isLoading } = useBucketsQuery({ projectRef: ref }) + const { data, isLoading, isError } = useBucketsQuery({ projectRef: ref }) const { openDeleteBucketModal } = storageExplorerStore || {} const buckets = data ?? [] + console.log({ isError }) + return ( <> diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js index 89b977bebe1..48e9dc32bad 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js @@ -166,9 +166,10 @@ const StorageExplorer = observer(({ bucket }) => { } const onCopyUrl = (name, url) => { - const formattedUrl = customDomainData?.customDomain - ? url.replace(apiUrl, `https://${customDomainData.customDomain.hostname}`) - : url + const formattedUrl = + customDomainData?.customDomain.status === 'active' + ? url.replace(apiUrl, `https://${customDomainData.customDomain.hostname}`) + : url copyToClipboard(formattedUrl, () => { ui.setNotification({ category: 'success', diff --git a/studio/data/storage/buckets-query.ts b/studio/data/storage/buckets-query.ts index 530c257a52b..7913db627cb 100644 --- a/studio/data/storage/buckets-query.ts +++ b/studio/data/storage/buckets-query.ts @@ -21,6 +21,8 @@ export async function getBuckets({ projectRef }: BucketsVariables, signal?: Abor if (!projectRef) throw new Error('projectRef is required') const response = await get(`${API_URL}/storage/${projectRef}/buckets`, { signal }) + throw new Error('test') + if (response.error) throw response.error return response as Bucket[] } From e5bd00c1c03476dec338c496ad051dd6e6791b11 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 22 Jun 2023 13:45:17 +0800 Subject: [PATCH 067/108] Add error handling if buckets fetch failed --- .../StorageLayout/StorageBucketsError.tsx | 43 +++++++++++++++++++ .../layouts/StorageLayout/StorageMenu.tsx | 19 +++++--- studio/data/storage/buckets-query.ts | 2 - .../[ref]/storage/buckets/[bucketId].tsx | 5 ++- .../project/[ref]/storage/buckets/index.tsx | 11 ++++- studio/types/base.ts | 6 +++ 6 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 studio/components/layouts/StorageLayout/StorageBucketsError.tsx diff --git a/studio/components/layouts/StorageLayout/StorageBucketsError.tsx b/studio/components/layouts/StorageLayout/StorageBucketsError.tsx new file mode 100644 index 00000000000..973abb0727a --- /dev/null +++ b/studio/components/layouts/StorageLayout/StorageBucketsError.tsx @@ -0,0 +1,43 @@ +import { useParams } from 'common' +import Link from 'next/link' +import { ResponseError } from 'types' +import { Alert, Button } from 'ui' + +export interface StorageBucketsErrorProps { + error: ResponseError +} + +const StorageBucketsError = ({ error }: StorageBucketsErrorProps) => { + const { ref } = useParams() + + return ( +
+
+ + + + + , + ]} + > +

+ Please try refreshing your browser, or contact support if the issue persists +

+

Error: {(error as any)?.message ?? 'Unknown'}

+
+
+
+ ) +} + +export default StorageBucketsError diff --git a/studio/components/layouts/StorageLayout/StorageMenu.tsx b/studio/components/layouts/StorageLayout/StorageMenu.tsx index 5ccbbc42bc4..c523aedf93d 100644 --- a/studio/components/layouts/StorageLayout/StorageMenu.tsx +++ b/studio/components/layouts/StorageLayout/StorageMenu.tsx @@ -31,13 +31,11 @@ const StorageMenu = () => { | 'logs' const storageExplorerStore = useStorageStore() - const { data, isLoading, isError } = useBucketsQuery({ projectRef: ref }) + const { data, isLoading, isError, isSuccess } = useBucketsQuery({ projectRef: ref }) const { openDeleteBucketModal } = storageExplorerStore || {} const buckets = data ?? [] - console.log({ isError }) - return ( <> @@ -82,13 +80,24 @@ const StorageMenu = () => {
- {isLoading ? ( + + {isLoading && (
- ) : ( + )} + + {isError && ( +
+ + Please refresh to try again. + +
+ )} + + {isSuccess && ( <> {buckets.length === 0 && (
diff --git a/studio/data/storage/buckets-query.ts b/studio/data/storage/buckets-query.ts index 7913db627cb..530c257a52b 100644 --- a/studio/data/storage/buckets-query.ts +++ b/studio/data/storage/buckets-query.ts @@ -21,8 +21,6 @@ export async function getBuckets({ projectRef }: BucketsVariables, signal?: Abor if (!projectRef) throw new Error('projectRef is required') const response = await get(`${API_URL}/storage/${projectRef}/buckets`, { signal }) - throw new Error('test') - if (response.error) throw response.error return response as Bucket[] } diff --git a/studio/pages/project/[ref]/storage/buckets/[bucketId].tsx b/studio/pages/project/[ref]/storage/buckets/[bucketId].tsx index 2d3d0c06987..8eee76102c8 100644 --- a/studio/pages/project/[ref]/storage/buckets/[bucketId].tsx +++ b/studio/pages/project/[ref]/storage/buckets/[bucketId].tsx @@ -4,6 +4,7 @@ import { useEffect } from 'react' import { useParams } from 'common' import { StorageLayout } from 'components/layouts' +import StorageBucketsError from 'components/layouts/StorageLayout/StorageBucketsError' import { StorageExplorer } from 'components/to-be-cleaned/Storage' import { useBucketsQuery } from 'data/storage/buckets-query' import { useFlag, useStore } from 'hooks' @@ -17,7 +18,7 @@ const PageLayout: NextPageWithLayout = () => { const { ui } = useStore() const project = ui.selectedProject - const { data, isSuccess } = useBucketsQuery({ projectRef: ref }) + const { data, isSuccess, isError, error } = useBucketsQuery({ projectRef: ref }) const buckets = data ?? [] const kpsEnabled = useFlag('initWithKps') @@ -34,6 +35,8 @@ const PageLayout: NextPageWithLayout = () => { return (
+ {isError && } + {isSuccess ? ( !bucket ? (
diff --git a/studio/pages/project/[ref]/storage/buckets/index.tsx b/studio/pages/project/[ref]/storage/buckets/index.tsx index dc5752c423a..5084b25a7fd 100644 --- a/studio/pages/project/[ref]/storage/buckets/index.tsx +++ b/studio/pages/project/[ref]/storage/buckets/index.tsx @@ -1,11 +1,14 @@ +import { observer } from 'mobx-react-lite' +import { useEffect } from 'react' + import { useParams } from 'common/hooks' import { StorageLayout } from 'components/layouts' +import StorageBucketsError from 'components/layouts/StorageLayout/StorageBucketsError' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' +import { useBucketsQuery } from 'data/storage/buckets-query' import { useFlag, useStore } from 'hooks' import { post } from 'lib/common/fetch' import { API_URL, PROJECT_STATUS } from 'lib/constants' -import { observer } from 'mobx-react-lite' -import { useEffect } from 'react' import { NextPageWithLayout } from 'types' const PageLayout: NextPageWithLayout = ({}) => { @@ -14,6 +17,8 @@ const PageLayout: NextPageWithLayout = ({}) => { const project = ui.selectedProject const kpsEnabled = useFlag('initWithKps') + const { error, isError } = useBucketsQuery({ projectRef: ref }) + useEffect(() => { if (project && project.status === PROJECT_STATUS.INACTIVE) { post(`${API_URL}/projects/${ref}/restore`, { kps_enabled: kpsEnabled }) @@ -22,6 +27,8 @@ const PageLayout: NextPageWithLayout = ({}) => { if (!project) return
+ if (isError) + return (
= T & ResponseFailure + +export interface ResponseError { + code: number + message: string + requestId: string +} From c6341a050a0f8c59b5541fc9be698f2ede5872ef Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Thu, 22 Jun 2023 19:06:34 +0800 Subject: [PATCH 068/108] Fix plural and spelling concerns --- .../interfaces/BillingV2/Subscription/BillingBreakdown.tsx | 4 ++-- .../BillingV2/Subscription/CostControl/CostControl.tsx | 2 +- .../BillingV2/Subscription/Tier/SubscriptionTier.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/studio/components/interfaces/BillingV2/Subscription/BillingBreakdown.tsx b/studio/components/interfaces/BillingV2/Subscription/BillingBreakdown.tsx index 92455682a6f..5d4ee5b82a9 100644 --- a/studio/components/interfaces/BillingV2/Subscription/BillingBreakdown.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/BillingBreakdown.tsx @@ -80,14 +80,14 @@ const BillingBreakdown = ({}: BillingBreakdownProps) => { {isUsageBillingEnabled ? (

Your plan includes a limited amount of usage. If the usage on your project exceeds - these quotas, your subscription will be charged for the overage. It may take up to 24 + these quotas, your subscription will be charged for the overages. It may take up to 24 hours for usage stats to update.

) : (

Your plan includes a limited amount of usage. If the usage on your project exceeds these quotas, you may experience restrictions, as you are currently not billed for - overage. It may take up to 24 hours for usage stats to update. + overages. It may take up to 24 hours for usage stats to update.

)} diff --git a/studio/components/interfaces/BillingV2/Subscription/CostControl/CostControl.tsx b/studio/components/interfaces/BillingV2/Subscription/CostControl/CostControl.tsx index 9d00bf180c6..34ce550a355 100644 --- a/studio/components/interfaces/BillingV2/Subscription/CostControl/CostControl.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/CostControl/CostControl.tsx @@ -34,7 +34,7 @@ const CostControl = ({}: CostControlProps) => {

Cost Control

- Control whether to usage beyond your plans included quota + Control whether to use beyond your plans included quota

diff --git a/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx b/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx index a26e3eec82b..3d22dee00fd 100644 --- a/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx @@ -121,7 +121,7 @@ const SubscriptionTier = ({}: SubscriptionTierProps) => { actions={ currentPlan?.id === 'free' ? ( ) : (
) From 73b6092a39e4a98ef6ff712e9e681068765d5e22 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Thu, 22 Jun 2023 15:11:43 +0200 Subject: [PATCH 071/108] remove event listener when unmounting --- apps/www/components/Globe.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/www/components/Globe.tsx b/apps/www/components/Globe.tsx index 49470073455..8cf57e7ff59 100644 --- a/apps/www/components/Globe.tsx +++ b/apps/www/components/Globe.tsx @@ -51,7 +51,10 @@ const Globe = () => { }, }) setTimeout(() => (canvasRef.current.style.opacity = '1')) - return () => cobe.destroy() + return () => { + cobe.destroy() + window.removeEventListener('resize', onResize) + } }, [isDarkMode]) return ( From a1cc7e1d0c9c694f5782ae72798fd70838aa7d69 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Thu, 22 Jun 2023 22:04:07 +0800 Subject: [PATCH 072/108] Update PITRSelection.tsx --- .../interfaces/Database/Backups/PITR/PITRSelection.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx b/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx index eb1ecceb2f0..1517f52ff3d 100644 --- a/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx +++ b/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx @@ -147,7 +147,6 @@ const PITRSelection = ({}) => { {showCmdkHelper && ( handleNewQuery()}> +
+

New blank query

+
+ , { @@ -123,19 +119,19 @@ const SideBarContent = observer(() => { >

New AI query

-

- Generate a SQL query using Supabase AI -

, ]} >
)}
From 0104324ed0d0cc6e5c7996f8dd7ac5e1341d1752 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Thu, 22 Jun 2023 19:14:47 -0230 Subject: [PATCH 081/108] Shift logic for showmenu --- .../layouts/SQLEditorLayout/SQLEditorMenu.tsx | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx b/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx index 76785da28a8..1ae369e77f9 100644 --- a/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx +++ b/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx @@ -98,18 +98,18 @@ const SideBarContent = observer(() => { {IS_PLATFORM && (
- {showCmdkHelper && ( - handleNewQuery()}> -
-

New blank query

-
- , + handleNewQuery()}> +
+

New blank query

+
+ , + showCmdkHelper ? ( { @@ -120,20 +120,20 @@ const SideBarContent = observer(() => {

New AI query

-
, - ]} + + ) : null, + ]} + > + -
- )} + New query + +
Date: Thu, 22 Jun 2023 16:54:13 -0600 Subject: [PATCH 082/108] feat(studio): link api keys to client docs --- .../ui/ProjectSettings/DisplayApiSettings.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx b/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx index e1bebe6e641..3029463350c 100644 --- a/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx +++ b/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx @@ -1,12 +1,12 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { JwtSecretUpdateStatus } from '@supabase/shared-types/out/events' -import { IconAlertCircle, IconLoader, Input } from 'ui' +import { Button, IconAlertCircle, IconBookOpen, IconLoader, Input } from 'ui' -import Panel from 'components/ui/Panel' -import { useProjectSettingsQuery } from 'data/config/project-settings-query' -import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' -import { checkPermissions } from 'hooks' import { useParams } from 'common/hooks' +import Panel from 'components/ui/Panel' +import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' +import { useProjectSettingsQuery } from 'data/config/project-settings-query' +import { checkPermissions } from 'hooks' import { DEFAULT_PROJECT_API_SERVICE_ID } from 'lib/constants' const DisplayApiSettings = () => { @@ -44,7 +44,14 @@ const DisplayApiSettings = () => {

Your API is secured behind an API gateway which requires an API Key for every request.
- You can use the keys below to use Supabase client libraries. + You can use the keys below in the Supabase client libraries. +
+ + + + .

} From ef812e3012fe70210ae5a3969dc2a099d15deba2 Mon Sep 17 00:00:00 2001 From: Greg Richardson Date: Thu, 22 Jun 2023 18:13:34 -0600 Subject: [PATCH 083/108] fix(docs): line breaks on ssr guide --- .../guides/auth/server-side-rendering.mdx | 172 ++++++------------ 1 file changed, 53 insertions(+), 119 deletions(-) diff --git a/apps/docs/pages/guides/auth/server-side-rendering.mdx b/apps/docs/pages/guides/auth/server-side-rendering.mdx index 9bcd4a75e41..109c6351565 100644 --- a/apps/docs/pages/guides/auth/server-side-rendering.mdx +++ b/apps/docs/pages/guides/auth/server-side-rendering.mdx @@ -7,58 +7,40 @@ export const meta = { description: 'Render pages with user information on the server.', } -Single-page apps with server-side rendering (SSR) is a popular way to optimize rendering -performance and leverage advanced caching strategies. +Single-page apps with server-side rendering (SSR) is a popular way to optimize rendering performance and leverage advanced caching strategies. -Supabase Auth supports server-side rendering when you need access to user -information, or your server needs to authorize API requests on behalf of your -user to render content. +Supabase Auth supports server-side rendering when you need access to user information, or your server needs to authorize API requests on behalf of your user to render content. -When a user authenticates with Supabase Auth, two pieces of information are -issued by the server: +When a user authenticates with Supabase Auth, two pieces of information are issued by the server: 1. **Access token** in the form of a JWT. 2. **Refresh token** which is a randomly generated string. -Most Supabase projects have their auth server listening on -`.supabase.co/auth/v1`, thus the access token and refresh token are -set as `sb-access-token` and `sb-refresh-token` cookies on the -`.supabase.co` domain. +Most Supabase projects have their auth server listening on `.supabase.co/auth/v1`, thus the access token and refresh token are set as `sb-access-token` and `sb-refresh-token` cookies on the `.supabase.co` domain. -These cookie names are for internal Supabase use only and may change without -warning. They are included in this guide for illustration purposes only. +These cookie names are for internal Supabase use only and may change without warning. They are included in this guide for illustration purposes only. -Web browsers limit access to cookies across domains, consistent with the -[Same-Origin Policy -(SOP)](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy). +Web browsers limit access to cookies across domains, consistent with the [Same-Origin Policy (SOP)](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy). -Your web application cannot access these cookies, -nor will these cookies be sent to your application's server. +Your web application cannot access these cookies, nor will these cookies be sent to your application's server. ## Understanding the authentication flow -When you call one of the `signIn` methods, the client library running in the -browser sends the request to the Supabase Auth server. The Auth server determines -whether to verify a phone number, email and password combination, a Magic Link, -or use a social login (if you have any setup in your project). +When you call one of the `signIn` methods, the client library running in the browser sends the request to the Supabase Auth server. The Auth server determines whether to verify a phone number, email and password combination, a Magic Link, or use a social login (if you have any setup in your project). -Upon successful verification of the identity of the user, the Supabase Auth -server redirects the user back to your single-page app. +Upon successful verification of the identity of the user, the Supabase Auth server redirects the user back to your single-page app. -You can configure [redirects URLs](https://app.supabase.com/project/_/auth/url-configuration) in the Supabase Dashboard. You can use [wildcard match patterns](/docs/guides/auth#redirect-urls-and-wildcards) -like `*` and `**` to allow redirects to different forms of URLs. +You can configure [redirects URLs](https://app.supabase.com/project/_/auth/url-configuration) in the Supabase Dashboard. You can use [wildcard match patterns](/docs/guides/auth#redirect-urls-and-wildcards) like `*` and `**` to allow redirects to different forms of URLs. -Supabase Auth supports two authentication flows: **Implicit** and **PKCE**. The **PKCE** flow is generally preferred when on the server. -It introduces a few additional steps which guard a against replay and URL capture attacks. Unlike the implicit flow, it also allows users to access the -`access_token` and `refresh_token` on the server. +Supabase Auth supports two authentication flows: **Implicit** and **PKCE**. The **PKCE** flow is generally preferred when on the server. It introduces a few additional steps which guard a against replay and URL capture attacks. Unlike the implicit flow, it also allows users to access the `access_token` and `refresh_token` on the server. -When using the implicit flow, a redirect URL will be returned with the following structure: -``` -https://yourapp.com/...#access_token=<...>&refresh_token=<...>&... -``` + When using the implicit flow, a redirect URL will be returned with the following structure: + ``` + https://yourapp.com/...#access_token=<...>&refresh_token=<...>&... + ``` -The first access and refresh tokens after a successful verification are -contained in the URL fragment (anything after the `#` sign) of the redirect -location. This is intentional and not configurable. + The first access and refresh tokens after a successful verification are contained in the URL fragment (anything after the `#` sign) of the redirect location. This is intentional and not configurable. -The client libraries are designed to listen for this type of URL, extract -the access token, refresh token and some extra information from it, and finally -persist it in local storage for further use by the library and your app. + The client libraries are designed to listen for this type of URL, extract the access token, refresh token and some extra information from it, and finally persist it in local storage for further use by the library and your app. - + + + Web browsers do not send the URL fragment to the server they're making the request to. Since you may not be hosting the single-page app on a server under your direct control (such as on GitHub Pages or other freemium hosting providers), we want to prevent hosting services from getting access to your user's authorization credentials by default. + + Even if the server is under your direct control, `GET` requests and their full URLs are often logged. This approach also avoids leaking credentials in request or access logs. If you wish to obtain the `access_token` and `refresh_token` on a server, please consider using the PKCE flow. + + -Web browsers do not send the URL fragment to the server they're making the -request to. Since you may not be hosting the single-page app on a server under -your direct control (such as on GitHub Pages or other freemium hosting -providers), we want to prevent hosting services from getting access to your -user's authorization credentials by default. Even if the server is under your -direct control, `GET` requests and their full URLs are often logged. This -approach also avoids leaking credentials in request or access logs. If you wish to obtain the -`access_token` and `refresh_token` on a server, please consider using the PKCE flow. -
@@ -105,18 +80,21 @@ approach also avoids leaking credentials in request or access logs. If you wish header={PKCE} id={`ssr-pkce-flow`} > + When using the PKCE flow, a redirect URL will be returned with the following structure: ``` https://yourapp.com/...?code=<...> ``` The `code` parameter is commonly known as the Auth Code and can be exchanged for an access token by calling `exchangeCodeForSession(code)`. + - For security purposes, the code has a validity of 5 minutes and can only be exchanged for an access token once. You - will need to restart the authentication flow from scratch if you wish to obtain a new access token. + + For security purposes, the code has a validity of 5 minutes and can only be exchanged for an access token once. You will need to restart the authentication flow from scratch if you wish to obtain a new access token. + - As the flow is run server side, `localStorage` may not be available. You may configure the client library to use a custom storage adapter an alternate backing storage such as cookies - by setting the `storage` option to an object with the following methods: + As the flow is run server side, `localStorage` may not be available. You may configure the client library to use a custom storage adapter an alternate backing storage such as cookies by setting the `storage` option to an object with the following methods: + ```js const customStorageAdapter: SupportedStorage = { getItem: (key) => { @@ -161,26 +139,19 @@ approach also avoids leaking credentials in request or access logs. If you wish } ) ``` - You can read more about the PKCE flow [here](https://oauth.net/2/pkce/) + [Learn more](https://oauth.net/2/pkce/) about the PKCE flow. +
## Bringing it together -As seen from the authentication flow, the initial request after successful -login made by the browser to your app's server after user login **does not -contain any information about the user**. This is because first the client-side -JavaScript library must run before it makes the access and refresh token -available to your server. +As seen from the authentication flow, the initial request after successful login made by the browser to your app's server after user login **does not contain any information about the user**. This is because first the client-side JavaScript library must run before it makes the access and refresh token available to your server. -It is very important to make sure that the redirect route right after login -works without any server-side rendering. Other routes requiring authorization -do not have the same limitation, provided you send the access and refresh -tokens to your server. +It is very important to make sure that the redirect route right after login works without any server-side rendering. Other routes requiring authorization do not have the same limitation, provided you send the access and refresh tokens to your server. -This is traditionally done by setting cookies. Here's an example you -can add to the root of your application: +This is traditionally done by setting cookies. Here's an example you can add to the root of your application: ```typescript supabase.auth.onAuthStateChange((event, session) => { @@ -197,15 +168,9 @@ supabase.auth.onAuthStateChange((event, session) => { }) ``` -This uses the standard -[`document.cookie` API](https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie) -to set cookies on all paths of your app's domain. All subsequent requests -made by the browser to your app's server include the `my-access-token` and -`my-refresh-token` cookies (the names of the cookies and additional -parameters can be changed). +This uses the standard [`document.cookie` API](https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie) to set cookies on all paths of your app's domain. All subsequent requests made by the browser to your app's server include the `my-access-token` and `my-refresh-token` cookies (the names of the cookies and additional parameters can be changed). -In your server-side rendering code you can now access user and session -information: +In your server-side rendering code you can now access user and session information: ```typescript const refreshToken = req.cookies['my-refresh-token'] @@ -228,23 +193,13 @@ if (refreshToken && accessToken) { await supabase.auth.getUser() ``` -Use `setSession({ access_token, refresh_token })` instead of -`setSession(refreshToken)` or `getUser(accessToken)` as refresh tokens or access tokens alone do not properly identify a user session. +Use `setSession({ access_token, refresh_token })` instead of `setSession(refreshToken)` or `getUser(accessToken)` as refresh tokens or access tokens alone do not properly identify a user session. Access tokens are valid only for a short amount of time. -Even though refresh tokens are long-lived, there is no guarantee that a user -has an active session. They may have logged out and your application failed to -remove the `my-refresh-token` cookie, or some other failure occurred that left -a stale refresh token in the browser. Furthermore, a refresh token can only be -used a few seconds after it was first used. Only use a refresh token if the -access token is about to expire, which will avoid the introduction of difficult -to diagnose logout bugs in your app. +Even though refresh tokens are long-lived, there is no guarantee that a user has an active session. They may have logged out and your application failed to remove the `my-refresh-token` cookie, or some other failure occurred that left a stale refresh token in the browser. Furthermore, a refresh token can only be used a few seconds after it was first used. Only use a refresh token if the access token is about to expire, which will avoid the introduction of difficult to diagnose logout bugs in your app. -A good practice is to handle unauthorized errors by deferring rendering the -page in the browser instead of in the server. Some user information is -contained in the access token though, so in certain cases, you may be able to -use this potentially stale information to render a page. +A good practice is to handle unauthorized errors by deferring rendering the page in the browser instead of in the server. Some user information is contained in the access token though, so in certain cases, you may be able to use this potentially stale information to render a page. ## Frequently Asked Questions @@ -256,57 +211,36 @@ To improve experience for your users, we recommend redirecting users to one spec ### How do I make the cookies `HttpOnly`? -This is not necessary. Both the access token and refresh token are designed to -be passed around to different components in your application. The browser-based -side of your application needs access to the refresh token to properly maintain -a browser session anyway. +This is not necessary. Both the access token and refresh token are designed to be passed around to different components in your application. The browser-based side of your application needs access to the refresh token to properly maintain a browser session anyway. ### My server is getting invalid refresh token errors. What's going on? -It is likely that the refresh token sent from the browser to your server is -stale. Make sure the `onAuthStateChange` listener callback is free of bugs and -is registered relatively early in your application's lifetime. +It is likely that the refresh token sent from the browser to your server is stale. Make sure the `onAuthStateChange` listener callback is free of bugs and is registered relatively early in your application's lifetime -When you receive this error on the server-side, try to defer -rendering to the browser where the client library can access an up-to-date -refresh token and present the user with a better experience. +When you receive this error on the server-side, try to defer rendering to the browser where the client library can access an up-to-date refresh token and present the user with a better experience. ### Should I set a shorter `Max-Age` parameter on the cookies? -The `Max-Age` or `Expires` cookie parameters only control whether the browser -sends the value to the server. Since a refresh token represents the -long-lived authentication session of the user on that browser, setting a short -`Max-Age` or `Expires` parameter on the cookies only results in a degraded -user experience. +The `Max-Age` or `Expires` cookie parameters only control whether the browser sends the value to the server. Since a refresh token represents the long-lived authentication session of the user on that browser, setting a short `Max-Age` or `Expires` parameter on the cookies only results in a degraded user experience. -The only way to ensure that a user has logged out or their session has ended -is to get the user's details with `getUser()`. +The only way to ensure that a user has logged out or their session has ended is to get the user's details with `getUser()`. ### What should I use for the `SameSite` property? -Make sure you [understand the behavior of the property in different -situations](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) -as some properties can degrade the user experience. +Make sure you [understand the behavior of the property in different situations](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) as some properties can degrade the user experience. -A good default is to use `Lax` which sends cookies when users are -navigating to your site. Cookies typically require the `Secure` attribute, -which only sends them over HTTPS. However, this can be a problem when -developing on `localhost`. +A good default is to use `Lax` which sends cookies when users are navigating to your site. Cookies typically require the `Secure` attribute, which only sends them over HTTPS. However, this can be a problem when developing on `localhost`. ### Can I use server-side rendering with a CDN or cache? -Yes, but you need to be careful to include at least the refresh token cookie -value in the cache key. Otherwise you may be accidentally serving pages with -data belonging to different users! +Yes, but you need to be careful to include at least the refresh token cookie value in the cache key. Otherwise you may be accidentally serving pages with data belonging to different users! -Also be sure you set proper cache control headers. We recommend invalidating -cache keys every hour or less. - -export const Page = ({ children }) => - -export default Page +Also be sure you set proper cache control headers. We recommend invalidating cache keys every hour or less. ### Which authentication flows have PKCE support? At present, PKCE is supported on the Magic Link, OAuth, Sign Up, and Password Recovery routes. These correspond to the `signInWithOtp`, `signInWithOAuth`, `signUp`, and `resetPasswordForEmail` methods on the Supabase client library. When using PKCE with Phone and Email OTPs, there is no behavior change with respect to the implicit flow - an access token will be returned in the body when a request is successful. +export const Page = ({ children }) => + +export default Page From bf118bcce63d2d3c19893f7ac4edb4694c2ee663 Mon Sep 17 00:00:00 2001 From: dshukertjr <18113850+dshukertjr@users.noreply.github.com> Date: Fri, 23 Jun 2023 10:20:09 +0900 Subject: [PATCH 084/108] fix: minor fix on Dart snippets on dashboard --- studio/components/to-be-cleaned/Docs/Snippets.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/studio/components/to-be-cleaned/Docs/Snippets.js b/studio/components/to-be-cleaned/Docs/Snippets.js index 4e0568b70c9..d52e2e277a7 100644 --- a/studio/components/to-be-cleaned/Docs/Snippets.js +++ b/studio/components/to-be-cleaned/Docs/Snippets.js @@ -47,8 +47,8 @@ supabase: Client = create_client(url, key) dart: { language: 'dart', code: ` -final supabaseUrl = '${endpoint}' -final supabaseKey = String.fromEnvironment('SUPABASE_KEY') +const supabaseUrl = '${endpoint}'; +const supabaseKey = String.fromEnvironment('SUPABASE_KEY'); Future main() async { await Supabase.initialize(url: supabaseUrl, anonKey: supabaseKey); From b20436c16136789fdb89a42d62b2a0a277594bee Mon Sep 17 00:00:00 2001 From: Jon Meyers Date: Fri, 23 Jun 2023 20:56:25 +1000 Subject: [PATCH 085/108] update docs --- apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx | 4 ++-- apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx | 4 ++-- apps/docs/pages/guides/auth/auth-helpers/remix.mdx | 4 ++-- apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx | 2 +- .../pages/guides/getting-started/tutorials/with-nextjs.mdx | 4 ++-- .../pages/guides/getting-started/tutorials/with-sveltekit.mdx | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx index 11d7f549a42..b592401fc90 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx @@ -23,7 +23,7 @@ This submodule provides convenience helpers for implementing user authentication ```sh -npm install @supabase/auth-helpers-nextjs +npm install @supabase/auth-helpers-nextjs @supabase/supabase-js ``` This library supports the following tooling versions: @@ -41,7 +41,7 @@ npm install @supabase/auth-helpers-react ```sh -yarn add @supabase/auth-helpers-nextjs +yarn add @supabase/auth-helpers-nextjs @supabase/supabase-js ``` This library supports the following tooling versions: diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx index 99ceaa1f86b..d6aea4ace3f 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx @@ -72,14 +72,14 @@ npm run dev ```sh -npm install @supabase/auth-helpers-nextjs +npm install @supabase/auth-helpers-nextjs @supabase/supabase-js ``` ```sh -yarn add @supabase/auth-helpers-nextjs +yarn add @supabase/auth-helpers-nextjs @supabase/supabase-js ``` diff --git a/apps/docs/pages/guides/auth/auth-helpers/remix.mdx b/apps/docs/pages/guides/auth/auth-helpers/remix.mdx index 80b91640f8e..2cb3e0a1bce 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/remix.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/remix.mdx @@ -31,7 +31,7 @@ This submodule provides convenience helpers for implementing user authentication ```sh -npm install @supabase/auth-helpers-remix +npm install @supabase/auth-helpers-remix @supabase/supabase-js ``` This library supports the following tooling versions: @@ -42,7 +42,7 @@ This library supports the following tooling versions: ```sh -yarn add @supabase/auth-helpers-remix +yarn add @supabase/auth-helpers-remix @supabase/supabase-js ``` This library supports the following tooling versions: diff --git a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx index 8137d96df44..8d15a1af650 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx @@ -16,7 +16,7 @@ This submodule provides convenience helpers for implementing user authentication This library supports Node.js `^16.15.0`. ```sh Terminal -npm install @supabase/auth-helpers-sveltekit +npm install @supabase/auth-helpers-sveltekit @supabase/supabase-js ``` ### Declare Environment Variables diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx index e2e84524416..108325ba6d7 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx @@ -74,7 +74,7 @@ It can be challenging to authenticate your users in all these different environm Install the auth helpers for Next.js ```bash -npm install @supabase/auth-helpers-nextjs +npm install @supabase/auth-helpers-nextjs @supabase/supabase-js ``` ### NextJS Middleware @@ -241,7 +241,7 @@ export default function AuthForm() { - + If you are using TypeScript for this project, see [generating types](/docs/guides/api/rest/generating-types) to automatically generate types from your database tables. diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx index aa330f7154c..91a53b8cb3b 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx @@ -55,7 +55,7 @@ It can be challenging to authenticate your users in all these different environm Install the auth helpers for SvelteKit: ```bash -npm install @supabase/auth-helpers-sveltekit +npm install @supabase/auth-helpers-sveltekit @supabase/supabase-js ``` Add the code below to your `src/hooks.server.ts` to initialize the client on the server: From e5e007cadd0d462013af4cf2f7e0e27ba676ff9c Mon Sep 17 00:00:00 2001 From: Jon Meyers Date: Fri, 23 Jun 2023 21:04:50 +1000 Subject: [PATCH 086/108] update example apps --- examples/auth/nextjs/package-lock.json | 103 ++++-------------- examples/auth/nextjs/package.json | 7 +- .../nextjs-user-management/package-lock.json | 53 ++------- .../nextjs-user-management/package.json | 7 +- package-lock.json | 1 - 5 files changed, 42 insertions(+), 129 deletions(-) diff --git a/examples/auth/nextjs/package-lock.json b/examples/auth/nextjs/package-lock.json index 7b6d00438c9..36ba21b981a 100644 --- a/examples/auth/nextjs/package-lock.json +++ b/examples/auth/nextjs/package-lock.json @@ -8,18 +8,19 @@ "name": "full-nextjs-auth-helpers", "version": "0.1.0", "dependencies": { - "@supabase/auth-helpers-nextjs": "^0.7.0-next.8", + "@supabase/auth-helpers-nextjs": "latest", + "@supabase/supabase-js": "latest", "@types/node": "^20.2.3", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", "eslint": "^8.41.0", - "next": "^13.4.3", + "next": "latest", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.0.4" }, "devDependencies": { - "eslint-config-next": "^13.4.3" + "eslint-config-next": "latest" } }, "node_modules/@babel/runtime": { @@ -350,7 +351,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.1.tgz", "integrity": "sha512-bIR1Puae6W+1/MzPfYBWOG/SCWGo4B5CB7c0ZZksvliNEAzhxNBJ0UFKYINcGdGtxG8ZC+1xr3utWpNZNwnoRw==", - "peer": true, "dependencies": { "cross-fetch": "^3.1.5" } @@ -359,7 +359,6 @@ "version": "2.27.1", "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.27.1.tgz", "integrity": "sha512-rewbfKTC9DZNnPy4rRpr5ViBXre55wEXPWOueh/ZgwunR1TGmntRcraIM6SDKrP6iRD6ucx325a467uZ5U+KWg==", - "peer": true, "dependencies": { "cross-fetch": "^3.1.5" } @@ -368,7 +367,6 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.6.1.tgz", "integrity": "sha512-WDBUPOCOwcZonaCwEodwdA8hwWYOiXroDF9vWGxZxKAnuSVE2Ieci/kvhR4EsWvgGST2h90LRowgO+msXe8+fA==", - "peer": true, "dependencies": { "cross-fetch": "^3.1.5" } @@ -377,7 +375,6 @@ "version": "2.7.2", "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.2.tgz", "integrity": "sha512-Fi6xAl5PUkqnjl3wo4rdcQIbMG3+yTRX1aUZe/yfvTG84RMvmCXJ1yN6MmafVLeZpU1xkaz5Vx4L0tnHcLiy6w==", - "peer": true, "dependencies": { "@types/phoenix": "^1.5.4", "@types/websocket": "^1.0.3", @@ -388,7 +385,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.1.tgz", "integrity": "sha512-nkR0fQA9ScAtIKA3vNoPEqbZv1k5B5HVRYEvRWdlP6mUpFphM9TwPL2jZ/ztNGMTG5xT6SrHr+H7Ykz8qzbhjw==", - "peer": true, "dependencies": { "cross-fetch": "^3.1.5" } @@ -397,7 +393,6 @@ "version": "2.22.0", "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.22.0.tgz", "integrity": "sha512-omkgSWL1HwnpXZZy+yshFLx/qqxwk9kx9jbRM6IHNEylKrH/sz6JX7rGyIOmN9niDxMRjsCu1nwuBhD6IiF2xg==", - "peer": true, "dependencies": { "@supabase/functions-js": "^2.1.0", "@supabase/gotrue-js": "^2.26.0", @@ -429,8 +424,7 @@ "node_modules/@types/phoenix": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.6.tgz", - "integrity": "sha512-e7jZ6I9uyRGsg7MNwQcarmBvRlbGb9DibbocE9crVnxqsy6C23RMxLWbJ2CQ3vgCW7taoL1L+F02EcjA6ld7XA==", - "peer": true + "integrity": "sha512-e7jZ6I9uyRGsg7MNwQcarmBvRlbGb9DibbocE9crVnxqsy6C23RMxLWbJ2CQ3vgCW7taoL1L+F02EcjA6ld7XA==" }, "node_modules/@types/prop-types": { "version": "15.7.5", @@ -464,7 +458,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.5.tgz", "integrity": "sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==", - "peer": true, "dependencies": { "@types/node": "*" } @@ -797,7 +790,6 @@ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", "hasInstallScript": true, - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -897,7 +889,6 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "peer": true, "dependencies": { "node-fetch": "^2.6.11" } @@ -924,7 +915,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "peer": true, "dependencies": { "es5-ext": "^0.10.50", "type": "^1.0.1" @@ -1165,7 +1155,6 @@ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", "hasInstallScript": true, - "peer": true, "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", @@ -1179,7 +1168,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "peer": true, "dependencies": { "d": "1", "es5-ext": "^0.10.35", @@ -1190,7 +1178,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "peer": true, "dependencies": { "d": "^1.0.1", "ext": "^1.1.2" @@ -1651,7 +1638,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "peer": true, "dependencies": { "type": "^2.7.2" } @@ -1659,8 +1645,7 @@ "node_modules/ext/node_modules/type": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "peer": true + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -2380,8 +2365,7 @@ "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "peer": true + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "node_modules/is-weakmap": { "version": "2.0.1", @@ -2687,14 +2671,12 @@ "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "peer": true + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "node_modules/node-fetch": { "version": "2.6.11", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -2714,7 +2696,6 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "peer": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -3477,8 +3458,7 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "peer": true + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/tsconfig-paths": { "version": "3.14.2", @@ -3521,8 +3501,7 @@ "node_modules/type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "peer": true + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, "node_modules/type-check": { "version": "0.4.0", @@ -3564,7 +3543,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "peer": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -3609,7 +3587,6 @@ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", "hasInstallScript": true, - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -3620,14 +3597,12 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "peer": true + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/websocket": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", - "peer": true, "dependencies": { "bufferutil": "^4.0.1", "debug": "^2.2.0", @@ -3644,7 +3619,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -3652,14 +3626,12 @@ "node_modules/websocket/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -3747,7 +3719,6 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", - "peer": true, "engines": { "node": ">=0.10.32" } @@ -3974,7 +3945,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.1.tgz", "integrity": "sha512-bIR1Puae6W+1/MzPfYBWOG/SCWGo4B5CB7c0ZZksvliNEAzhxNBJ0UFKYINcGdGtxG8ZC+1xr3utWpNZNwnoRw==", - "peer": true, "requires": { "cross-fetch": "^3.1.5" } @@ -3983,7 +3953,6 @@ "version": "2.27.1", "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.27.1.tgz", "integrity": "sha512-rewbfKTC9DZNnPy4rRpr5ViBXre55wEXPWOueh/ZgwunR1TGmntRcraIM6SDKrP6iRD6ucx325a467uZ5U+KWg==", - "peer": true, "requires": { "cross-fetch": "^3.1.5" } @@ -3992,7 +3961,6 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.6.1.tgz", "integrity": "sha512-WDBUPOCOwcZonaCwEodwdA8hwWYOiXroDF9vWGxZxKAnuSVE2Ieci/kvhR4EsWvgGST2h90LRowgO+msXe8+fA==", - "peer": true, "requires": { "cross-fetch": "^3.1.5" } @@ -4001,7 +3969,6 @@ "version": "2.7.2", "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.2.tgz", "integrity": "sha512-Fi6xAl5PUkqnjl3wo4rdcQIbMG3+yTRX1aUZe/yfvTG84RMvmCXJ1yN6MmafVLeZpU1xkaz5Vx4L0tnHcLiy6w==", - "peer": true, "requires": { "@types/phoenix": "^1.5.4", "@types/websocket": "^1.0.3", @@ -4012,7 +3979,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.1.tgz", "integrity": "sha512-nkR0fQA9ScAtIKA3vNoPEqbZv1k5B5HVRYEvRWdlP6mUpFphM9TwPL2jZ/ztNGMTG5xT6SrHr+H7Ykz8qzbhjw==", - "peer": true, "requires": { "cross-fetch": "^3.1.5" } @@ -4021,7 +3987,6 @@ "version": "2.22.0", "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.22.0.tgz", "integrity": "sha512-omkgSWL1HwnpXZZy+yshFLx/qqxwk9kx9jbRM6IHNEylKrH/sz6JX7rGyIOmN9niDxMRjsCu1nwuBhD6IiF2xg==", - "peer": true, "requires": { "@supabase/functions-js": "^2.1.0", "@supabase/gotrue-js": "^2.26.0", @@ -4053,8 +4018,7 @@ "@types/phoenix": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.6.tgz", - "integrity": "sha512-e7jZ6I9uyRGsg7MNwQcarmBvRlbGb9DibbocE9crVnxqsy6C23RMxLWbJ2CQ3vgCW7taoL1L+F02EcjA6ld7XA==", - "peer": true + "integrity": "sha512-e7jZ6I9uyRGsg7MNwQcarmBvRlbGb9DibbocE9crVnxqsy6C23RMxLWbJ2CQ3vgCW7taoL1L+F02EcjA6ld7XA==" }, "@types/prop-types": { "version": "15.7.5", @@ -4088,7 +4052,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.5.tgz", "integrity": "sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==", - "peer": true, "requires": { "@types/node": "*" } @@ -4315,7 +4278,6 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", - "peer": true, "requires": { "node-gyp-build": "^4.3.0" } @@ -4384,7 +4346,6 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "peer": true, "requires": { "node-fetch": "^2.6.11" } @@ -4408,7 +4369,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "peer": true, "requires": { "es5-ext": "^0.10.50", "type": "^1.0.1" @@ -4601,7 +4561,6 @@ "version": "0.10.62", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "peer": true, "requires": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", @@ -4612,7 +4571,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "peer": true, "requires": { "d": "1", "es5-ext": "^0.10.35", @@ -4623,7 +4581,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "peer": true, "requires": { "d": "^1.0.1", "ext": "^1.1.2" @@ -4968,7 +4925,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "peer": true, "requires": { "type": "^2.7.2" }, @@ -4976,8 +4932,7 @@ "type": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "peer": true + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" } } }, @@ -5485,8 +5440,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "peer": true + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "is-weakmap": { "version": "2.0.1", @@ -5705,14 +5659,12 @@ "next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "peer": true + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "node-fetch": { "version": "2.6.11", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "peer": true, "requires": { "whatwg-url": "^5.0.0" } @@ -5720,8 +5672,7 @@ "node-gyp-build": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "peer": true + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==" }, "object-assign": { "version": "4.1.1", @@ -6228,8 +6179,7 @@ "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "peer": true + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "tsconfig-paths": { "version": "3.14.2", @@ -6268,8 +6218,7 @@ "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "peer": true + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, "type-check": { "version": "0.4.0", @@ -6299,7 +6248,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "peer": true, "requires": { "is-typedarray": "^1.0.0" } @@ -6333,7 +6281,6 @@ "version": "5.0.10", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "peer": true, "requires": { "node-gyp-build": "^4.3.0" } @@ -6341,14 +6288,12 @@ "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "peer": true + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "websocket": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", - "peer": true, "requires": { "bufferutil": "^4.0.1", "debug": "^2.2.0", @@ -6362,7 +6307,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, "requires": { "ms": "2.0.0" } @@ -6370,8 +6314,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -6379,7 +6322,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "peer": true, "requires": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -6445,8 +6387,7 @@ "yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", - "peer": true + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==" }, "yallist": { "version": "4.0.0", diff --git a/examples/auth/nextjs/package.json b/examples/auth/nextjs/package.json index 97dd88bcd43..bb8f44af4a3 100644 --- a/examples/auth/nextjs/package.json +++ b/examples/auth/nextjs/package.json @@ -9,17 +9,18 @@ "lint": "next lint" }, "dependencies": { - "@supabase/auth-helpers-nextjs": "^0.7.0", + "@supabase/auth-helpers-nextjs": "latest", + "@supabase/supabase-js": "latest", "@types/node": "^20.2.3", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", "eslint": "^8.41.0", - "next": "^13.4.3", + "next": "latest", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.0.4" }, "devDependencies": { - "eslint-config-next": "^13.4.3" + "eslint-config-next": "latest" } } diff --git a/examples/user-management/nextjs-user-management/package-lock.json b/examples/user-management/nextjs-user-management/package-lock.json index 70c22579779..a6e680c3585 100644 --- a/examples/user-management/nextjs-user-management/package-lock.json +++ b/examples/user-management/nextjs-user-management/package-lock.json @@ -8,16 +8,17 @@ "name": "nextjs-user-management", "version": "0.1.0", "dependencies": { - "@supabase/auth-helpers-nextjs": "^0.7.1", + "@supabase/auth-helpers-nextjs": "latest", "@supabase/auth-ui-react": "^0.4.2", "@supabase/auth-ui-shared": "^0.1.6", + "@supabase/supabase-js": "latest", "@types/node": "20.1.4", "@types/react": "18.2.6", "@types/react-dom": "18.2.4", "encoding": "^0.1.13", "eslint": "8.40.0", - "eslint-config-next": "13.4.2", - "next": "13.4.2", + "eslint-config-next": "latest", + "next": "latest", "react": "18.2.0", "react-dom": "18.2.0", "typescript": "5.0.4" @@ -375,7 +376,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.1.tgz", "integrity": "sha512-bIR1Puae6W+1/MzPfYBWOG/SCWGo4B5CB7c0ZZksvliNEAzhxNBJ0UFKYINcGdGtxG8ZC+1xr3utWpNZNwnoRw==", - "peer": true, "dependencies": { "cross-fetch": "^3.1.5" } @@ -384,7 +384,6 @@ "version": "2.26.0", "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.26.0.tgz", "integrity": "sha512-orxz8nwaF5D1nY/9H5xxTfFSCTvYeDLx24UO/Mxsx83xFP0t5RNxQZ0lEHBOhHhXJ4vR/COv79AxoWCOTu/7Rg==", - "peer": true, "dependencies": { "cross-fetch": "^3.1.5" } @@ -393,7 +392,6 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.6.1.tgz", "integrity": "sha512-WDBUPOCOwcZonaCwEodwdA8hwWYOiXroDF9vWGxZxKAnuSVE2Ieci/kvhR4EsWvgGST2h90LRowgO+msXe8+fA==", - "peer": true, "dependencies": { "cross-fetch": "^3.1.5" } @@ -402,7 +400,6 @@ "version": "2.7.2", "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.2.tgz", "integrity": "sha512-Fi6xAl5PUkqnjl3wo4rdcQIbMG3+yTRX1aUZe/yfvTG84RMvmCXJ1yN6MmafVLeZpU1xkaz5Vx4L0tnHcLiy6w==", - "peer": true, "dependencies": { "@types/phoenix": "^1.5.4", "@types/websocket": "^1.0.3", @@ -413,7 +410,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.1.tgz", "integrity": "sha512-nkR0fQA9ScAtIKA3vNoPEqbZv1k5B5HVRYEvRWdlP6mUpFphM9TwPL2jZ/ztNGMTG5xT6SrHr+H7Ykz8qzbhjw==", - "peer": true, "dependencies": { "cross-fetch": "^3.1.5" } @@ -422,7 +418,6 @@ "version": "2.21.0", "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.21.0.tgz", "integrity": "sha512-FW3ZzBoc4orSgfX0dXrmJoXAcI/hiekmqXTkN64vjtUF2Urp3UjyAf71UTtV9Jl6ejHoe3K++e0+Rg9zKUJh5w==", - "peer": true, "dependencies": { "@supabase/functions-js": "^2.1.0", "@supabase/gotrue-js": "^2.23.0", @@ -453,8 +448,7 @@ "node_modules/@types/phoenix": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.6.tgz", - "integrity": "sha512-e7jZ6I9uyRGsg7MNwQcarmBvRlbGb9DibbocE9crVnxqsy6C23RMxLWbJ2CQ3vgCW7taoL1L+F02EcjA6ld7XA==", - "peer": true + "integrity": "sha512-e7jZ6I9uyRGsg7MNwQcarmBvRlbGb9DibbocE9crVnxqsy6C23RMxLWbJ2CQ3vgCW7taoL1L+F02EcjA6ld7XA==" }, "node_modules/@types/prop-types": { "version": "15.7.5", @@ -488,7 +482,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.5.tgz", "integrity": "sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==", - "peer": true, "dependencies": { "@types/node": "*" } @@ -823,7 +816,6 @@ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", "hasInstallScript": true, - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -940,7 +932,6 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "peer": true, "dependencies": { "node-fetch": "^2.6.11" } @@ -967,7 +958,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "peer": true, "dependencies": { "es5-ext": "^0.10.50", "type": "^1.0.1" @@ -1240,7 +1230,6 @@ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", "hasInstallScript": true, - "peer": true, "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", @@ -1254,7 +1243,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "peer": true, "dependencies": { "d": "1", "es5-ext": "^0.10.35", @@ -1265,7 +1253,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "peer": true, "dependencies": { "d": "^1.0.1", "ext": "^1.1.2" @@ -1731,7 +1718,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "peer": true, "dependencies": { "type": "^2.7.2" } @@ -1739,8 +1725,7 @@ "node_modules/ext/node_modules/type": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "peer": true + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -2468,8 +2453,7 @@ "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "peer": true + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "node_modules/is-weakmap": { "version": "2.0.1", @@ -2807,14 +2791,12 @@ "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "peer": true + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "node_modules/node-fetch": { "version": "2.6.11", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -2834,7 +2816,6 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "peer": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -3723,8 +3704,7 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "peer": true + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/tsconfig-paths": { "version": "3.14.2", @@ -3764,8 +3744,7 @@ "node_modules/type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "peer": true + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, "node_modules/type-check": { "version": "0.4.0", @@ -3806,7 +3785,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "peer": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -3858,7 +3836,6 @@ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", "hasInstallScript": true, - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -3869,14 +3846,12 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "peer": true + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/websocket": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", - "peer": true, "dependencies": { "bufferutil": "^4.0.1", "debug": "^2.2.0", @@ -3893,7 +3868,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -3901,14 +3875,12 @@ "node_modules/websocket/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -3993,7 +3965,6 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", - "peer": true, "engines": { "node": ">=0.10.32" } diff --git a/examples/user-management/nextjs-user-management/package.json b/examples/user-management/nextjs-user-management/package.json index 46855c9727d..d1eb00e7889 100644 --- a/examples/user-management/nextjs-user-management/package.json +++ b/examples/user-management/nextjs-user-management/package.json @@ -9,7 +9,8 @@ "lint": "next lint" }, "dependencies": { - "@supabase/auth-helpers-nextjs": "^0.7.1", + "@supabase/auth-helpers-nextjs": "latest", + "@supabase/supabase-js": "latest", "@supabase/auth-ui-react": "^0.4.2", "@supabase/auth-ui-shared": "^0.1.6", "@types/node": "20.1.4", @@ -17,8 +18,8 @@ "@types/react-dom": "18.2.4", "encoding": "^0.1.13", "eslint": "8.40.0", - "eslint-config-next": "13.4.2", - "next": "13.4.2", + "eslint-config-next": "latest", + "next": "latest", "react": "18.2.0", "react-dom": "18.2.0", "typescript": "5.0.4" diff --git a/package-lock.json b/package-lock.json index 2291fa1b4f8..8f7cf61213f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "workspaces": [ "apps/www", "apps/docs", - "apps/new-docs", "studio", "tests", "packages/*" From ae656ae24216b37c2caed87a9b1e4b4b5c5ac952 Mon Sep 17 00:00:00 2001 From: Jon Meyers Date: Fri, 23 Jun 2023 21:15:03 +1000 Subject: [PATCH 087/108] remove yarn examples --- .../guides/auth/auth-helpers/nextjs-pages.mdx | 33 ++----------------- .../pages/guides/auth/auth-helpers/nextjs.mdx | 21 +----------- .../pages/guides/auth/auth-helpers/remix.mdx | 24 +------------- 3 files changed, 4 insertions(+), 74 deletions(-) diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx index b592401fc90..1ec291bd86b 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx @@ -14,15 +14,7 @@ This submodule provides convenience helpers for implementing user authentication ## Install the Next.js helper library - - - -```sh +```bash npm install @supabase/auth-helpers-nextjs @supabase/supabase-js ``` @@ -33,31 +25,10 @@ This library supports the following tooling versions: Additionally, install the **React Auth Helpers** for components and hooks that can be used across all React-based frameworks. -```sh +```bash npm install @supabase/auth-helpers-react ``` - - - -```sh -yarn add @supabase/auth-helpers-nextjs @supabase/supabase-js -``` - -This library supports the following tooling versions: - -- Node.js: `^10.13.0 || >=12.0.0` -- Next.js: `>=10` - -Additionally, install the **React Auth Helpers** for components and hooks that can be used across all React-based frameworks. - -```sh -yarn add @supabase/auth-helpers-react -``` - - - - ## Set up environment variables Retrieve your project URL and anon key in your project's [API settings](https://app.supabase.com/project/_/settings/api) in the Dashboard to set up the following environment variables. For local development you can set them in a `.env.local` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/nextjs/.env.local.example). diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx index d6aea4ace3f..601959afb21 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx @@ -62,29 +62,10 @@ npm run dev ### Install Next.js Auth Helpers library - - - - -```sh +```bash npm install @supabase/auth-helpers-nextjs @supabase/supabase-js ``` - - - -```sh -yarn add @supabase/auth-helpers-nextjs @supabase/supabase-js -``` - - - - ### Declare Environment Variables Retrieve your project's URL and anon key from your [API settings](https://app.supabase.com/project/_/settings/api), and create a `.env.local` file with the following environment variables: diff --git a/apps/docs/pages/guides/auth/auth-helpers/remix.mdx b/apps/docs/pages/guides/auth/auth-helpers/remix.mdx index 2cb3e0a1bce..96b92c4e0c5 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/remix.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/remix.mdx @@ -22,15 +22,7 @@ This submodule provides convenience helpers for implementing user authentication ## Install the Remix helper library - - - -```sh +```bash npm install @supabase/auth-helpers-remix @supabase/supabase-js ``` @@ -38,20 +30,6 @@ This library supports the following tooling versions: - Remix: `>=1.7.2` - - - -```sh -yarn add @supabase/auth-helpers-remix @supabase/supabase-js -``` - -This library supports the following tooling versions: - -- Remix: `>=1.7.2` - - - - ## Set up environment variables Retrieve your project URL and anon key in your project's [API settings](https://app.supabase.com/project/_/settings/api) in the Dashboard to set up the following environment variables. For local development you can set them in a `.env` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/remix/.env.example). From a15e23743a50f65c91fd14ae54f521ce3eb16fde Mon Sep 17 00:00:00 2001 From: Jon Meyers Date: Fri, 23 Jun 2023 21:19:19 +1000 Subject: [PATCH 088/108] use consistent headers for npm i blocks --- apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx | 4 ++-- apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx | 6 +++--- apps/docs/pages/guides/auth/auth-helpers/remix.mdx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx index 1ec291bd86b..bfadfe16dd3 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx @@ -14,7 +14,7 @@ This submodule provides convenience helpers for implementing user authentication ## Install the Next.js helper library -```bash +```sh Terminal npm install @supabase/auth-helpers-nextjs @supabase/supabase-js ``` @@ -25,7 +25,7 @@ This library supports the following tooling versions: Additionally, install the **React Auth Helpers** for components and hooks that can be used across all React-based frameworks. -```bash +```sh Terminal npm install @supabase/auth-helpers-react ``` diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx index 601959afb21..892c0a49373 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx @@ -29,7 +29,7 @@ If you are using the `pages` directory, check out [Auth Helpers in Next.js Pages Use the `create-next-app` command and the `with-supabase` template to automate the configuration of cookie-based auth with the Next.js Auth Helpers. -```sh +```sh Terminal npx create-next-app -e with-supabase ``` @@ -54,7 +54,7 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key Run the development server and navigate to http://localhost:3000. -```bash +```sh Terminal npm run dev ``` @@ -62,7 +62,7 @@ npm run dev ### Install Next.js Auth Helpers library -```bash +```sh Terminal npm install @supabase/auth-helpers-nextjs @supabase/supabase-js ``` diff --git a/apps/docs/pages/guides/auth/auth-helpers/remix.mdx b/apps/docs/pages/guides/auth/auth-helpers/remix.mdx index 96b92c4e0c5..85e3e2eaac8 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/remix.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/remix.mdx @@ -22,7 +22,7 @@ This submodule provides convenience helpers for implementing user authentication ## Install the Remix helper library -```bash +```sh Terminal npm install @supabase/auth-helpers-remix @supabase/supabase-js ``` From faec20853c2ea6149faae6cb03b0d94a13925ba9 Mon Sep 17 00:00:00 2001 From: Jon Meyers Date: Fri, 23 Jun 2023 21:25:34 +1000 Subject: [PATCH 089/108] revert set versions for package.json --- examples/auth/nextjs/package-lock.json | 120 +++++++++--------- examples/auth/nextjs/package.json | 8 +- .../nextjs-user-management/package-lock.json | 44 +++---- .../nextjs-user-management/package.json | 8 +- 4 files changed, 90 insertions(+), 90 deletions(-) diff --git a/examples/auth/nextjs/package-lock.json b/examples/auth/nextjs/package-lock.json index 36ba21b981a..7f7f8fb27e8 100644 --- a/examples/auth/nextjs/package-lock.json +++ b/examples/auth/nextjs/package-lock.json @@ -8,19 +8,19 @@ "name": "full-nextjs-auth-helpers", "version": "0.1.0", "dependencies": { - "@supabase/auth-helpers-nextjs": "latest", - "@supabase/supabase-js": "latest", + "@supabase/auth-helpers-nextjs": "^0.7.1", + "@supabase/supabase-js": "^2.26.0", "@types/node": "^20.2.3", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", "eslint": "^8.41.0", - "next": "latest", + "next": "^13.4.3", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.0.4" }, "devDependencies": { - "eslint-config-next": "latest" + "eslint-config-next": "^13.4.3" } }, "node_modules/@babel/runtime": { @@ -325,11 +325,11 @@ "dev": true }, "node_modules/@supabase/auth-helpers-nextjs": { - "version": "0.7.0-next.8", - "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.7.0-next.8.tgz", - "integrity": "sha512-7pCbdnyAQo+D4qYQCvfg4B3OHIPtrILy/4LNwmF+1uheDeK7B6pxfN2SVeOR2TU3lolxMwEKUP6Q0h1qiCXbQg==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.7.2.tgz", + "integrity": "sha512-n5IyGBYJV/WiR5Rgw4CUaiJYiOv9yW2of4ZP4EyzKt2O6/6rztt7PcGE4AoK2vERw+fb5F2QtJBdt6J5eOYCCw==", "dependencies": { - "@supabase/auth-helpers-shared": "0.4.0-next.3", + "@supabase/auth-helpers-shared": "0.4.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { @@ -337,9 +337,9 @@ } }, "node_modules/@supabase/auth-helpers-shared": { - "version": "0.4.0-next.3", - "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.4.0-next.3.tgz", - "integrity": "sha512-ELvEbYPJdTlwvqXsHkRN8ADcRe1IQxHt+UNqxbV2SKIpJCieQ4BMsgUVI/w+e3gMQHRbydk0PaJpOa9aStzMyg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.4.1.tgz", + "integrity": "sha512-IEDX9JzWkIjQiLUaP4Qy5YDiG0jFQatWfS+jw8cCQs6QfbNdEPd2Y3qonwGHnM90CZom9SvjuylBv2pFVAL7Lw==", "dependencies": { "jose": "^4.14.3" }, @@ -348,33 +348,33 @@ } }, "node_modules/@supabase/functions-js": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.1.tgz", - "integrity": "sha512-bIR1Puae6W+1/MzPfYBWOG/SCWGo4B5CB7c0ZZksvliNEAzhxNBJ0UFKYINcGdGtxG8ZC+1xr3utWpNZNwnoRw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.2.tgz", + "integrity": "sha512-QCR6pwJs9exCl37bmpMisUd6mf+0SUBJ6mUpiAjEkSJ/+xW8TCuO14bvkWHADd5hElJK9MxNlMQXxSA4DRz9nQ==", "dependencies": { "cross-fetch": "^3.1.5" } }, "node_modules/@supabase/gotrue-js": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.27.1.tgz", - "integrity": "sha512-rewbfKTC9DZNnPy4rRpr5ViBXre55wEXPWOueh/ZgwunR1TGmntRcraIM6SDKrP6iRD6ucx325a467uZ5U+KWg==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.31.0.tgz", + "integrity": "sha512-YcwlbbNfedlue/HVIXtYBb4fuOrs29gNOTl6AmyxPp4zryRxzFvslVN9kmLDBRUAVU9fnPJh2bgOR3chRjJX5w==", "dependencies": { "cross-fetch": "^3.1.5" } }, "node_modules/@supabase/postgrest-js": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.6.1.tgz", - "integrity": "sha512-WDBUPOCOwcZonaCwEodwdA8hwWYOiXroDF9vWGxZxKAnuSVE2Ieci/kvhR4EsWvgGST2h90LRowgO+msXe8+fA==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.7.1.tgz", + "integrity": "sha512-xPRYLaZrkLbXNlzmHW6Wtf9hmcBLjjI5xUz2zj8oE2hgXGaYoZBBkpN9bmW9i17Z1f6Ujxa942AqK439XOA36A==", "dependencies": { "cross-fetch": "^3.1.5" } }, "node_modules/@supabase/realtime-js": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.2.tgz", - "integrity": "sha512-Fi6xAl5PUkqnjl3wo4rdcQIbMG3+yTRX1aUZe/yfvTG84RMvmCXJ1yN6MmafVLeZpU1xkaz5Vx4L0tnHcLiy6w==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.3.tgz", + "integrity": "sha512-c7TzL81sx2kqyxsxcDduJcHL9KJdCOoKimGP6lQSqiZKX42ATlBZpWbyy9KFGFBjAP4nyopMf5JhPi2ZH9jyNw==", "dependencies": { "@types/phoenix": "^1.5.4", "@types/websocket": "^1.0.3", @@ -390,14 +390,14 @@ } }, "node_modules/@supabase/supabase-js": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.22.0.tgz", - "integrity": "sha512-omkgSWL1HwnpXZZy+yshFLx/qqxwk9kx9jbRM6IHNEylKrH/sz6JX7rGyIOmN9niDxMRjsCu1nwuBhD6IiF2xg==", + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.26.0.tgz", + "integrity": "sha512-RXmTPTobaYAwkSobadHZmEVLmzX3SGrtRZIGfLWnLv92VzBRrjuXn0a+bJqKl50GUzsyqPA+j5pod7EwMkcH5A==", "dependencies": { "@supabase/functions-js": "^2.1.0", - "@supabase/gotrue-js": "^2.26.0", - "@supabase/postgrest-js": "^1.1.1", - "@supabase/realtime-js": "^2.7.2", + "@supabase/gotrue-js": "^2.31.0", + "@supabase/postgrest-js": "^1.7.0", + "@supabase/realtime-js": "^2.7.3", "@supabase/storage-js": "^2.5.1", "cross-fetch": "^3.1.5" } @@ -422,9 +422,9 @@ "integrity": "sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw==" }, "node_modules/@types/phoenix": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.6.tgz", - "integrity": "sha512-e7jZ6I9uyRGsg7MNwQcarmBvRlbGb9DibbocE9crVnxqsy6C23RMxLWbJ2CQ3vgCW7taoL1L+F02EcjA6ld7XA==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.0.tgz", + "integrity": "sha512-qwfpsHmFuhAS/dVd4uBIraMxRd56vwBUYQGZ6GpXnFuM2XMRFJbIyruFKKlW2daQliuYZwe0qfn/UjFCDKic5g==" }, "node_modules/@types/prop-types": { "version": "15.7.5", @@ -3925,50 +3925,50 @@ "dev": true }, "@supabase/auth-helpers-nextjs": { - "version": "0.7.0-next.8", - "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.7.0-next.8.tgz", - "integrity": "sha512-7pCbdnyAQo+D4qYQCvfg4B3OHIPtrILy/4LNwmF+1uheDeK7B6pxfN2SVeOR2TU3lolxMwEKUP6Q0h1qiCXbQg==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.7.2.tgz", + "integrity": "sha512-n5IyGBYJV/WiR5Rgw4CUaiJYiOv9yW2of4ZP4EyzKt2O6/6rztt7PcGE4AoK2vERw+fb5F2QtJBdt6J5eOYCCw==", "requires": { - "@supabase/auth-helpers-shared": "0.4.0-next.3", + "@supabase/auth-helpers-shared": "0.4.1", "set-cookie-parser": "^2.6.0" } }, "@supabase/auth-helpers-shared": { - "version": "0.4.0-next.3", - "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.4.0-next.3.tgz", - "integrity": "sha512-ELvEbYPJdTlwvqXsHkRN8ADcRe1IQxHt+UNqxbV2SKIpJCieQ4BMsgUVI/w+e3gMQHRbydk0PaJpOa9aStzMyg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.4.1.tgz", + "integrity": "sha512-IEDX9JzWkIjQiLUaP4Qy5YDiG0jFQatWfS+jw8cCQs6QfbNdEPd2Y3qonwGHnM90CZom9SvjuylBv2pFVAL7Lw==", "requires": { "jose": "^4.14.3" } }, "@supabase/functions-js": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.1.tgz", - "integrity": "sha512-bIR1Puae6W+1/MzPfYBWOG/SCWGo4B5CB7c0ZZksvliNEAzhxNBJ0UFKYINcGdGtxG8ZC+1xr3utWpNZNwnoRw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.2.tgz", + "integrity": "sha512-QCR6pwJs9exCl37bmpMisUd6mf+0SUBJ6mUpiAjEkSJ/+xW8TCuO14bvkWHADd5hElJK9MxNlMQXxSA4DRz9nQ==", "requires": { "cross-fetch": "^3.1.5" } }, "@supabase/gotrue-js": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.27.1.tgz", - "integrity": "sha512-rewbfKTC9DZNnPy4rRpr5ViBXre55wEXPWOueh/ZgwunR1TGmntRcraIM6SDKrP6iRD6ucx325a467uZ5U+KWg==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.31.0.tgz", + "integrity": "sha512-YcwlbbNfedlue/HVIXtYBb4fuOrs29gNOTl6AmyxPp4zryRxzFvslVN9kmLDBRUAVU9fnPJh2bgOR3chRjJX5w==", "requires": { "cross-fetch": "^3.1.5" } }, "@supabase/postgrest-js": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.6.1.tgz", - "integrity": "sha512-WDBUPOCOwcZonaCwEodwdA8hwWYOiXroDF9vWGxZxKAnuSVE2Ieci/kvhR4EsWvgGST2h90LRowgO+msXe8+fA==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.7.1.tgz", + "integrity": "sha512-xPRYLaZrkLbXNlzmHW6Wtf9hmcBLjjI5xUz2zj8oE2hgXGaYoZBBkpN9bmW9i17Z1f6Ujxa942AqK439XOA36A==", "requires": { "cross-fetch": "^3.1.5" } }, "@supabase/realtime-js": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.2.tgz", - "integrity": "sha512-Fi6xAl5PUkqnjl3wo4rdcQIbMG3+yTRX1aUZe/yfvTG84RMvmCXJ1yN6MmafVLeZpU1xkaz5Vx4L0tnHcLiy6w==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.3.tgz", + "integrity": "sha512-c7TzL81sx2kqyxsxcDduJcHL9KJdCOoKimGP6lQSqiZKX42ATlBZpWbyy9KFGFBjAP4nyopMf5JhPi2ZH9jyNw==", "requires": { "@types/phoenix": "^1.5.4", "@types/websocket": "^1.0.3", @@ -3984,14 +3984,14 @@ } }, "@supabase/supabase-js": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.22.0.tgz", - "integrity": "sha512-omkgSWL1HwnpXZZy+yshFLx/qqxwk9kx9jbRM6IHNEylKrH/sz6JX7rGyIOmN9niDxMRjsCu1nwuBhD6IiF2xg==", + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.26.0.tgz", + "integrity": "sha512-RXmTPTobaYAwkSobadHZmEVLmzX3SGrtRZIGfLWnLv92VzBRrjuXn0a+bJqKl50GUzsyqPA+j5pod7EwMkcH5A==", "requires": { "@supabase/functions-js": "^2.1.0", - "@supabase/gotrue-js": "^2.26.0", - "@supabase/postgrest-js": "^1.1.1", - "@supabase/realtime-js": "^2.7.2", + "@supabase/gotrue-js": "^2.31.0", + "@supabase/postgrest-js": "^1.7.0", + "@supabase/realtime-js": "^2.7.3", "@supabase/storage-js": "^2.5.1", "cross-fetch": "^3.1.5" } @@ -4016,9 +4016,9 @@ "integrity": "sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw==" }, "@types/phoenix": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.6.tgz", - "integrity": "sha512-e7jZ6I9uyRGsg7MNwQcarmBvRlbGb9DibbocE9crVnxqsy6C23RMxLWbJ2CQ3vgCW7taoL1L+F02EcjA6ld7XA==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.0.tgz", + "integrity": "sha512-qwfpsHmFuhAS/dVd4uBIraMxRd56vwBUYQGZ6GpXnFuM2XMRFJbIyruFKKlW2daQliuYZwe0qfn/UjFCDKic5g==" }, "@types/prop-types": { "version": "15.7.5", diff --git a/examples/auth/nextjs/package.json b/examples/auth/nextjs/package.json index bb8f44af4a3..1e70631c6a7 100644 --- a/examples/auth/nextjs/package.json +++ b/examples/auth/nextjs/package.json @@ -9,18 +9,18 @@ "lint": "next lint" }, "dependencies": { - "@supabase/auth-helpers-nextjs": "latest", - "@supabase/supabase-js": "latest", + "@supabase/auth-helpers-nextjs": "^0.7.1", + "@supabase/supabase-js": "^2.26.0", "@types/node": "^20.2.3", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", "eslint": "^8.41.0", - "next": "latest", + "next": "^13.4.3", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.0.4" }, "devDependencies": { - "eslint-config-next": "latest" + "eslint-config-next": "^13.4.3" } } diff --git a/examples/user-management/nextjs-user-management/package-lock.json b/examples/user-management/nextjs-user-management/package-lock.json index a6e680c3585..3fc8087103f 100644 --- a/examples/user-management/nextjs-user-management/package-lock.json +++ b/examples/user-management/nextjs-user-management/package-lock.json @@ -8,17 +8,17 @@ "name": "nextjs-user-management", "version": "0.1.0", "dependencies": { - "@supabase/auth-helpers-nextjs": "latest", + "@supabase/auth-helpers-nextjs": "^0.7.1", "@supabase/auth-ui-react": "^0.4.2", "@supabase/auth-ui-shared": "^0.1.6", - "@supabase/supabase-js": "latest", + "@supabase/supabase-js": "^2.26.0", "@types/node": "20.1.4", "@types/react": "18.2.6", "@types/react-dom": "18.2.4", "encoding": "^0.1.13", "eslint": "8.40.0", - "eslint-config-next": "latest", - "next": "latest", + "eslint-config-next": "13.4.2", + "next": "13.4.2", "react": "18.2.0", "react-dom": "18.2.0", "typescript": "5.0.4" @@ -381,25 +381,25 @@ } }, "node_modules/@supabase/gotrue-js": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.26.0.tgz", - "integrity": "sha512-orxz8nwaF5D1nY/9H5xxTfFSCTvYeDLx24UO/Mxsx83xFP0t5RNxQZ0lEHBOhHhXJ4vR/COv79AxoWCOTu/7Rg==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.31.0.tgz", + "integrity": "sha512-YcwlbbNfedlue/HVIXtYBb4fuOrs29gNOTl6AmyxPp4zryRxzFvslVN9kmLDBRUAVU9fnPJh2bgOR3chRjJX5w==", "dependencies": { "cross-fetch": "^3.1.5" } }, "node_modules/@supabase/postgrest-js": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.6.1.tgz", - "integrity": "sha512-WDBUPOCOwcZonaCwEodwdA8hwWYOiXroDF9vWGxZxKAnuSVE2Ieci/kvhR4EsWvgGST2h90LRowgO+msXe8+fA==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.7.1.tgz", + "integrity": "sha512-xPRYLaZrkLbXNlzmHW6Wtf9hmcBLjjI5xUz2zj8oE2hgXGaYoZBBkpN9bmW9i17Z1f6Ujxa942AqK439XOA36A==", "dependencies": { "cross-fetch": "^3.1.5" } }, "node_modules/@supabase/realtime-js": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.2.tgz", - "integrity": "sha512-Fi6xAl5PUkqnjl3wo4rdcQIbMG3+yTRX1aUZe/yfvTG84RMvmCXJ1yN6MmafVLeZpU1xkaz5Vx4L0tnHcLiy6w==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.3.tgz", + "integrity": "sha512-c7TzL81sx2kqyxsxcDduJcHL9KJdCOoKimGP6lQSqiZKX42ATlBZpWbyy9KFGFBjAP4nyopMf5JhPi2ZH9jyNw==", "dependencies": { "@types/phoenix": "^1.5.4", "@types/websocket": "^1.0.3", @@ -415,14 +415,14 @@ } }, "node_modules/@supabase/supabase-js": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.21.0.tgz", - "integrity": "sha512-FW3ZzBoc4orSgfX0dXrmJoXAcI/hiekmqXTkN64vjtUF2Urp3UjyAf71UTtV9Jl6ejHoe3K++e0+Rg9zKUJh5w==", + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.26.0.tgz", + "integrity": "sha512-RXmTPTobaYAwkSobadHZmEVLmzX3SGrtRZIGfLWnLv92VzBRrjuXn0a+bJqKl50GUzsyqPA+j5pod7EwMkcH5A==", "dependencies": { "@supabase/functions-js": "^2.1.0", - "@supabase/gotrue-js": "^2.23.0", - "@supabase/postgrest-js": "^1.1.1", - "@supabase/realtime-js": "^2.7.2", + "@supabase/gotrue-js": "^2.31.0", + "@supabase/postgrest-js": "^1.7.0", + "@supabase/realtime-js": "^2.7.3", "@supabase/storage-js": "^2.5.1", "cross-fetch": "^3.1.5" } @@ -446,9 +446,9 @@ "integrity": "sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q==" }, "node_modules/@types/phoenix": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.6.tgz", - "integrity": "sha512-e7jZ6I9uyRGsg7MNwQcarmBvRlbGb9DibbocE9crVnxqsy6C23RMxLWbJ2CQ3vgCW7taoL1L+F02EcjA6ld7XA==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.0.tgz", + "integrity": "sha512-qwfpsHmFuhAS/dVd4uBIraMxRd56vwBUYQGZ6GpXnFuM2XMRFJbIyruFKKlW2daQliuYZwe0qfn/UjFCDKic5g==" }, "node_modules/@types/prop-types": { "version": "15.7.5", diff --git a/examples/user-management/nextjs-user-management/package.json b/examples/user-management/nextjs-user-management/package.json index d1eb00e7889..27ebcd19f6a 100644 --- a/examples/user-management/nextjs-user-management/package.json +++ b/examples/user-management/nextjs-user-management/package.json @@ -9,8 +9,8 @@ "lint": "next lint" }, "dependencies": { - "@supabase/auth-helpers-nextjs": "latest", - "@supabase/supabase-js": "latest", + "@supabase/auth-helpers-nextjs": "^0.7.1", + "@supabase/supabase-js": "^2.26.0", "@supabase/auth-ui-react": "^0.4.2", "@supabase/auth-ui-shared": "^0.1.6", "@types/node": "20.1.4", @@ -18,8 +18,8 @@ "@types/react-dom": "18.2.4", "encoding": "^0.1.13", "eslint": "8.40.0", - "eslint-config-next": "latest", - "next": "latest", + "eslint-config-next": "13.4.2", + "next": "13.4.2", "react": "18.2.0", "react-dom": "18.2.0", "typescript": "5.0.4" From 2b48d42b6f216782fe1fd4b9dbc37675fc81aa3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Fri, 23 Jun 2023 13:38:49 +0200 Subject: [PATCH 090/108] fix: overages banner should link to usage page --- studio/components/ui/OveragesBanner/OveragesBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/ui/OveragesBanner/OveragesBanner.tsx b/studio/components/ui/OveragesBanner/OveragesBanner.tsx index 9962c9d5ca2..f07981140d5 100644 --- a/studio/components/ui/OveragesBanner/OveragesBanner.tsx +++ b/studio/components/ui/OveragesBanner/OveragesBanner.tsx @@ -85,7 +85,7 @@ const OveragesBanner: FC = ({ tier, minimal }) => { actions={ minimal ? (
- + From 8d332e49375cf3ed46fa50cbacb2363873582753 Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Fri, 23 Jun 2023 17:05:21 +0200 Subject: [PATCH 091/108] feat: add loading indicator to `Sign in with Github` button --- studio/components/interfaces/SignIn/SignInWithGitHub.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/studio/components/interfaces/SignIn/SignInWithGitHub.tsx b/studio/components/interfaces/SignIn/SignInWithGitHub.tsx index 091b2ee750c..36199298427 100644 --- a/studio/components/interfaces/SignIn/SignInWithGitHub.tsx +++ b/studio/components/interfaces/SignIn/SignInWithGitHub.tsx @@ -1,9 +1,14 @@ +import { useState } from 'react' import { BASE_PATH } from 'lib/constants' import { auth, getReturnToPath } from 'lib/gotrue' import { Button, IconGitHub } from 'ui' const SignInWithGitHub = () => { + const [loading, setLoading] = useState(false) + async function handleGithubSignIn() { + setLoading(true) + try { const { error } = await auth.signInWithOAuth({ provider: 'github', @@ -18,6 +23,8 @@ const SignInWithGitHub = () => { if (error) throw error } catch (error) { console.error(error) + } finally { + setLoading(false) } } @@ -28,6 +35,7 @@ const SignInWithGitHub = () => { icon={} size="large" type="default" + loading={loading} > Continue with GitHub From 222bdec204f5f58f349d16e820cf7e85ea7db17d Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Fri, 23 Jun 2023 18:10:40 +0200 Subject: [PATCH 092/108] feat: add Sentry events to monitor random logouts --- .../interfaces/SignIn/SignInForm.tsx | 7 +++ .../interfaces/SignIn/SignInWithGitHub.tsx | 7 +++ .../interfaces/SignIn/SignUpForm.tsx | 3 ++ studio/lib/auth.tsx | 4 +- studio/lib/local-storage.ts | 53 ++++++++++++++++++- 5 files changed, 72 insertions(+), 2 deletions(-) diff --git a/studio/components/interfaces/SignIn/SignInForm.tsx b/studio/components/interfaces/SignIn/SignInForm.tsx index d9adb6e4b6a..4d20f3fa307 100644 --- a/studio/components/interfaces/SignIn/SignInForm.tsx +++ b/studio/components/interfaces/SignIn/SignInForm.tsx @@ -3,10 +3,12 @@ import { useQueryClient } from '@tanstack/react-query' import { useStore } from 'hooks' import { usePushNext } from 'hooks/misc/useAutoAuthRedirect' import { auth } from 'lib/gotrue' +import { incrementSignInClicks } from 'lib/local-storage' import Link from 'next/link' import { useRef, useState } from 'react' import { Button, Form, Input } from 'ui' import { object, string } from 'yup' +import * as Sentry from '@sentry/nextjs' const signInSchema = object({ email: string().email('Must be a valid email').required('Email is required'), @@ -33,6 +35,11 @@ const SignInForm = () => { token = captchaResponse?.response ?? null } + const signInClicks = incrementSignInClicks() + if (signInClicks > 1) { + Sentry.captureMessage('Sign in without previous sing out detected') + } + const { error } = await auth.signInWithPassword({ email, password, diff --git a/studio/components/interfaces/SignIn/SignInWithGitHub.tsx b/studio/components/interfaces/SignIn/SignInWithGitHub.tsx index 36199298427..d6cad1da894 100644 --- a/studio/components/interfaces/SignIn/SignInWithGitHub.tsx +++ b/studio/components/interfaces/SignIn/SignInWithGitHub.tsx @@ -1,7 +1,9 @@ import { useState } from 'react' import { BASE_PATH } from 'lib/constants' import { auth, getReturnToPath } from 'lib/gotrue' +import { incrementSignInClicks } from 'lib/local-storage' import { Button, IconGitHub } from 'ui' +import * as Sentry from '@sentry/nextjs' const SignInWithGitHub = () => { const [loading, setLoading] = useState(false) @@ -10,6 +12,11 @@ const SignInWithGitHub = () => { setLoading(true) try { + const signInClicks = incrementSignInClicks() + if (signInClicks > 1) { + Sentry.captureMessage('Sign in without previous sign out detected') + } + const { error } = await auth.signInWithOAuth({ provider: 'github', options: { diff --git a/studio/components/interfaces/SignIn/SignUpForm.tsx b/studio/components/interfaces/SignIn/SignUpForm.tsx index b84ae70b19e..b206e80e8a6 100644 --- a/studio/components/interfaces/SignIn/SignUpForm.tsx +++ b/studio/components/interfaces/SignIn/SignUpForm.tsx @@ -7,6 +7,7 @@ import { useStore } from 'hooks' import { post } from 'lib/common/fetch' import { API_URL, BASE_PATH } from 'lib/constants' import { passwordSchema } from 'lib/schemas' +import { resetSignInClicks } from 'lib/local-storage' import PasswordConditionsHelper from './PasswordConditionsHelper' const signUpSchema = passwordSchema.shape({ @@ -33,6 +34,8 @@ const SignUpForm = () => { token = captchaResponse?.response ?? null } + resetSignInClicks() + const response = await post(`${API_URL}/signup`, { email, password, diff --git a/studio/lib/auth.tsx b/studio/lib/auth.tsx index 762cc3da511..e8fcc561adf 100644 --- a/studio/lib/auth.tsx +++ b/studio/lib/auth.tsx @@ -12,7 +12,7 @@ import { useProfileQuery } from 'data/profile/profile-query' import { useStore } from 'hooks' import Telemetry from 'lib/telemetry' import { GOTRUE_ERRORS, IS_PLATFORM } from './constants' -import { clearLocalStorage } from './local-storage' +import { clearLocalStorage, resetSignInClicks } from './local-storage' import { useProfileCreateMutation } from 'data/profile/profile-create-mutation' export const AuthContext = AuthContextInternal @@ -99,6 +99,8 @@ export function useSignOut() { const queryClient = useQueryClient() return useCallback(async () => { + resetSignInClicks() + const result = await gotrueClient.signOut() clearLocalStorage() await queryClient.resetQueries() diff --git a/studio/lib/local-storage.ts b/studio/lib/local-storage.ts index 611bcd1616c..d9747df8753 100644 --- a/studio/lib/local-storage.ts +++ b/studio/lib/local-storage.ts @@ -1,4 +1,11 @@ -export const LOCAL_STORAGE_KEYS_ALLOWLIST = ['graphiql:theme', 'theme', 'supabaseDarkMode'] +import { IS_PLATFORM } from './constants' + +export const LOCAL_STORAGE_KEYS_ALLOWLIST = [ + 'graphiql:theme', + 'theme', + 'supabaseDarkMode', + 'supabase.dashboard.sign_in_clicks', +] export function clearLocalStorage() { for (const key in localStorage) { @@ -7,3 +14,47 @@ export function clearLocalStorage() { } } } + +function inferSignInClicks() { + if (localStorage.getItem('supabase.dashboard.sign_in_clicks')) { + return + } + + localStorage.setItem( + 'supabase.dashboard.sign_in_clicks', + localStorage.getItem('supabase.dashboard.auth.token') ? '1' : '0' + ) +} + +export function getSignInClicks(): number { + let count: number | null = null + + try { + count = JSON.parse(localStorage.getItem('supabase.dashboard.sign_in_clicks') || '0') + } catch (e: any) { + // do nothing + } + + return count || 0 +} + +export function incrementSignInClicks(): number { + const clicks = getSignInClicks() + + localStorage.setItem('supabase.dashboard.sign_in_clicks', JSON.stringify(clicks + 1)) + + return clicks + 1 +} + +export function resetSignInClicks(): number { + const clicks = getSignInClicks() + + localStorage.setItem('supabase.dashboard.sign_in_clicks', '0') + + return clicks +} + +if (globalThis && globalThis.localStorage && IS_PLATFORM) { + // populate the value based on the current local storage state + inferSignInClicks() +} From ace0f5aa9a00612f28da59221aec54d457b40807 Mon Sep 17 00:00:00 2001 From: Diego Valenzuela Date: Fri, 23 Jun 2023 14:06:51 -0400 Subject: [PATCH 093/108] Update nuxtjs.mdx, import line missing with the current example a just made nuxt 3 returns a 500 error, createClient is not defined so you need to import it from the library --- apps/docs/pages/guides/getting-started/quickstarts/nuxtjs.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/pages/guides/getting-started/quickstarts/nuxtjs.mdx b/apps/docs/pages/guides/getting-started/quickstarts/nuxtjs.mdx index 60c3a3ee0f5..e082c676a93 100644 --- a/apps/docs/pages/guides/getting-started/quickstarts/nuxtjs.mdx +++ b/apps/docs/pages/guides/getting-started/quickstarts/nuxtjs.mdx @@ -88,6 +88,7 @@ export const meta = { ```vue src/App.vue

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%0DZ-AC#N=|z=_3TzcLef4?Auz!K2Ebmjr)1mQK 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�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%0A?HZ60F8vXlc4! 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 z