diff --git a/.github/workflows/og_images.yml b/.github/workflows/og_images.yml index 2e1461461d2..74ded0819c8 100644 --- a/.github/workflows/og_images.yml +++ b/.github/workflows/og_images.yml @@ -25,4 +25,4 @@ jobs: with: version: 1.0.0 - - run: supabase functions deploy og-images --project-ref $PROJECT_ID + - run: supabase functions deploy og-images --project-ref $PROJECT_ID --no-verify-jwt diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 371aa1b881e..aef4d8f166d 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -51,3 +51,15 @@ jobs: # reporter: github-pr-review # level: warning # command: npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,json}' + + format-sql: + runs-on: ubuntu-latest + steps: + - name: Check out repo + uses: actions/checkout@v3 + - name: Install plugin + run: npm ci + - name: Run prettier + run: |- + # Check mdx files which contain sql code blocks + grep -lr '```sql' apps/docs/pages/**/*.mdx | xargs npx prettier -c diff --git a/.prettierignore b/.prettierignore index db287264935..3703e192616 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,4 +4,10 @@ node_modules package-lock.json docker* apps/**/out -**/**.mdx \ No newline at end of file +# prettier-plugin-sql-cst only supports sqlite syntax +**/supabase/migrations/*.sql +apps/www/schema.sql +examples/slack-clone/nextjs-slack-clone/full-schema.sql +# ignore files with custom js formatting +apps/docs/pages/guides/auth/*.mdx +apps/docs/pages/guides/integrations/*.mdx diff --git a/.prettierrc b/.prettierrc index 76c3f65353b..b9bda117c38 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,5 +4,6 @@ "semi": false, "singleQuote": true, "printWidth": 100, - "endOfLine": "lf" + "endOfLine": "lf", + "sqlKeywordCase": "lower" } diff --git a/README.md b/README.md index d6498236c88..fcf1cfe5aba 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ To see how to Contribute, visit [Getting Started](./DEVELOPERS.md) - [x] Alpha: We are testing Supabase with a closed set of customers - [x] Public Alpha: Anyone can sign up over at [app.supabase.com](https://app.supabase.com). But go easy on us, there are a few kinks - [x] Public Beta: Stable enough for most non-enterprise use-cases -- [ ] Public: Production-ready +- [ ] Public: General Availability [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)] We are currently in Public Beta. Watch "releases" of this repo to get notified of major updates. diff --git a/apps/docs/components/Extensions.tsx b/apps/docs/components/Extensions.tsx index a3ab9bff99e..b7b7260cd64 100644 --- a/apps/docs/components/Extensions.tsx +++ b/apps/docs/components/Extensions.tsx @@ -94,8 +94,15 @@ export default function Extensions() { filters.length === 0 ? x : x.tags.some((item) => filters.includes(item)) ) .map((extension) => ( - - + +

{extension.comment.charAt(0).toUpperCase() + extension.comment.slice(1)} diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 88c2d614951..11043ca46c4 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -512,6 +512,24 @@ export const database = { }, ], }, + { + name: 'Postgres resources', + url: undefined, + items: [ + { + name: 'Drop all tables in schema', + url: '/guides/database/postgres/dropping-all-tables-in-schema', + }, + { + name: 'Select first row per group', + url: '/guides/database/postgres/first-row-in-group', + }, + { + name: 'Print PostgreSQL version', + url: '/guides/database/postgres/which-version-of-postgres', + }, + ], + }, { name: 'Configuration', url: undefined, @@ -637,7 +655,7 @@ export const platform = { url: undefined, items: [ { name: 'Access Control', url: '/guides/platform/access-control', items: [] }, - { name: 'Database Usage', url: '/guides/platform/database-usage', items: [] }, + { name: 'Database Size', url: '/guides/platform/database-size', items: [] }, { name: 'HTTP Status Codes', url: '/guides/platform/http-status-codes', items: [] }, { name: 'Logging', url: '/guides/platform/logs', items: [] }, { name: 'Metrics', url: '/guides/platform/metrics', items: [] }, @@ -727,24 +745,6 @@ export const resources = { }, ], }, - { - name: 'Postgres resources', - url: undefined, - items: [ - { - name: 'Drop all tables in schema', - url: '/guides/resources/postgres/dropping-all-tables-in-schema', - }, - { - name: 'Select first row per group', - url: '/guides/resources/postgres/first-row-in-group', - }, - { - name: 'Print PostgreSQL version', - url: '/guides/resources/postgres/which-version-of-postgres', - }, - ], - }, ], } diff --git a/apps/docs/layouts/DefaultLayout.tsx b/apps/docs/layouts/DefaultLayout.tsx index 0a6b48c5547..2d0b3e9696c 100644 --- a/apps/docs/layouts/DefaultLayout.tsx +++ b/apps/docs/layouts/DefaultLayout.tsx @@ -1,34 +1,68 @@ import { MDXProvider } from '@mdx-js/react' +import { NextSeo } from 'next-seo' import Head from 'next/head' +import { useRouter } from 'next/router' import { FC } from 'react' import components from '~/components' import TableOfContents from '~/components/TableOfContents' interface Props { - meta: { title: string; description?: string; hide_table_of_contents?: boolean; video?: string } + meta: { + title: string + description?: string + hide_table_of_contents?: boolean + video?: string + canonical?: string + } children: any toc?: any menuItems: any } const Layout: FC = (props: Props) => { + const { asPath, basePath } = useRouter() + const hasTableOfContents = props.toc !== undefined && props.toc.json.filter((item) => item.lvl !== 1 && item.lvl <= 3).length > 0 + console.log('asPath', asPath) + return ( <> - {props.meta?.title} | Supabase Docs + + {asPath === '/' ? 'Supabase Docs' : `${props.meta?.title} | Supabase Docadsdsds`} + + + - - - - - - +

{/*

Tutorials

*/}
diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index 5899d282fc7..d6f245d989e 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -1,5 +1,6 @@ import { MDXProvider } from '@mdx-js/react' import { NextSeo } from 'next-seo' +import Head from 'next/head' import Link from 'next/link' import { useRouter } from 'next/router' import { FC, useEffect, useRef, useState } from 'react' @@ -63,17 +64,27 @@ const Layout: FC = (props) => { const hasTableOfContents = tocList.length > 0 + // page type, ie, Auth, Database, Storage etc const ogPageType = asPath.split('/')[2] + // open graph image url constructor + const ogImageUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ + ogPageType ? `&type=${ogPageType}` : '' + }&title=${encodeURIComponent(props.meta?.title)}&description=${encodeURIComponent( + props.meta?.description + )}` return ( <> + + {props.meta?.title} | Supabase Docs + + + + + = (props) => { modifiedTime: new Date().toISOString(), authors: ['Supabase'], }, - images: [ - { - url: `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ - ogPageType ? `&type=${ogPageType}` : '' - }&title=${encodeURIComponent(props.meta?.title)}&description=${encodeURIComponent( - props.meta?.description - )}`, - }, - ], }} />
diff --git a/apps/docs/package.json b/apps/docs/package.json index 717e8d05d28..af029c6fc86 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -46,7 +46,7 @@ "@radix-ui/react-accordion": "^1.0.1", "@supabase/auth-helpers-nextjs": "^0.5.4", "@supabase/auth-helpers-react": "^0.3.1", - "@supabase/supabase-js": "^2.4.1", + "@supabase/supabase-js": "^2.8.0", "algoliasearch": "^4.14.2", "babel": "^6.23.0", "clsx": "^1.2.1", @@ -101,7 +101,6 @@ "next-transpile-modules": "9.0.0", "npm-run-all": "^4.1.5", "openapi-types": "^12.0.2", - "prettier": "^2.2.1", "sass": "^1.55.0", "ts-node": "^10.9.1", "tsconfig": "*", diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx index 4a3f65a5680..8c50d49a468 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -2,6 +2,7 @@ import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' import { SessionContextProvider } from '@supabase/auth-helpers-react' import { ThemeProvider } from 'common/Providers' import { DefaultSeo } from 'next-seo' +import Head from 'next/head' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import { AppPropsWithLayout } from 'types' @@ -9,8 +10,8 @@ import ClippyProvider from '~/components/Clippy/ClippyProvider' import { SearchProvider } from '~/components/DocSearch' import Favicons from '~/components/Favicons' import SiteLayout from '~/layouts/SiteLayout' -import { post } from '~/lib/fetchWrappers' import { IS_PLATFORM } from '~/lib/constants' +import { post } from '~/lib/fetchWrappers' import '../styles/algolia-search.scss' import '../styles/ch.scss' import '../styles/docsearch.scss' @@ -59,34 +60,10 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { }, [router.events]) const SITE_TITLE = 'Supabase Documentation' - const SITE_DESCRIPTION = 'The open source Firebase alternative.' - const { basePath } = useRouter() return ( <> - {IS_PLATFORM ? ( diff --git a/apps/docs/pages/guides/auth/managing-user-data.mdx b/apps/docs/pages/guides/auth/managing-user-data.mdx index 462f160b238..8b25736949a 100644 --- a/apps/docs/pages/guides/auth/managing-user-data.mdx +++ b/apps/docs/pages/guides/auth/managing-user-data.mdx @@ -38,6 +38,12 @@ Primary keys are **guaranteed not to change**. Columns, indices, constraints or You may delete users directly or via the management console at Authentication > Users. Note that deleting a user from the `auth.users` table does not automatically sign out a user. As Supabase makes use of JSON Web Tokens (JWT), a user's JWT will remain "valid" until it has expired. Should you wish to immediately revoke access for a user, do considering making use of a Row Level Security policy as described below. + +You cannot delete a user if they are the owner of any objects in Supabase Storage. + +You will encounter an error when you try to delete an Auth user that owns any Storage objects. If this happens, try deleting all the objects for that user, or reassign ownership to another user. + + ## Public access Since Row Level Security is enabled, this table is accessible via the API but no data will be returned unless we set up some Policies. diff --git a/apps/docs/pages/guides/auth/row-level-security.mdx b/apps/docs/pages/guides/auth/row-level-security.mdx index 1be866869e0..473bbb154d3 100644 --- a/apps/docs/pages/guides/auth/row-level-security.mdx +++ b/apps/docs/pages/guides/auth/row-level-security.mdx @@ -329,6 +329,129 @@ Tip: Make sure to enable RLS for all your tables, so that your tables are inacce Supabase provides special "Service" keys, which can be used to bypass all RLS. These should never be used in the browser or exposed to customers, but they are useful for administrative tasks. +### Testing policies + +To test policies on the database itself (i.e., from the [SQL Editor](https://app.supabase.com/project/_/sql) or from `psql`) without switching to your frontend and logging in as different users, you can utilize the following helper SQL procedures ([credits](https://github.com/supabase/supabase/issues/7311#issuecomment-1398648114)): + +```sql +grant anon, authenticated to postgres; + +create or replace procedure auth.login_as_user (user_email text) + language plpgsql + as $$ +declare + auth_user auth.users; +begin + select + * into auth_user + from + auth.users + where + email = user_email; + execute format('set request.jwt.claim.sub=%L', (auth_user).id::text); + execute format('set request.jwt.claim.role=%I', (auth_user).role); + execute format('set request.jwt.claim.email=%L', (auth_user).email); + execute format('set request.jwt.claims=%L', json_strip_nulls(json_build_object('app_metadata', (auth_user).raw_app_meta_data))::text); + + raise notice '%', format( 'set role %I; -- logging in as %L (%L)', (auth_user).role, (auth_user).id, (auth_user).email); + execute format('set role %I', (auth_user).role); +end; +$$; + +create or replace procedure auth.login_as_anon () + language plpgsql + as $$ +begin + set request.jwt.claim.sub=''; + set request.jwt.claim.role=''; + set request.jwt.claim.email=''; + set request.jwt.claims=''; + set role anon; +end; +$$; + +create or replace procedure auth.logout () + language plpgsql + as $$ +begin + set request.jwt.claim.sub=''; + set request.jwt.claim.role=''; + set request.jwt.claim.email=''; + set request.jwt.claims=''; + set role postgres; +end; +$$; +``` + +To switch to a given user (by email), use `call auth.login_as_user('my@email.com');`. You can also switch to the `anon` role using `call auth.login_as_anon();`. When you are done, use `call auth.logout();` to return yourself to the `postgres` role. + +These procedures can also be used for writing [pgTAP](/docs/guides/database/extensions/pgtap) unit tests for policies. + +
+ Click here to see an example `psql` interaction using this. + +This example shows that the `public.profiles` table from the tutorial example can indeed be updated by the `postgres` role and the owner of the row but not from `anon` connections: + +```shell +postgres=> select id, email from auth.users; + id | email +--------------------------------------+------------------- + d4f0aa86-e6f6-41d1-bd32-391f077cf1b9 | user1@example.com + 15d6811a-16ee-4fa2-9b18-b63085688be4 | user2@example.com + 4e1010bb-eb37-4a4d-a05a-b0ee315c9d56 | user3@example.com +(3 rows) + +postgres=> table public.profiles; + id | updated_at | username | full_name | avatar_url | website +--------------------------------------+------------+----------+-----------+------------+--------- + d4f0aa86-e6f6-41d1-bd32-391f077cf1b9 | | user1 | User 1 | | + 15d6811a-16ee-4fa2-9b18-b63085688be4 | | user2 | User 2 | | + 4e1010bb-eb37-4a4d-a05a-b0ee315c9d56 | | user3 | User 3 | | +(3 rows) + +postgres=> call auth.login_as_anon(); +CALL +postgres=> update public.profiles set updated_at=now(); +UPDATE 0 -- anon users cannot update any profile but see all of them +postgres=> table public.profiles; + id | updated_at | username | full_name | avatar_url | website +--------------------------------------+------------+----------+-----------+------------+--------- + d4f0aa86-e6f6-41d1-bd32-391f077cf1b9 | | user1 | User 1 | | + 15d6811a-16ee-4fa2-9b18-b63085688be4 | | user2 | User 2 | | + 4e1010bb-eb37-4a4d-a05a-b0ee315c9d56 | | user3 | User 3 | | +(3 rows) + +postgres=> call auth.logout(); +CALL +postgres=> call auth.login_as_user('user1@example.com'); +NOTICE: set role authenticated; -- logging in as 'd4f0aa86-e6f6-41d1-bd32-391f077cf1b9' ('user1@example.com') +CALL +postgres=> update public.profiles set updated_at=now(); +UPDATE 1 -- authenticated users can update their own profile and see all of them +postgres=> table public.profiles; + id | updated_at | username | full_name | avatar_url | website +--------------------------------------+-------------------------------+----------+-----------+------------+--------- + 15d6811a-16ee-4fa2-9b18-b63085688be4 | | user1 | User 1 | | + 4e1010bb-eb37-4a4d-a05a-b0ee315c9d56 | | user2 | User 2 | | + d4f0aa86-e6f6-41d1-bd32-391f077cf1b9 | 2023-02-18 21:39:16.204612+00 | user3 | User 3 | | +(3 rows) + +postgres=> call auth.logout(); +CALL +postgres=> update public.profiles set updated_at=now(); +UPDATE 3 -- the 'postgres' role can update and see all profiles +postgres=> table public.profiles; + id | updated_at | username | full_name | avatar_url | website +--------------------------------------+-------------------------------+----------+-----------+------------+--------- + 15d6811a-16ee-4fa2-9b18-b63085688be4 | 2023-02-18 21:40:08.216324+00 | user1 | User 1 | | + 4e1010bb-eb37-4a4d-a05a-b0ee315c9d56 | 2023-02-18 21:40:08.216324+00 | user2 | User 2 | | + d4f0aa86-e6f6-41d1-bd32-391f077cf1b9 | 2023-02-18 21:40:08.216324+00 | user3 | User 3 | | +(3 rows) + +``` + +
+ ## Deprecated features We have deprecate some functions to ensure better performance and extensibilty of RLS policies. diff --git a/apps/docs/pages/guides/auth/server-side-rendering.mdx b/apps/docs/pages/guides/auth/server-side-rendering.mdx index 635407cd986..346984a63fb 100644 --- a/apps/docs/pages/guides/auth/server-side-rendering.mdx +++ b/apps/docs/pages/guides/auth/server-side-rendering.mdx @@ -160,6 +160,12 @@ use this potentially stale information to render a page. ## Frequently Asked Questions +### No session on the server side with Next.js route prefetching? + +When you use route prefetching in Next.js using `` components or the `Router.push()` APIs can send server-side requests before the browser processes the access and refresh tokens. This means that those requests may not have any cookies set and your server code will render unauthenticated content. + +To improve experience for your users, we recommend redirecting users to one specific page after sign-in that does not include any route prefetching from Next.js. Once the Supabase client library running in the browser has obtained the access and refresh tokens from the URL fragment, you can send users to any pages that use prefetching. + ### How do I make the cookies `HttpOnly`? This is not necessary. Both the access token and refresh token are designed to diff --git a/apps/docs/pages/guides/cli/local-development.mdx b/apps/docs/pages/guides/cli/local-development.mdx index 1c8f21a9b05..0514e6b108a 100644 --- a/apps/docs/pages/guides/cli/local-development.mdx +++ b/apps/docs/pages/guides/cli/local-development.mdx @@ -178,9 +178,9 @@ This creates a new migration named `supabase/migrations/_create_emplo Use the seed script in `supabase/seed.sql` (created with [`supabase init`](/docs/reference/cli/usage#supabase-init)) to add sample data to the table. ```sql - -- in supabase/seed.sql -insert into - public.employees (name) +-- in supabase/seed.sql +insert into public.employees + (name) values ('Erlich Bachman'), ('Richard Hendricks'), @@ -200,9 +200,9 @@ You should now see the contents of `employees` in Studio. Use the [`reset`](/docs/reference/cli/usage#supabase-db-reset) command to revert any changes to the local database. ```sql - -- run on local database to make a change -alter table - employees add department text default 'Hooli'; +-- run on local database to make a change +alter table employees +add department text default 'Hooli'; ``` Run the following command to reset the local database: diff --git a/apps/docs/pages/guides/database/api.mdx b/apps/docs/pages/guides/database/api.mdx index fc8f86a18ae..04e5ecb97db 100644 --- a/apps/docs/pages/guides/database/api.mdx +++ b/apps/docs/pages/guides/database/api.mdx @@ -390,7 +390,7 @@ This ensures that any `delete()` or `update()` would fail if there are no accomp To confirm that safeupdate is enabled for queries going through the API of your project, the following query could be run: ```sql -select usename,useconfig from pg_shadow where usename = 'authenticator' ; +select usename, useconfig from pg_shadow where usename = 'authenticator'; ``` The expected value for `useconfig` should be: diff --git a/apps/docs/pages/guides/database/arrays.mdx b/apps/docs/pages/guides/database/arrays.mdx index 17595464aee..e59be4a6d4b 100644 --- a/apps/docs/pages/guides/database/arrays.mdx +++ b/apps/docs/pages/guides/database/arrays.mdx @@ -30,7 +30,10 @@ Create a test table with a text array (an array of strings): ```sql -CREATE TABLE arraytest (id integer NOT NULL, textarray text ARRAY); +create table arraytest ( + id integer not null, + textarray text array +); ``` @@ -95,7 +98,7 @@ You should see: ```sql -SELECT * FROM arraytest; +select * from arraytest; ``` You should see: diff --git a/apps/docs/pages/guides/database/extensions/pg_stat_monitor.mdx b/apps/docs/pages/guides/database/extensions/pg_stat_monitor.mdx index 4c1363b83b9..232bbb116e0 100644 --- a/apps/docs/pages/guides/database/extensions/pg_stat_monitor.mdx +++ b/apps/docs/pages/guides/database/extensions/pg_stat_monitor.mdx @@ -76,6 +76,7 @@ The following table shows a subset of available columns: A full list of statistics is available in the [pg_stat_monitor docs](https://docs.percona.com/pg-stat-monitor/reference.html#postgresql-15). ## Functions + - [`pg_stat_monitor_reset()`](https://docs.percona.com/pg-stat-monitor/functions.html): Resets the statistics tracked by the `pg_stat_monitor` view and deletes all previous data. - [`pg_stat_monitor_version()`](https://docs.percona.com/pg-stat-monitor/functions.html): Displays the version of the `pg_stat_monitor` extension. diff --git a/apps/docs/pages/guides/database/extensions/pgcron.mdx b/apps/docs/pages/guides/database/extensions/pgcron.mdx index 1062db0fd32..8b4b5016c4d 100644 --- a/apps/docs/pages/guides/database/extensions/pgcron.mdx +++ b/apps/docs/pages/guides/database/extensions/pgcron.mdx @@ -84,7 +84,7 @@ select cron.schedule ( Vacuum every day at 3:00am (GMT) ```sql -SELECT cron.schedule('nightly-vacuum', '0 3 * * *', 'VACUUM'); +select cron.schedule('nightly-vacuum', '0 3 * * *', 'VACUUM'); ``` ### Invoke Supabase Edge Function every minute @@ -112,7 +112,7 @@ select Unschedules a job called `'nightly-vacuum'` ```sql -SELECT cron.unschedule('nightly-vacuum'); +select cron.unschedule('nightly-vacuum'); ``` ## Resources diff --git a/apps/docs/pages/guides/database/extensions/pgnet.mdx b/apps/docs/pages/guides/database/extensions/pgnet.mdx index 3246fb4f5df..5a93b7a48b5 100644 --- a/apps/docs/pages/guides/database/extensions/pgnet.mdx +++ b/apps/docs/pages/guides/database/extensions/pgnet.mdx @@ -16,7 +16,6 @@ The pg_net API is in alpha. Functions signatures may change. It differs from the `http` extension in that it is asynchronous by default. This makes it useful in blocking functions (like triggers). - ## Enable the extension @@ -70,8 +83,7 @@ The functions we'll cover in this guide are: Converts your data into searchable "tokens". `to_tsvector()` stands for "to text search vector". For example: ```sql -select to_tsvector('green eggs and ham') - +select to_tsvector('green eggs and ham'); -- Returns 'egg':2 'green':1 'ham':4 ``` diff --git a/apps/docs/pages/guides/database/functions.mdx b/apps/docs/pages/guides/database/functions.mdx index fda28116abe..7d55d1e7e01 100644 --- a/apps/docs/pages/guides/database/functions.mdx +++ b/apps/docs/pages/guides/database/functions.mdx @@ -138,7 +138,8 @@ create table planets ( name text ); -insert into planets (id, name) +insert into planets + (id, name) values (1, 'Tattoine'), (2, 'Alderaan'), @@ -150,7 +151,8 @@ create table people ( planet_id bigint references planets ); -insert into people (id, name, planet_id) +insert into people + (id, name, planet_id) values (1, 'Anakin Skywalker', 1), (2, 'Luke Skywalker', 1), diff --git a/apps/docs/pages/guides/database/json.mdx b/apps/docs/pages/guides/database/json.mdx index 969fdb74a77..60bbda028ee 100644 --- a/apps/docs/pages/guides/database/json.mdx +++ b/apps/docs/pages/guides/database/json.mdx @@ -221,12 +221,11 @@ Select the title, description, price, and age range for each book. ```sql select title, - metadata -> 'description' AS description, + metadata -> 'description' as description, metadata -> 'price' as price, metadata -> 'ages' -> 0 as low_age, metadata -> 'ages' -> 1 as high_age -from - books; +from books; ``` diff --git a/apps/docs/pages/guides/resources/postgres/dropping-all-tables-in-schema.mdx b/apps/docs/pages/guides/database/postgres/dropping-all-tables-in-schema.mdx similarity index 100% rename from apps/docs/pages/guides/resources/postgres/dropping-all-tables-in-schema.mdx rename to apps/docs/pages/guides/database/postgres/dropping-all-tables-in-schema.mdx diff --git a/apps/docs/pages/guides/resources/postgres/first-row-in-group.mdx b/apps/docs/pages/guides/database/postgres/first-row-in-group.mdx similarity index 100% rename from apps/docs/pages/guides/resources/postgres/first-row-in-group.mdx rename to apps/docs/pages/guides/database/postgres/first-row-in-group.mdx diff --git a/apps/docs/pages/guides/resources/postgres/which-version-of-postgres.mdx b/apps/docs/pages/guides/database/postgres/which-version-of-postgres.mdx similarity index 98% rename from apps/docs/pages/guides/resources/postgres/which-version-of-postgres.mdx rename to apps/docs/pages/guides/database/postgres/which-version-of-postgres.mdx index 29bea3ab32b..3a04c7ce06a 100644 --- a/apps/docs/pages/guides/resources/postgres/which-version-of-postgres.mdx +++ b/apps/docs/pages/guides/database/postgres/which-version-of-postgres.mdx @@ -12,7 +12,7 @@ Run the following query using the [SQL Editor](https://app.supabase.com/project/ ```sql select - version (); + version(); ``` Which should return something like: diff --git a/apps/docs/pages/guides/database/sql-to-api.mdx b/apps/docs/pages/guides/database/sql-to-api.mdx index 636addcf3b9..6c17121d110 100644 --- a/apps/docs/pages/guides/database/sql-to-api.mdx +++ b/apps/docs/pages/guides/database/sql-to-api.mdx @@ -11,10 +11,11 @@ export const meta = { Select a set of columns from a single table with where, order by, and limit clauses. ```sql -select first_name, last_name, team_id, age from players +select first_name, last_name, team_id, age +from players where age between 20 and 24 and team_id <> 'STL' order by last_name, first_name desc -limit 20 +limit 20; ``` ```js @@ -32,8 +33,9 @@ const { data, error } = await supabase Select all columns from a single table with a complex where clause: OR AND OR ```sql -select * from players -where ((team_id = 'CHN' or team_id is null) and (age > 35 or age is null)) +select * +from players +where ((team_id = 'CHN' or team_id is null) and (age > 35 or age is null)); ``` ```js @@ -48,8 +50,9 @@ const { data, error } = await supabase Select all columns from a single table with a complex where clause: AND OR AND ```sql -select * from players -where ((team_id = 'CHN' and age > 35) or (team_id <> 'CHN' and age is not null)) +select * +from players +where ((team_id = 'CHN' and age > 35) or (team_id <> 'CHN' and age is not null)); ``` ```js @@ -62,8 +65,9 @@ const { data, error } = await supabase Get a count of rows, but don't return any data. ```sql -select count(*) from players -where team_id = 'NYM' +select count(*) +from players +where team_id = 'NYM'; ``` ```js diff --git a/apps/docs/pages/guides/database/tables.mdx b/apps/docs/pages/guides/database/tables.mdx index 9ed2619b47d..dd75065339f 100644 --- a/apps/docs/pages/guides/database/tables.mdx +++ b/apps/docs/pages/guides/database/tables.mdx @@ -189,8 +189,14 @@ Use the "Bulk Loading" instructions if you are loading large data sets. insert into movies (name, description) values - ('The Empire Strikes Back', 'After the Rebels are brutally overpowered by the Empire on the ice planet Hoth, Luke Skywalker begins Jedi training with Yoda.'), - ('Return of the Jedi', 'After a daring mission to rescue Han Solo from Jabba the Hutt, the Rebels dispatch to Endor to destroy the second Death Star.'); + ( + 'The Empire Strikes Back', + 'After the Rebels are brutally overpowered by the Empire on the ice planet Hoth, Luke Skywalker begins Jedi training with Yoda.' + ), + ( + 'Return of the Jedi', + 'After a daring mission to rescue Han Solo from Jabba the Hutt, the Rebels dispatch to Endor to destroy the second Death Star.' + ); ``` @@ -425,14 +431,15 @@ As a query becomes complex it becomes a hassle to call it. Especially when we ru ```sql select - students.name, - students.type, - courses.title, - courses.code, - grades.result -from grades -left join students on grades.student_id = students.id -left join courses on grades.course_id = courses.id; + students.name, + students.type, + courses.title, + courses.code, + grades.result +from + grades + left join students on grades.student_id = students.id + left join courses on grades.course_id = courses.id; ``` We can run this instead: @@ -449,14 +456,15 @@ Views ensure that the likelihood of mistakes decreases when repeatedly executing ```sql select - students.name, - students.type, - courses.title, - courses.code, - grades.result -from grades - left join students on grades.student_id = students.id - left join courses on grades.course_id = courses.id + students.name, + students.type, + courses.title, + courses.code, + grades.result +from + grades + left join students on grades.student_id = students.id + left join courses on grades.course_id = courses.id where courses.code != 'PG101'; ``` @@ -478,13 +486,14 @@ Using our example above, a materialized view can be created like this: ```sql create materialized view transcripts as - select - students.name, - students.type, - courses.title, - courses.code, - grades.result - from grades + select + students.name, + students.type, + courses.title, + courses.code, + grades.result + from + grades left join students on grades.student_id = students.id left join courses on grades.course_id = courses.id; ``` diff --git a/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx b/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx index 830e91723c8..7cfcc5feb19 100644 --- a/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx +++ b/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx @@ -16,7 +16,7 @@ export const meta = { >
-[Clouflare Turnstile](https://www.cloudflare.com/products/turnstile/) is a friendly, free CAPTCHA replacement, and it works seamlessly with Supabase Edge Functions to protect your forms. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/cloudflare-turnstile). +[Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/) is a friendly, free CAPTCHA replacement, and it works seamlessly with Supabase Edge Functions to protect your forms. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/cloudflare-turnstile). ## Setup diff --git a/apps/docs/pages/guides/getting-started/quickstarts/flutter.mdx b/apps/docs/pages/guides/getting-started/quickstarts/flutter.mdx index 339eb20dd2f..476c5813490 100644 --- a/apps/docs/pages/guides/getting-started/quickstarts/flutter.mdx +++ b/apps/docs/pages/guides/getting-started/quickstarts/flutter.mdx @@ -176,6 +176,7 @@ export const meta = { Run your app on a platform of your choosing! By default an app should launch in your web browser. Note that `supabase_flutter` is compatible with web, iOS, Android, macOS, and Windows apps. + Running the app on MacOS requires additional configuration to [set the entitlements](https://docs.flutter.dev/development/platform-integration/macos/building#setting-up-entitlements). diff --git a/apps/docs/pages/guides/integrations/illa.mdx b/apps/docs/pages/guides/integrations/illa.mdx index e30c07e88f3..3dbe3b53616 100644 --- a/apps/docs/pages/guides/integrations/illa.mdx +++ b/apps/docs/pages/guides/integrations/illa.mdx @@ -25,9 +25,9 @@ Supabase offers a variety of options for populating tables with data, including Fill out the info in the table. The database is now set up. -### Step 2: Build UI on ILLA Builder +### Step 2: Build UI on ILLA Cloud -On [ILLA Builder](https://fast-try.illacloud.com/), click Create New to create a new application. +On [ILLA Cloud](https://cloud.illacloud.com/), click Create New to create a new application. ![Create new project on ILLA Builder](/docs/img/guides/integrations/illa/supabase-illa-create-project.png) diff --git a/apps/docs/pages/guides/integrations/prisma.mdx b/apps/docs/pages/guides/integrations/prisma.mdx index c2f0d5a8f14..3623a52ec40 100644 --- a/apps/docs/pages/guides/integrations/prisma.mdx +++ b/apps/docs/pages/guides/integrations/prisma.mdx @@ -17,6 +17,7 @@ This guide explains how to quickly connect the Postgres database provided by Sup ## Step 1: Get the connection string from Supabase project settings Go to the settings page from the sidebar and navigate to the **Database** tab. You’ll find the database’s connection string with a placeholder for the password you provided when you created the project. + ![Getting the connection string](/docs/img/guides/integrations/prisma/zntcsh3ic91gf1gy8j73.png) ## Step 2: Testing the connection @@ -29,22 +30,17 @@ In case you don’t have a Prisma project or this is your first time working wit ### Cloning the starter project -Navigate into a directory of your choice and run the following command in your terminal if you’re on a Windows machine: +Navigate into a directory of your choice and run the following command in your terminal: ```bash -curl https://pris.ly/quickstart -L -o quickstart-main.tar.gz && tar -zxvf quickstart-main.tar.gz quickstart-main/typescript/starter && move quickstart-main\typescript\starter starter && rmdir /S /Q quickstart-main && del /Q quickstart-main.tar.gz -``` - -And if you’re using Mac OS or Linux, run the following command: - -```bash -curl -L https://pris.ly/quickstart | tar -xz --strip=2 quickstart-main/typescript/starter +curl https://codeload.github.com/prisma/prisma-examples/tar.gz/latest | tar -xz --strip=2 prisma-examples-latest/databases/postgresql-supabase ``` You can now navigate into the directory and install the project’s dependencies: ```bash -cd starter && npm install +cd postgresql-supabase +npm install ``` ### A look at the project’s structure @@ -52,53 +48,43 @@ cd starter && npm install This project comes with TypeScript configured and has the following structure. - A `prisma` directory which contains: - - A `dev.db` file: This is a SQLite database. - - A `schema.prisma` file: Where we define the different database models and relations between them. -- A `.env` file: Contains the `DATABASE_URL` variable, which Prisma will use. -- A `script.ts` file: where we will run some queries using Prisma Client. - This starter also comes with the following packages installed: + - A `seed.ts` file: This is the data used to seed your database. + - A `schema.prisma` file: Where you define the different database models and relations between them. +- A `script.ts` file: where you will run some queries using Prisma Client. + +This starter also comes with the following packages installed: - [`@prisma/client`](https://www.npmjs.com/package/@prisma/client): An auto-generated and type-safe query builder that’s _tailored_ to your data. - [`prisma`](https://www.npmjs.com/package/prisma): Prisma’s command-line interface (CLI). It allows you to initialize new project assets, generate Prisma Client, and analyze existing database structures through introspection to automatically create your application models. - > Note: Prisma works with both JavaScript and TypeScript. However, to get the best possible development experience, using TypeScript is highly recommended. -### Configuring the project to use PostgreSQL +> Note: Prisma works with both JavaScript and TypeScript. However, to get the best possible development experience, using TypeScript is highly recommended. -By default, Prisma migrations will try to drop the `postgres` database, which can lead to conflicts with Supabase databases. For this scenario, use [Prisma Shadow Databases](https://www.prisma.io/docs/concepts/components/prisma-migrate/shadow-database#cloud-hosted-shadow-databases-must-be-created-manually). +### Configuring the project -Create a shadow database in your PostgreSQL server within the same Supabase project using the `psql` CLI and the `DATABASE_URL` from the previous steps (or use the local database). +Create a `.env` file at the root of your project: ```bash -psql postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432 +touch .env ``` -After you connect to your project's PostgreSQL instance, create another database (e.g., `postgres_shadow`): - -```bash -postgres=> CREATE DATABASE postgres_shadow; -postgres=> exit -``` - -Go ahead and delete the `prisma/dev.db` file because we will be switching to PostgreSQL. -In the `.env` file, update `DATABASE_URL` and `SHADOW_DATABASE_URL` to the connection string from **step 1**. The `.env` file should look like: +In the `.env` file, add a `DATABASE_URL` variable and add the connection string from **step 1**. The `.env` file should look like: ```env # .env DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres" -SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres_shadow" ``` -In the `schema.prisma` file, change the `provider` from "sqlite" to `"postgresql"` and add the `shadowDatabaseUrl` property. This is what your `schema.prisma` file should look like: ```go datasource db { - provider = "postgresql" - url = env("DATABASE_URL") - shadowDatabaseUrl = env("SHADOW_DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") } + generator client { provider = "prisma-client-js" } + model Post { id Int @id @default(autoincrement()) title String @@ -107,6 +93,7 @@ model Post { author User? @relation(fields: [authorId], references: [id]) authorId Int? } + model User { id Int @id @default(autoincrement()) email String @unique @@ -115,53 +102,81 @@ model User { } ``` + To test that everything works correctly, run the following command to create a migration: ```bash -prisma migrate dev --name init +npx prisma migrate dev --name init ``` -You can optionally give your migration a name, depending on the changes you made. Since this is the project’s first migration, you’re setting the `--name` flag to “init”. -If everything works correctly, you should get the following message in your terminal: +You can optionally give your migration a name, depending on the changes you made. Since this is the project’s first migration, you’re setting the `--name` flag to “init”. If everything works correctly, you should get the following message in your terminal: ```text Your database is now in sync with your schema. -:heavy_check_mark: Generated Prisma Client (2.x.x) to ./node_modules/@prisma/client in 111ms +:heavy_check_mark: Generated Prisma Client (4.x.x) to ./node_modules/@prisma/client in 111ms ``` This will create a `prisma/migrations` folder inside your `prisma` directory and synchronize your Prisma schema with your database schema. -> Note: if you want to skip the process of creating a migration history, you can use the [`db push`](https://www.prisma.io/docs/concepts/components/prisma-migrate/db-push) command instead of `migrate dev`. -> If you go to your Supabase project, in the table editor, you should see that two tables have been created, a `Post` and a `User` table. -> ![tables created in the UI](/docs/img/guides/integrations/prisma/7y4qq4wwvfrheti6r09u.png) -> That’s it! You have now successfully connected a Prisma project to a PostgreSQL database hosted on Supabase and ran your first migration. +> **Note**: If you want to skip the process of creating a migration history, you can use the [`prisma db push`](https://www.prisma.io/docs/concepts/components/prisma-migrate/db-push) command instead of `prisma migrate dev`. However, we recommend using `prisma migrate dev` to evolve your database schema in development. +> If you would like to get a conceptual overview of how Prisma Migrate works and which commands to use in what environment, refer to [this page in the Prisma documentation](https://www.prisma.io/docs/concepts/components/prisma-migrate/mental-model). + +If you go to your Supabase project, in the table editor, you should see that two tables have been created, a `Post`, `User`, and `_prisma_migrations` tables. The `_prisma_migrations` table is used to keep + +![tables created in the UI](/docs/img/guides/integrations/prisma/7y4qq4wwvfrheti6r09u.png) + +That’s it! You have now successfully connected a Prisma project to a PostgreSQL database hosted on Supabase and ran your first migration. ## Connection pooling with Supabase -If you’re working in a serverless environment (for example Node.js functions hosted on AWS Lambda, Vercel or Netlify Functions), you need to set up [connection pooling](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#serverless-environments-faas) using a tool like [PgBouncer](https://www.pgbouncer.org/). That’s because every function invocation may result in a [new connection to the database](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#the-serverless-challenge). Supabase [supports connection management using PgBouncer](https://supabase.io/blog/2021/04/02/supabase-pgbouncer#what-is-connection-pooling) and are enabled by default. -Go to the **Database** page from the sidebar in the Supabase dashboard and navigate to **connection pool** settings +If you’re working in a serverless environment (for example Node.js functions hosted on AWS Lambda, Vercel or Netlify Functions), you need to set up [connection pooling](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#serverless-environments-faas) using a tool like [PgBouncer](https://www.pgbouncer.org/). That’s because every function invocation may result in a [new connection to the database](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#the-serverless-challenge). + +Supabase [supports connection management using PgBouncer](/docs/guides/database/connecting-to-postgres#connection-pool) which prevents a traffic spike from overwhelming your database. + +Go to the **Database** page from the sidebar in the Supabase dashboard and navigate to **Connection pool** settings: + ![Connection pool settings](/docs/img/guides/integrations/prisma/w0oowg8vq435ob5c3gf0.png) -When migrating, you need to use the non-pooled connection URL (like the one used in **step 1**). However, when deploying your app, use the pooled connection URL and add the `?pgbouncer=true` flag to the PostgreSQL connection URL. It's also recommended to minimize the number of concurrent connections by setting the `connection_limit` to `1`. The `.env` file should look like: + +When updating your database schema, you need to use the non-pooled connection URL (like the one used in **step 1**). You can configure the non-pooled connection string by using the `directUrl` property in the datasource block. + +Update your `.env` file with the following changes: +1. Rename the `DATABASE_URL` environment variable to `DIRECT_URL` +1. Create a `DATABASE_URL` environment variable and paste in the new connection string from the dashboard as its value + +Append the `?pgbouncer=true` flag to the `DATABASE_URL` variable. + +Your `.env` file should resemble the following: ```env # .env -DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:6543/postgres?pgbouncer=true&connection_limit=1" -SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres_shadow" +# PostgreSQL connection string used for migrations +DIRECT_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres" +# PostgreSQL connection string with pgBouncer config — used by Prisma Client +DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:6543/postgres?pgbouncer=true" + ``` -Prisma Migrate uses database transactions to check out the current state of the database and the migrations table. However, the Migration Engine is designed to use a single connection to the database, and does not support connection pooling with PgBouncer. If you attempt to run Prisma Migrate commands in any environment that uses PgBouncer for connection pooling, you might see the following error: +Update your Prisma schema by setting the `directUrl` in the datasource block: -```bash -Error: undefined: Database error -Error querying the database: db error: ERROR: prepared statement “s0” already exists +```go +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directURL = env("DIRECT_URL") +} ``` -This is a known issue and it is being worked on, you can follow the progress on this [GitHub issue](https://github.com/prisma/prisma/issues/6485). -If you want to learn more about Prisma, check out the [docs](https://www.prisma.io/docs). Also in case you have any questions or run into any issue, feel free to start a discussion in the repo’s [discussions section](https://github.com/prisma/prisma/discussions). +> **Note**: This feature is available from Prisma version [4.10.0](https://github.com/prisma/prisma/releases/tag/4.10.0) and higher. + +If you want to learn more about Prisma, check out the [docs](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#fields). Also in case you have any questions or run into any issue, feel free to start a discussion in the repo’s [discussions section](https://github.com/prisma/prisma/discussions). ## Troubleshooting -If you run `prisma migrate dev --name init` multiple times, it sometimes asks if you want to recreate the whole schema. If you chose yes, it will delete the public schema and recreate it. The default grants are missing after this. If you run into this problem, add a helper SQL for fixing the grants: +### Missing grants + +If your database schema is out of sync from your migration history, `prisma migrate dev` will detect a migration history conflict or a [schema drift](https://www.prisma.io/docs/guides/database/developing-with-prisma-migrate/troubleshooting-development#schema-drift). When `prisma migrate dev` detects the drift, it might ask to to reset your database schema. If you choose yes, it will delete the `public` schema along with the default grants defined in your database. + +If you run into this problem, create a draft migration using `prisma migrate dev --create-only`, and add the following helper SQL: ```sql grant usage on schema public to postgres, anon, authenticated, service_role; @@ -175,6 +190,98 @@ alter default privileges in schema public grant all on functions to postgres, an alter default privileges in schema public grant all on sequences to postgres, anon, authenticated, service_role; ``` +Run `prisma migrate dev` to apply the draft migration to the database. + +### Using Supabase Auth with Prisma + +If you would like to use Supabase Auth and Prisma in your application, you will have to enable the `multiSchema` Preview feature flag in the `generator` block of your Prisma schema: + +```go +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directURL = env("DIRECT_URL") +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["multiSchema"] +} +``` + +Next, specify the database schemas you would like to include in your Prisma schema: + +```go +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directURL = env("DIRECT_URL") + schemas = ["public", "auth"] +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["multiSchema"] +} +``` + +You can then specify what schema a model or enum belongs to using the `@@schema` attribute: + +```go +model User { + id Int @id + // ... + + @@schema("auth") // or @@schema("public") +} +``` + +To learn more about using Prisma with multiple database schemas, refer to [this page in the Prisma docs](https://www.prisma.io/docs/guides/database/multi-schema#learn-more-about-the-multischema-preview-feature). + +### Using PostgreSQL Row Level Security with Prisma + + +If you would like to use Row Level Security (RLS) with Prisma, check out the [Prisma Client Extension - Row Level Security example](https://github.com/prisma/prisma-client-extensions/tree/main/row-level-security) that provides the primitives you could use to build and extend Prisma Client in PostgreSQL. + +Also check out [useSupabaseRowLevelSecurity](https://github.com/dthyresson/prisma-extension-supabase-rls) Prisma Client extension that supports [Supabase RLS](/docs/guides/auth/row-level-security#authrole) and policies written to use [Supabase auth](/docs/guides/auth/overview). + +The example and extension use [Prisma Client extensions](https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions) Preview feature. + +### Enabling PosgreSQL extensions + +If you would like to use a PostgreSQL extension with Prisma, enable the `postgresqlExtensions` Preview feature flag in the `generator` block of your Prisma schema: + +```go +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directURL = env("DIRECT_URL") +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["postgresqlExtensions"] +} +``` + +Next, specify the extensions you need in the `datasource` block: + +```go +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directURL = env("DIRECT_URL") + extensions = [hstore(schema: "myHstoreSchema"), pg_trgm, postgis(version: "2.1")] +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["postgresqlExtensions"] +} +``` + +To learn more about using Prisma with PostgreSQL extensions, refer to [this page in the Prisma docs](https://www.prisma.io/docs/concepts/components/prisma-schema/postgresql-extensions). + ## Resources - [Prisma](https://prisma.io) official website. diff --git a/apps/docs/pages/guides/platform/custom-domains.mdx b/apps/docs/pages/guides/platform/custom-domains.mdx index 558496506d1..441ebef2f87 100644 --- a/apps/docs/pages/guides/platform/custom-domains.mdx +++ b/apps/docs/pages/guides/platform/custom-domains.mdx @@ -155,6 +155,7 @@ As with the final activation stage of the process for setting up a vanity subdom - Edge functions do not honor the custom domain or the vanity subdomain setting and they still have to be invoked via the `foobarbaz.supabase.co` domain. - A Supabase project can—at this time—use either a Custom Domain or a Vanity Subdomain, but not both. +- Some authentication flows like Sign-in with Twitter set cookies to track the progress of the flow. Make sure you use only one domain in your frontend application for this reason. Mixing calls to the Supabase domain `foobarbaz.supabase.co` and your custom domain could cause those flows to stop working due to the [Same Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) enforced on cookies by the browser. export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/platform/database-size.mdx b/apps/docs/pages/guides/platform/database-size.mdx new file mode 100644 index 00000000000..ebef529f39c --- /dev/null +++ b/apps/docs/pages/guides/platform/database-size.mdx @@ -0,0 +1,100 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'database-size', + title: 'Database size', + description: 'Understanding how database size applies to your subscription.', +} + +Database size refers to the _monthly average storage usage_, as reported by Postgres. This metric is reported in your project's [billing usage](https://app.supabase.com/project/_/settings/billing/usage) and is updated daily. As you read this document we will refer to "database size" and "disk size": + +- "Database size" is the total size of used storage from your database. +- "Disk size" describes the size of the underlying available storage. + +## Database space management + +### Database size + +This SQL query will show the current size of your Postgres database: + +```sql +select + sum(pg_database_size(pg_database.datname)) / (1024 * 1024) as db_size_mb +from pg_database; +``` + +This value is reported in the [database settings page](https://app.supabase.com/project/_/settings/database). + +Database Space is consumed primarily by your data, indexes, and materialized views. You can reduce your disk size by removing any of these and running a Vacuum operation. + + + +Depending on your billing tier, your database can go into read-only mode which can prevent you inserting and deleting data. There are instructions for managing read-only mode in the [Disk Management](#disk-management) section. + + + +### Vacuum operations + +Postgres does not immediately reclaim the physical space used by dead tuples (i.e., deleted rows) in the DB. They are marked as "removed" until a [vacuum operation](https://www.postgresql.org/docs/current/routine-vacuuming.html) is executed. As a result, deleting data from your database may not immediately reduce the reported disk usage. + + + +Vacuum operations can temporarily increase resource utilization, which may adversely impact the observed performance of your project until the maintenance is completed. + + + +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. +Running a manual vacuum after deleting large amounts of data from your DB could help reduce the database size reported by Postgres. + +### Preoccupied Space + +New Supabase projects have a database size of ~40-60mb. This space includes pre-installed extensions, schemas, and default Postgres data. Additional database size is used when installing extensions, even if those extensions are inactive. + +## Disk management + +Supabase uses network-attached storage to balance performance with scalability. The behavior of your disk depends on your billing tier. + +### Paid Tier Behavior + +Pro and Enterprise projects have auto-scaling Disk Storage. + +Disk storage expands automatically when the database reaches 90% of the disk size. The disk is expanded to be 50% larger (e.g., 8GB -> 12GB). Auto-scaling can only take place once every 6 hours. If within those 6 hours you reach 95% of the disk space, your project will enter read-only mode. + + + +If you intend to import a lot of data into your database which requires multiple disk expansions then [reach out to our team](https://app.supabase.com/support/new). For example, uploading more than 1.5x the current size of your database storage will put your database into [read-only mode](#read-only-mode). + + + +The maximum Disk Size for Pro Tier is 1024TB. If you need more than this, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan. + +### Free Tier Behavior + +Free Tier projects enter [read-only](#read-only-mode) mode when you exceed the 500mb limit. Once in read-only mode, you have several options: + +- [Upgrade to the Pro or Enterprise tier](https://app.supabase.com/project/_/settings/billing/subscription) to enable auto-scaling and expand beyond the 500mb database size limit. +- [Disable read-only mode](#disabling-read-only-mode) and reduce your database size. + +### Read-only mode + +In some cases Supabase may put your database into read-only mode to prevent your database from exceeding the billing or disk limitations. + +In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`. Regular operation (read-write mode) is automatically re-enabled once usage is below 95% of the disk size, + +### Disabling read-only mode + +You can manually override read-only mode to reduce disk size. To do this, run the following in the [SQL Editor](https://app.supabase.com/project/_/sql): + +```sql +SET + default_transaction_read_only = 'off'; +``` + +This allows you to delete data from within the session. After deleting data, you should run a vacuum to reclaim as much space as possible. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/platform/database-usage.mdx b/apps/docs/pages/guides/platform/database-usage.mdx deleted file mode 100644 index 6ce6385770d..00000000000 --- a/apps/docs/pages/guides/platform/database-usage.mdx +++ /dev/null @@ -1,74 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'database-usage', - title: 'Database usage', - description: 'Understanding how database usage applies to your subscription.', -} - -Database size refers to the _monthly average storage usage_, as reported by Postgres. This metric is reported in your project's [billing usage](https://app.supabase.com/project/_/settings/billing/usage) and is updated daily. -Database size is the total size of used storage from your database, whereas disk size describes the size of the underlying available storage. - -For an instantaneous live view of the DB size, you can execute in Postgres: - -```sql -select - sum(pg_database_size (pg_database.datname)) / (1024 * 1024) as db_size_mb -from - pg_database; -``` - -This value is also reported in the [database settings page](https://app.supabase.com/project/_/settings/database). - -## Database storage management - -Supabase uses network-attached storage to balance performance with scalability. -For Pro and Enterprise projects, disk size expands ~1.5x automatically (e.g., 8GB -> 12GB) when you reach 90% of the disk size. -Disk size expansion can only occur once every six hours. -Pro projects can store up to 1024TB. - -All projects enter read-only mode when you reach 95% of the disk size. In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`. -Regular operation (read-write mode) is automatically re-enabled once usage is below 95% of the disk size. - -If you need more than 1024TB of disk size or require multiple storage expansions in a short period of time, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan. - -### Increasing available disk size - -1. [Upgrade to the Pro or Enterprise plan](https://app.supabase.com/project/_/settings/billing/subscription) to increase your quota and expand your disk size automatically. - -2. Delete data from your project's database to lower its disk usage. If your database is already in read-only mode, run the following command to change the transaction mode to read-write for your session: - - ```sql - SET - default_transaction_read_only = 'off'; - ``` - - This allows you to delete data from within the session. - -### Preoccupied Space - -When launching a new project, your database size will be roughly ~40-60mb. -The space is used up by preinstalled extensions, schema/data used by our services that are offered with each project and default Postgres data. - -When installing additional extensions, even if you don't actively use them, additional database size is used. - -## Vacuum operations - -Postgres does not immediately reclaim the physical space used by dead tuples (i.e., deleted rows) in the DB. Instead, they are internally marked as removed until a [vacuum operation](https://www.postgresql.org/docs/current/routine-vacuuming.html) is executed. -As a result, deleting data from your database may not immediately reduce the reported disk usage. - - - -Vacuum operations can temporarily increase resource utilization, which may adversely impact the observed performance of your project until the maintenance is completed. - - - -Supabase projects have automatic vacuuming enabled, which ensures that these operations are performed regularly to keep the database healthy and performant. -However, it can be necessary to either [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. -For example, running a manual vacuum after deleting large amounts of data from your DB could help reduce the reported disk usage by Postgres. - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/guides/platform/logs.mdx b/apps/docs/pages/guides/platform/logs.mdx index f7d0f4d14be..2f1e8d79dd4 100644 --- a/apps/docs/pages/guides/platform/logs.mdx +++ b/apps/docs/pages/guides/platform/logs.mdx @@ -168,7 +168,7 @@ Each log event stores metadata an array of objects with multiple levels, and can For example, to query the edge logs without any joins: ```sql -select timestamp, metadata from edge_logs t +select timestamp, metadata from edge_logs as t; ``` The resulting `metadata` key is rendered as an array of objects in the Logs Explorer. In the following diagram, each box represents a nested array of objects: @@ -183,10 +183,11 @@ To query for a nested value, add a join for each array level: ```sql select timestamp, request.method, header.cf_ipcountry -from edge_logs t -cross join unnest(t.metadata) as metadata -cross join unnest(metadata.request) as request -cross join unnest(request.headers) as header +from + edge_logs as t + cross join unnest(t.metadata) as metadata + cross join unnest(metadata.request) as request + cross join unnest(request.headers) as header; ``` This surfaces the following columns available for selection: @@ -217,17 +218,19 @@ Instead, select only the values required. -- ❌ Avoid doing this select datetime(timestamp), - m as metadata -- <- metadata contains many nested keys -from edge_logs t -cross join unnest(t.metadata) as m; + m as metadata -- <- metadata contains many nested keys +from + edge_logs as t + cross join unnest(t.metadata) as m; -- ✅ Do this select -datetime(timestamp), -r.method -- <- select only the required values -from edge_logs t -cross join unnest(t.metadata) as m -cross join unnest(m.request) as r + datetime(timestamp), + r.method -- <- select only the required values +from + edge_logs as t + cross join unnest(t.metadata) as m + cross join unnest(m.request) as r; ``` ### Examples and Templates @@ -238,11 +241,12 @@ For example, you can enter the following query in the SQL Editor to retrieve eac ```sql select datetime(timestamp), h.x_real_ip -from edge_logs +from + edge_logs cross join unnest(metadata) as m - cross join unnest(m.request) AS r - cross join unnest(r.headers) AS h -where h.x_real_ip is not null and r.method = "GET" + cross join unnest(m.request) as r + cross join unnest(r.headers) as h +where h.x_real_ip is not null and r.method = "GET"; ``` export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/platform/performance.mdx b/apps/docs/pages/guides/platform/performance.mdx index 8d7ae48e422..adbe3a241b9 100644 --- a/apps/docs/pages/guides/platform/performance.mdx +++ b/apps/docs/pages/guides/platform/performance.mdx @@ -8,6 +8,161 @@ export const meta = { The Supabase platform automatically optimizes your Postgres database to take advantage of the compute resources of the tier your project is on. However, these optimizations are based on assumptions about the type of workflow the project is being utilized for, and it is likely that better results can be obtained by tuning the database for your particular workflow. +## Examining Query Performance + +Unoptimized queries are a major cause of poor database performance. The techniques on this page can help you identify and understand queries that take the most time and resources from your database. + +Database performance is a large topic and many factors can contribute. Some of the most common causes of poor performance include: + +- An inefficiently designed schema +- Inefficiently designed queries +- A lack of indexes causing slower than required queries over large tables +- Unused indexes causing slow `INSERT`, `UPDATE` and `DELETE` operations +- Not enough compute resources, such as memory, causing your database to go to disk for results too often +- Lock contention from multiple queries operating on highly utilized tables +- Large amount of bloat on your tables causing poor query planning + +Thankfully there are solutions to all these issues, which we will cover in the following sections. + +### Postgres Cumulative Statistics system + +Postgres collects data about its own operations using the [cumulative statistics system](https://www.postgresql.org/docs/current/monitoring-stats.html). In addition to this, every Supabase project has the [pg_stat_statements extension](/docs/guides/database/extensions/pg_stat_statements) enabled by default. This extension records query execution performance details and is the best way to find inefficient queries. This information can be combined with the Postgres query plan analyzer to develop more efficient queries. + +Here are some example queries to get you started. + +#### Most frequently called queries: + +```sql +select + auth.rolname, + statements.query, + statements.calls, + -- -- Postgres 13, 14, 15 + statements.total_exec_time + statements.total_plan_time as total_time, + statements.min_exec_time + statements.min_plan_time as min_time, + statements.max_exec_time + statements.max_plan_time as max_time, + statements.mean_exec_time + statements.mean_plan_time as mean_time, + -- -- Postgres <= 12 + -- total_time, + -- min_time, + -- max_time, + -- mean_time, + statements.rows / statements.calls as avg_rows +from + pg_stat_statements as statements + inner join pg_authid as auth on statements.userid = auth.oid +order by statements.calls desc +limit 100; +``` + +This query shows: + +- query statistics, ordered by the number of times each query has been executed +- the role that ran the query +- the number of times it has been called +- the average number of rows returned +- the cumulative total time the query has spent running +- the min, max and mean query times. + +This provides useful information about the queries you run most frequently. Queries that have high `max_time` or `mean_time` times and are being called often can be good candidates for optimization. + +#### Slowest queries by execution time: + +```sql +select + auth.rolname, + statements.query, + statements.calls, + -- -- Postgres 13, 14, 15 + statements.total_exec_time + statements.total_plan_time as total_time, + statements.min_exec_time + statements.min_plan_time as min_time, + statements.max_exec_time + statements.max_plan_time as max_time, + statements.mean_exec_time + statements.mean_plan_time as mean_time, + -- -- Postgres <= 12 + -- total_time, + -- min_time, + -- max_time, + -- mean_time, + statements.rows / statements.calls as avg_rows +from + pg_stat_statements as statements + inner join pg_authid as auth on statements.userid = auth.oid +order by max_time desc +limit 100; +``` + +This query will show you statistics about queries ordered by the maximum execution time. It is similar to the query above ordered by calls, but this one highlights outliers that may have high executions times. Queries which have high or mean execution times are good candidates for optimisation. + +#### Most time consuming queries: + +```sql +select + auth.rolname, + statements.query, + statements.calls, + statements.total_exec_time + statements.total_plan_time as total_time, + to_char( + ( + (statements.total_exec_time + statements.total_plan_time) / sum( + statements.total_exec_time + statements.total_plan_time + ) over () + ) * 100, + 'FM90D0' + ) || '%' as prop_total_time +from + pg_stat_statements as statements + inner join pg_authid as auth on statements.userid = auth.oid +order by total_time desc +limit 100; +``` + +This query will show you statistics about queries ordered by the cumulative total execution time. It shows the total time the query has spent running as well as the proportion of total execution time the query has taken up. + +Queries which are the most time consuming are not necessarily bad, you may have a very effiecient and frequently ran queries that end up taking a large total % time, but it can be useful to help spot queries that are taking up more time than they should. + +### Hit rate + +Generally for most applications a small percentage of data is accessed more regularly than the rest. To make sure that your regularly accessed data is available, Postgres tracks your data access patterns and keeps this in its [shared_buffers](https://www.postgresql.org/docs/15/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-MEMORY) cache. + +Applications with lower cache hit rates generally perform more poorly since they have to hit the disk to get results rather than serving them from memory. Very poor hit rates can also cause you to burst past your [Disk I/O limits](https://supabase.com/docs/guides/platform/compute-add-ons#disk-io-bandwidth) causing significant performance issues. + +You can view your cache and index hit rate by executing the following query: + +```sql +select + 'index hit rate' as name, + (sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read), 0) * 100 as ratio +from pg_statio_user_indexes +union all +select + 'table hit rate' as name, + sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read), 0) * 100 as ratio +from pg_statio_user_tables; +``` + +This shows the ratio of data blocks fetched from the Postgres [shared_buffers](https://www.postgresql.org/docs/15/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-MEMORY) cache against the data blocks that were read from disk/OS cache. + +If either of your index or table hit rate are < 99% then this can indicate your compute plan is too small for your current workload and you would benefit from more memory. [Upgrading your compute](https://supabase.com/docs/guides/platform/compute-add-ons) is easy and can be done from your [project dashboard](https://app.supabase.com/project/_/settings/billing/subscription). + +### Optimizing poor performing queries + +Postgres has built in tooling to help you optimize poorly performing queries. You can use the [query plan analyzer](https://www.postgresql.org/docs/current/sql-explain.html) on any expensive queries that you have identified: + +```sql +explain analyze ; +``` + +Be careful using `explain analyze` with `insert`/`update`/`delete` queries, because the query will actually run, and could have unintended side-effects. + +Using the query plan analyzer to optimize your queries is a large topic, with a number of online resources available: + +- [Official docs.](https://www.postgresql.org/docs/current/using-explain.html) +- [The Art of PostgreSQL.](https://theartofpostgresql.com/explain-plan-visualizer/) +- [Postgres Wiki.](https://wiki.postgresql.org/wiki/Using_EXPLAIN) +- [Enterprise DB.](https://www.enterprisedb.com/blog/postgresql-query-optimization-performance-tuning-with-explain-analyze) + +You can pair the information available from `pg_stat_statements` with the detailed system metrics available [via your metrics endpoint](../platform/metrics) to better understand the behavior of your DB and the queries you're executing against it. + ## Optimizing the number of connections By default, the number of connections allowed to Postgres and PgBouncer is configured based on the resources available to the database. @@ -64,33 +219,6 @@ alter system reset ; Configuring the number of PgBouncer connections is not supported at this time. -## Examining Query Performance - -Every Supabase project has [the pg_stat_statements extension](https://www.postgresql.org/docs/14/pgstatstatements.html) enabled by default. This extension records query execution performance details and is the best way to find queries that take the most time to execute. This information can be combined with the Postgres query plan analyzer to develop more efficient queries. - -Obtaining information from pg_stat_statements: - -```sql -select mean_exec_time + stddev_exec_time, * from pg_stat_statements order by 1 desc; -``` - -Using the query plan analyzer on your expensive queries: - -```sql -explain analyze ; -``` - -Be careful using `explain analyze` with `insert`/`update`/`delete` queries, because the query will actually run, and could have unintended side-effects. - -Using the query plan analyzer to optimize your queries is a large topic, with a number of online resources available: - -- [Official docs.](https://www.postgresql.org/docs/current/using-explain.html) -- [The Art of PostgreSQL.](https://theartofpostgresql.com/explain-plan-visualizer/) -- [Postgres Wiki.](https://wiki.postgresql.org/wiki/Using_EXPLAIN) -- [Enterprise DB.](https://www.enterprisedb.com/blog/postgresql-query-optimization-performance-tuning-with-explain-analyze) - -You can pair the information available from `pg_stat_statements` with the detailed system metrics available [via your metrics endpoint](../platform/metrics) to better understand the behavior of your DB and the queries you're executing against it. - export const Page = ({ children }) => export default Page diff --git a/apps/docs/pages/guides/resources/examples.mdx b/apps/docs/pages/guides/resources/examples.mdx index 23b102fb216..18c835d8298 100644 --- a/apps/docs/pages/guides/resources/examples.mdx +++ b/apps/docs/pages/guides/resources/examples.mdx @@ -113,6 +113,7 @@ Build a basic Todo List with Supabase and your favorite frontend framework: - In-depth self-hosting guide using Nginx [Blog](https://dev.to/chronsyn/self-hosting-with-supabase-1aii) - Build an Email and Social Auth for Next JS with Supabase, Tailwind CSS 3.0 and TypeScript [Blog](https://creativedesignsguru.com/next-js-supabase-auth/) - Link Shortener using Supabase and Ory [3-part Blog Series](https://www.ory.sh/tutorial-url-shortener-supabase-ory-integration-backend/) +- Building a CRUD API with FastAPI and Supabase: A Step-by-Step Guide [Blog](https://blog.theinfosecguy.xyz/building-a-crud-api-with-fastapi-and-supabase-a-step-by-step-guide) ### Example apps diff --git a/apps/docs/pages/guides/storage/quickstart.mdx b/apps/docs/pages/guides/storage/quickstart.mdx index 7309f2730af..ba0f54e6943 100644 --- a/apps/docs/pages/guides/storage/quickstart.mdx +++ b/apps/docs/pages/guides/storage/quickstart.mdx @@ -41,8 +41,10 @@ client libraries. Here we create a bucket called "avatars": ```sql -- Use Postgres to create a bucket. -insert into storage.buckets (id, name) -values ('avatars', 'avatars'); +insert into storage.buckets + (id, name) +values + ('avatars', 'avatars'); ``` diff --git a/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx b/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx index 00caf731fbc..94fe1602450 100644 --- a/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx +++ b/apps/docs/pages/learn/auth-deep-dive/auth-policies.mdx @@ -158,8 +158,8 @@ Supabase comes with two built-in helper functions: `auth.uid()` and `auth.jwt()` To create your own functions, navigate to the SQL editor and create a a new query. ```sql -create or replace function user_agent() -returns text +create or replace function user_agent() +returns text language sql as $$ select nullif(current_setting('request.headers', true)::json->>'user-agent', '')::text; diff --git a/apps/docs/pages/learn/auth-deep-dive/auth-row-level-security.mdx b/apps/docs/pages/learn/auth-deep-dive/auth-row-level-security.mdx index 95a03060bc9..c75b446e972 100644 --- a/apps/docs/pages/learn/auth-deep-dive/auth-row-level-security.mdx +++ b/apps/docs/pages/learn/auth-deep-dive/auth-row-level-security.mdx @@ -45,12 +45,16 @@ Let's say we create a leaderboard table. We want people on our website to be abl ```sql create table leaderboard ( - name text, - score int + name text, + score int ); -insert into leaderboard(name, score) -values ('Paul', 100), ('Leto', 50), ('Chani', 200); +insert into leaderboard + (name, score) +values + ('Paul', 100), + ('Leto', 50), + ('Chani', 200); ``` Now let's set up a client to read the data, I've created a repl here to show a living example: [https://replit.com/@awalias/supabase-leaderboard-demo#index.js](https://replit.com/@awalias/supabase-leaderboard-demo#index.js). If you copy the snippet you can plug in your own Supabase URL and anon key. diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index 2daf85cdf42..01df68c58f9 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -17,6 +17,7 @@ Dave Wilson Div Arora Egor Romanov Fabrizio Fenoglio +Francesco Sansalvadore Haydn Maley Hieu Pham Inian P diff --git a/apps/docs/public/img/guides/integrations/prisma/w0oowg8vq435ob5c3gf0.png b/apps/docs/public/img/guides/integrations/prisma/w0oowg8vq435ob5c3gf0.png index 9bfb34db474..05db2d109b6 100644 Binary files a/apps/docs/public/img/guides/integrations/prisma/w0oowg8vq435ob5c3gf0.png and b/apps/docs/public/img/guides/integrations/prisma/w0oowg8vq435ob5c3gf0.png differ diff --git a/apps/docs/public/sitemap.xml b/apps/docs/public/sitemap.xml index 35bf4d7ff55..598060f55aa 100644 --- a/apps/docs/public/sitemap.xml +++ b/apps/docs/public/sitemap.xml @@ -18,12 +18,6 @@ 0.5 - - https://supabase.com/docs/guides/database/api - weekly - 0.5 - - https://supabase.com/docs/guides/cli weekly @@ -36,12 +30,6 @@ 0.5 - - https://supabase.com/docs/guides/examples - weekly - 0.5 - - https://supabase.com/docs/guides/functions weekly @@ -90,12 +78,6 @@ 0.5 - - https://supabase.com/docs/reference/index - weekly - 0.5 - - https://supabase.com/docs/handbook/contributing weekly @@ -108,30 +90,12 @@ 0.5 - - https://supabase.com/docs/new/auth - weekly - 0.5 - - https://supabase.com/docs/reference/index weekly 0.5 - - https://supabase.com/docs/tutorials/nextjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/api/generating-types - weekly - 0.5 - - https://supabase.com/docs/guides/auth/auth-captcha weekly @@ -216,6 +180,12 @@ 0.5 + + https://supabase.com/docs/guides/database/api + weekly + 0.5 + + https://supabase.com/docs/guides/database/arrays weekly @@ -318,60 +288,6 @@ 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/cors - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/debugging - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/import-maps - 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/integrations/appsmith weekly @@ -534,6 +450,60 @@ 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/cors + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/debugging + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/import-maps + 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/platform/access-control weekly @@ -559,7 +529,7 @@ - https://supabase.com/docs/guides/platform/database-usage + https://supabase.com/docs/guides/platform/database-size weekly 0.5 @@ -612,6 +582,12 @@ 0.5 + + https://supabase.com/docs/guides/platform/ssl-enforcement + weekly + 0.5 + + https://supabase.com/docs/guides/platform/sso weekly @@ -624,12 +600,24 @@ 0.5 + + https://supabase.com/docs/guides/realtime/broadcast + 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/quickstart weekly @@ -642,6 +630,12 @@ 0.5 + + https://supabase.com/docs/guides/resources/examples + weekly + 0.5 + + https://supabase.com/docs/guides/resources/glossary weekly @@ -715,49 +709,139 @@ - https://supabase.com/docs/guides/database/extensions/http + https://supabase.com/docs/guides/auth/auth-helpers/auth-ui weekly 0.5 - https://supabase.com/docs/guides/database/extensions/pgcron + https://supabase.com/docs/guides/auth/auth-helpers/nextjs-server-components weekly 0.5 - https://supabase.com/docs/guides/database/extensions/pgnet + https://supabase.com/docs/guides/auth/auth-helpers/nextjs weekly 0.5 - https://supabase.com/docs/guides/database/extensions/pgtap + https://supabase.com/docs/guides/auth/auth-helpers/remix weekly 0.5 - https://supabase.com/docs/guides/database/extensions/plv8 + https://supabase.com/docs/guides/auth/auth-helpers/sveltekit weekly 0.5 - https://supabase.com/docs/guides/database/extensions/postgis + https://supabase.com/docs/guides/auth/social-login/auth-apple weekly 0.5 - https://supabase.com/docs/guides/database/extensions/rum + https://supabase.com/docs/guides/auth/social-login/auth-azure weekly 0.5 - https://supabase.com/docs/guides/database/extensions/uuid-ossp + 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/database/api/generating-types weekly 0.5 @@ -888,36 +972,6 @@ 0.5 - - https://supabase.com/docs/guides/auth/auth-helpers/auth-ui - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/auth-helpers/nextjs-server-components - 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/phone-login/messagebird weekly @@ -948,6 +1002,12 @@ 0.5 + + https://supabase.com/docs/guides/functions/examples/discord-bot + weekly + 0.5 + + https://supabase.com/docs/guides/functions/examples/github-actions weekly @@ -960,6 +1020,12 @@ 0.5 + + https://supabase.com/docs/guides/functions/examples/openai + weekly + 0.5 + + https://supabase.com/docs/guides/functions/examples/storage-caching weekly @@ -978,6 +1044,186 @@ 0.5 + + https://supabase.com/docs/guides/functions/examples/upstash-redis + 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/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/resources/migrating-to-supabase/firebase-auth weekly @@ -1003,25 +1249,7 @@ - https://supabase.com/docs/guides/self-hosting/realtime/config - 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 + https://supabase.com/docs/guides/resources/migrating-to-supabase/render weekly 0.5 @@ -1032,114 +1260,36 @@ 0.5 + + https://supabase.com/docs/guides/resources/postgres/dropping-all-tables-in-schema + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/postgres/first-row-in-group + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/postgres/which-version-of-postgres + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting/realtime/config + weekly + 0.5 + + https://supabase.com/docs/guides/self-hosting/storage/config 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/reference/javascript/initializing weekly @@ -1562,6 +1712,12 @@ 0.5 + + https://supabase.com/docs/reference/javascript/db-returns + weekly + 0.5 + + https://supabase.com/docs/reference/javascript/functions-invoke weekly @@ -3867,4 +4023,4 @@ weekly 0.5 - \ No newline at end of file + diff --git a/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx b/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx index 0773f7c9713..85b061947c7 100644 --- a/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx +++ b/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx @@ -11,7 +11,7 @@ date: '2023-02-07' toc_depth: 3 --- -We all know that Microsoft's real agenda for pouring billions into OpenAI to revive their favorite friend Clippy. +We all know that Microsoft's real agenda for pouring billions into OpenAI is to revive their favorite friend Clippy. Today, we're doing our part to support the momentum by releasing “Supabase Clippy” for our docs (and we don't expect this name to last long before the lawyers catch on). ![Clippy](/images/blog/docsgpt/clippy.png) @@ -30,7 +30,7 @@ Our product suite has grown in the past 2 years and our docs have grown as a res ### The “ask” interface -Developers have recently gained an the ability to trust a bot. Where Clippy failed, ChatGPT succeeded. +Developers have recently gained the ability to trust a bot. Where Clippy failed, ChatGPT succeeded. This is convenient timing for us, since our documentation content is more than the average developer wants to consume in one go. Today we're providing a similar interface to ChatGPT which is trained on our own docs. diff --git a/apps/www/_blog/2023-03-01-geo-queries-with-postgis-in-ionic-angular.mdx b/apps/www/_blog/2023-03-01-geo-queries-with-postgis-in-ionic-angular.mdx new file mode 100644 index 00000000000..8898cbe0f78 --- /dev/null +++ b/apps/www/_blog/2023-03-01-geo-queries-with-postgis-in-ionic-angular.mdx @@ -0,0 +1,1057 @@ +--- +title: Geo Queries with PostGIS in Ionic Angular +description: Using the PostGIS extension to build a cross-platform application with Ionic Angular. +author: simon_grimm +image: 2023-02-28-geoqueries-postgis/postgis-ionic-angular.png +thumb: 2023-02-28-geoqueries-postgis/postgis-ionic-angular.png +tags: + - ionic + - postgis + - geoqueries + - storage +date: '2023-03-01' +toc_depth: 3 +--- + +Does your app need to handle geo data like latitude, longitude, or distance between geographic locations? + +Then Supabase got you covered again as you can unlock all of this with the **PostGIS** extension! + +![Supabase Postgis app Ionic](/images/blog/2023-02-28-geoqueries-postgis/supabase-gis.png) + +In this tutorial you will learn to: + +- Enable and use PostGIS extension with Supabase +- Store and retrieve geolocation data +- Use [database functions](https://supabase.com/docs/guides/database/functions) for geo-queries with PostGIS +- Display a [Capacitor Google Map](https://capacitorjs.com/docs/apis/google-maps) with a marker +- Upload files to Supabase Storage and use [image transformations](https://supabase.com/docs/guides/storage/image-transformations) + +Since there are quite some code snippets we need I've put together the [full source code on Github](https://github.com/saimon24/supabase-postgis-ionic-angular) so you can easily run the project yourself! + +Ready for some action? + +Let's start within Supabase. + +## Creating the Supabase Project + +To get started we need a new Supabase project. If you don't have a Supabase account yet, you can [get started for free](https://supabase.com/)! + +In your dashboard, click "New Project" and leave it to the default settings, but make sure you keep a copy o your Database password! + +![Supabase new project](/images/blog/2023-02-28-geoqueries-postgis/supabase-project-setup.png) + +After a minute your project should be ready, and we can configure our tables and extensions with SQL. + +## Why PostGIS Extension? + +Why do we actually need the [PostGIS](https://postgis.net/) extension for our Postgres database? + +Turns out storing lat/long coordinates and querying them isn't very effective and doesn't scale well. + +By enabling this extension, we get access to additional data types like `Point` or `Polygon`, and we can easily add an index to our data that makes retrieving locations within certain bounds super simpler. + +It's super easy to use [PostGIS with Supabase](https://supabase.com/docs/guides/database/extensions/postgis) as we just need to enable the extension - which is just one of many other Postgres extensions that you can toggle on with just a click! + +## Defining your Tables with SQL + +### Adding the PostGIS Extensions + +We could enable PostGIS from the Supabase project UI but we can actually do it with `SQL` as well, so let's navigate to the **SQL Editor** from the menu and run the following: + +```sql +-- enable the PostGIS extension +create extension postgis with schema extensions; +``` + +You can now find this and many other extensions under **Database** -> **Extensions**: + +![Supabase extensions](/images/blog/2023-02-28-geoqueries-postgis/supabase-extensions.png) + +It's as easy as that, and we can now create the rest of our table structure. + +### Creating the SQL Tables + +For our example, we need one `Stores` table so we can add stores with some text and their location. + +Additionally, we create a [spartial index](https://postgis.net/docs/using_postgis_dbmanagement.html#build-indexes) on the location of our store to make our queries more performant. + +Finally, we can also create a new storage bucket for file upload, so go ahead and run the following in the **SQL Editor**: + +```sql +-- create our table +create table if not exists public.stores ( + id int generated by default as identity primary key, + name text not null, + description text, + location geography(POINT) not null +); + +-- add the spatial index +create index stores_geo_index + on public.stores + using GIST (location); + +-- create a storage bucket and allow file upload/download +insert into storage.buckets (id, name) +values ('stores', 'stores'); + +CREATE POLICY "Select images" ON storage.objects FOR SELECT TO public USING (bucket_id = 'stores'); +CREATE POLICY "Upload images" ON storage.objects FOR INSERT TO public WITH CHECK (bucket_id = 'stores'); +``` + +For our tests, I also added some dummy data. Feel free to use mine or use coordinates closer to you: + +```sql +-- add some dummy data +insert into public.stores +(name, description, location) +values + ('The Galaxies.dev Shop', 'Galaxies.dev - your favourite place to learn', st_point(7.6005702, 51.8807174)), + ('The Local Dev', 'Local people, always best', st_point(7.614454, 51.876565)), + ('City Store', 'Get the supplies a dev needs', st_point(7.642581, 51.945606)), + ('MEGA Store', 'Everything you need', st_point(13.404315, 52.511640)); +``` + +To wrap this up we define 2 database functions: + +- `nearby_stores` will return a list of all stores and their distance to a lat/long place +- `stores_in_view` uses more functions like `ST_MakeBox2D` to find all locations in a specific box of coordinates + +Those are some powerful calculations, and we can easily use them through the PostGIS extension and by defining database functions like this: + +```sql +-- create database function to find nearby stores +create or replace function nearby_stores(lat float, long float) +returns setof record +language sql +as $$ + select id, name, description, st_astext(location) as location, st_distance(location, st_point(long, lat)::geography) as dist_meters + from public.stores + order by location <-> st_point(long, lat)::geography; +$$; + + +-- create database function to find stores in a specific box +create or replace function stores_in_view(min_lat float, min_long float, max_lat float, max_long float) +returns setof record +language sql +as $$ + select id, name, ST_Y(location::geometry) as lat, ST_X(location::geometry) as long, st_astext(location) as location + from public.stores + where location && ST_SetSRID(ST_MakeBox2D(ST_Point(min_long, min_lat), ST_Point(max_long, max_lat)),4326) +$$; +``` + +With all of that in place we are ready to build a powerful app with geo-queries based on our Supabase geolocation data! + +## Working with Geo Queries in Ionic Angular + +### Setting up the Project + +We are not bound to any framework, but in this article, we are using [Ionic Angular](https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-angular) to build a cross-platform application. + +Additionally we use [Capacitor](https://capacitorjs.com/) to include a [native Google Maps](https://capacitorjs.com/docs/apis/google-maps) component and to retrieve the user location. + +Get started by bringing up a new Ionic project, then add two pages and a service and run the first build so we can generate the native platforms with Capacitor. + +Finally we can install the [Supabase JS package](https://github.com/supabase/supabase-js), so go ahead and run: + +```bash +ionic start supaMap blank --type=angular +cd ./supaMap + +ionic g page store +ionic g page nearby +ionic g service services/stores + +ionic build +ionic cap add ios +ionic cap add android + + +# Add Maps and Geolocation plugins +npm install @capacitor/google-maps +npm install @capacitor/geolocation + +# Install Supabase +npm install @supabase/supabase-js + +# Ionic 7 wasn't released so I installed the next version +# not required if you are already on Ionic 7 +npm install @ionic/core@next @ionic/angular@next +``` + +Within the new project we need to add our Supabase credentials and a key for the Google Maps API to the **src/environments/environment.ts** like this: + +```ts +export const environment = { + production: false, + mapsKey: 'YOUR-GOOGLE-MAPS-KEY', + supabaseUrl: "YOUR-URL", + supabaseKey: "YOUR-ANON-KEY", +}; +``` + +You can find those values in your Supabase project by clicking on the **Settings** icon and then navigating to **API** where it shows your **Project API keys**. + +The Google Maps API key can be obtained from the [Google Cloud Platform](https://console.cloud.google.com/) where you can add a new project and then create credentials for the Maps Javascript API. + +### Native Project Configuration + +To use the Capacitor plugin we also need to update the permissions of our native projects, so within the **ios/App/App/Info.plist** we need to include these: + +```xml + NSLocationAlwaysUsageDescription + We want to show your nearby places + NSLocationWhenInUseUsageDescription + We want to show your nearby places + NSLocationAlwaysAndWhenInUseUsageDescription + To show your location +``` + +Additionally, we need to add our Maps Key to the **android/app/src/main/AndroidManifest.xml**: +```xml + +``` + +Finally also add the required permissions for Android in the **android/app/src/main/AndroidManifest.xml** at the bottom: + +```xml + + + +``` + +You can also find more information about using [Capacitor maps with Ionic in my Ionic Academy](https://ionicacademy.com/capacitor-google-maps-plugin/ +)! + +### Finding Nearby Places with Database Functions + +Now the fun begins, and we can start by adding a function to our **src/app/services/stores.service.ts** that calls the database function (Remote Procedure Call) that we defined in the beginning: + +```ts +import { Injectable } from '@angular/core'; +import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; +import { SupabaseClient, User, createClient } from '@supabase/supabase-js'; +import { environment } from 'src/environments/environment'; + +export interface StoreEntry { + lat?: number; + long?: number; + name: string; + description: string; + image?: File; +} +export interface StoreResult { + id: number; + lat: number; + long: number; + name: string; + description: string; + image?: SafeUrl; + dist_meters?: number; +} +@Injectable({ + providedIn: 'root', +}) +export class StoresService { + private supabase: SupabaseClient; + + constructor(private sanitizer: DomSanitizer) { + this.supabase = createClient( + environment.supabaseUrl, + environment.supabaseKey + ); + } + + // Get all places with calculated distance + async getNearbyStores(lat: number, long: number) { + const { data, error } = await this.supabase.rpc('nearby_stores', { + lat, + long, + }); + return data; + } +} +``` + +This should return a nice list of `StoreResult` items that we can render in a list. + +For that, let's display a modal from our **src/app/home/home.page.ts**: + +```ts +import { Component } from '@angular/core'; +import { ModalController } from '@ionic/angular'; +import { NearbyPage } from '../nearby/nearby.page'; + +export interface StoreMarker { + markerId: string; + storeId: number; +} + +@Component({ + selector: 'app-home', + templateUrl: 'home.page.html', + styleUrls: ['home.page.scss'], +}) +export class HomePage { + constructor( + private modalCtrl: ModalController + ) {} + + async showNearby() { + const modal = await this.modalCtrl.create({ + component: NearbyPage, + }); + modal.present(); + } +} +``` + +We also need a button to present that modal, so change the **src/app/home/home.page.html** to include one: + +```html + + + + + Nearby + + + Supa Stores + + + + + + +``` + +Now we are able to use the `getNearbyStores` from our service on that modal page, and we also load the current user location using Capacitor. + +Once we got the user coordinates, we pass them to our function and PostGIS will do its magic to calculate the distance between us and the stores of our database! + +Go ahead and change the **src/app/nearby/nearby.page.ts** to this now: + +```ts +import { Component, OnInit } from '@angular/core'; +import { Geolocation } from '@capacitor/geolocation'; +import { StoresService, StoreResult } from '../services/stores.service'; +import { LoadingController, ModalController } from '@ionic/angular'; + +@Component({ + selector: 'app-nearby', + templateUrl: './nearby.page.html', + styleUrls: ['./nearby.page.scss'], +}) +export class NearbyPage implements OnInit { + stores: StoreResult[] = []; + + constructor( + private storesService: StoresService, + public modalCtrl: ModalController, + private loadingCtrl: LoadingController + ) {} + + async ngOnInit() { + // Show loading while getting data from Supabase + const loading = await this.loadingCtrl.create({ + message: 'Loading nearby places...', + }); + loading.present(); + + const coordinates = await Geolocation.getCurrentPosition(); + + if (coordinates) { + // Get nearby places sorted by distance using PostGIS + this.stores = await this.storesService.getNearbyStores( + coordinates.coords.latitude, + coordinates.coords.longitude + ); + loading.dismiss(); + } + } +} +``` + +At this point, you can already log the values, but we can also quickly display them in a nice list by updating the **src/app/nearby/nearby.page.html** to: + +```html + + + + + + + + Nearby Places + + + + + + + + {{ store.name }} +

{{store.description }}

+
+ {{store.dist_meters!/1000 | number:'1.0-2' }} km +
+
+
+``` + +If you open the modal, you should now see a list like this after your position was loaded: + +![Ionic nearby list](/images/blog/2023-02-28-geoqueries-postgis/ionic-nearby.png) + +It looks so easy - but so many things are already coming together at this point: + +- Capacitor geolocation inside the browser +- Supabase RPC to a stored database function +- PostGIS geolocation calculation + +We will see more of this powerful extension soon, but let's quickly add another modal to add our own data. + +### Add Stores with Coordinates to Supabase + +To add data to Supabase we create a new function in our **src/app/services/stores.service.ts**: + +```ts + async addStore(info: StoreEntry) { + // Add a new database entry using the POINT() syntax for the coordinates + const { data } = await this.supabase + .from('stores') + .insert({ + name: info.name, + description: info.description, + location: `POINT(${info.long} ${info.lat})`, + }) + .select() + .single(); + + if (data && info.image) { + // Upload the image to Supabase + const foo = await this.supabase.storage + .from('stores') + .upload(`/images/${data.id}.png`, info.image); + } + } +``` + +Notice how we convert the lat/long information of an entry to a string. + +This is how PostGIS expects those values! + +We use our Supabase storage bucket to upload an image file if it's included in the new `StoreEntry`. It's almost too easy and feels like cheating to upload a file to cloud storage in just three lines... + +Now we need a simple modal, so just like before we add a new function to the **src/app/home/home.page.ts**: + +```ts + async addStore() { + const modal = await this.modalCtrl.create({ + component: StorePage, + }); + modal.present(); + } +``` + +That function get's called from another button in our **src/app/home/home.page.html**: + +```html + + + + + Nearby + + + Supa Stores + + + Store + + + +``` + +Back in this new modal, we will define an empty `StoreEntry` object and then connect it to the input fields in our view. + +Because we defined the rest of the functionality in our service, we can simply update the **src/app/store/store.page.ts** to: + +```ts +import { Component, OnInit } from '@angular/core'; +import { ModalController } from '@ionic/angular'; +import { StoreEntry, StoresService } from '../services/stores.service'; + +@Component({ + selector: 'app-store', + templateUrl: './store.page.html', + styleUrls: ['./store.page.scss'], +}) +export class StorePage implements OnInit { + store: StoreEntry = { + name: '', + description: '', + image: undefined, + lat: undefined, + long: undefined, + }; + + constructor( + public modalCtrl: ModalController, + private storesService: StoresService + ) {} + + ngOnInit() {} + + imageSelected(ev: any) { + this.store.image = ev.detail.event.target.files[0]; + } + + async addStore() { + this.storesService.addStore(this.store); + this.modalCtrl.dismiss(); + } +} +``` + +The view is not really special and simply holds a bunch of input fields that are connected to the new `store` entry, so bring up the **src/app/store/store.page.html** and change it to: + +```html + + + + + + + + Add Store + + + + + + + + + + + Add Store + +``` + +As a result, you should have a clean input modal: + +![Ionic add store](/images/blog/2023-02-28-geoqueries-postgis/ionic-inputs.png) + +Give your storage inserter a try and add some places around you - they should be available in your nearby list immediately! + +## Working with Google Maps and Marker + +### Adding a Map + +Now we have some challenges ahead: adding a map, loading data, and creating markers. + +But if you've come this far, I'm sure you can do it! + +Get started by adding the `CUSTOM_ELEMENTS_SCHEMA` to the **src/app/home/home.module.ts** which is required to use Capacitor native maps: + +```ts +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; +import { FormsModule } from '@angular/forms'; +import { HomePage } from './home.page'; + +import { HomePageRoutingModule } from './home-routing.module'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; + +@NgModule({ + imports: [CommonModule, FormsModule, IonicModule, HomePageRoutingModule], + declarations: [HomePage], + schemas: [CUSTOM_ELEMENTS_SCHEMA], +}) +export class HomePageModule {} +``` + +In our **src/app/home/home.page.ts** we can now create the map by passing in a reference to a DOM element and some initial settings for the map and of course your key. + +Update the page with our first step that adds some new variables: + +```ts +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { GoogleMap } from '@capacitor/google-maps'; +import { LatLngBounds } from '@capacitor/google-maps/dist/typings/definitions'; +import { ModalController } from '@ionic/angular'; +import { BehaviorSubject } from 'rxjs'; +import { environment } from 'src/environments/environment'; +import { NearbyPage } from '../nearby/nearby.page'; +import { StoreResult, StoresService } from '../services/stores.service'; +import { StorePage } from '../store/store.page'; + +export interface StoreMarker { + markerId: string; + storeId: number; +} + +@Component({ + selector: 'app-home', + templateUrl: 'home.page.html', + styleUrls: ['home.page.scss'], +}) +export class HomePage { + @ViewChild('map') mapRef!: ElementRef; + map!: GoogleMap; + mapBounds = new BehaviorSubject(null); + activeMarkers: StoreMarker[] = []; + selectedMarker: StoreMarker | null = null; + selectedStore: StoreResult | null = null; + + constructor( + private storesService: StoresService, + private modalCtrl: ModalController + ) {} + + ionViewDidEnter() { + this.createMap(); + } + + async createMap() { + this.map = await GoogleMap.create({ + forceCreate: true, // Prevent issues with live reload + id: 'my-map', + element: this.mapRef.nativeElement, + apiKey: environment.mapsKey, + config: { + center: { + lat: 51.8, + lng: 7.6, + }, + zoom: 7, + }, + }); + this.map.enableCurrentLocation(true); + } + + async showNearby() { + const modal = await this.modalCtrl.create({ + component: NearbyPage, + }); + modal.present(); + } + + async addStore() { + const modal = await this.modalCtrl.create({ + component: StorePage, + }); + modal.present(); + } +} +``` + +The map needs a place to render, so we can now add it to our **src/app/home/home.page.html** and wrap it in a div to add some additional styling later: + +```html + + + + + Nearby + + + Supa Stores + + + Store + + + + + +
+ +
+
+``` + +Because the Capacitor map essentially renders **behind your webview** inside a native app, we need to make the background of our current page invisible. + +For this, simply add the following to the **src/app/home/home.page.scss**: + +```scss +ion-content { + --background: none; +} + +.container { + width: 100%; + height: 100%; +} + +capacitor-google-map { + display: inline-block; + width: 100%; + height: 100%; +} +``` + +Now the map should fill the whole screen. + +![Capacitor antive map](/images/blog/2023-02-28-geoqueries-postgis/capacitor-map.png) + +This brings us to the last missing piece… + +### Loading Places in a Box of Coordinates + +Getting all stores is usually too much - you want to show what's nearby to a user, and you can do this by sending basically a box of coordinates to our previously stored database function. + +For this, we first add another call in our **src/app/services/stores.service.ts**: + +```ts + // Get all places in a box of coordinates + async getStoresInView( + min_lat: number, + min_long: number, + max_lat: number, + max_long: number + ) { + const { data } = await this.supabase.rpc('stores_in_view', { + min_lat, + min_long, + max_lat, + max_long, + }); + return data; + } +``` + +Nothing fancy, just passing those values to the database function. + +The challenging part is now **listening to map boundary updates**, which happen whenever you slightly touch the list. + +Because we don't want to call our function 100 times in one second, we use a bit of RxJS to delay the update of our coordinates so the `updateStoresInView` function is called after the user finished swiping the list. + +At that point, we grab the map bounds and call our function, so go ahead and update the **src/app/home/home.page.ts** with the following: + +```ts + async createMap() { + this.map = await GoogleMap.create({ + forceCreate: true, // Prevent issues with live reload + id: 'my-map', + element: this.mapRef.nativeElement, + apiKey: environment.mapsKey, + config: { + center: { + lat: 51.8, + lng: 7.6, + }, + zoom: 7, + }, + }); + this.map.enableCurrentLocation(true); + + // Listen to biew changes and emit to our Behavior Subject + this.map.setOnBoundsChangedListener((ev) => { + this.mapBounds.next(ev.bounds); + }); + + // React to changes of our subject with a 300ms delay so we don't trigger a reload all the time + this.mapBounds.pipe(debounce((i) => interval(300))).subscribe((res) => { + this.updateStoresInView(); + }); + + // Get the current user coordinates + this.loadUserLocation(); + } + + async updateStoresInView() { + const bounds = await this.map.getMapBounds(); + + // Get stores in our bounds using PostGIS + const stores = await this.storesService.getStoresInView( + bounds.southwest.lat, + bounds.southwest.lng, + bounds.northeast.lat, + bounds.northeast.lng + ); + + // Update markers for elements + this.addMarkers(stores); + } + + async loadUserLocation() { + // TODO + } + + async addMarkers(stores: StoreResult[]) { + // TODO + } +``` + +We can also fill one of our functions with some code as we already used the `Geolocation` plugin to load users' coordinates before, so update the function to: + +```ts + async loadUserLocation() { + // Get location with Capacitor Geolocation plugin + const coordinates = await Geolocation.getCurrentPosition(); + + if (coordinates) { + // Focus the map on user and zoom in + this.map.setCamera({ + coordinate: { + lat: coordinates.coords.latitude, + lng: coordinates.coords.longitude, + }, + zoom: 14, + }); + } + } +``` + +Now we are loading the user location and zooming in to the current place, which will then cause our `updateStoresInView` function to be triggered and we receive a list of places that we just need to render! + +### Displaying Marker on our Google Map + +You can already play around with the app and log the stores after moving the map - it really feels magical how PostGIS returns only the elements that are within the box of coordinates. + +To actually display them we can add the following function to our **src/app/home/home.page.ts** now: + +```ts + async addMarkers(stores: StoreResult[]) { + // Skip if there are no results + if (stores.length === 0) { + return; + } + + // Find marker that are outside of the view + const toRemove = this.activeMarkers.filter((marker) => { + const exists = stores.find((item) => item.id === marker.storeId); + return !exists; + }); + + // Remove markers + if (toRemove.length) { + await this.map.removeMarkers(toRemove.map((marker) => marker.markerId)); + } + + // Create new marker array + const markers: Marker[] = stores.map((store) => { + return { + coordinate: { + lat: store.lat, + lng: store.long, + }, + title: store.name, + }; + }); + + // Add markers, store IDs + const newMarkerIds = await this.map.addMarkers(markers); + + // Crate active markers by combining information + this.activeMarkers = stores.map((store, index) => { + return { + markerId: newMarkerIds[index], + storeId: store.id, + }; + }); + + this.addMarkerClicks(); + } + + addMarkerClicks() { + // TODO + } +``` + +This function got a bit longer because we need to **manage our marker information**. If we just remove and repaint all markers, it looks and feels horrible so we always keep track of existing markers and only render new markers. + +Additionally, these `Marker` have limited information, and if we click a marker we want to present a modal with information about the store from Supabase. + +That means we also need the real ID of that object, and so we create an array `activeMarkers` that basically connects the information of a store ID with the marker ID! + +At this point, you should be able to see markers on your map. If you can't see them, zoom out and you might find them. + +![Ionic map with marker](/images/blog/2023-02-28-geoqueries-postgis/ionic-map-marker.png) + +To wrap this up, let's take a look at one more cool Supabase feature. + +### Presenting Marker with Image Transform + +We have the marker and store ID, so we can simply load the information from our Supabase database. + +Now a store might have an image, and while we download the image from our storage bucket we can use [image transformations](https://supabase.com/docs/guides/storage/image-transformations) to get an image exactly in the right dimensions to save time and bandwidth! + +For this, add two new functions to our **src/app/services/stores.service.ts**: + +```ts + // Load data from Supabase database + async loadStoreInformation(id: number) { + const { data } = await this.supabase + .from('stores') + .select('*') + .match({ id }) + .single(); + return data; + } + + async getStoreImage(id: number) { + // Get image for a store and transform it automatically! + return this.supabase.storage + .from('stores') + .getPublicUrl(`images/${id}.png`, { + transform: { + width: 300, + resize: 'contain', + }, + }).data.publicUrl; + } +``` + +To use image transformations we only need to add an object to the `getPublicUrl()` function and define the different properties we want to have. + +Again, it's _that_ easy. + +Now we just need to load this information when we click on a marker, so add the following function to our **src/app/home/home.page.ts** which handles the click on a map marker: + +```ts + addMarkerClicks() { + // Handle marker clicks + this.map.setOnMarkerClickListener(async (marker) => { + // Find our local object based on the marker ID + const info = this.activeMarkers.filter( + (item) => item.markerId === marker.markerId.toString() + ); + if (info.length) { + this.selectedMarker = info[0]; + + // Load the store information from Supabase Database + this.selectedStore = await this.storesService.loadStoreInformation( + info[0].storeId + ); + + // Get the iamge from Supabase Storage + const img = await this.storesService.getStoreImage( + this.selectedStore!.id + ); + if (img) { + this.selectedStore!.image = img; + } + } + }); + } +``` + +We simply load the information and image and set this to our `selectedStore` variable. + +This will now be used to trigger an **inline modal**, so we don't need to come up with another component and can simply define our Ionic modal right inside the **src/app/home/home.page.html** like this: + +```html + + + + + Nearby + + + Supa Stores + + + Store + + + + + +
+ +
+ + + + + +

{{selectedStore?.name}}

+ {{selectedStore?.description}} +
+
+ +
+
+
+
+
+``` + +Because we also used `breakpoints` and the `initialBreakpoint` properties of the modal we get this nice bottom sheet modal UI whenever we click on a marker: + +![Ionic marker modal with image](/images/blog/2023-02-28-geoqueries-postgis/ionic-marker-with-image.png) + +And with that, we have finished our Ionic app with Supabase geo-queries using PostGIS! + +## Conclusion + +I was fascinated by the power of this simple PostGIS extension that we enabled with just one command (or click). + +Building apps based on geolocation data is a very common scenario, and with PostGIS we can build these applications easily on the back of a [Supabase](https://supabase.com/) database (and [auth](https://supabase.com/blog/authentication-in-ionic-angular) +), and storage, and so much more..) + +You can [find the full code of this tutorial on Github](https://github.com/saimon24/supabase-postgis-ionic-angular) where you just need to insert your own Supabase instance. your Google Maps key and then create the tables with the included SQL file. + +If you enjoyed the tutorial, you can [find many more tutorials and courses on Galaxies.dev](https://galaxies.dev) where I help modern web and mobile developers build epic apps 🚀 + +Until next time and happy coding with Supabase! + +## Related resources + +- [Authentication in Ionic Angular with Supabase](https://supabase.com/blog/authentication-in-ionic-angular) +- [Building a Realtime Trello Board with Supabase and Angular](https://supabase.com/blog/building-a-realtime-trello-board-with-supabase-and-angular) +- [Build a User Management App with Ionic Angular](https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-angular) + diff --git a/apps/www/data/Community.tsx b/apps/www/data/Community.tsx index ea9573cdcc4..89013d080fe 100644 --- a/apps/www/data/Community.tsx +++ b/apps/www/data/Community.tsx @@ -30,7 +30,7 @@ const data: CommunityItem[] = [ }, { title: 'GitHub', - stat: '44,000+', + stat: '46,000+', statLabel: 'GitHub stars', img: 'github.png', invertImgDarkMode: true, @@ -42,7 +42,7 @@ const data: CommunityItem[] = [ }, { title: 'Twitter', - stat: '54,000+', + stat: '60,000+', statLabel: 'Followers', img: 'twitter.png', detail: () => ( diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index e73aa509b57..05e8c945236 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -1857,4 +1857,24 @@ module.exports = [ source: '/docs/reference/javascript/v0/rpc', destination: '/docs/reference/javascript/rpc', }, + { + permanent: true, + source: '/docs/guides/platform/database-usage', + destination: '/docs/guides/platform/database-size', + }, + { + permanent: true, + source: '/docs/guides/resources/postgres/dropping-all-tables-in-schema', + destination: '/docs/guides/database/postgres/dropping-all-tables-in-schema', + }, + { + permanent: true, + source: '/docs/guides/resources/postgres/first-row-in-group', + destination: '/docs/guides/database/postgres/first-row-in-group', + }, + { + permanent: true, + source: '/docs/guides/resources/postgres/which-version-of-postgres', + destination: '/docs/guides/database/postgres/which-version-of-postgres', + }, ] diff --git a/apps/www/package.json b/apps/www/package.json index 69108a1066b..ee23fc16021 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -78,7 +78,6 @@ "file-loader": "^6.2.0", "postcss": "^8.4.18", "postcss-preset-env": "^6.7.0", - "prettier": "^2.2.1", "tailwindcss": "^3.1.8", "tsconfig": "*" }, diff --git a/apps/www/pages/sla.mdx b/apps/www/pages/sla.mdx index 0c367489da1..e535758d638 100644 --- a/apps/www/pages/sla.mdx +++ b/apps/www/pages/sla.mdx @@ -87,8 +87,8 @@ An Information request about Supabase or feature request. | Severity Level | Standard | Priority | Priority Plus | | -------------- | ------------------------------------- | ------------------------------------- | -------------------------------------- | -| 1. Urgent | 1 business hour
24/7 × 365 | 1 business hour
24/7 × 365 | 1 business hour
24/7 × 365 | -| 2. High | 4 business hours
Monday - Friday | 2 business hours
Monday - Friday | 2 business hours
24/7 × 365 | +| 1. Urgent | 1 hour
24/7 × 365 | 1 hour
24/7 × 365 | 1 hour
24/7 × 365 | +| 2. High | 4 business hours
Monday - Friday | 2 business hours
Monday - Friday | 2 hours
24/7 × 365 | | 3. Normal | 1 business day
Monday - Friday | 1 business day
Monday - Friday | 12 business hours
Monday - Friday | | 4. Low | 2 business days
Monday - Friday | 2 business days
Monday - Friday | 1 business day
Monday - Friday | diff --git a/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/capacitor-map.png b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/capacitor-map.png new file mode 100644 index 00000000000..be25c5f428f Binary files /dev/null and b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/capacitor-map.png differ diff --git a/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/ionic-inputs.png b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/ionic-inputs.png new file mode 100644 index 00000000000..60af6081d3e Binary files /dev/null and b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/ionic-inputs.png differ diff --git a/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/ionic-map-marker.png b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/ionic-map-marker.png new file mode 100644 index 00000000000..b2a989abfc2 Binary files /dev/null and b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/ionic-map-marker.png differ diff --git a/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/ionic-marker-with-image.png b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/ionic-marker-with-image.png new file mode 100644 index 00000000000..4b8cc6ebb98 Binary files /dev/null and b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/ionic-marker-with-image.png differ diff --git a/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/ionic-nearby.png b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/ionic-nearby.png new file mode 100644 index 00000000000..99a8b474cbf Binary files /dev/null and b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/ionic-nearby.png differ diff --git a/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/postgis-ionic-angular.png b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/postgis-ionic-angular.png new file mode 100644 index 00000000000..e3a9268ccf6 Binary files /dev/null and b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/postgis-ionic-angular.png differ diff --git a/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/supabase-extensions.png b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/supabase-extensions.png new file mode 100644 index 00000000000..4b32a04c7d8 Binary files /dev/null and b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/supabase-extensions.png differ diff --git a/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/supabase-gis.png b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/supabase-gis.png new file mode 100644 index 00000000000..fa4f448b407 Binary files /dev/null and b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/supabase-gis.png differ diff --git a/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/supabase-project-setup.png b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/supabase-project-setup.png new file mode 100644 index 00000000000..d946dbc3370 Binary files /dev/null and b/apps/www/public/images/blog/2023-02-28-geoqueries-postgis/supabase-project-setup.png differ diff --git a/examples/todo-list/nextjs-todo-list/.env.local.example b/examples/todo-list/nextjs-todo-list/.env.local.example new file mode 100644 index 00000000000..477da3d401d --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/.env.local.example @@ -0,0 +1,3 @@ +# Update these with your Supabase details from your project settings > API +NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key \ No newline at end of file diff --git a/examples/todo-list/nextjs-todo-list/.eslintrc.json b/examples/todo-list/nextjs-todo-list/.eslintrc.json new file mode 100644 index 00000000000..bffb357a712 --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/examples/todo-list/nextjs-todo-list/.gitignore b/examples/todo-list/nextjs-todo-list/.gitignore new file mode 100644 index 00000000000..c87c9b392c0 --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/examples/todo-list/nextjs-todo-list/README.md b/examples/todo-list/nextjs-todo-list/README.md new file mode 100644 index 00000000000..83ba22e28bd --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/README.md @@ -0,0 +1,75 @@ +# Todo example using Supabase + +- Frontend: + - [Next.js](https://github.com/vercel/next.js) - a React framework for production. + - [Tailwind](https://tailwindcss.com/) for styling and layout. + - [Supabase.js](https://supabase.com/docs/library/getting-started) for user management and realtime data syncing. +- Backend: + - [app.supabase.com](https://app.supabase.com/): hosted Postgres database with restful API for usage with Supabase.js. + +## Deploy with Vercel + +The Vercel deployment will guide you through creating a Supabase account and project. After installation of the Supabase integration, all relevant environment variables will be set up so that the project is usable immediately after deployment 🚀 + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fexamples%2Ftree%2Fmain%2Fsupabase-js-v1%2Ftodo-list%2Fnextjs-todo-list&project-name=supabase-todo-list&repository-name=supabase-todo-list&demo-title=Todo%20list&demo-description=An%20example%20web%20app%20using%20Supabase%20and%20Next.js&demo-url=https%3A%2F%2Fsupabase-nextjs-todo-list.vercel.app&demo-image=https%3A%2F%2Fi.imgur.com%2FGJauPlN.png&integration-ids=oac_jUduyjQgOyzev1fjrW83NYOv&external-id=supabase-todo-list) + +## Build from scratch + +### 1. Create new project + +Sign up to Supabase - [https://app.supabase.com](https://app.supabase.com) and create a new project. Wait for your database to start. + +### 2. Run "Todo List" Quickstart + +Once your database has started, run the "Todo List" quickstart. Inside of your project, enter the `SQL editor` tab and scroll down until you see `TODO LIST: Build a basic todo list with Row Level Security`. + +### 3. Get the URL and Key + +Go to the Project Settings (the cog icon), open the API tab, and find your API URL and `anon` key, you'll need these in the next step. + +The `anon` key is your client-side API key. It allows "anonymous access" to your database, until the user has logged in. Once they have logged in, the keys will switch to the user's own login token. This enables row level security for your data. Read more about this [below](#postgres-row-level-security). + +![image](https://user-images.githubusercontent.com/10214025/88916245-528c2680-d298-11ea-8a71-708f93e1ce4f.png) + +**_NOTE_**: The `service_role` key has full access to your data, bypassing any security policies. These keys have to be kept secret and are meant to be used in server environments and never on a client or browser. + +## Supabase details + +### Postgres Row level security + +This project uses very high-level Authorization using Postgres' Role Level Security. +When you start a Postgres database on Supabase, we populate it with an `auth` schema, and some helper functions. +When a user logs in, they are issued a JWT with the role `authenticated` and their UUID. +We can use these details to provide fine-grained control over what each user can and cannot do. + +This is a trimmed-down schema, with the policies: + +```sql +create table todos ( + id bigint generated by default as identity primary key, + user_id uuid references auth.users not null, + task text check (char_length(task) > 3), + is_complete boolean default false, + inserted_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +alter table todos enable row level security; + +create policy "Individuals can create todos." on todos for + insert with check (auth.uid() = user_id); + +create policy "Individuals can view their own todos. " on todos for + select using (auth.uid() = user_id); + +create policy "Individuals can update their own todos." on todos for + update using (auth.uid() = user_id); + +create policy "Individuals can delete their own todos." on todos for + delete using (auth.uid() = user_id); +``` + +## Authors + +- [Supabase](https://supabase.com) + +Supabase is open source. We'd love for you to follow along and get involved at https://github.com/supabase/supabase diff --git a/examples/todo-list/nextjs-todo-list/components/TodoList.tsx b/examples/todo-list/nextjs-todo-list/components/TodoList.tsx new file mode 100644 index 00000000000..6d060b5a964 --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/components/TodoList.tsx @@ -0,0 +1,151 @@ +import { Database } from '@/lib/schema' +import { Session, useSupabaseClient } from '@supabase/auth-helpers-react' +import { useEffect, useState } from 'react' + +type Todos = Database['public']['Tables']['todos']['Row'] + +export default function TodoList({ session }: { session: Session }) { + const supabase = useSupabaseClient() + const [todos, setTodos] = useState([]) + const [newTaskText, setNewTaskText] = useState('') + const [errorText, setErrorText] = useState('') + + const user = session.user + + useEffect(() => { + const fetchTodos = async () => { + const { data: todos, error } = await supabase + .from('todos') + .select('*') + .order('id', { ascending: true }) + + if (error) console.log('error', error) + else setTodos(todos) + } + + fetchTodos() + }, [supabase]) + + const addTodo = async (taskText: string) => { + let task = taskText.trim() + if (task.length) { + const { data: todo, error } = await supabase + .from('todos') + .insert({ task, user_id: user.id }) + .select() + .single() + + if (error) { + setErrorText(error.message) + } else { + setTodos([...todos, todo]) + setNewTaskText('') + } + } + } + + const deleteTodo = async (id: number) => { + try { + await supabase.from('todos').delete().eq('id', id).throwOnError() + setTodos(todos.filter((x) => x.id != id)) + } catch (error) { + console.log('error', error) + } + } + + return ( +
+

Todo List.

+
{ + e.preventDefault() + addTodo(newTaskText) + }} + className="flex gap-2 my-2" + > + { + setErrorText('') + setNewTaskText(e.target.value) + }} + /> + +
+ {!!errorText && } +
+
    + {todos.map((todo) => ( + deleteTodo(todo.id)} /> + ))} +
+
+
+ ) +} + +const Todo = ({ todo, onDelete }: { todo: Todos; onDelete: () => void }) => { + const supabase = useSupabaseClient() + const [isCompleted, setIsCompleted] = useState(todo.is_complete) + + const toggle = async () => { + try { + const { data } = await supabase + .from('todos') + .update({ is_complete: !isCompleted }) + .eq('id', todo.id) + .throwOnError() + .select() + .single() + + if (data) setIsCompleted(data.is_complete) + } catch (error) { + console.log('error', error) + } + } + + return ( +
  • +
    +
    +
    {todo.task}
    +
    +
    + toggle()} + type="checkbox" + checked={isCompleted ? true : false} + /> +
    + +
    +
  • + ) +} + +const Alert = ({ text }: { text: string }) => ( +
    +
    {text}
    +
    +) diff --git a/examples/todo-list/nextjs-todo-list/lib/initSupabase.ts b/examples/todo-list/nextjs-todo-list/lib/initSupabase.ts new file mode 100644 index 00000000000..5457bdff41f --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/lib/initSupabase.ts @@ -0,0 +1,7 @@ +import { createClient } from '@supabase/supabase-js' +import { Database } from './schema' + +export const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY +) diff --git a/examples/todo-list/nextjs-todo-list/lib/schema.ts b/examples/todo-list/nextjs-todo-list/lib/schema.ts new file mode 100644 index 00000000000..ec8b8f854b2 --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/lib/schema.ts @@ -0,0 +1,49 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json } + | Json[] + +export interface Database { + public: { + Tables: { + todos: { + Row: { + id: number + inserted_at: string + is_complete: boolean | null + task: string | null + user_id: string + } + Insert: { + id?: number + inserted_at?: string + is_complete?: boolean | null + task?: string | null + user_id: string + } + Update: { + id?: number + inserted_at?: string + is_complete?: boolean | null + task?: string | null + user_id?: string + } + } + } + Views: { + [_ in never]: never + } + Functions: { + [_ in never]: never + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } +} diff --git a/examples/todo-list/nextjs-todo-list/next.config.js b/examples/todo-list/nextjs-todo-list/next.config.js new file mode 100644 index 00000000000..a843cbee09a --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, +} + +module.exports = nextConfig diff --git a/examples/todo-list/nextjs-todo-list/package-lock.json b/examples/todo-list/nextjs-todo-list/package-lock.json new file mode 100644 index 00000000000..98500e935ef --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/package-lock.json @@ -0,0 +1,4276 @@ +{ + "name": "nextjs-todo-list", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nextjs-todo-list", + "version": "0.1.0", + "dependencies": { + "@next/font": "13.1.6", + "@supabase/auth-helpers-react": "^0.3.1", + "@supabase/auth-ui-react": "^0.2.8", + "@supabase/supabase-js": "^2.8.0", + "@types/node": "18.14.0", + "@types/react": "18.0.28", + "@types/react-dom": "18.0.11", + "eslint": "8.34.0", + "eslint-config-next": "13.1.6", + "next": "13.1.6", + "react": "18.2.0", + "react-dom": "18.2.0", + "typescript": "4.9.5" + }, + "devDependencies": { + "concurrently": "^7.6.0", + "tailwindcss": "^3.2.7" + } + }, + "node_modules/@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + }, + "node_modules/@next/env": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.6.tgz", + "integrity": "sha512-s+W9Fdqh5MFk6ECrbnVmmAOwxKQuhGMT7xXHrkYIBMBcTiOqNWhv5KbJIboKR5STXxNXl32hllnvKaffzFaWQg==" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.1.6.tgz", + "integrity": "sha512-o7cauUYsXjzSJkay8wKjpKJf2uLzlggCsGUkPu3lP09Pv97jYlekTC20KJrjQKmSv5DXV0R/uks2ZXhqjNkqAw==", + "dependencies": { + "glob": "7.1.7" + } + }, + "node_modules/@next/font": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/font/-/font-13.1.6.tgz", + "integrity": "sha512-AITjmeb1RgX1HKMCiA39ztx2mxeAyxl4ljv2UoSBUGAbFFMg8MO7YAvjHCgFhD39hL7YTbFjol04e/BPBH5RzQ==" + }, + "node_modules/@next/swc-android-arm-eabi": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.1.6.tgz", + "integrity": "sha512-F3/6Z8LH/pGlPzR1AcjPFxx35mPqjE5xZcf+IL+KgbW9tMkp7CYi1y7qKrEWU7W4AumxX/8OINnDQWLiwLasLQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-android-arm64": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.1.6.tgz", + "integrity": "sha512-cMwQjnB8vrYkWyK/H0Rf2c2pKIH4RGjpKUDvbjVAit6SbwPDpmaijLio0LWFV3/tOnY6kvzbL62lndVA0mkYpw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.6.tgz", + "integrity": "sha512-KKRQH4DDE4kONXCvFMNBZGDb499Hs+xcFAwvj+rfSUssIDrZOlyfJNy55rH5t2Qxed1e4K80KEJgsxKQN1/fyw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.6.tgz", + "integrity": "sha512-/uOky5PaZDoaU99ohjtNcDTJ6ks/gZ5ykTQDvNZDjIoCxFe3+t06bxsTPY6tAO6uEAw5f6vVFX5H5KLwhrkZCA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-freebsd-x64": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.1.6.tgz", + "integrity": "sha512-qaEALZeV7to6weSXk3Br80wtFQ7cFTpos/q+m9XVRFggu+8Ib895XhMWdJBzew6aaOcMvYR6KQ6JmHA2/eMzWw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm-gnueabihf": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.1.6.tgz", + "integrity": "sha512-OybkbC58A1wJ+JrJSOjGDvZzrVEQA4sprJejGqMwiZyLqhr9Eo8FXF0y6HL+m1CPCpPhXEHz/2xKoYsl16kNqw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.1.6.tgz", + "integrity": "sha512-yCH+yDr7/4FDuWv6+GiYrPI9kcTAO3y48UmaIbrKy8ZJpi7RehJe3vIBRUmLrLaNDH3rY1rwoHi471NvR5J5NQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.1.6.tgz", + "integrity": "sha512-ECagB8LGX25P9Mrmlc7Q/TQBb9rGScxHbv/kLqqIWs2fIXy6Y/EiBBiM72NTwuXUFCNrWR4sjUPSooVBJJ3ESQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.1.6.tgz", + "integrity": "sha512-GT5w2mruk90V/I5g6ScuueE7fqj/d8Bui2qxdw6lFxmuTgMeol5rnzAv4uAoVQgClOUO/MULilzlODg9Ib3Y4Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.1.6.tgz", + "integrity": "sha512-keFD6KvwOPzmat4TCnlnuxJCQepPN+8j3Nw876FtULxo8005Y9Ghcl7ACcR8GoiKoddAq8gxNBrpjoxjQRHeAQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.1.6.tgz", + "integrity": "sha512-OwertslIiGQluFvHyRDzBCIB07qJjqabAmINlXUYt7/sY7Q7QPE8xVi5beBxX/rxTGPIbtyIe3faBE6Z2KywhQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.1.6.tgz", + "integrity": "sha512-g8zowiuP8FxUR9zslPmlju7qYbs2XBtTLVSxVikPtUDQedhcls39uKYLvOOd1JZg0ehyhopobRoH1q+MHlIN/w==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.1.6.tgz", + "integrity": "sha512-Ls2OL9hi3YlJKGNdKv8k3X/lLgc3VmLG3a/DeTkAd+lAituJp8ZHmRmm9f9SL84fT3CotlzcgbdaCDfFwFA6bA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/utils": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", + "integrity": "sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==", + "dependencies": { + "cross-spawn": "^7.0.3", + "is-glob": "^4.0.3", + "open": "^8.4.0", + "picocolors": "^1.0.0", + "tiny-glob": "^0.2.9", + "tslib": "^2.4.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", + "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" + }, + "node_modules/@stitches/core": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz", + "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==" + }, + "node_modules/@stitches/react": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@stitches/react/-/react-1.2.8.tgz", + "integrity": "sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA==", + "peerDependencies": { + "react": ">= 16.3.0" + } + }, + "node_modules/@supabase/auth-helpers-react": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-react/-/auth-helpers-react-0.3.1.tgz", + "integrity": "sha512-g3SFv08Dz9FapNif/ZY1b7qKGlMJDyTLSayHBz3kb3FuYxg7aLWgQtydDhm5AGbc0XtvpIBuhGTIOVevwpdosA==", + "peerDependencies": { + "@supabase/supabase-js": "^2.0.4" + } + }, + "node_modules/@supabase/auth-ui-react": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@supabase/auth-ui-react/-/auth-ui-react-0.2.8.tgz", + "integrity": "sha512-CfN9Zt2t1PMfri8htLCFYYIWho6F15ZvbWICE3RuOZ348z1P15iP6scjxB3704Jbu334Q+ykCQP+UzdnB7cXHQ==", + "dependencies": { + "@stitches/core": "^1.2.8", + "@stitches/react": "^1.2.8", + "prop-types": "^15.7.2" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.1 || ^18.0.0", + "react-dom": "^16.13.1 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.0.0.tgz", + "integrity": "sha512-ozb7bds2yvf5k7NM2ZzUkxvsx4S4i2eRKFSJetdTADV91T65g4gCzEs9L3LUXSrghcGIkUaon03VPzOrFredqg==", + "dependencies": { + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@supabase/gotrue-js": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.12.1.tgz", + "integrity": "sha512-r8Jfq8FvP6q4kp7sI33X1RWfEEHzJFu9uM1Q6HgiDVkY89NNgqYy2kxaRGtidPFllND7vpcJUcpoWS5oq+4u0g==", + "dependencies": { + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.4.1.tgz", + "integrity": "sha512-aruqwV/aTggkM7OVv2JinCeXmRMKHJCZpkuS1nuoa0NgLw7g3NyILSyWOKYTBJ/PxE/zXtWsBhdxFzaaNz5uxg==", + "dependencies": { + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.6.0.tgz", + "integrity": "sha512-tOVulMobhpxyDuu8VIImpL8FXmZOKsGNOSyS5ihJdj2xYmPPvYG+D2J51Ewfl+MFF65tweiB6p9N9bNIW1cDNA==", + "dependencies": { + "@types/phoenix": "^1.5.4", + "websocket": "^1.0.34" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.3.0.tgz", + "integrity": "sha512-YGWVCEYYYF3+UiyL8O4xC78N9n9paLbT0hHl8dmYAtd3DqyWtu5Eph9JTu0PWm+/29Zhns5TbhUZW4xpWjJfPQ==", + "dependencies": { + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.8.0.tgz", + "integrity": "sha512-uzf4J+qAKdUMhB2tnJl6BrQRUQBinwjJ2eWo2ZsDw9EUUP5JcHsxTamiq6p91DpqzmTIRg3xRAT+bItTzbfa0w==", + "dependencies": { + "@supabase/functions-js": "^2.0.0", + "@supabase/gotrue-js": "^2.12.0", + "@supabase/postgrest-js": "^1.1.1", + "@supabase/realtime-js": "^2.4.0", + "@supabase/storage-js": "^2.1.0", + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@swc/helpers": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", + "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + }, + "node_modules/@types/node": { + "version": "18.14.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.0.tgz", + "integrity": "sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==" + }, + "node_modules/@types/phoenix": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.5.tgz", + "integrity": "sha512-1eWWT19k0L4ZiTvdXjAvJ9KvW0B8SdiVftQmFPJGTEx78Q4PCSIQDpz+EfkFVR1N4U9gREjlW4JXL8YCIlY0bw==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.0.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", + "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.53.0.tgz", + "integrity": "sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ==", + "dependencies": { + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz", + "integrity": "sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==", + "dependencies": { + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.53.0.tgz", + "integrity": "sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz", + "integrity": "sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==", + "dependencies": { + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz", + "integrity": "sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==", + "dependencies": { + "@typescript-eslint/types": "5.53.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "node_modules/acorn-node/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz", + "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001457", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz", + "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concurrently": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", + "integrity": "sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "date-fns": "^2.29.1", + "lodash": "^4.17.21", + "rxjs": "^7.0.0", + "shell-quote": "^1.7.3", + "spawn-command": "^0.0.2-1", + "supports-color": "^8.1.0", + "tree-kill": "^1.2.2", + "yargs": "^17.3.1" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" + }, + "node_modules/date-fns": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", + "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", + "dev": true, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", + "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "dependencies": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defined": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", + "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detective": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", + "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", + "dev": true, + "dependencies": { + "acorn-node": "^1.8.2", + "defined": "^1.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "detective": "bin/detective.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "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==", + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz", + "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==", + "dependencies": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-next": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.1.6.tgz", + "integrity": "sha512-0cg7h5wztg/SoLAlxljZ0ZPUQ7i6QKqRiP4M2+MgTZtxWwNKb2JSwNc18nJ6/kXBI6xYvPraTbQSIhAuVw6czw==", + "dependencies": { + "@next/eslint-plugin-next": "13.1.6", + "@rushstack/eslint-patch": "^1.1.3", + "@typescript-eslint/parser": "^5.42.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.31.7", + "eslint-plugin-react-hooks": "^4.5.0" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.3.tgz", + "integrity": "sha512-njRcKYBc3isE42LaTcJNVANR3R99H9bAxBDMNDr2W7yq5gYPxbU3MkdhsQukxZ/Xg9C2vcyLlDsbKfRDg0QvCQ==", + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.10.0", + "get-tsconfig": "^4.2.0", + "globby": "^13.1.2", + "is-core-module": "^2.10.0", + "is-glob": "^4.0.3", + "synckit": "^0.8.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", + "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "aria-query": "^5.1.3", + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.6.2", + "axobject-query": "^3.1.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.3", + "language-tags": "=1.0.5", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.32.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", + "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.2.tgz", + "integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "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==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.4.0.tgz", + "integrity": "sha512-0Gdjo/9+FzsYhXCEFueo2aY1z1tpXrxWZzP7k8ul9qt1U5o8rYJwTJYmaeHdrVosYIVYkOy2iwCJ9FdpocJhPQ==", + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==" + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" + }, + "node_modules/language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dependencies": { + "language-subtag-registry": "~0.3.2" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/next": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-13.1.6.tgz", + "integrity": "sha512-hHlbhKPj9pW+Cymvfzc15lvhaOZ54l+8sXDXJWm3OBNBzgrVj6hwGPmqqsXg40xO1Leq+kXpllzRPuncpC0Phw==", + "dependencies": { + "@next/env": "13.1.6", + "@swc/helpers": "0.4.14", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.14", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=14.6.0" + }, + "optionalDependencies": { + "@next/swc-android-arm-eabi": "13.1.6", + "@next/swc-android-arm64": "13.1.6", + "@next/swc-darwin-arm64": "13.1.6", + "@next/swc-darwin-x64": "13.1.6", + "@next/swc-freebsd-x64": "13.1.6", + "@next/swc-linux-arm-gnueabihf": "13.1.6", + "@next/swc-linux-arm64-gnu": "13.1.6", + "@next/swc-linux-arm64-musl": "13.1.6", + "@next/swc-linux-x64-gnu": "13.1.6", + "@next/swc-linux-x64-musl": "13.1.6", + "@next/swc-win32-arm64-msvc": "13.1.6", + "@next/swc-win32-ia32-msvc": "13.1.6", + "@next/swc-win32-x64-msvc": "13.1.6" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^6.0.0 || ^7.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "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==" + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/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==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", + "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", + "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-command": { + "version": "0.0.2-1", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", + "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", + "dev": true + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tailwindcss": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.7.tgz", + "integrity": "sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==", + "dev": true, + "dependencies": { + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "color-name": "^1.1.4", + "detective": "^5.2.1", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "lilconfig": "^2.0.6", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.0.9", + "postcss-import": "^14.1.0", + "postcss-js": "^4.0.0", + "postcss-load-config": "^3.1.4", + "postcss-nested": "6.0.0", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "quick-lru": "^5.1.1", + "resolve": "^1.22.1" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/tailwindcss/node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/tailwindcss/node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray-to-buffer": { + "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==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf-8-validate": { + "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==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "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==" + }, + "node_modules/websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "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==" + }, + "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==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/examples/todo-list/nextjs-todo-list/package.json b/examples/todo-list/nextjs-todo-list/package.json new file mode 100644 index 00000000000..32fac003794 --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/package.json @@ -0,0 +1,32 @@ +{ + "name": "nextjs-todo-list", + "version": "2.0.0", + "private": true, + "scripts": { + "dev": "concurrently \"npm run dev:css\" \"next dev\"", + "dev:css": "tailwindcss -w -i ./styles/tailwind.css -o styles/app.css", + "build": "next build", + "build:css": "tailwindcss -m -i ./styles/tailwind.css -o styles/app.css", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@next/font": "13.1.6", + "@supabase/auth-helpers-react": "^0.3.1", + "@supabase/auth-ui-react": "^0.2.8", + "@supabase/supabase-js": "^2.8.0", + "@types/node": "18.14.0", + "@types/react": "18.0.28", + "@types/react-dom": "18.0.11", + "eslint": "8.34.0", + "eslint-config-next": "13.1.6", + "next": "13.1.6", + "react": "18.2.0", + "react-dom": "18.2.0", + "typescript": "4.9.5" + }, + "devDependencies": { + "concurrently": "^7.6.0", + "tailwindcss": "^3.2.7" + } +} diff --git a/examples/todo-list/nextjs-todo-list/pages/_app.tsx b/examples/todo-list/nextjs-todo-list/pages/_app.tsx new file mode 100644 index 00000000000..59d31d438bc --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/pages/_app.tsx @@ -0,0 +1,12 @@ +import { supabase } from '@/lib/initSupabase' +import '@/styles/app.css' +import { SessionContextProvider } from '@supabase/auth-helpers-react' +import type { AppProps } from 'next/app' + +export default function App({ Component, pageProps }: AppProps) { + return ( + + + + ) +} diff --git a/examples/todo-list/nextjs-todo-list/pages/_document.tsx b/examples/todo-list/nextjs-todo-list/pages/_document.tsx new file mode 100644 index 00000000000..54e8bf3e2a2 --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
    + + + + ) +} diff --git a/examples/todo-list/nextjs-todo-list/pages/api/hello.ts b/examples/todo-list/nextjs-todo-list/pages/api/hello.ts new file mode 100644 index 00000000000..f8bcc7e5cae --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/pages/api/hello.ts @@ -0,0 +1,13 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from 'next' + +type Data = { + name: string +} + +export default function handler( + req: NextApiRequest, + res: NextApiResponse +) { + res.status(200).json({ name: 'John Doe' }) +} diff --git a/examples/todo-list/nextjs-todo-list/pages/index.tsx b/examples/todo-list/nextjs-todo-list/pages/index.tsx new file mode 100644 index 00000000000..d498b19419f --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/pages/index.tsx @@ -0,0 +1,50 @@ +import Head from 'next/head' +import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react' +import { Auth, ThemeSupa } from '@supabase/auth-ui-react' +import TodoList from '@/components/TodoList' + +export default function Home() { + const session = useSession() + const supabase = useSupabaseClient() + + return ( + <> + + Create Next App + + + + +
    + {!session ? ( +
    +
    +
    + + Login + + +
    +
    +
    + ) : ( +
    + + +
    + )} +
    + + ) +} diff --git a/examples/todo-list/nextjs-todo-list/public/favicon.ico b/examples/todo-list/nextjs-todo-list/public/favicon.ico new file mode 100644 index 00000000000..718d6fea483 Binary files /dev/null and b/examples/todo-list/nextjs-todo-list/public/favicon.ico differ diff --git a/examples/todo-list/nextjs-todo-list/public/next.svg b/examples/todo-list/nextjs-todo-list/public/next.svg new file mode 100644 index 00000000000..5174b28c565 --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/todo-list/nextjs-todo-list/public/thirteen.svg b/examples/todo-list/nextjs-todo-list/public/thirteen.svg new file mode 100644 index 00000000000..8977c1bd123 --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/public/thirteen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/todo-list/nextjs-todo-list/public/vercel.svg b/examples/todo-list/nextjs-todo-list/public/vercel.svg new file mode 100644 index 00000000000..d2f84222734 --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/todo-list/nextjs-todo-list/styles/app.css b/examples/todo-list/nextjs-todo-list/styles/app.css new file mode 100644 index 00000000000..0530e31cacc --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/styles/app.css @@ -0,0 +1,844 @@ +/* +! tailwindcss v3.2.7 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +*/ + +html { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font family by default. +2. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +.mx-4 { + margin-left: 1rem; + margin-right: 1rem; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.my-3 { + margin-top: 0.75rem; + margin-bottom: 0.75rem; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + +.mb-12 { + margin-bottom: 3rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.mt-12 { + margin-top: 3rem; +} + +.block { + display: block; +} + +.flex { + display: flex; +} + +.h-4 { + height: 1rem; +} + +.h-full { + height: 100%; +} + +.min-h-screen { + min-height: 100vh; +} + +.w-4 { + width: 1rem; +} + +.w-full { + width: 100%; +} + +.min-w-0 { + min-width: 0px; +} + +.min-w-full { + min-width: 100%; +} + +.max-w-sm { + max-width: 24rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +.cursor-pointer { + cursor: pointer; +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.gap-2 { + gap: 0.5rem; +} + +.overflow-hidden { + overflow: hidden; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.border-2 { + border-width: 2px; +} + +.border-b { + border-bottom-width: 1px; +} + +.bg-gray-200 { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.bg-red-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.p-2 { + padding: 0.5rem; +} + +.p-4 { + padding: 1rem; +} + +.p-5 { + padding: 1.25rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.pb-2 { + padding-bottom: 0.5rem; +} + +.text-center { + text-align: center; +} + +.font-sans { + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} + +.text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; +} + +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.font-medium { + font-weight: 500; +} + +.leading-5 { + line-height: 1.25rem; +} + +.text-red-700 { + --tw-text-opacity: 1; + color: rgb(185 28 28 / var(--tw-text-opacity)); +} + +.shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-150 { + transition-duration: 150ms; +} + +.ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Write your own custom component styles here */ + +.btn-black { + border-radius: 0.25rem; + border-width: 1px; + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity)); + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + padding-right: 1rem; + text-align: center; + font-weight: 700; + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.btn-black:hover { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.btn-black-outline { + border-radius: 0.25rem; + border-width: 1px; + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity)); + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + padding-right: 1rem; + text-align: center; + font-weight: 700; + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); +} + +.btn-black-outline:hover { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +html, +body, +#__next { + height: 100vh; + min-height: 100vh; +} + +h1 { + font-size: 4rem; + font-weight: bold; + display: block; +} + +.hover\:border-black:hover { + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity)); +} + +.hover\:bg-gray-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.focus\:bg-gray-200:focus { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +@media (min-width: 640px) { + .sm\:h-auto { + height: auto; + } + + .sm\:w-2\/5 { + width: 40%; + } + + .sm\:px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; + } +} diff --git a/examples/todo-list/nextjs-todo-list/styles/tailwind.css b/examples/todo-list/nextjs-todo-list/styles/tailwind.css new file mode 100644 index 00000000000..a183f14adb7 --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/styles/tailwind.css @@ -0,0 +1,30 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Write your own custom component styles here */ +.btn-black { + @apply border border-black bg-black text-white font-bold py-2 px-4 rounded text-center; +} +.btn-black:hover { + @apply transition duration-150 bg-white text-black; +} +.btn-black-outline { + @apply border border-black text-black py-2 px-4 rounded font-bold text-center; +} +.btn-black-outline:hover { + @apply transition duration-150 bg-black text-white; +} + +html, +body, +#__next { + height: 100vh; + min-height: 100vh; +} + +h1 { + font-size: 4rem; + font-weight: bold; + display: block; +} diff --git a/examples/todo-list/nextjs-todo-list/tailwind.config.js b/examples/todo-list/nextjs-todo-list/tailwind.config.js new file mode 100644 index 00000000000..81f91ba2f15 --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/tailwind.config.js @@ -0,0 +1,13 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./components/**/*.{js,ts,jsx,tsx}', './pages/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: { + colors: { + 'accent-1': '#333', + }, + }, + }, + variants: {}, + plugins: [], +} diff --git a/examples/todo-list/nextjs-todo-list/tsconfig.json b/examples/todo-list/nextjs-todo-list/tsconfig.json new file mode 100644 index 00000000000..f4ab65fd2eb --- /dev/null +++ b/examples/todo-list/nextjs-todo-list/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/examples/todo-list/sveltejs-todo-list/src/assets/app.css b/examples/todo-list/sveltejs-todo-list/src/assets/app.css index e20cf35c6f2..2bf19da9df6 100644 --- a/examples/todo-list/sveltejs-todo-list/src/assets/app.css +++ b/examples/todo-list/sveltejs-todo-list/src/assets/app.css @@ -457,53 +457,6 @@ video { --tw-backdrop-sepia: ; } -::-webkit-backdrop { - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(59 130 246 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; -} - ::backdrop { --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; diff --git a/package-lock.json b/package-lock.json index ad7da916061..dbf8afff773 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "supabase", "version": "0.0.0", "license": "Apache-2.0", "workspaces": [ @@ -15,20 +16,14 @@ "tests", "packages/*" ], - "dependencies": { - "@code-hike/mdx": "^0.7.4", - "@mdx-js/react": "^2.1.1", - "aws-sdk": "^2.1315.0", - "mdx-mermaid": "^1.3.2", - "mermaid": "^9.2.2", - "mini-svg-data-uri": "^1.4.4" - }, "devDependencies": { "@types/json-stringify-safe": "^5.0.0", + "aws-sdk": "^2.1315.0", "eslint": "^7.32.0", "eslint-config-custom": "*", "json-stringify-safe": "^5.0.1", - "prettier": "^2.5.1", + "prettier": "^2.8.4", + "prettier-plugin-sql-cst": "^0.5.0", "ts-jest": "^27.0.7", "turbo": "^1.4.7" }, @@ -49,7 +44,7 @@ "@radix-ui/react-accordion": "^1.0.1", "@supabase/auth-helpers-nextjs": "^0.5.4", "@supabase/auth-helpers-react": "^0.3.1", - "@supabase/supabase-js": "^2.4.1", + "@supabase/supabase-js": "^2.8.0", "algoliasearch": "^4.14.2", "babel": "^6.23.0", "clsx": "^1.2.1", @@ -104,7 +99,6 @@ "next-transpile-modules": "9.0.0", "npm-run-all": "^4.1.5", "openapi-types": "^12.0.2", - "prettier": "^2.2.1", "sass": "^1.55.0", "ts-node": "^10.9.1", "tsconfig": "*", @@ -406,6 +400,36 @@ "node": ">= 10" } }, + "apps/docs/node_modules/@supabase/gotrue-js": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.12.1.tgz", + "integrity": "sha512-r8Jfq8FvP6q4kp7sI33X1RWfEEHzJFu9uM1Q6HgiDVkY89NNgqYy2kxaRGtidPFllND7vpcJUcpoWS5oq+4u0g==", + "dependencies": { + "cross-fetch": "^3.1.5" + } + }, + "apps/docs/node_modules/@supabase/realtime-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.6.0.tgz", + "integrity": "sha512-tOVulMobhpxyDuu8VIImpL8FXmZOKsGNOSyS5ihJdj2xYmPPvYG+D2J51Ewfl+MFF65tweiB6p9N9bNIW1cDNA==", + "dependencies": { + "@types/phoenix": "^1.5.4", + "websocket": "^1.0.34" + } + }, + "apps/docs/node_modules/@supabase/supabase-js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.8.0.tgz", + "integrity": "sha512-uzf4J+qAKdUMhB2tnJl6BrQRUQBinwjJ2eWo2ZsDw9EUUP5JcHsxTamiq6p91DpqzmTIRg3xRAT+bItTzbfa0w==", + "dependencies": { + "@supabase/functions-js": "^2.0.0", + "@supabase/gotrue-js": "^2.12.0", + "@supabase/postgrest-js": "^1.1.1", + "@supabase/realtime-js": "^2.4.0", + "@supabase/storage-js": "^2.1.0", + "cross-fetch": "^3.1.5" + } + }, "apps/docs/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -942,7 +966,6 @@ "file-loader": "^6.2.0", "postcss": "^8.4.18", "postcss-preset-env": "^6.7.0", - "prettier": "^2.2.1", "tailwindcss": "^3.1.8", "tsconfig": "*" } @@ -3131,7 +3154,8 @@ "node_modules/@braintree/sanitize-url": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz", - "integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==" + "integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==", + "peer": true }, "node_modules/@cnakazawa/watch": { "version": "1.0.4", @@ -3149,26 +3173,35 @@ "node": ">=0.1.95" } }, - "node_modules/@code-hike/mdx": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@code-hike/mdx/-/mdx-0.7.4.tgz", - "integrity": "sha512-MHnoSJ7wxJ2LJfJ/x+18jh6JOBCPwk1EMQRgVINj3yyejqsYgOQx2ChE+/n+n3AdP7J5RLKfQ6qm/jiNxD8+Lg==", + "node_modules/@codemirror/language": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.0.0.tgz", + "integrity": "sha512-rtjk5ifyMzOna1c7PBu7J1VCt0PvA5wy3o8eMVnxMKb7z8KA7JFecvD04dSn14vj/bBaAbqRsGed5OjtofEnLA==", + "peer": true, "dependencies": { - "hast-util-to-estree": "^1.4.0", - "is-plain-obj": "^3.0.0", - "node-fetch": "^2.0.0", - "remark-rehype": "^8.1.0", - "shiki": "^0.10.1", - "unified": "^9.2.2", - "unist-util-visit": "^2.0.0", - "unist-util-visit-parents": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/code-hike" - }, - "peerDependencies": { - "react": "^16.8.3 || ^17 || ^18" + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/state": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz", + "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", + "peer": true + }, + "node_modules/@codemirror/view": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.0.tgz", + "integrity": "sha512-uFaqE6fBOp0Dj/tmWoe/TFlSSIPdpAzhvATgbq1eAKRkgq3hY79FioZ7nfdiT+24kz68AIWuSZ9hi3psKe36WQ==", + "peer": true, + "dependencies": { + "@codemirror/state": "^6.1.4", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" } }, "node_modules/@colors/colors": { @@ -3501,6 +3534,66 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, + "node_modules/@graphiql/react": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@graphiql/react/-/react-0.15.0.tgz", + "integrity": "sha512-kJqkdf6d4Cck05Wt5yCDZXWfs7HZgcpuoWq/v8nOa698qVaNMM3qdG4CpRsZEexku0DSSJzWWuanxd5x+sRcFg==", + "dependencies": { + "@graphiql/toolkit": "^0.8.0", + "@reach/combobox": "^0.17.0", + "@reach/dialog": "^0.17.0", + "@reach/listbox": "^0.17.0", + "@reach/menu-button": "^0.17.0", + "@reach/tooltip": "^0.17.0", + "@reach/visually-hidden": "^0.17.0", + "codemirror": "^5.65.3", + "codemirror-graphql": "^2.0.2", + "copy-to-clipboard": "^3.2.0", + "graphql-language-service": "^5.1.0", + "markdown-it": "^12.2.0", + "set-value": "^4.1.0" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@graphiql/react/node_modules/set-value": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-4.1.0.tgz", + "integrity": "sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==", + "funding": [ + "https://github.com/sponsors/jonschlinkert", + "https://paypal.me/jonathanschlinkert", + "https://jonschlinkert.dev/sponsor" + ], + "dependencies": { + "is-plain-object": "^2.0.4", + "is-primitive": "^3.0.1" + }, + "engines": { + "node": ">=11.0" + } + }, + "node_modules/@graphiql/toolkit": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.8.0.tgz", + "integrity": "sha512-DbMFhEKejpPzB6k8W3Mj+Rl8geXiw49USDF9Wdi06EEk1XLVh1iebDqveYY+4lViITsV4+BeGikxlqi8umfP4g==", + "dependencies": { + "@n1ru4l/push-pull-async-iterable-iterator": "^3.1.0", + "meros": "^1.1.4" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0", + "graphql-ws": ">= 4.5.0" + }, + "peerDependenciesMeta": { + "graphql-ws": { + "optional": true + } + } + }, "node_modules/@hcaptcha/react-hcaptcha": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/@hcaptcha/react-hcaptcha/-/react-hcaptcha-1.4.4.tgz", @@ -4500,122 +4593,28 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", - "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", - "dev": true, + "node_modules/@lezer/common": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz", + "integrity": "sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==", + "peer": true + }, + "node_modules/@lezer/highlight": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz", + "integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==", + "peer": true, "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" + "@lezer/common": "^1.0.0" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, + "node_modules/@lezer/lr": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.3.tgz", + "integrity": "sha512-JPQe3mwJlzEVqy67iQiiGozhcngbO8QBgpqZM6oL1Wj/dXckrEexpBLeFkq0edtW5IqnPRFxA24BHJni8Js69w==", + "peer": true, "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "@lezer/common": "^1.0.0" } }, "node_modules/@mdx-js/loader": { @@ -4980,6 +4979,14 @@ "integrity": "sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==", "dev": true }, + "node_modules/@n1ru4l/push-pull-async-iterable-iterator": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-3.2.0.tgz", + "integrity": "sha512-3fkKj25kEjsfObL6IlKPAlHYPq/oYwUkkQ03zsTTiDjD7vg/RxjdiLeCydqtxHZP0JgsXL3D/X5oAkMGzuUp/Q==", + "engines": { + "node": ">=12" + } + }, "node_modules/@next/bundle-analyzer": { "version": "13.0.5", "resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-13.0.5.tgz", @@ -7196,6 +7203,229 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@reach/auto-id": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.17.0.tgz", + "integrity": "sha512-ud8iPwF52RVzEmkHq1twuqGuPA+moreumUHdtgvU3sr3/15BNhwp3KyDLrKKSz0LP1r3V4pSdyF9MbYM8BoSjA==", + "dependencies": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/combobox": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/combobox/-/combobox-0.17.0.tgz", + "integrity": "sha512-2mYvU5agOBCQBMdlM4cri+P1BbNwp05P1OuDyc33xJSNiBG7BMy4+ZSHJ0X4fyle6rHwSgCAOCLOeWV1XUYjoQ==", + "dependencies": { + "@reach/auto-id": "0.17.0", + "@reach/descendants": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/portal": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/descendants": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/descendants/-/descendants-0.17.0.tgz", + "integrity": "sha512-c7lUaBfjgcmKFZiAWqhG+VnXDMEhPkI4kAav/82XKZD6NVvFjsQOTH+v3tUkskrAPV44Yuch0mFW/u5Ntifr7Q==", + "dependencies": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/dialog": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/dialog/-/dialog-0.17.0.tgz", + "integrity": "sha512-AnfKXugqDTGbeG3c8xDcrQDE4h9b/vnc27Sa118oQSquz52fneUeX9MeFb5ZEiBJK8T5NJpv7QUTBIKnFCAH5A==", + "dependencies": { + "@reach/portal": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "react-focus-lock": "^2.5.2", + "react-remove-scroll": "^2.4.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/dropdown": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/dropdown/-/dropdown-0.17.0.tgz", + "integrity": "sha512-qBTIGInhxtPHtdj4Pl2XZgZMz3e37liydh0xR3qc48syu7g71sL4nqyKjOzThykyfhA3Pb3/wFgsFJKGTSdaig==", + "dependencies": { + "@reach/auto-id": "0.17.0", + "@reach/descendants": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/listbox": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/listbox/-/listbox-0.17.0.tgz", + "integrity": "sha512-AMnH1P6/3VKy2V/nPb4Es441arYR+t4YRdh9jdcFVrCOD6y7CQrlmxsYjeg9Ocdz08XpdoEBHM3PKLJqNAUr7A==", + "dependencies": { + "@reach/auto-id": "0.17.0", + "@reach/descendants": "0.17.0", + "@reach/machine": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/machine": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/machine/-/machine-0.17.0.tgz", + "integrity": "sha512-9EHnuPgXzkbRENvRUzJvVvYt+C2jp7PGN0xon7ffmKoK8rTO6eA/bb7P0xgloyDDQtu88TBUXKzW0uASqhTXGA==", + "dependencies": { + "@reach/utils": "0.17.0", + "@xstate/fsm": "1.4.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/menu-button": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/menu-button/-/menu-button-0.17.0.tgz", + "integrity": "sha512-YyuYVyMZKamPtivoEI6D0UEILYH3qZtg4kJzEAuzPmoR/aHN66NZO75Fx0gtjG1S6fZfbiARaCOZJC0VEiDOtQ==", + "dependencies": { + "@reach/dropdown": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x", + "react-is": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/observe-rect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz", + "integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==" + }, + "node_modules/@reach/popover": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/popover/-/popover-0.17.0.tgz", + "integrity": "sha512-yYbBF4fMz4Ml4LB3agobZjcZ/oPtPsNv70ZAd7lEC2h7cvhF453pA+zOBGYTPGupKaeBvgAnrMjj7RnxDU5hoQ==", + "dependencies": { + "@reach/portal": "0.17.0", + "@reach/rect": "0.17.0", + "@reach/utils": "0.17.0", + "tabbable": "^4.0.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/portal": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz", + "integrity": "sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A==", + "dependencies": { + "@reach/utils": "0.17.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/rect": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.17.0.tgz", + "integrity": "sha512-3YB7KA5cLjbLc20bmPkJ06DIfXSK06Cb5BbD2dHgKXjUkT9WjZaLYIbYCO8dVjwcyO3GCNfOmPxy62VsPmZwYA==", + "dependencies": { + "@reach/observe-rect": "1.2.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/tooltip": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/tooltip/-/tooltip-0.17.0.tgz", + "integrity": "sha512-HP8Blordzqb/Cxg+jnhGmWQfKgypamcYLBPlcx6jconyV5iLJ5m93qipr1giK7MqKT2wlsKWy44ZcOrJ+Wrf8w==", + "dependencies": { + "@reach/auto-id": "0.17.0", + "@reach/portal": "0.17.0", + "@reach/rect": "0.17.0", + "@reach/utils": "0.17.0", + "@reach/visually-hidden": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/utils": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz", + "integrity": "sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA==", + "dependencies": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/visually-hidden": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/visually-hidden/-/visually-hidden-0.17.0.tgz", + "integrity": "sha512-T6xF3Nv8vVnjVkGU6cm0+kWtvliLqPAo8PcZ+WxkKacZsaHTjaZb4v1PaCcyQHmuTNT/vtTVNOJLG0SjQOIb7g==", + "dependencies": { + "prop-types": "^15.7.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, "node_modules/@react-dnd/asap": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz", @@ -7483,9 +7713,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.25.21", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.21.tgz", - "integrity": "sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g==", + "version": "0.25.23", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.23.tgz", + "integrity": "sha512-VEB8ygeP42CFLWyAJhN5OklpxUliqdNEUcXb4xZ/CINqtYGTjL5ukluKdKzQ0iWdUxyQ7B0539PAUhHKrCNWSQ==", "dev": true }, "node_modules/@sinonjs/commons": { @@ -9990,9 +10220,9 @@ } }, "node_modules/@supabase/postgres-meta": { - "version": "0.60.2", - "resolved": "https://registry.npmjs.org/@supabase/postgres-meta/-/postgres-meta-0.60.2.tgz", - "integrity": "sha512-Fjc5xKPzZJ38FhY3IbDFZ5cgVxPJTn8psLkgljZgBOMUyCd3QY3W1jV8rjwAkLfz/lxIOa2KlKZTIswMZMx4WQ==", + "version": "0.60.7", + "resolved": "https://registry.npmjs.org/@supabase/postgres-meta/-/postgres-meta-0.60.7.tgz", + "integrity": "sha512-vC5mwZ4sVAA5Hw57GSSDI7rGBY8/m8RscRHaFwfnqKgXFxKMvlfrzxnP/MwXg7EXRW1jfgvMAEkxuJNLDe1YMA==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.25.1", @@ -11362,6 +11592,11 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@xstate/fsm": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-1.4.0.tgz", + "integrity": "sha512-uTHDeu2xI5E1IFwf37JFQM31RrH7mY7877RqPBS4ZqSNUwoLDuct8AhBWaXGnVizBAYyimVwgCyGa9z/NiRhXA==" + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -11387,12 +11622,6 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -11473,29 +11702,6 @@ "node": ">= 6.0.0" } }, - "node_modules/agentkeepalive": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", - "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/agentkeepalive/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -12171,6 +12377,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -12218,6 +12425,7 @@ "version": "2.1315.0", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1315.0.tgz", "integrity": "sha512-fDlK3iqA0bpXW6azVBAB8RTKhJ5YUjfe6sqKQF1lKT2cYT5qU4rtH5qUlMixlWkb8oKeSMA2voW3wZZtSpBFKQ==", + "dev": true, "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -12238,6 +12446,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "dev": true, "engines": { "node": ">=0.4.x" } @@ -12245,17 +12454,20 @@ "node_modules/aws-sdk/node_modules/ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true }, "node_modules/aws-sdk/node_modules/punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "dev": true }, "node_modules/aws-sdk/node_modules/url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "dev": true, "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" @@ -12265,6 +12477,7 @@ "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -12277,6 +12490,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", + "dev": true, "bin": { "uuid": "dist/bin/uuid" } @@ -12893,6 +13107,7 @@ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", "dev": true, + "optional": true, "engines": { "node": ">=0.6" } @@ -13243,15 +13458,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, - "node_modules/buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -13905,6 +14111,24 @@ "node": ">=0.10.0" } }, + "node_modules/codemirror": { + "version": "5.65.11", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.11.tgz", + "integrity": "sha512-Gp62g2eKSCHYt10axmGhKq3WoJSvVpvhXmowNq7pZdRVowwtvBR/hi2LSP5srtctKkRT33T6/n8Kv1UGp7JW4A==" + }, + "node_modules/codemirror-graphql": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-2.0.3.tgz", + "integrity": "sha512-T58f+XQVKE6/6goAADJfu56wPRcdF2RUZMHITUol6h6Uo1KKUmknV4muKNFOl6GM15BjBraysF6HATp4oY6kyA==", + "dependencies": { + "graphql-language-service": "5.1.1" + }, + "peerDependencies": { + "@codemirror/language": "6.0.0", + "codemirror": "^5.65.3", + "graphql": "^15.5.0 || ^16.0.0" + } + }, "node_modules/coffee-script": { "version": "1.12.7", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", @@ -15115,6 +15339,7 @@ "version": "7.6.1", "resolved": "https://registry.npmjs.org/d3/-/d3-7.6.1.tgz", "integrity": "sha512-txMTdIHFbcpLx+8a0IFhZsbp+PfBBPt8yfbmukZTQFroKuFqIwqswF0qE5JXWefylaAVpSXFoKm3yP+jpNLFLw==", + "peer": true, "dependencies": { "d3-array": "3", "d3-axis": "3", @@ -15155,6 +15380,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", + "peer": true, "dependencies": { "internmap": "1 - 2" }, @@ -15166,6 +15392,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "peer": true, "engines": { "node": ">=12" } @@ -15174,6 +15401,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "peer": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -15189,6 +15417,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "peer": true, "dependencies": { "d3-path": "1 - 3" }, @@ -15199,12 +15428,14 @@ "node_modules/d3-collection": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", - "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==", + "peer": true }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "peer": true, "engines": { "node": ">=12" } @@ -15213,6 +15444,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.0.tgz", "integrity": "sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==", + "peer": true, "dependencies": { "d3-array": "^3.2.0" }, @@ -15224,6 +15456,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", + "peer": true, "dependencies": { "delaunator": "5" }, @@ -15235,6 +15468,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "peer": true, "engines": { "node": ">=12" } @@ -15243,6 +15477,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "peer": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" @@ -15255,6 +15490,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "peer": true, "dependencies": { "commander": "7", "iconv-lite": "0.6", @@ -15279,6 +15515,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "peer": true, "engines": { "node": ">= 10" } @@ -15287,6 +15524,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -15298,6 +15536,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "peer": true, "engines": { "node": ">=12" } @@ -15306,6 +15545,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "peer": true, "dependencies": { "d3-dsv": "1 - 3" }, @@ -15317,6 +15557,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "peer": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", @@ -15330,6 +15571,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "peer": true, "engines": { "node": ">=12" } @@ -15338,6 +15580,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", + "peer": true, "dependencies": { "d3-array": "2.5.0 - 3" }, @@ -15349,6 +15592,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "peer": true, "engines": { "node": ">=12" } @@ -15357,6 +15601,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "peer": true, "dependencies": { "d3-color": "1 - 3" }, @@ -15368,6 +15613,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==", + "peer": true, "engines": { "node": ">=12" } @@ -15376,6 +15622,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "peer": true, "engines": { "node": ">=12" } @@ -15384,6 +15631,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "peer": true, "engines": { "node": ">=12" } @@ -15392,6 +15640,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "peer": true, "engines": { "node": ">=12" } @@ -15400,6 +15649,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "peer": true, "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", @@ -15415,6 +15665,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "peer": true, "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" @@ -15427,6 +15678,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "peer": true, "engines": { "node": ">=12" } @@ -15435,6 +15687,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", + "peer": true, "dependencies": { "d3-path": "1 - 3" }, @@ -15446,6 +15699,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", + "peer": true, "dependencies": { "d3-array": "2 - 3" }, @@ -15457,6 +15711,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "peer": true, "dependencies": { "d3-time": "1 - 3" }, @@ -15468,6 +15723,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "peer": true, "engines": { "node": ">=12" } @@ -15476,6 +15732,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "peer": true, "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", @@ -15493,12 +15750,14 @@ "node_modules/d3-voronoi": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", - "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==", + "peer": true }, "node_modules/d3-zoom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "peer": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -15514,6 +15773,7 @@ "version": "0.8.5", "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "peer": true, "dependencies": { "graphlib": "^2.1.8", "lodash": "^4.17.15" @@ -15523,6 +15783,7 @@ "version": "0.6.4", "resolved": "https://registry.npmjs.org/dagre-d3/-/dagre-d3-0.6.4.tgz", "integrity": "sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==", + "peer": true, "dependencies": { "d3": "^5.14", "dagre": "^0.8.5", @@ -15533,12 +15794,14 @@ "node_modules/dagre-d3/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3": { "version": "5.16.0", "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", + "peer": true, "dependencies": { "d3-array": "1", "d3-axis": "1", @@ -15576,17 +15839,20 @@ "node_modules/dagre-d3/node_modules/d3-array": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", - "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-axis": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", - "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" + "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-brush": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.6.tgz", "integrity": "sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==", + "peer": true, "dependencies": { "d3-dispatch": "1", "d3-drag": "1", @@ -15599,6 +15865,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "peer": true, "dependencies": { "d3-array": "1", "d3-path": "1" @@ -15607,12 +15874,14 @@ "node_modules/dagre-d3/node_modules/d3-color": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", - "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-contour": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "peer": true, "dependencies": { "d3-array": "^1.1.1" } @@ -15620,12 +15889,14 @@ "node_modules/dagre-d3/node_modules/d3-dispatch": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", - "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-drag": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", + "peer": true, "dependencies": { "d3-dispatch": "1", "d3-selection": "1" @@ -15635,6 +15906,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", + "peer": true, "dependencies": { "commander": "2", "iconv-lite": "0.4", @@ -15655,12 +15927,14 @@ "node_modules/dagre-d3/node_modules/d3-ease": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", - "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" + "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-fetch": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", + "peer": true, "dependencies": { "d3-dsv": "1" } @@ -15669,6 +15943,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "peer": true, "dependencies": { "d3-collection": "1", "d3-dispatch": "1", @@ -15679,12 +15954,14 @@ "node_modules/dagre-d3/node_modules/d3-format": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", - "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-geo": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", + "peer": true, "dependencies": { "d3-array": "1" } @@ -15692,12 +15969,14 @@ "node_modules/dagre-d3/node_modules/d3-hierarchy": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", - "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-interpolate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", + "peer": true, "dependencies": { "d3-color": "1" } @@ -15705,27 +15984,32 @@ "node_modules/dagre-d3/node_modules/d3-path": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-polygon": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", - "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" + "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-quadtree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", - "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-random": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", - "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-scale": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "peer": true, "dependencies": { "d3-array": "^1.2.0", "d3-collection": "1", @@ -15739,6 +16023,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "peer": true, "dependencies": { "d3-color": "1", "d3-interpolate": "1" @@ -15747,12 +16032,14 @@ "node_modules/dagre-d3/node_modules/d3-selection": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", - "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-shape": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "peer": true, "dependencies": { "d3-path": "1" } @@ -15760,12 +16047,14 @@ "node_modules/dagre-d3/node_modules/d3-time": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", - "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-time-format": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "peer": true, "dependencies": { "d3-time": "1" } @@ -15773,12 +16062,14 @@ "node_modules/dagre-d3/node_modules/d3-timer": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", - "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==", + "peer": true }, "node_modules/dagre-d3/node_modules/d3-transition": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "peer": true, "dependencies": { "d3-color": "1", "d3-dispatch": "1", @@ -15792,6 +16083,7 @@ "version": "1.8.3", "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "peer": true, "dependencies": { "d3-dispatch": "1", "d3-drag": "1", @@ -16016,6 +16308,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "peer": true, "dependencies": { "robust-predicates": "^3.0.0" } @@ -16091,15 +16384,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -16213,12 +16497,6 @@ "node": ">=8" } }, - "node_modules/discontinuous-range": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", - "dev": true - }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -16361,7 +16639,8 @@ "node_modules/dompurify": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz", - "integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==" + "integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==", + "peer": true }, "node_modules/domutils": { "version": "2.8.0", @@ -16402,12 +16681,6 @@ "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", "dev": true }, - "node_modules/dotty": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dotty/-/dotty-0.1.2.tgz", - "integrity": "sha512-V0EWmKeH3DEhMwAZ+8ZB2Ao4OK6p++Z0hsDtZq3N0+0ZMVqkzrcEGROvOnZpLnvBg5PTNG23JEDLAm64gPaotQ==", - "dev": true - }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -16516,6 +16789,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "optional": true, + "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -16525,6 +16799,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "optional": true, + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -16591,21 +16866,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, "node_modules/errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -17922,6 +18182,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-1.0.0.tgz", "integrity": "sha512-sL7dTwFGqzelPlB56lRZY1CC/yDxCe365WQpxNd49ispL40Yv8Tv4SmteGbvZeFwShOOVKfMlo4jrVvwoaMosA==", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -18405,7 +18666,8 @@ "node_modules/fast-clone": { "version": "1.5.13", "resolved": "https://registry.npmjs.org/fast-clone/-/fast-clone-1.5.13.tgz", - "integrity": "sha512-0ez7coyFBQFjZtId+RJqJ+EQs61w9xARfqjqK0AD9vIUkSxWD4HvPt80+5evebZ1tTnv1GYKrPTipx7kOW5ipA==" + "integrity": "sha512-0ez7coyFBQFjZtId+RJqJ+EQs61w9xARfqjqK0AD9vIUkSxWD4HvPt80+5evebZ1tTnv1GYKrPTipx7kOW5ipA==", + "peer": true }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -18809,6 +19071,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -19471,6 +19734,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -19493,10 +19757,45 @@ "version": "2.1.8", "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "peer": true, "dependencies": { "lodash": "^4.17.15" } }, + "node_modules/graphql": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", + "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-language-service": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-5.1.1.tgz", + "integrity": "sha512-gpaDT9E3+3eWhoqO4C81CGhkzr7Vp2jH/eq+ykoUbgfvMEpqhGTfCeNmrf+S4K/+4WTkAAJBsYT0/ZPZkqe/Hg==", + "dependencies": { + "nullthrows": "^1.0.0", + "vscode-languageserver-types": "^3.17.1" + }, + "bin": { + "graphql": "dist/temp-bin.js" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0" + } + }, + "node_modules/graphql-ws": { + "version": "5.11.3", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.11.3.tgz", + "integrity": "sha512-fU8zwSgAX2noXAsuFiCZ8BtXeXZOzXyK5u1LloCdacsVth4skdBMPO74EG51lBoWSIZ8beUocdpV8+cQHBODnQ==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": ">=0.11 <=16" + } + }, "node_modules/gray-matter": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", @@ -20039,6 +20338,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-1.4.0.tgz", "integrity": "sha512-CiOAIESUKkSOcYbvTth9+yM28z5ArpsYqxWc7LWJxOx975WRUBDjvVuuzZR2o09BNlkf7bp8G2GlOHepBRKJ8Q==", + "peer": true, "dependencies": { "comma-separated-tokens": "^1.0.0", "estree-util-attach-comments": "^1.0.0", @@ -20059,6 +20359,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-1.1.0.tgz", "integrity": "sha512-OVJZ3fGGt9By77Ix9NhaRbzfbDV/2rx9EP7YIDJTmsZSEc5kYn2vWcNccYyahJL2uAQZK2a5Or2i0wtIKTPoRQ==", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -20171,6 +20472,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz", "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" @@ -20386,12 +20688,6 @@ "entities": "^2.0.0" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -20448,15 +20744,6 @@ "node": ">=10.17.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -20728,6 +21015,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "peer": true, "engines": { "node": ">=12" } @@ -20827,6 +21115,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -21054,6 +21343,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -21084,12 +21374,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, "node_modules/is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -21155,17 +21439,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -21183,6 +21456,14 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "node_modules/is-primitive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz", + "integrity": "sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-reference": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.0.tgz", @@ -21280,6 +21561,7 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -23292,6 +23574,7 @@ "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "dev": true, "engines": { "node": ">= 0.6.0" } @@ -23637,7 +23920,8 @@ "node_modules/khroma": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.0.0.tgz", - "integrity": "sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==" + "integrity": "sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==", + "peer": true }, "node_modules/kind-of": { "version": "6.0.3", @@ -23737,18 +24021,6 @@ "node": ">= 0.8.0" } }, - "node_modules/libpg-query": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/libpg-query/-/libpg-query-13.3.0.tgz", - "integrity": "sha512-9PKW8iRWnSCw+IW5LlZYq+bLMmAhKsAIR2en8iRYnoRLBg4x+HKHBb+PQyqFy8+jC7AmzV65A+DsUO7tiZddEQ==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.8", - "node-addon-api": "^1.6.3", - "node-gyp": "^8.0.0" - } - }, "node_modules/lie": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", @@ -23771,6 +24043,14 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "devOptional": true }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/list-item": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/list-item/-/list-item-1.1.1.tgz", @@ -24078,33 +24358,6 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "devOptional": true }, - "node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -24166,6 +24419,29 @@ "node": ">=0.10.0" } }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/markdown-link": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/markdown-link/-/markdown-link-0.1.1.tgz", @@ -24868,16 +25144,6 @@ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" }, - "node_modules/mdx-mermaid": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mdx-mermaid/-/mdx-mermaid-1.3.2.tgz", - "integrity": "sha512-8kw0tg3isKKBFzFwoe2DhIaEgKYtVeJXQtxZCCrdTPO0CTpXHnTHT0atDqsp7YkXi5iUCp/zAZPZu1cmr68T3w==", - "peerDependencies": { - "mermaid": ">=8.11.0", - "react": "^16.8.4 || ^17.0.0 || ^18.0.0", - "unist-util-visit": "^2.0.0" - } - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -25101,6 +25367,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.2.2.tgz", "integrity": "sha512-6s7eKMqFJGS+0MYjmx8f6ZigqKBJVoSx5ql2gw6a4Aa+WJ49QiEJg7gPwywaBg3DZMs79UP7trESp4+jmaQccw==", + "peer": true, "dependencies": { "@braintree/sanitize-url": "^6.0.0", "d3": "^7.0.0", @@ -25121,10 +25388,27 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "peer": true, "bin": { "uuid": "dist/bin/uuid" } }, + "node_modules/meros": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/meros/-/meros-1.2.1.tgz", + "integrity": "sha512-R2f/jxYqCAGI19KhAvaxSOxALBMkaXWH2a7rOyqQw+ZmizX5bKkEYWLzdhC+U82ZVVPVp6MCXe3EkVligh+12g==", + "engines": { + "node": ">=13" + }, + "peerDependencies": { + "@types/node": ">=13" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -25984,23 +26268,6 @@ "node": ">= 8" } }, - "node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "dev": true, - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, "node_modules/minipass-flush": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", @@ -26025,18 +26292,6 @@ "node": ">=8" } }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -26137,13 +26392,8 @@ "node_modules/moment-mini": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.29.4.tgz", - "integrity": "sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg==" - }, - "node_modules/moo": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", - "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", - "dev": true + "integrity": "sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg==", + "peer": true }, "node_modules/moo-color": { "version": "1.0.3", @@ -26276,34 +26526,6 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, - "node_modules/nearley": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", - "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", - "dev": true, - "dependencies": { - "commander": "^2.19.0", - "moo": "^0.5.0", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6" - }, - "bin": { - "nearley-railroad": "bin/nearley-railroad.js", - "nearley-test": "bin/nearley-test.js", - "nearley-unparse": "bin/nearley-unparse.js", - "nearleyc": "bin/nearleyc.js" - }, - "funding": { - "type": "individual", - "url": "https://nearley.js.org/#give-to-nearley" - } - }, - "node_modules/nearley/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -26863,12 +27085,6 @@ "tslib": "^2.0.3" } }, - "node_modules/node-addon-api": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", - "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", - "dev": true - }, "node_modules/node-cache": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", @@ -26930,30 +27146,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "dev": true, - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, "node_modules/node-gyp-build": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", @@ -26964,82 +27156,6 @@ "node-gyp-build-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -27086,18 +27202,6 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, - "node_modules/node-sql-parser": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-4.6.4.tgz", - "integrity": "sha512-OPzbsiN5VX37/HM5BgGxcoDt1ggvFUc6gdzyQzoPjk8orTOHBlgHuDUugP5oRU+CcOw8GniilvV3pt5FBxp47A==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.48" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/nodemailer": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.8.0.tgz", @@ -27109,22 +27213,8 @@ "node_modules/non-layered-tidy-tree-layout": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", - "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==" - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==", + "peer": true }, "node_modules/normalize-package-data": { "version": "2.5.0", @@ -27361,6 +27451,11 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" + }, "node_modules/num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -27935,12 +28030,6 @@ "node": ">=6" } }, - "node_modules/packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", - "dev": true - }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -28144,142 +28233,6 @@ "is-reference": "^3.0.0" } }, - "node_modules/pg": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", - "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", - "dev": true, - "dependencies": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "^2.5.0", - "pg-pool": "^3.5.2", - "pg-protocol": "^1.5.0", - "pg-types": "^2.1.0", - "pgpass": "1.x" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-connection-string": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==", - "dev": true - }, - "node_modules/pg-format": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz", - "integrity": "sha512-YyKEF78pEA6wwTAqOUaHIN/rWpfzzIuMh9KdAhc3rSLQ/7zkRFcCgYBAEGatDstLyZw4g0s9SNICmaTGnBVeyw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", - "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", - "dev": true, - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", - "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==", - "dev": true - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "dev": true, - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pg-types/node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "dev": true, - "dependencies": { - "split2": "^4.1.0" - } - }, - "node_modules/pgsql-deparser": { - "version": "13.3.15", - "resolved": "https://registry.npmjs.org/pgsql-deparser/-/pgsql-deparser-13.3.15.tgz", - "integrity": "sha512-Q/ZrQWYsymM+HefLaLK6PX1DNeDdfoKAH5wzfRrOxCsxRoTouIbWhwgVp3QaiovStjVPSeYNH+pqkLH4JsSn2A==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.11.2", - "dotty": "^0.1.0", - "lodash": "^4.17.20", - "pgsql-enums": "^13.1.3" - } - }, - "node_modules/pgsql-enums": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/pgsql-enums/-/pgsql-enums-13.1.3.tgz", - "integrity": "sha512-h3OSGbv1rB1r3lWL3l0gHIMVMVBTzEzROkP2/Wpz2s1eDAffPf9O5x0OqrRdLzhze92S9GUqO/FrS15u027xyg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.4.2" - } - }, - "node_modules/pgsql-parser": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/pgsql-parser/-/pgsql-parser-13.4.0.tgz", - "integrity": "sha512-d3ZQtQnUptz19f+WpmRP4OLfAZ33LgYDpycTA9ZSNCF+AZyURfm9e3sRZJ/amBcLtpwnZuto9EHjVz3EPpiksA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.11.2", - "libpg-query": "13.3.0", - "minimist": "^1.2.6", - "pgsql-deparser": "^13.3.14", - "pgsql-enums": "^13.1.3" - }, - "bin": { - "pgsql-parser": "main/cli.js" - } - }, "node_modules/phenomenon": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/phenomenon/-/phenomenon-1.6.0.tgz", @@ -30254,45 +30207,6 @@ "url": "https://github.com/sponsors/porsager" } }, - "node_modules/postgres-array": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.1.tgz", - "integrity": "sha512-h7i53Dw2Yq3a1uuZ6lbVFAkvMMwssJ8jkzeAg0XaZm1XIFF/t/s+tockdqbWTymyEm07dVenOQbFisEi+kj8uA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "dev": true, - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/preact": { "version": "10.11.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", @@ -30311,9 +30225,9 @@ } }, "node_modules/prettier": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz", - "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -30325,24 +30239,14 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/prettier-plugin-sql": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-sql/-/prettier-plugin-sql-0.12.1.tgz", - "integrity": "sha512-2z0pMmQEC61Xywerw2FJFpEIaA9lJUjB5mj+9dfRU8HkU7iICFrIizpMjSSDMoGkhh5W+IRROVgLlgRdpphI7g==", + "node_modules/prettier-plugin-sql-cst": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-sql-cst/-/prettier-plugin-sql-cst-0.5.0.tgz", + "integrity": "sha512-uyYaeSCfI6cCW1u+fZatzlirT9dEN5npqwHwFhxOH/aE53BPunE1a6c1fWFgZjYF2Mkdf8B7xL8aBpVKJCcfHA==", "dev": true, "dependencies": { - "node-sql-parser": "^4.4.0", - "sql-formatter": "^10.0.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - }, - "peerDependencies": { - "prettier": "^2.0.0" + "prettier": "^2.8.2", + "sql-parser-cst": "^0.12.0" } }, "node_modules/pretty-error": { @@ -30438,19 +30342,6 @@ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/promise.allsettled": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.6.tgz", @@ -30698,12 +30589,6 @@ "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" }, - "node_modules/railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", - "dev": true - }, "node_modules/ramda": { "version": "0.28.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz", @@ -30714,19 +30599,6 @@ "url": "https://opencollective.com/ramda" } }, - "node_modules/randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "dev": true, - "dependencies": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" - }, - "engines": { - "node": ">=0.12" - } - }, "node_modules/randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -30848,6 +30720,17 @@ "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-clientside-effect": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz", + "integrity": "sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==", + "dependencies": { + "@babel/runtime": "^7.12.13" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-contexify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/react-contexify/-/react-contexify-5.0.0.tgz", @@ -31089,6 +30972,39 @@ "react": ">=16.8.6" } }, + "node_modules/react-focus-lock": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.9.3.tgz", + "integrity": "sha512-cGNkz9p5Fpqio6hBHlkKxzRYrBYtcPosFOL6Q3N/LSbHjwP/PTBqHpvbgaOYoE7rWfzw8qXPKTB3Tk/VPgw4NQ==", + "dependencies": { + "@babel/runtime": "^7.0.0", + "focus-lock": "^0.11.5", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-focus-lock/node_modules/focus-lock": { + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.11.5.tgz", + "integrity": "sha512-1mTr6pl9HBpJ8CqY7hRc38MCrcuTZIeYAkBD1gBTzbx5/to+bRBaBYtJ68iDq7ryTzAAbKrG3dVKjkrWTaaEaw==", + "dependencies": { + "tslib": "^2.0.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/react-from-dom": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/react-from-dom/-/react-from-dom-0.6.2.tgz", @@ -32788,55 +32704,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/remark-rehype": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-8.1.0.tgz", - "integrity": "sha512-EbCu9kHgAxKmW1yEYjx3QafMyGY3q8noUbNUI5xyKbaFP89wbhDrKxyIQNukNYthzjNHZu6J7hwFg7hRm1svYA==", - "dependencies": { - "mdast-util-to-hast": "^10.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype/node_modules/mdast-util-to-hast": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz", - "integrity": "sha512-JoPBfJ3gBnHZ18icCwHR50orC9kNH81tiR1gs01D8Q5YpV6adHNO9nKNuFBCJQ941/32PT1a63UF/DitmS3amQ==", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "mdast-util-definitions": "^4.0.0", - "mdurl": "^1.0.0", - "unist-builder": "^2.0.0", - "unist-util-generated": "^1.0.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype/node_modules/unist-builder": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", - "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype/node_modules/unist-util-generated": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz", - "integrity": "sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/remark-slug": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/remark-slug/-/remark-slug-7.0.1.tgz", @@ -33305,15 +33172,6 @@ "node": ">=0.12" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -33349,7 +33207,8 @@ "node_modules/robust-predicates": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", - "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" + "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==", + "peer": true }, "node_modules/rsvp": { "version": "4.8.5", @@ -33393,7 +33252,8 @@ "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "peer": true }, "node_modules/sade": { "version": "1.8.1", @@ -33742,7 +33602,8 @@ "node_modules/sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", + "dev": true }, "node_modules/saxes": { "version": "5.0.1", @@ -34012,16 +33873,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/shiki": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz", - "integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==", - "dependencies": { - "jsonc-parser": "^3.0.0", - "vscode-oniguruma": "^1.6.1", - "vscode-textmate": "5.2.0" - } - }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -34083,16 +33934,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, "node_modules/snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -34254,34 +34095,6 @@ "node": ">=0.10.0" } }, - "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "dev": true, - "dependencies": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -34419,32 +34232,16 @@ "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz", "integrity": "sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==" }, - "node_modules/split2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", - "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", - "dev": true, - "engines": { - "node": ">= 10.x" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, - "node_modules/sql-formatter": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-10.7.2.tgz", - "integrity": "sha512-YspJXMr2rKMGZY50zYRBs2nCtEO/AKDrmQOxmV2CrGEOFSXUhY3YbsOUe6lX0T/7xbBs4pAYv4UObmwf78vtKg==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1", - "nearley": "^2.20.1" - }, - "bin": { - "sql-formatter": "bin/sql-formatter-cli.js" - } + "node_modules/sql-parser-cst": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/sql-parser-cst/-/sql-parser-cst-0.12.1.tgz", + "integrity": "sha512-J1jIkXuZGyEYJjRBalF2FRrhyfD/P2zaUGiHsBhLvs9PsC9g7sN9Q5LZhA4xlONOKW+825vxE1vx0Dm8dwwZvg==", + "dev": true }, "node_modules/sqlstring": { "version": "2.3.3", @@ -34964,6 +34761,12 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/style-mod": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", + "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", + "peer": true + }, "node_modules/style-to-object": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", @@ -34994,7 +34797,8 @@ "node_modules/stylis": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", - "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" + "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==", + "peer": true }, "node_modules/supabase-tests": { "resolved": "tests", @@ -35110,6 +34914,11 @@ "integrity": "sha512-qImOD23aDfnIDNqlG1NOehdB9IYsn1V9oByPjKY1nakv2MQYCEMyX033/q+aEtYCpmYK1cv2+NTmlH+ra6GA5A==", "dev": true }, + "node_modules/tabbable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz", + "integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==" + }, "node_modules/table": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", @@ -36169,6 +35978,11 @@ "node": ">=4.2.0" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -37180,16 +36994,16 @@ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", + "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" + }, "node_modules/vscode-oniguruma": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", "integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==" }, - "node_modules/vscode-textmate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz", - "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==" - }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -37200,6 +37014,12 @@ "browser-process-hrtime": "^1.0.0" } }, + "node_modules/w3c-keyname": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", + "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", + "peer": true + }, "node_modules/w3c-xmlserializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", @@ -38200,6 +38020,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -38347,6 +38168,7 @@ "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~9.0.1" @@ -38356,6 +38178,7 @@ "version": "9.0.7", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==", + "dev": true, "engines": { "node": ">=4.0" } @@ -38875,6 +38698,8 @@ "studio": { "version": "0.0.9", "dependencies": { + "@graphiql/react": "^0.15.0", + "@graphiql/toolkit": "^0.8.0", "@hcaptcha/react-hcaptcha": "^1.4.4", "@headlessui/react": "^1.4.1", "@monaco-editor/react": "^4.3.1", @@ -38888,7 +38713,7 @@ "@stripe/stripe-js": "^1.25.0", "@supabase/react-data-grid": "^7.1.0-beta.7", "@supabase/shared-types": "^0.1.29", - "@supabase/supabase-js": "^2.1.1", + "@supabase/supabase-js": "^2.8.0", "@tanstack/react-query": "^4.22.0", "@tanstack/react-query-devtools": "^4.22.0", "@zip.js/zip.js": "^2.6.61", @@ -38897,12 +38722,15 @@ "awesome-debounce-promise": "^2.1.0", "blueimp-md5": "^2.19.0", "clipboard": "^2.0.8", + "clsx": "^1.2.1", "common": "*", "config": "*", "configcat-js": "^7.0.0", "dayjs": "^1.11.0", "file-saver": "^2.0.5", "generate-password": "^1.7.0", + "graphql": "^16.6.0", + "graphql-ws": "^5.11.3", "html-to-image": "^1.10.8", "immutability-helper": "^3.1.1", "ip-address": "^8.1.0", @@ -38961,7 +38789,7 @@ "@storybook/addon-links": "^6.4.19", "@storybook/react": "^6.4.19", "@storybook/testing-library": "^0.0.9", - "@supabase/postgres-meta": "^0.60.2", + "@supabase/postgres-meta": "^0.60.7", "@tailwindcss/typography": "^0.5.2", "@testing-library/dom": "^8.19.0", "@testing-library/react": "^12.1.5", @@ -39010,6 +38838,36 @@ "webpack-bundle-analyzer": "4.3.0" } }, + "studio/node_modules/@supabase/gotrue-js": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.12.1.tgz", + "integrity": "sha512-r8Jfq8FvP6q4kp7sI33X1RWfEEHzJFu9uM1Q6HgiDVkY89NNgqYy2kxaRGtidPFllND7vpcJUcpoWS5oq+4u0g==", + "dependencies": { + "cross-fetch": "^3.1.5" + } + }, + "studio/node_modules/@supabase/realtime-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.6.0.tgz", + "integrity": "sha512-tOVulMobhpxyDuu8VIImpL8FXmZOKsGNOSyS5ihJdj2xYmPPvYG+D2J51Ewfl+MFF65tweiB6p9N9bNIW1cDNA==", + "dependencies": { + "@types/phoenix": "^1.5.4", + "websocket": "^1.0.34" + } + }, + "studio/node_modules/@supabase/supabase-js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.8.0.tgz", + "integrity": "sha512-uzf4J+qAKdUMhB2tnJl6BrQRUQBinwjJ2eWo2ZsDw9EUUP5JcHsxTamiq6p91DpqzmTIRg3xRAT+bItTzbfa0w==", + "dependencies": { + "@supabase/functions-js": "^2.0.0", + "@supabase/gotrue-js": "^2.12.0", + "@supabase/postgrest-js": "^1.1.1", + "@supabase/realtime-js": "^2.4.0", + "@supabase/storage-js": "^2.1.0", + "cross-fetch": "^3.1.5" + } + }, "studio/node_modules/@tailwindcss/typography": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.8.tgz", @@ -39164,6 +39022,14 @@ "postcss": "^8.1.0" } }, + "studio/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "studio/node_modules/configcat-common": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/configcat-common/-/configcat-common-7.0.1.tgz", @@ -40975,7 +40841,8 @@ "@braintree/sanitize-url": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz", - "integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==" + "integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==", + "peer": true }, "@cnakazawa/watch": { "version": "1.0.4", @@ -40987,19 +40854,35 @@ "minimist": "^1.2.0" } }, - "@code-hike/mdx": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@code-hike/mdx/-/mdx-0.7.4.tgz", - "integrity": "sha512-MHnoSJ7wxJ2LJfJ/x+18jh6JOBCPwk1EMQRgVINj3yyejqsYgOQx2ChE+/n+n3AdP7J5RLKfQ6qm/jiNxD8+Lg==", + "@codemirror/language": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.0.0.tgz", + "integrity": "sha512-rtjk5ifyMzOna1c7PBu7J1VCt0PvA5wy3o8eMVnxMKb7z8KA7JFecvD04dSn14vj/bBaAbqRsGed5OjtofEnLA==", + "peer": true, "requires": { - "hast-util-to-estree": "^1.4.0", - "is-plain-obj": "^3.0.0", - "node-fetch": "^2.0.0", - "remark-rehype": "^8.1.0", - "shiki": "^0.10.1", - "unified": "^9.2.2", - "unist-util-visit": "^2.0.0", - "unist-util-visit-parents": "^3.0.0" + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "@codemirror/state": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz", + "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", + "peer": true + }, + "@codemirror/view": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.0.tgz", + "integrity": "sha512-uFaqE6fBOp0Dj/tmWoe/TFlSSIPdpAzhvATgbq1eAKRkgq3hY79FioZ7nfdiT+24kz68AIWuSZ9hi3psKe36WQ==", + "peer": true, + "requires": { + "@codemirror/state": "^6.1.4", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" } }, "@colors/colors": { @@ -41268,6 +41151,46 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, + "@graphiql/react": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@graphiql/react/-/react-0.15.0.tgz", + "integrity": "sha512-kJqkdf6d4Cck05Wt5yCDZXWfs7HZgcpuoWq/v8nOa698qVaNMM3qdG4CpRsZEexku0DSSJzWWuanxd5x+sRcFg==", + "requires": { + "@graphiql/toolkit": "^0.8.0", + "@reach/combobox": "^0.17.0", + "@reach/dialog": "^0.17.0", + "@reach/listbox": "^0.17.0", + "@reach/menu-button": "^0.17.0", + "@reach/tooltip": "^0.17.0", + "@reach/visually-hidden": "^0.17.0", + "codemirror": "^5.65.3", + "codemirror-graphql": "^2.0.2", + "copy-to-clipboard": "^3.2.0", + "graphql-language-service": "^5.1.0", + "markdown-it": "^12.2.0", + "set-value": "^4.1.0" + }, + "dependencies": { + "set-value": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-4.1.0.tgz", + "integrity": "sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==", + "requires": { + "is-plain-object": "^2.0.4", + "is-primitive": "^3.0.1" + } + } + } + }, + "@graphiql/toolkit": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.8.0.tgz", + "integrity": "sha512-DbMFhEKejpPzB6k8W3Mj+Rl8geXiw49USDF9Wdi06EEk1XLVh1iebDqveYY+4lViITsV4+BeGikxlqi8umfP4g==", + "requires": { + "@n1ru4l/push-pull-async-iterable-iterator": "^3.1.0", + "meros": "^1.1.4" + } + }, "@hcaptcha/react-hcaptcha": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/@hcaptcha/react-hcaptcha/-/react-hcaptcha-1.4.4.tgz", @@ -42089,99 +42012,28 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, - "@mapbox/node-pre-gyp": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", - "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", - "dev": true, + "@lezer/common": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz", + "integrity": "sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==", + "peer": true + }, + "@lezer/highlight": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz", + "integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==", + "peer": true, "requires": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "dependencies": { - "are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - } - }, - "gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dev": true, - "requires": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, - "requires": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } + "@lezer/common": "^1.0.0" + } + }, + "@lezer/lr": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.3.tgz", + "integrity": "sha512-JPQe3mwJlzEVqy67iQiiGozhcngbO8QBgpqZM6oL1Wj/dXckrEexpBLeFkq0edtW5IqnPRFxA24BHJni8Js69w==", + "peer": true, + "requires": { + "@lezer/common": "^1.0.0" } }, "@mdx-js/loader": { @@ -42423,6 +42275,11 @@ } } }, + "@n1ru4l/push-pull-async-iterable-iterator": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-3.2.0.tgz", + "integrity": "sha512-3fkKj25kEjsfObL6IlKPAlHYPq/oYwUkkQ03zsTTiDjD7vg/RxjdiLeCydqtxHZP0JgsXL3D/X5oAkMGzuUp/Q==" + }, "@next/bundle-analyzer": { "version": "13.0.5", "resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-13.0.5.tgz", @@ -44135,6 +43992,172 @@ "@babel/runtime": "^7.13.10" } }, + "@reach/auto-id": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.17.0.tgz", + "integrity": "sha512-ud8iPwF52RVzEmkHq1twuqGuPA+moreumUHdtgvU3sr3/15BNhwp3KyDLrKKSz0LP1r3V4pSdyF9MbYM8BoSjA==", + "requires": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + } + }, + "@reach/combobox": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/combobox/-/combobox-0.17.0.tgz", + "integrity": "sha512-2mYvU5agOBCQBMdlM4cri+P1BbNwp05P1OuDyc33xJSNiBG7BMy4+ZSHJ0X4fyle6rHwSgCAOCLOeWV1XUYjoQ==", + "requires": { + "@reach/auto-id": "0.17.0", + "@reach/descendants": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/portal": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + }, + "@reach/descendants": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/descendants/-/descendants-0.17.0.tgz", + "integrity": "sha512-c7lUaBfjgcmKFZiAWqhG+VnXDMEhPkI4kAav/82XKZD6NVvFjsQOTH+v3tUkskrAPV44Yuch0mFW/u5Ntifr7Q==", + "requires": { + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + } + }, + "@reach/dialog": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/dialog/-/dialog-0.17.0.tgz", + "integrity": "sha512-AnfKXugqDTGbeG3c8xDcrQDE4h9b/vnc27Sa118oQSquz52fneUeX9MeFb5ZEiBJK8T5NJpv7QUTBIKnFCAH5A==", + "requires": { + "@reach/portal": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "react-focus-lock": "^2.5.2", + "react-remove-scroll": "^2.4.3", + "tslib": "^2.3.0" + } + }, + "@reach/dropdown": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/dropdown/-/dropdown-0.17.0.tgz", + "integrity": "sha512-qBTIGInhxtPHtdj4Pl2XZgZMz3e37liydh0xR3qc48syu7g71sL4nqyKjOzThykyfhA3Pb3/wFgsFJKGTSdaig==", + "requires": { + "@reach/auto-id": "0.17.0", + "@reach/descendants": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/utils": "0.17.0", + "tslib": "^2.3.0" + } + }, + "@reach/listbox": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/listbox/-/listbox-0.17.0.tgz", + "integrity": "sha512-AMnH1P6/3VKy2V/nPb4Es441arYR+t4YRdh9jdcFVrCOD6y7CQrlmxsYjeg9Ocdz08XpdoEBHM3PKLJqNAUr7A==", + "requires": { + "@reach/auto-id": "0.17.0", + "@reach/descendants": "0.17.0", + "@reach/machine": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2" + } + }, + "@reach/machine": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/machine/-/machine-0.17.0.tgz", + "integrity": "sha512-9EHnuPgXzkbRENvRUzJvVvYt+C2jp7PGN0xon7ffmKoK8rTO6eA/bb7P0xgloyDDQtu88TBUXKzW0uASqhTXGA==", + "requires": { + "@reach/utils": "0.17.0", + "@xstate/fsm": "1.4.0", + "tslib": "^2.3.0" + } + }, + "@reach/menu-button": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/menu-button/-/menu-button-0.17.0.tgz", + "integrity": "sha512-YyuYVyMZKamPtivoEI6D0UEILYH3qZtg4kJzEAuzPmoR/aHN66NZO75Fx0gtjG1S6fZfbiARaCOZJC0VEiDOtQ==", + "requires": { + "@reach/dropdown": "0.17.0", + "@reach/popover": "0.17.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + }, + "@reach/observe-rect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz", + "integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==" + }, + "@reach/popover": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/popover/-/popover-0.17.0.tgz", + "integrity": "sha512-yYbBF4fMz4Ml4LB3agobZjcZ/oPtPsNv70ZAd7lEC2h7cvhF453pA+zOBGYTPGupKaeBvgAnrMjj7RnxDU5hoQ==", + "requires": { + "@reach/portal": "0.17.0", + "@reach/rect": "0.17.0", + "@reach/utils": "0.17.0", + "tabbable": "^4.0.0", + "tslib": "^2.3.0" + } + }, + "@reach/portal": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz", + "integrity": "sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A==", + "requires": { + "@reach/utils": "0.17.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + }, + "@reach/rect": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.17.0.tgz", + "integrity": "sha512-3YB7KA5cLjbLc20bmPkJ06DIfXSK06Cb5BbD2dHgKXjUkT9WjZaLYIbYCO8dVjwcyO3GCNfOmPxy62VsPmZwYA==", + "requires": { + "@reach/observe-rect": "1.2.0", + "@reach/utils": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + }, + "@reach/tooltip": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/tooltip/-/tooltip-0.17.0.tgz", + "integrity": "sha512-HP8Blordzqb/Cxg+jnhGmWQfKgypamcYLBPlcx6jconyV5iLJ5m93qipr1giK7MqKT2wlsKWy44ZcOrJ+Wrf8w==", + "requires": { + "@reach/auto-id": "0.17.0", + "@reach/portal": "0.17.0", + "@reach/rect": "0.17.0", + "@reach/utils": "0.17.0", + "@reach/visually-hidden": "0.17.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + }, + "@reach/utils": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz", + "integrity": "sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA==", + "requires": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + } + }, + "@reach/visually-hidden": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@reach/visually-hidden/-/visually-hidden-0.17.0.tgz", + "integrity": "sha512-T6xF3Nv8vVnjVkGU6cm0+kWtvliLqPAo8PcZ+WxkKacZsaHTjaZb4v1PaCcyQHmuTNT/vtTVNOJLG0SjQOIb7g==", + "requires": { + "prop-types": "^15.7.2", + "tslib": "^2.3.0" + } + }, "@react-dnd/asap": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz", @@ -44386,9 +44409,9 @@ } }, "@sinclair/typebox": { - "version": "0.25.21", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.21.tgz", - "integrity": "sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g==", + "version": "0.25.23", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.23.tgz", + "integrity": "sha512-VEB8ygeP42CFLWyAJhN5OklpxUliqdNEUcXb4xZ/CINqtYGTjL5ukluKdKzQ0iWdUxyQ7B0539PAUhHKrCNWSQ==", "dev": true }, "@sinonjs/commons": { @@ -46158,15 +46181,15 @@ } }, "@supabase/postgres-meta": { - "version": "0.60.2", - "resolved": "https://registry.npmjs.org/@supabase/postgres-meta/-/postgres-meta-0.60.2.tgz", - "integrity": "sha512-Fjc5xKPzZJ38FhY3IbDFZ5cgVxPJTn8psLkgljZgBOMUyCd3QY3W1jV8rjwAkLfz/lxIOa2KlKZTIswMZMx4WQ==", + "version": "0.60.7", + "resolved": "https://registry.npmjs.org/@supabase/postgres-meta/-/postgres-meta-0.60.7.tgz", + "integrity": "sha512-vC5mwZ4sVAA5Hw57GSSDI7rGBY8/m8RscRHaFwfnqKgXFxKMvlfrzxnP/MwXg7EXRW1jfgvMAEkxuJNLDe1YMA==", "dev": true, "requires": { "@sinclair/typebox": "^0.25.1", "pg": "^8.7.1", "pg-format": "^1.0.4", - "pgsql-parser": "^13.3.0", + "pgsql-parser": "13.4.0", "postgres-array": "^3.0.1", "prettier": "^2.6.0", "prettier-plugin-sql": "^0.12.1" @@ -47351,6 +47374,11 @@ "@xtuc/long": "4.2.2" } }, + "@xstate/fsm": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-1.4.0.tgz", + "integrity": "sha512-uTHDeu2xI5E1IFwf37JFQM31RrH7mY7877RqPBS4ZqSNUwoLDuct8AhBWaXGnVizBAYyimVwgCyGa9z/NiRhXA==" + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -47372,12 +47400,6 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -47438,25 +47460,6 @@ "debug": "4" } }, - "agentkeepalive": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", - "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" - }, - "dependencies": { - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true - } - } - }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -47962,7 +47965,8 @@ "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true }, "awesome-debounce-promise": { "version": "2.1.0", @@ -47992,6 +47996,7 @@ "version": "2.1315.0", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1315.0.tgz", "integrity": "sha512-fDlK3iqA0bpXW6azVBAB8RTKhJ5YUjfe6sqKQF1lKT2cYT5qU4rtH5qUlMixlWkb8oKeSMA2voW3wZZtSpBFKQ==", + "dev": true, "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -48008,22 +48013,26 @@ "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==" + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "dev": true }, "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true }, "punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "dev": true }, "url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "dev": true, "requires": { "punycode": "1.3.2", "querystring": "0.2.0" @@ -48033,6 +48042,7 @@ "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, "requires": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -48044,7 +48054,8 @@ "uuid": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", - "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==" + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", + "dev": true } } }, @@ -48513,7 +48524,8 @@ "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true + "dev": true, + "optional": true }, "big.js": { "version": "5.2.2", @@ -48810,12 +48822,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, - "buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", - "dev": true - }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -49297,6 +49303,19 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==" }, + "codemirror": { + "version": "5.65.11", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.11.tgz", + "integrity": "sha512-Gp62g2eKSCHYt10axmGhKq3WoJSvVpvhXmowNq7pZdRVowwtvBR/hi2LSP5srtctKkRT33T6/n8Kv1UGp7JW4A==" + }, + "codemirror-graphql": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-2.0.3.tgz", + "integrity": "sha512-T58f+XQVKE6/6goAADJfu56wPRcdF2RUZMHITUol6h6Uo1KKUmknV4muKNFOl6GM15BjBraysF6HATp4oY6kyA==", + "requires": { + "graphql-language-service": "5.1.1" + } + }, "coffee-script": { "version": "1.12.7", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", @@ -50301,6 +50320,7 @@ "version": "7.6.1", "resolved": "https://registry.npmjs.org/d3/-/d3-7.6.1.tgz", "integrity": "sha512-txMTdIHFbcpLx+8a0IFhZsbp+PfBBPt8yfbmukZTQFroKuFqIwqswF0qE5JXWefylaAVpSXFoKm3yP+jpNLFLw==", + "peer": true, "requires": { "d3-array": "3", "d3-axis": "3", @@ -50338,6 +50358,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", + "peer": true, "requires": { "internmap": "1 - 2" } @@ -50345,12 +50366,14 @@ "d3-axis": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==" + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "peer": true }, "d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "peer": true, "requires": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -50363,6 +50386,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "peer": true, "requires": { "d3-path": "1 - 3" } @@ -50370,17 +50394,20 @@ "d3-collection": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", - "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==", + "peer": true }, "d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "peer": true }, "d3-contour": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.0.tgz", "integrity": "sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==", + "peer": true, "requires": { "d3-array": "^3.2.0" } @@ -50389,6 +50416,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", + "peer": true, "requires": { "delaunator": "5" } @@ -50396,12 +50424,14 @@ "d3-dispatch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==" + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "peer": true }, "d3-drag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "peer": true, "requires": { "d3-dispatch": "1 - 3", "d3-selection": "3" @@ -50411,6 +50441,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "peer": true, "requires": { "commander": "7", "iconv-lite": "0.6", @@ -50420,12 +50451,14 @@ "commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "peer": true }, "iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "peer": true, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } @@ -50435,12 +50468,14 @@ "d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "peer": true }, "d3-fetch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "peer": true, "requires": { "d3-dsv": "1 - 3" } @@ -50449,6 +50484,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "peer": true, "requires": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", @@ -50458,12 +50494,14 @@ "d3-format": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "peer": true }, "d3-geo": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", + "peer": true, "requires": { "d3-array": "2.5.0 - 3" } @@ -50471,12 +50509,14 @@ "d3-hierarchy": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==" + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "peer": true }, "d3-interpolate": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "peer": true, "requires": { "d3-color": "1 - 3" } @@ -50484,27 +50524,32 @@ "d3-path": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", - "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==" + "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==", + "peer": true }, "d3-polygon": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==" + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "peer": true }, "d3-quadtree": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==" + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "peer": true }, "d3-random": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==" + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "peer": true }, "d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "peer": true, "requires": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", @@ -50517,6 +50562,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "peer": true, "requires": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" @@ -50525,12 +50571,14 @@ "d3-selection": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "peer": true }, "d3-shape": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", + "peer": true, "requires": { "d3-path": "1 - 3" } @@ -50539,6 +50587,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", + "peer": true, "requires": { "d3-array": "2 - 3" } @@ -50547,6 +50596,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "peer": true, "requires": { "d3-time": "1 - 3" } @@ -50554,12 +50604,14 @@ "d3-timer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "peer": true }, "d3-transition": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "peer": true, "requires": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", @@ -50571,12 +50623,14 @@ "d3-voronoi": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", - "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==", + "peer": true }, "d3-zoom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "peer": true, "requires": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -50589,6 +50643,7 @@ "version": "0.8.5", "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "peer": true, "requires": { "graphlib": "^2.1.8", "lodash": "^4.17.15" @@ -50598,6 +50653,7 @@ "version": "0.6.4", "resolved": "https://registry.npmjs.org/dagre-d3/-/dagre-d3-0.6.4.tgz", "integrity": "sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==", + "peer": true, "requires": { "d3": "^5.14", "dagre": "^0.8.5", @@ -50608,12 +50664,14 @@ "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "peer": true }, "d3": { "version": "5.16.0", "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", + "peer": true, "requires": { "d3-array": "1", "d3-axis": "1", @@ -50651,17 +50709,20 @@ "d3-array": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", - "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "peer": true }, "d3-axis": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", - "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" + "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==", + "peer": true }, "d3-brush": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.6.tgz", "integrity": "sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==", + "peer": true, "requires": { "d3-dispatch": "1", "d3-drag": "1", @@ -50674,6 +50735,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "peer": true, "requires": { "d3-array": "1", "d3-path": "1" @@ -50682,12 +50744,14 @@ "d3-color": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", - "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==", + "peer": true }, "d3-contour": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "peer": true, "requires": { "d3-array": "^1.1.1" } @@ -50695,12 +50759,14 @@ "d3-dispatch": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", - "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==", + "peer": true }, "d3-drag": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", + "peer": true, "requires": { "d3-dispatch": "1", "d3-selection": "1" @@ -50710,6 +50776,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", + "peer": true, "requires": { "commander": "2", "iconv-lite": "0.4", @@ -50719,12 +50786,14 @@ "d3-ease": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", - "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" + "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==", + "peer": true }, "d3-fetch": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", + "peer": true, "requires": { "d3-dsv": "1" } @@ -50733,6 +50802,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "peer": true, "requires": { "d3-collection": "1", "d3-dispatch": "1", @@ -50743,12 +50813,14 @@ "d3-format": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", - "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==", + "peer": true }, "d3-geo": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", + "peer": true, "requires": { "d3-array": "1" } @@ -50756,12 +50828,14 @@ "d3-hierarchy": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", - "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==", + "peer": true }, "d3-interpolate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", + "peer": true, "requires": { "d3-color": "1" } @@ -50769,27 +50843,32 @@ "d3-path": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "peer": true }, "d3-polygon": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", - "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" + "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==", + "peer": true }, "d3-quadtree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", - "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==", + "peer": true }, "d3-random": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", - "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==", + "peer": true }, "d3-scale": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "peer": true, "requires": { "d3-array": "^1.2.0", "d3-collection": "1", @@ -50803,6 +50882,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "peer": true, "requires": { "d3-color": "1", "d3-interpolate": "1" @@ -50811,12 +50891,14 @@ "d3-selection": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", - "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==", + "peer": true }, "d3-shape": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "peer": true, "requires": { "d3-path": "1" } @@ -50824,12 +50906,14 @@ "d3-time": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", - "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==", + "peer": true }, "d3-time-format": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "peer": true, "requires": { "d3-time": "1" } @@ -50837,12 +50921,14 @@ "d3-timer": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", - "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==", + "peer": true }, "d3-transition": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "peer": true, "requires": { "d3-color": "1", "d3-dispatch": "1", @@ -50856,6 +50942,7 @@ "version": "1.8.3", "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "peer": true, "requires": { "d3-dispatch": "1", "d3-drag": "1", @@ -51034,6 +51121,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "peer": true, "requires": { "robust-predicates": "^3.0.0" } @@ -51092,12 +51180,6 @@ "repeat-string": "^1.5.4" } }, - "detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", - "dev": true - }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -51185,12 +51267,6 @@ "path-type": "^4.0.0" } }, - "discontinuous-range": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", - "dev": true - }, "dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -51218,7 +51294,7 @@ "@radix-ui/react-accordion": "^1.0.1", "@supabase/auth-helpers-nextjs": "^0.5.4", "@supabase/auth-helpers-react": "^0.3.1", - "@supabase/supabase-js": "^2.4.1", + "@supabase/supabase-js": "^2.8.0", "@types/node": "^17.0.12", "@types/react": "17.0.39", "algoliasearch": "^4.14.2", @@ -51252,7 +51328,6 @@ "npm-run-all": "^4.1.5", "openai": "^3.1.0", "openapi-types": "^12.0.2", - "prettier": "^2.2.1", "react": "17.0.2", "react-copy-to-clipboard": "^5.0.2", "react-dom": "17.0.2", @@ -51443,6 +51518,36 @@ "integrity": "sha512-tpQJYUH+TzPMIsdVl9fH8uDg47iwiNjKY+8e9da3dXqlkztKzjSw0OwSADoqh3KrifplXeKSta+BBGLdBqg3sg==", "optional": true }, + "@supabase/gotrue-js": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.12.1.tgz", + "integrity": "sha512-r8Jfq8FvP6q4kp7sI33X1RWfEEHzJFu9uM1Q6HgiDVkY89NNgqYy2kxaRGtidPFllND7vpcJUcpoWS5oq+4u0g==", + "requires": { + "cross-fetch": "^3.1.5" + } + }, + "@supabase/realtime-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.6.0.tgz", + "integrity": "sha512-tOVulMobhpxyDuu8VIImpL8FXmZOKsGNOSyS5ihJdj2xYmPPvYG+D2J51Ewfl+MFF65tweiB6p9N9bNIW1cDNA==", + "requires": { + "@types/phoenix": "^1.5.4", + "websocket": "^1.0.34" + } + }, + "@supabase/supabase-js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.8.0.tgz", + "integrity": "sha512-uzf4J+qAKdUMhB2tnJl6BrQRUQBinwjJ2eWo2ZsDw9EUUP5JcHsxTamiq6p91DpqzmTIRg3xRAT+bItTzbfa0w==", + "requires": { + "@supabase/functions-js": "^2.0.0", + "@supabase/gotrue-js": "^2.12.0", + "@supabase/postgrest-js": "^1.1.1", + "@supabase/realtime-js": "^2.4.0", + "@supabase/storage-js": "^2.1.0", + "cross-fetch": "^3.1.5" + } + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -51830,7 +51935,8 @@ "dompurify": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz", - "integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==" + "integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==", + "peer": true }, "domutils": { "version": "2.8.0", @@ -51865,12 +51971,6 @@ "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", "dev": true }, - "dotty": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dotty/-/dotty-0.1.2.tgz", - "integrity": "sha512-V0EWmKeH3DEhMwAZ+8ZB2Ao4OK6p++Z0hsDtZq3N0+0ZMVqkzrcEGROvOnZpLnvBg5PTNG23JEDLAm64gPaotQ==", - "dev": true - }, "duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -51963,6 +52063,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "optional": true, + "peer": true, "requires": { "iconv-lite": "^0.6.2" }, @@ -51972,6 +52073,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "optional": true, + "peer": true, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } @@ -52027,18 +52129,6 @@ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true }, - "env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true - }, - "err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, "errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -52944,7 +53034,8 @@ "estree-util-attach-comments": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-1.0.0.tgz", - "integrity": "sha512-sL7dTwFGqzelPlB56lRZY1CC/yDxCe365WQpxNd49ispL40Yv8Tv4SmteGbvZeFwShOOVKfMlo4jrVvwoaMosA==" + "integrity": "sha512-sL7dTwFGqzelPlB56lRZY1CC/yDxCe365WQpxNd49ispL40Yv8Tv4SmteGbvZeFwShOOVKfMlo4jrVvwoaMosA==", + "peer": true }, "estree-util-build-jsx": { "version": "2.2.0", @@ -53338,7 +53429,8 @@ "fast-clone": { "version": "1.5.13", "resolved": "https://registry.npmjs.org/fast-clone/-/fast-clone-1.5.13.tgz", - "integrity": "sha512-0ez7coyFBQFjZtId+RJqJ+EQs61w9xARfqjqK0AD9vIUkSxWD4HvPt80+5evebZ1tTnv1GYKrPTipx7kOW5ipA==" + "integrity": "sha512-0ez7coyFBQFjZtId+RJqJ+EQs61w9xARfqjqK0AD9vIUkSxWD4HvPt80+5evebZ1tTnv1GYKrPTipx7kOW5ipA==", + "peer": true }, "fast-deep-equal": { "version": "3.1.3", @@ -53668,6 +53760,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, "requires": { "is-callable": "^1.1.3" } @@ -54135,6 +54228,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, "requires": { "get-intrinsic": "^1.1.3" } @@ -54154,10 +54248,31 @@ "version": "2.1.8", "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "peer": true, "requires": { "lodash": "^4.17.15" } }, + "graphql": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", + "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==" + }, + "graphql-language-service": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-5.1.1.tgz", + "integrity": "sha512-gpaDT9E3+3eWhoqO4C81CGhkzr7Vp2jH/eq+ykoUbgfvMEpqhGTfCeNmrf+S4K/+4WTkAAJBsYT0/ZPZkqe/Hg==", + "requires": { + "nullthrows": "^1.0.0", + "vscode-languageserver-types": "^3.17.1" + } + }, + "graphql-ws": { + "version": "5.11.3", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.11.3.tgz", + "integrity": "sha512-fU8zwSgAX2noXAsuFiCZ8BtXeXZOzXyK5u1LloCdacsVth4skdBMPO74EG51lBoWSIZ8beUocdpV8+cQHBODnQ==", + "requires": {} + }, "gray-matter": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", @@ -54540,9 +54655,9 @@ } }, "hast-util-to-estree": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-1.4.0.tgz", + "version": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-1.4.0.tgz", "integrity": "sha512-CiOAIESUKkSOcYbvTth9+yM28z5ArpsYqxWc7LWJxOx975WRUBDjvVuuzZR2o09BNlkf7bp8G2GlOHepBRKJ8Q==", + "peer": true, "requires": { "comma-separated-tokens": "^1.0.0", "estree-util-attach-comments": "^1.0.0", @@ -54558,7 +54673,8 @@ "estree-util-is-identifier-name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-1.1.0.tgz", - "integrity": "sha512-OVJZ3fGGt9By77Ix9NhaRbzfbDV/2rx9EP7YIDJTmsZSEc5kYn2vWcNccYyahJL2uAQZK2a5Or2i0wtIKTPoRQ==" + "integrity": "sha512-OVJZ3fGGt9By77Ix9NhaRbzfbDV/2rx9EP7YIDJTmsZSEc5kYn2vWcNccYyahJL2uAQZK2a5Or2i0wtIKTPoRQ==", + "peer": true } } }, @@ -54634,7 +54750,8 @@ "hast-util-whitespace": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz", - "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==" + "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==", + "peer": true }, "hastscript": { "version": "6.0.0", @@ -54799,12 +54916,6 @@ "entities": "^2.0.0" } }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -54849,15 +54960,6 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -55056,7 +55158,8 @@ "internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "peer": true }, "interpret": { "version": "2.2.0", @@ -55132,6 +55235,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -55288,6 +55392,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -55305,12 +55410,6 @@ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" }, - "is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, "is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -55354,11 +55453,6 @@ "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", "dev": true }, - "is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -55373,6 +55467,11 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "is-primitive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz", + "integrity": "sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==" + }, "is-reference": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.0.tgz", @@ -55434,6 +55533,7 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -57090,7 +57190,8 @@ "jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", - "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==" + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "dev": true }, "join-component": { "version": "1.1.0", @@ -57356,7 +57457,8 @@ "khroma": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.0.0.tgz", - "integrity": "sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==" + "integrity": "sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==", + "peer": true }, "kind-of": { "version": "6.0.3", @@ -57432,17 +57534,6 @@ "type-check": "~0.4.0" } }, - "libpg-query": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/libpg-query/-/libpg-query-13.3.0.tgz", - "integrity": "sha512-9PKW8iRWnSCw+IW5LlZYq+bLMmAhKsAIR2en8iRYnoRLBg4x+HKHBb+PQyqFy8+jC7AmzV65A+DsUO7tiZddEQ==", - "dev": true, - "requires": { - "@mapbox/node-pre-gyp": "^1.0.8", - "node-addon-api": "^1.6.3", - "node-gyp": "^8.0.0" - } - }, "lie": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", @@ -57462,6 +57553,14 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "devOptional": true }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "requires": { + "uc.micro": "^1.0.1" + } + }, "list-item": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/list-item/-/list-item-1.1.1.tgz", @@ -57716,30 +57815,6 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "devOptional": true }, - "make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, - "requires": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - } - }, "makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -57785,6 +57860,25 @@ "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", "integrity": "sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==" }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + } + } + }, "markdown-link": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/markdown-link/-/markdown-link-0.1.1.tgz", @@ -58297,12 +58391,6 @@ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" }, - "mdx-mermaid": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mdx-mermaid/-/mdx-mermaid-1.3.2.tgz", - "integrity": "sha512-8kw0tg3isKKBFzFwoe2DhIaEgKYtVeJXQtxZCCrdTPO0CTpXHnTHT0atDqsp7YkXi5iUCp/zAZPZu1cmr68T3w==", - "requires": {} - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -58486,6 +58574,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.2.2.tgz", "integrity": "sha512-6s7eKMqFJGS+0MYjmx8f6ZigqKBJVoSx5ql2gw6a4Aa+WJ49QiEJg7gPwywaBg3DZMs79UP7trESp4+jmaQccw==", + "peer": true, "requires": { "@braintree/sanitize-url": "^6.0.0", "d3": "^7.0.0", @@ -58505,10 +58594,17 @@ "uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "peer": true } } }, + "meros": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/meros/-/meros-1.2.1.tgz", + "integrity": "sha512-R2f/jxYqCAGI19KhAvaxSOxALBMkaXWH2a7rOyqQw+ZmizX5bKkEYWLzdhC+U82ZVVPVp6MCXe3EkVligh+12g==", + "requires": {} + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -59042,18 +59138,6 @@ "minipass": "^3.0.0" } }, - "minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "dev": true, - "requires": { - "encoding": "^0.1.12", - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - } - }, "minipass-flush": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", @@ -59072,15 +59156,6 @@ "minipass": "^3.0.0" } }, - "minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, "minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -59149,13 +59224,8 @@ "moment-mini": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.29.4.tgz", - "integrity": "sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg==" - }, - "moo": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", - "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", - "dev": true + "integrity": "sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg==", + "peer": true }, "moo-color": { "version": "1.0.3", @@ -59268,26 +59338,6 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, - "nearley": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", - "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", - "dev": true, - "requires": { - "commander": "^2.19.0", - "moo": "^0.5.0", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -59693,12 +59743,6 @@ "tslib": "^2.0.3" } }, - "node-addon-api": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", - "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", - "dev": true - }, "node-cache": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", @@ -59745,84 +59789,6 @@ } } }, - "node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "dev": true, - "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "dependencies": { - "are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - } - }, - "gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dev": true, - "requires": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - } - }, - "npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, - "requires": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, "node-gyp-build": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", @@ -59876,15 +59842,6 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, - "node-sql-parser": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-4.6.4.tgz", - "integrity": "sha512-OPzbsiN5VX37/HM5BgGxcoDt1ggvFUc6gdzyQzoPjk8orTOHBlgHuDUugP5oRU+CcOw8GniilvV3pt5FBxp47A==", - "dev": true, - "requires": { - "big-integer": "^1.6.48" - } - }, "nodemailer": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.8.0.tgz", @@ -59893,16 +59850,8 @@ "non-layered-tidy-tree-layout": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", - "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==" - }, - "nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, - "requires": { - "abbrev": "1" - } + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==", + "peer": true }, "normalize-package-data": { "version": "2.5.0", @@ -60087,6 +60036,11 @@ "boolbase": "^1.0.0" } }, + "nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" + }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -60514,12 +60468,6 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, - "packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", - "dev": true - }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -60689,116 +60637,6 @@ "is-reference": "^3.0.0" } }, - "pg": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", - "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", - "dev": true, - "requires": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "^2.5.0", - "pg-pool": "^3.5.2", - "pg-protocol": "^1.5.0", - "pg-types": "^2.1.0", - "pgpass": "1.x" - } - }, - "pg-connection-string": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==", - "dev": true - }, - "pg-format": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz", - "integrity": "sha512-YyKEF78pEA6wwTAqOUaHIN/rWpfzzIuMh9KdAhc3rSLQ/7zkRFcCgYBAEGatDstLyZw4g0s9SNICmaTGnBVeyw==", - "dev": true - }, - "pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "dev": true - }, - "pg-pool": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", - "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", - "dev": true, - "requires": {} - }, - "pg-protocol": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", - "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==", - "dev": true - }, - "pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "dev": true, - "requires": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "dependencies": { - "postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "dev": true - } - } - }, - "pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "dev": true, - "requires": { - "split2": "^4.1.0" - } - }, - "pgsql-deparser": { - "version": "13.3.15", - "resolved": "https://registry.npmjs.org/pgsql-deparser/-/pgsql-deparser-13.3.15.tgz", - "integrity": "sha512-Q/ZrQWYsymM+HefLaLK6PX1DNeDdfoKAH5wzfRrOxCsxRoTouIbWhwgVp3QaiovStjVPSeYNH+pqkLH4JsSn2A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.11.2", - "dotty": "^0.1.0", - "lodash": "^4.17.20", - "pgsql-enums": "^13.1.3" - } - }, - "pgsql-enums": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/pgsql-enums/-/pgsql-enums-13.1.3.tgz", - "integrity": "sha512-h3OSGbv1rB1r3lWL3l0gHIMVMVBTzEzROkP2/Wpz2s1eDAffPf9O5x0OqrRdLzhze92S9GUqO/FrS15u027xyg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.4.2" - } - }, - "pgsql-parser": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/pgsql-parser/-/pgsql-parser-13.4.0.tgz", - "integrity": "sha512-d3ZQtQnUptz19f+WpmRP4OLfAZ33LgYDpycTA9ZSNCF+AZyURfm9e3sRZJ/amBcLtpwnZuto9EHjVz3EPpiksA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.11.2", - "libpg-query": "13.3.0", - "minimist": "^1.2.6", - "pgsql-deparser": "^13.3.14", - "pgsql-enums": "^13.1.3" - } - }, "phenomenon": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/phenomenon/-/phenomenon-1.6.0.tgz", @@ -62262,33 +62100,6 @@ "integrity": "sha512-NaPqFpUC6C7aCQkJXLvuO/3RKNKL4en8opY53YrcXK3//xXra6CZ2qX6290lxuQ1dW1LbRGYCmsawRlCxSBonQ==", "dev": true }, - "postgres-array": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.1.tgz", - "integrity": "sha512-h7i53Dw2Yq3a1uuZ6lbVFAkvMMwssJ8jkzeAg0XaZm1XIFF/t/s+tockdqbWTymyEm07dVenOQbFisEi+kj8uA==", - "dev": true - }, - "postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "dev": true - }, - "postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "dev": true - }, - "postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "dev": true, - "requires": { - "xtend": "^4.0.0" - } - }, "preact": { "version": "10.11.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", @@ -62300,20 +62111,19 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, "prettier": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz", - "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", "dev": true }, - "prettier-plugin-sql": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-sql/-/prettier-plugin-sql-0.12.1.tgz", - "integrity": "sha512-2z0pMmQEC61Xywerw2FJFpEIaA9lJUjB5mj+9dfRU8HkU7iICFrIizpMjSSDMoGkhh5W+IRROVgLlgRdpphI7g==", + "prettier-plugin-sql-cst": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-sql-cst/-/prettier-plugin-sql-cst-0.5.0.tgz", + "integrity": "sha512-uyYaeSCfI6cCW1u+fZatzlirT9dEN5npqwHwFhxOH/aE53BPunE1a6c1fWFgZjYF2Mkdf8B7xL8aBpVKJCcfHA==", "dev": true, "requires": { - "node-sql-parser": "^4.4.0", - "sql-formatter": "^10.0.0", - "tslib": "^2.4.0" + "prettier": "^2.8.2", + "sql-parser-cst": "^0.12.0" } }, "pretty-error": { @@ -62388,16 +62198,6 @@ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" }, - "promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "requires": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - } - }, "promise.allsettled": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.6.tgz", @@ -62588,28 +62388,12 @@ "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" }, - "railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", - "dev": true - }, "ramda": { "version": "0.28.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz", "integrity": "sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==", "dev": true }, - "randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "dev": true, - "requires": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" - } - }, "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -62703,6 +62487,14 @@ "use-memo-one": "^1.1.1" } }, + "react-clientside-effect": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz", + "integrity": "sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==", + "requires": { + "@babel/runtime": "^7.12.13" + } + }, "react-contexify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/react-contexify/-/react-contexify-5.0.0.tgz", @@ -62878,6 +62670,29 @@ "prop-types": "^15.7.2" } }, + "react-focus-lock": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.9.3.tgz", + "integrity": "sha512-cGNkz9p5Fpqio6hBHlkKxzRYrBYtcPosFOL6Q3N/LSbHjwP/PTBqHpvbgaOYoE7rWfzw8qXPKTB3Tk/VPgw4NQ==", + "requires": { + "@babel/runtime": "^7.0.0", + "focus-lock": "^0.11.5", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "dependencies": { + "focus-lock": { + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.11.5.tgz", + "integrity": "sha512-1mTr6pl9HBpJ8CqY7hRc38MCrcuTZIeYAkBD1gBTzbx5/to+bRBaBYtJ68iDq7ryTzAAbKrG3dVKjkrWTaaEaw==", + "requires": { + "tslib": "^2.0.3" + } + } + } + }, "react-from-dom": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/react-from-dom/-/react-from-dom-0.6.2.tgz", @@ -64123,41 +63938,6 @@ } } }, - "remark-rehype": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-8.1.0.tgz", - "integrity": "sha512-EbCu9kHgAxKmW1yEYjx3QafMyGY3q8noUbNUI5xyKbaFP89wbhDrKxyIQNukNYthzjNHZu6J7hwFg7hRm1svYA==", - "requires": { - "mdast-util-to-hast": "^10.2.0" - }, - "dependencies": { - "mdast-util-to-hast": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz", - "integrity": "sha512-JoPBfJ3gBnHZ18icCwHR50orC9kNH81tiR1gs01D8Q5YpV6adHNO9nKNuFBCJQ941/32PT1a63UF/DitmS3amQ==", - "requires": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "mdast-util-definitions": "^4.0.0", - "mdurl": "^1.0.0", - "unist-builder": "^2.0.0", - "unist-util-generated": "^1.0.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^2.0.0" - } - }, - "unist-builder": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", - "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==" - }, - "unist-util-generated": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz", - "integrity": "sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==" - } - } - }, "remark-slug": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/remark-slug/-/remark-slug-7.0.1.tgz", @@ -64420,12 +64200,6 @@ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -64451,7 +64225,8 @@ "robust-predicates": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", - "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" + "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==", + "peer": true }, "rsvp": { "version": "4.8.5", @@ -64478,7 +64253,8 @@ "rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "peer": true }, "sade": { "version": "1.8.1", @@ -64757,7 +64533,8 @@ "sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", + "dev": true }, "saxes": { "version": "5.0.1", @@ -64984,16 +64761,6 @@ "integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==", "dev": true }, - "shiki": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz", - "integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==", - "requires": { - "jsonc-parser": "^3.0.0", - "vscode-oniguruma": "^1.6.1", - "vscode-textmate": "5.2.0" - } - }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -65040,12 +64807,6 @@ "is-fullwidth-code-point": "^3.0.0" } }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true - }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -65178,27 +64939,6 @@ } } }, - "socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "dev": true, - "requires": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - } - }, - "socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "dev": true, - "requires": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - } - }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -65316,26 +65056,16 @@ "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz", "integrity": "sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==" }, - "split2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", - "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", - "dev": true - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, - "sql-formatter": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-10.7.2.tgz", - "integrity": "sha512-YspJXMr2rKMGZY50zYRBs2nCtEO/AKDrmQOxmV2CrGEOFSXUhY3YbsOUe6lX0T/7xbBs4pAYv4UObmwf78vtKg==", - "dev": true, - "requires": { - "argparse": "^2.0.1", - "nearley": "^2.20.1" - } + "sql-parser-cst": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/sql-parser-cst/-/sql-parser-cst-0.12.1.tgz", + "integrity": "sha512-J1jIkXuZGyEYJjRBalF2FRrhyfD/P2zaUGiHsBhLvs9PsC9g7sN9Q5LZhA4xlONOKW+825vxE1vx0Dm8dwwZvg==", + "dev": true }, "sqlstring": { "version": "2.3.3", @@ -65695,6 +65425,8 @@ "version": "file:studio", "requires": { "@babel/core": "^7.17.5", + "@graphiql/react": "^0.15.0", + "@graphiql/toolkit": "^0.8.0", "@hcaptcha/react-hcaptcha": "^1.4.4", "@headlessui/react": "^1.4.1", "@monaco-editor/react": "^4.3.1", @@ -65712,10 +65444,10 @@ "@storybook/testing-library": "^0.0.9", "@stripe/react-stripe-js": "^1.7.0", "@stripe/stripe-js": "^1.25.0", - "@supabase/postgres-meta": "^0.60.2", + "@supabase/postgres-meta": "^0.60.7", "@supabase/react-data-grid": "^7.1.0-beta.7", "@supabase/shared-types": "^0.1.29", - "@supabase/supabase-js": "^2.1.1", + "@supabase/supabase-js": "^2.8.0", "@tailwindcss/typography": "^0.5.2", "@tanstack/react-query": "^4.22.0", "@tanstack/react-query-devtools": "^4.22.0", @@ -65752,12 +65484,15 @@ "babel-loader": "^8.2.3", "blueimp-md5": "^2.19.0", "clipboard": "^2.0.8", + "clsx": "^1.2.1", "common": "*", "config": "*", "configcat-js": "^7.0.0", "dayjs": "^1.11.0", "file-saver": "^2.0.5", "generate-password": "^1.7.0", + "graphql": "^16.6.0", + "graphql-ws": "^5.11.3", "html-to-image": "^1.10.8", "immutability-helper": "^3.1.1", "ip-address": "^8.1.0", @@ -65825,6 +65560,36 @@ "webpack-bundle-analyzer": "4.3.0" } }, + "@supabase/gotrue-js": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.12.1.tgz", + "integrity": "sha512-r8Jfq8FvP6q4kp7sI33X1RWfEEHzJFu9uM1Q6HgiDVkY89NNgqYy2kxaRGtidPFllND7vpcJUcpoWS5oq+4u0g==", + "requires": { + "cross-fetch": "^3.1.5" + } + }, + "@supabase/realtime-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.6.0.tgz", + "integrity": "sha512-tOVulMobhpxyDuu8VIImpL8FXmZOKsGNOSyS5ihJdj2xYmPPvYG+D2J51Ewfl+MFF65tweiB6p9N9bNIW1cDNA==", + "requires": { + "@types/phoenix": "^1.5.4", + "websocket": "^1.0.34" + } + }, + "@supabase/supabase-js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.8.0.tgz", + "integrity": "sha512-uzf4J+qAKdUMhB2tnJl6BrQRUQBinwjJ2eWo2ZsDw9EUUP5JcHsxTamiq6p91DpqzmTIRg3xRAT+bItTzbfa0w==", + "requires": { + "@supabase/functions-js": "^2.0.0", + "@supabase/gotrue-js": "^2.12.0", + "@supabase/postgrest-js": "^1.1.1", + "@supabase/realtime-js": "^2.4.0", + "@supabase/storage-js": "^2.1.0", + "cross-fetch": "^3.1.5" + } + }, "@tailwindcss/typography": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.8.tgz", @@ -65917,6 +65682,11 @@ "postcss-value-parser": "^4.2.0" } }, + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + }, "configcat-common": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/configcat-common/-/configcat-common-7.0.1.tgz", @@ -66121,6 +65891,12 @@ } } }, + "style-mod": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", + "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", + "peer": true + }, "style-to-object": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", @@ -66138,7 +65914,8 @@ "stylis": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", - "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" + "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==", + "peer": true }, "supabase-tests": { "version": "file:tests", @@ -66318,6 +66095,11 @@ "integrity": "sha512-qImOD23aDfnIDNqlG1NOehdB9IYsn1V9oByPjKY1nakv2MQYCEMyX033/q+aEtYCpmYK1cv2+NTmlH+ra6GA5A==", "dev": true }, + "tabbable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz", + "integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==" + }, "table": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", @@ -67081,6 +66863,11 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==" }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -68032,16 +67819,16 @@ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, + "vscode-languageserver-types": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", + "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" + }, "vscode-oniguruma": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", "integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==" }, - "vscode-textmate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz", - "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==" - }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -68051,6 +67838,12 @@ "browser-process-hrtime": "^1.0.0" } }, + "w3c-keyname": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", + "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", + "peer": true + }, "w3c-xmlserializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", @@ -68848,6 +68641,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -68981,7 +68775,6 @@ "parse-numeric-range": "^1.3.0", "postcss": "^8.4.18", "postcss-preset-env": "^6.7.0", - "prettier": "^2.2.1", "react": "^17.0.2", "react-copy-to-clipboard": "^5.0.2", "react-dom": "^17.0.2", @@ -69127,6 +68920,7 @@ "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, "requires": { "sax": ">=0.6.0", "xmlbuilder": "~9.0.1" @@ -69135,7 +68929,8 @@ "xmlbuilder": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==" + "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==", + "dev": true }, "xmlchars": { "version": "2.2.0", diff --git a/package.json b/package.json index 9d5a7d60242..0874465f301 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,12 @@ }, "devDependencies": { "@types/json-stringify-safe": "^5.0.0", + "aws-sdk": "^2.1315.0", "eslint": "^7.32.0", "eslint-config-custom": "*", "json-stringify-safe": "^5.0.1", - "prettier": "^2.5.1", + "prettier": "^2.8.4", + "prettier-plugin-sql-cst": "^0.5.0", "ts-jest": "^27.0.7", "turbo": "^1.4.7" }, @@ -55,16 +57,5 @@ "functions", "database", "auth" - ], - "dependencies": { - "@code-hike/mdx": "^0.7.4", - "@mdx-js/react": "^2.1.1", - "aws-sdk": "^2.1315.0", - "mdx-mermaid": "^1.3.2", - "mermaid": "^9.2.2", - "mini-svg-data-uri": "^1.4.4" - }, - "overrides": { - "pgsql-parser": "13.4.0" - } + ] } diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index 772f434d345..3edb9ec62c3 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -305,6 +305,7 @@ export * from './src/components/Icon/icons/IconSun' export * from './src/components/Icon/icons/IconSunrise' export * from './src/components/Icon/icons/IconSunset' export * from './src/components/Icon/icons/IconTablet' +export * from './src/components/Icon/icons/IconTable' export * from './src/components/Icon/icons/IconTag' export * from './src/components/Icon/icons/IconTarget' export * from './src/components/Icon/icons/IconTerminal' diff --git a/packages/ui/internals/icons.js b/packages/ui/internals/icons.js index 07a7f0e5539..1cde50bfbd9 100644 --- a/packages/ui/internals/icons.js +++ b/packages/ui/internals/icons.js @@ -234,6 +234,7 @@ export default { IconSunrise: 'src/components/Icon/icons/IconSunrise/index.tsx', IconSunset: 'src/components/Icon/icons/IconSunset/index.tsx', IconTablet: 'src/components/Icon/icons/IconTablet/index.tsx', + IconTable: 'src/components/Icon/icons/IconTable/index.tsx', IconTag: 'src/components/Icon/icons/IconTag/index.tsx', IconTarget: 'src/components/Icon/icons/IconTarget/index.tsx', IconTerminal: 'src/components/Icon/icons/IconTerminal/index.tsx', @@ -284,5 +285,5 @@ export default { IconZapOff: 'src/components/Icon/icons/IconZapOff/index.tsx', IconZap: 'src/components/Icon/icons/IconZap/index.tsx', IconZoomIn: 'src/components/Icon/icons/IconZoomIn/index.tsx', - IconZoomOut: 'src/components/Icon/icons/IconZoomOut/index.tsx' -} \ No newline at end of file + IconZoomOut: 'src/components/Icon/icons/IconZoomOut/index.tsx', +} diff --git a/packages/ui/internals/icons.json b/packages/ui/internals/icons.json index 992be561d20..a348ede4cf5 100644 --- a/packages/ui/internals/icons.json +++ b/packages/ui/internals/icons.json @@ -939,6 +939,10 @@ "name": "Tablet", "path": "react-feather/dist/icons/tablet" }, + "Table": { + "name": "Table", + "path": "react-feather/dist/icons/table" + }, "Tag": { "name": "Tag", "path": "react-feather/dist/icons/tag" diff --git a/packages/ui/src/components/Accordion/Accordion.tsx b/packages/ui/src/components/Accordion/Accordion.tsx index 11ca83de563..167a4414ffa 100644 --- a/packages/ui/src/components/Accordion/Accordion.tsx +++ b/packages/ui/src/components/Accordion/Accordion.tsx @@ -39,8 +39,8 @@ interface AccordionProps { iconPosition?: Align onChange?: (item: string | string[]) => void openBehaviour: 'single' | 'multiple' - type: Type - size: Size + type?: Type + size?: Size defaultValue?: string | string[] | undefined justified?: Boolean chevronAlign?: Align @@ -98,7 +98,7 @@ function Accordion({ className={containerClasses.join(' ')} children={ -
    {children}
    +
    {children}
    } > diff --git a/packages/ui/src/components/Dropdown/Dropdown.tsx b/packages/ui/src/components/Dropdown/Dropdown.tsx index 50d8861002a..90a343191ff 100644 --- a/packages/ui/src/components/Dropdown/Dropdown.tsx +++ b/packages/ui/src/components/Dropdown/Dropdown.tsx @@ -173,13 +173,18 @@ interface RadioProps { children: React.ReactNode value: string ItemIndicator?: React.ReactNode + disabled?: boolean } -export function Radio({ children, value, ItemIndicator }: RadioProps) { +export function Radio({ children, value, ItemIndicator, disabled }: RadioProps) { let __styles = styleHandler('dropdown') return ( - + {ItemIndicator ? ItemIndicator : } diff --git a/packages/ui/src/components/Icon/IconImportHandler.tsx b/packages/ui/src/components/Icon/IconImportHandler.tsx index 0d158b428c6..3863d419700 100644 --- a/packages/ui/src/components/Icon/IconImportHandler.tsx +++ b/packages/ui/src/components/Icon/IconImportHandler.tsx @@ -241,6 +241,7 @@ export const Sun: Icon = require(`react-feather/dist/icons/sun`) export const Sunrise: Icon = require(`react-feather/dist/icons/sunrise`) export const Sunset: Icon = require(`react-feather/dist/icons/sunset`) export const Tablet: Icon = require(`react-feather/dist/icons/tablet`) +export const Table: Icon = require(`react-feather/dist/icons/table`) export const Tag: Icon = require(`react-feather/dist/icons/tag`) export const Target: Icon = require(`react-feather/dist/icons/target`) export const Terminal: Icon = require(`react-feather/dist/icons/terminal`) diff --git a/packages/ui/src/components/Icon/icons/IconTable/IconTable.tsx b/packages/ui/src/components/Icon/icons/IconTable/IconTable.tsx new file mode 100644 index 00000000000..c0e41b29099 --- /dev/null +++ b/packages/ui/src/components/Icon/icons/IconTable/IconTable.tsx @@ -0,0 +1,13 @@ +// DO NOT EDIT + // this file is generated by /internals/populate-icons script + import * as React from 'react' + // @ts-ignore + import { Table } from 'react-feather' + import IconBase from './../../IconBase' + + function IconTable(props: any) { + return + } + + export default IconTable + \ No newline at end of file diff --git a/packages/ui/src/components/Icon/icons/IconTable/index.tsx b/packages/ui/src/components/Icon/icons/IconTable/index.tsx new file mode 100644 index 00000000000..906c04de230 --- /dev/null +++ b/packages/ui/src/components/Icon/icons/IconTable/index.tsx @@ -0,0 +1 @@ +export { default as IconTable } from './IconTable' \ No newline at end of file diff --git a/packages/ui/src/components/Icon/icons/index.tsx b/packages/ui/src/components/Icon/icons/index.tsx index 9fc2134a5a7..5975707ac72 100644 --- a/packages/ui/src/components/Icon/icons/index.tsx +++ b/packages/ui/src/components/Icon/icons/index.tsx @@ -234,6 +234,7 @@ export * as IconSun from './IconSun' export * as IconSunrise from './IconSunrise' export * as IconSunset from './IconSunset' export * as IconTablet from './IconTablet' +export * as IconTable from './IconTable' export * as IconTag from './IconTag' export * as IconTarget from './IconTarget' export * as IconTerminal from './IconTerminal' diff --git a/packages/ui/src/components/SidePanel/SidePanel.tsx b/packages/ui/src/components/SidePanel/SidePanel.tsx index d04b062b3c2..b9595e7fec3 100644 --- a/packages/ui/src/components/SidePanel/SidePanel.tsx +++ b/packages/ui/src/components/SidePanel/SidePanel.tsx @@ -22,7 +22,7 @@ interface CustomProps { children?: React.ReactNode header?: string | React.ReactNode visible: boolean - size?: 'medium' | 'large' | 'xlarge' + size?: 'medium' | 'large' | 'xlarge' | 'xxlarge' loading?: boolean align?: 'right' | 'left' hideFooter?: boolean diff --git a/packages/ui/src/lib/theme/defaultTheme.ts b/packages/ui/src/lib/theme/defaultTheme.ts index 28fe703fb73..d5bffd27180 100644 --- a/packages/ui/src/lib/theme/defaultTheme.ts +++ b/packages/ui/src/lib/theme/defaultTheme.ts @@ -187,7 +187,11 @@ export default { chevron: { base: ` text-scale-900 - rotate-0 group-state-open:rotate-180 + rotate-0 + group-state-open:rotate-180 + group-data-[state=open]:rotate-180 + ease-[cubic-bezier(0.87,_0,_0.13,_1)] + transition-transform duration-300 duration-200 `, align: { @@ -998,6 +1002,7 @@ export default { medium: `w-screen max-w-md h-full`, large: `w-screen max-w-2xl h-full`, xlarge: `w-screen max-w-3xl h-full`, + xxlarge: `w-screen max-w-4xl h-full`, }, align: { left: ` diff --git a/spec/supabase_py_v2.yml b/spec/supabase_py_v2.yml index a3af18a5acd..486e429f3e1 100644 --- a/spec/supabase_py_v2.yml +++ b/spec/supabase_py_v2.yml @@ -281,18 +281,360 @@ functions: name: Getting your data code: | ``` - r = supabase - .from('countries') - .select("*").execute() + response = supabase.table('countries').select("*").execute() + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + + insert into + countries (id, name) + values + (1, 'Afghanistan'), + (2, 'Albania'), + (3, 'Algeria'); + ``` + response: | + ``` + APIResponse(data=[{'id': 1, 'name': 'Afghanistan'}, + {'id': 2, 'name': 'Albania'}, + {'id': 3, 'name': 'Algeria'}], + count=None) ``` - id: selecting-specific-columns name: Selecting specific columns code: | ``` - r = await supabase - .from('countries') - .select('name').execute() + response = supabase.table('countries').select('name').execute() ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + + insert into + countries (id, name) + values + (1, 'Afghanistan'), + (2, 'Albania'), + (3, 'Algeria'); + ``` + response: | + ``` + APIResponse(data=[{'name': 'Afghanistan'}, + {'name': 'Albania'}, + {'name': 'Algeria'}], + count=None) + ``` + - id: query-foreign-tables + name: Query foreign tables + description: | + If your database has foreign key relationships, you can query related tables too. + code: | + ``` + response = supabase.table('countries').select('name, cities(name)').execute() + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + create table + cities ( + id int8 primary key, + country_id int8 not null references countries, + name text + ); + + insert into + countries (id, name) + values + (1, 'Germany'), + (2, 'Indonesia'); + insert into + cities (id, country_id, name) + values + (1, 2, 'Bali'), + (2, 1, 'Munich'); + ``` + response: | + ``` + APIResponse(data=[{'name': 'Germany', 'cities': [{'name': 'Munich'}]}, + {'name': 'Indonesia', 'cities': [{'name': 'Bali'}]}], + count=None) + ``` + - id: query-foreign-tables-through-a-join-table + name: Query foreign tables through a join table + code: | + ``` + response = supabase.table('users').select('name, teams(name)').execute() + ``` + data: + sql: | + ```sql + create table + users ( + id int8 primary key, + name text + ); + create table + teams ( + id int8 primary key, + name text + ); + -- join table + create table + users_teams ( + user_id int8 not null references users, + team_id int8 not null references teams, + -- both foreign keys must be part of a composite primary key + primary key (user_id, team_id) + ); + + insert into + users (id, name) + values + (1, 'Kiran'), + (2, 'Evan'); + insert into + teams (id, name) + values + (1, 'Green'), + (2, 'Blue'); + insert into + users_teams (user_id, team_id) + values + (1, 1), + (1, 2), + (2, 2); + ``` + response: | + ``` + APIResponse(data=[ + {'name': 'Kiran', 'teams': [{'name': 'Green'}, {'name': 'Blue'}]}, + {'name': 'Evan', 'teams': [{'name': 'Blue'}]} + ], + count=None) + ``` + description: | + If you're in a situation where your tables are **NOT** directly + related, but instead are joined by a _join table_, you can still use + the `select()` method to query the related data. The join table needs + to have the foreign keys as part of its composite primary key. + hideCodeBlock: true + - id: query-the-same-foreign-table-multiple-times + name: Query the same foreign table multiple times + code: | + ``` + response = supabase.table('messages').select('content,from:sender_id(name),to:receiver_id(name)').execute() + ``` + data: + sql: | + ```sql + create table + users (id int8 primary key, name text); + + create table + messages ( + sender_id int8 not null references users, + receiver_id int8 not null references users, + content text + ); + + insert into + users (id, name) + values + (1, 'Kiran'), + (2, 'Evan'); + + insert into + messages (sender_id, receiver_id, content) + values + (1, 2, '👋'); + ``` + response: | + ``` + APIResponse(data=[{'content': '👋', + 'from': {'name': 'Kiran'}, + 'to': {'name': 'Evan'}} + ], + count=None) + ``` + description: | + If you need to query the same foreign table twice, use the name of the + joined column to identify which join to use. You can also give each + column an alias. + hideCodeBlock: true + - id: filtering-through-foreign-tables + name: Filtering through foreign tables + code: | + ``` + response = supabase.table('cities').select('name, countries(*)').eq('countries.name', 'Estonia').execute() + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + create table + cities ( + id int8 primary key, + country_id int8 not null references countries, + name text + ); + + insert into + countries (id, name) + values + (1, 'Germany'), + (2, 'Indonesia'); + insert into + cities (id, country_id, name) + values + (1, 2, 'Bali'), + (2, 1, 'Munich'); + ``` + response: | + ``` + APIResponse(data=[{'name': 'Bali', 'countries': None}, + {'name': 'Munich', 'countries': None}], + count=None) + ``` + description: | + NOT WORKING CURRENTLY + If the filter on a foreign table's column is not satisfied, the foreign + table returns `[]` or `null` but the parent table is not filtered out. + If you want to filter out the parent table rows, use the `!inner` hint + hideCodeBlock: true + - id: querying-foreign-table-with-count + name: Querying foreign table with count + code: | + ``` + response = supabase.table('countries').select('*, cities(count)').execute() + ``` + data: + sql: | + ```sql + create table countries ( + "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null, + "name" text + ); + + create table cities ( + "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null, + "name" text, + "country_id" "uuid" references public.countries on delete cascade + ); + + with country as ( + insert into countries (name) + values ('united kingdom') returning id + ) + insert into cities (name, country_id) values + ('London', (select id from country)), + ('Manchester', (select id from country)), + ('Liverpool', (select id from country)), + ('Bristol', (select id from country)); + ``` + response: | + ``` + APIResponse(data=[{'id': 1, 'name': 'Germany', 'cities': [{'count': 1}]}, + {'id': 2, 'name': 'Indonesia', 'cities': [{'count': 1}]}], + count=None) + ``` + description: | + You can get the number of rows in a related table by using the + **count** property. + hideCodeBlock: true + - id: querying-with-count-option + name: Querying with count option + code: | + ``` + response = supabase.table('countries').select('*', count='exact').execute() + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + + insert into + countries (id, name) + values + (1, 'Afghanistan'), + (2, 'Albania'), + (3, 'Algeria'); + ``` + response: | + ``` + APIResponse(data=[{'id': 1, 'name': 'Germany'}, + {'id': 2, 'name': 'Indonesia'}], + count=2) + ``` + description: | + You can get the number of rows by using the + *count* parameter in the select query. + hideCodeBlock: true + - id: querying-json-data + name: Querying JSON data + code: | + ``` + response = supabase.table('users').select('id, name, address->city').execute() + ``` + data: + sql: | + ```sql + create table + users ( + id int8 primary key, + name text, + address jsonb + ); + + insert into + users (id, name, address) + values + (1, 'Avdotya', '{"city":"Saint Petersburg"}'); + ``` + response: | + ``` + APIResponse(data=[{'id': 1, 'name': 'Avdotya', 'city': 'Saint Petersburg'}], count=None) + ``` + description: | + You can select and filter data inside of + [JSON](/docs/guides/database/json) columns. Postgres offers some + [operators](/docs/guides/database/json#query-the-jsonb-data) for + querying JSON data. + hideCodeBlock: true + + - id: insert + title: 'Create data: insert()' + $ref: '@supabase/postgrest-js.PostgrestQueryBuilder.insert' + examples: + - id: create-a-record + name: Create a record + code: | + ``` + data, count = supabase + .table('countries') + .insert({"id": 1, "name": "Denmark"}) + .execute() + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + ``` + response: | + ``` + APIResponse(data=[{'id': 1, 'name': 'Denmark'}], count=None) + ``` + hideCodeBlock: true + isSpotlight: true - id: invoke title: 'invoke()' diff --git a/studio/components/grid/SupabaseGrid.tsx b/studio/components/grid/SupabaseGrid.tsx index 4e3e2d5ab0b..3229e4a771d 100644 --- a/studio/components/grid/SupabaseGrid.tsx +++ b/studio/components/grid/SupabaseGrid.tsx @@ -7,9 +7,8 @@ import { DataGridHandle } from '@supabase/react-data-grid' import { useUrlState } from 'hooks' import { Dictionary, SupabaseGridProps, SupabaseGridRef } from './types' +import { useTableRowsQuery } from 'data/table-rows/table-rows-query' import { StoreProvider, useDispatch, useTrackedState } from './store' -import { fetchCount, fetchPage, refreshPageDebounced } from './utils' -import { REFRESH_PAGE_IMMEDIATELY, TOTAL_ROWS_RESET } from './constants' import { Shortcuts } from './components/common' import { Grid } from './components/grid' import Header from './components/header' @@ -22,6 +21,7 @@ import { formatFilterURLParams, formatSortURLParams, } from './SupabaseGrid.utils' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' /** Supabase Grid: React component to render database table */ @@ -71,9 +71,30 @@ const SupabaseGridLayout = forwardRef((props const sorts = formatSortURLParams(sort as string[]) const filters = formatFilterURLParams(filter as string[]) - // [Joshen] This is not a perfect fix, but the useEffect to initialize the data in the editor - // was not getting retriggered for views, works fine for tables. - const table = typeof props.table === 'string' ? { name: props.table } : props.table + const table = props.table + + const { project } = useProjectContext() + const { data, isLoading, isRefetching } = useTableRowsQuery( + { + queryKey: [table.schema, table.name], + projectRef: project?.ref, + connectionString: project?.connectionString, + table, + sorts, + filters, + page: state.page, + limit: state.rowsPerPage, + }, + { + keepPreviousData: true, + onSuccess(data) { + dispatch({ + type: 'SET_ROWS_COUNT', + payload: data.rows.length, + }) + }, + } + ) useImperativeHandle(ref, () => ({ rowAdded(row: Dictionary) { @@ -94,20 +115,6 @@ const SupabaseGridLayout = forwardRef((props if (!mounted) setMounted(true) }, []) - useEffect(() => { - if (state.refreshPageFlag === REFRESH_PAGE_IMMEDIATELY) { - fetchPage(state, dispatch, sorts, filters) - } else if (state.refreshPageFlag !== 0) { - refreshPageDebounced(state, dispatch, sorts, filters) - } - }, [state.refreshPageFlag]) - - useEffect(() => { - if (state.totalRows === TOTAL_ROWS_RESET) { - fetchCount(state, dispatch, filters) - } - }, [state.totalRows]) - useEffect(() => { if (mounted) { dispatch({ type: 'UPDATE_FILTERS', payload: {} }) @@ -191,10 +198,10 @@ const SupabaseGridLayout = forwardRef((props onAddColumn={editable ? props.onAddColumn : undefined} headerActions={headerActions} /> - -
    + +
    - {mounted && createPortal(, document.body)} + {mounted && createPortal(, document.body)}
    ) }) diff --git a/studio/components/grid/components/footer/Footer.tsx b/studio/components/grid/components/footer/Footer.tsx index f950319460b..129cf8faba6 100644 --- a/studio/components/grid/components/footer/Footer.tsx +++ b/studio/components/grid/components/footer/Footer.tsx @@ -1,15 +1,17 @@ -import * as React from 'react' import Pagination from './pagination' -type FooterProps = {} +export interface FooterProps { + isLoading?: boolean +} -const Footer: React.FC = () => { +const Footer = ({ isLoading }: FooterProps) => { return (
    - +
    ) } + export default Footer diff --git a/studio/components/grid/components/footer/pagination/Pagination.tsx b/studio/components/grid/components/footer/pagination/Pagination.tsx index 85a5713369e..04331fb2854 100644 --- a/studio/components/grid/components/footer/pagination/Pagination.tsx +++ b/studio/components/grid/components/footer/pagination/Pagination.tsx @@ -1,9 +1,13 @@ -import { FC, useState, useEffect } from 'react' +import { useState, useEffect } from 'react' import AwesomeDebouncePromise from 'awesome-debounce-promise' import { Button, InputNumber, IconArrowRight, IconArrowLeft, IconLoader } from 'ui' +import { useUrlState } from 'hooks' +import { useTableRowsCountQuery } from 'data/table-rows/table-rows-count-query' import { DropdownControl } from '../../common' import { useDispatch, useTrackedState } from '../../../store' import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' +import { formatFilterURLParams } from 'components/grid/SupabaseGrid.utils' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' const updatePage = (payload: number, dispatch: (value: unknown) => void) => { dispatch({ @@ -19,14 +23,34 @@ const rowsPerPageOptions = [ { value: 1000, label: '1000 rows' }, ] -type PaginationProps = {} +export interface PaginationProps { + isLoading?: boolean +} -const Pagination: FC = () => { +const Pagination = ({ isLoading: isLoadingRows = false }: PaginationProps) => { const state = useTrackedState() const dispatch = useDispatch() const [page, setPage] = useState(state.page) - const maxPages = Math.ceil(state.totalRows / state.rowsPerPage) - const totalPages = state.totalRows > 0 ? maxPages : 1 + + const [{ filter }] = useUrlState({ + arrayKeys: ['filter'], + }) + const filters = formatFilterURLParams(filter as string[]) + const table = state.table ?? undefined + const { project } = useProjectContext() + const { data, isLoading, isSuccess, isError } = useTableRowsCountQuery( + { + queryKey: [table?.schema, table?.name, 'count'], + projectRef: project?.ref, + connectionString: project?.connectionString, + table, + filters, + }, + { keepPreviousData: true } + ) + + const maxPages = Math.ceil((data?.count ?? 0) / state.rowsPerPage) + const totalPages = (data?.count ?? 0) > 0 ? maxPages : 1 // [Joshen] Oddly without this, state.selectedRows will be stale useEffect(() => {}, [state.selectedRows]) @@ -75,6 +99,8 @@ const Pagination: FC = () => { } } + // TODO: look at aborting useTableRowsQuery if the user presses the button quickly + const goToPreviousPage = () => { const previousPage = state.page - 1 setPage(previousPage) @@ -102,14 +128,14 @@ const Pagination: FC = () => { return (
    - {state.totalRows < 0 ? ( -

    Loading records...

    - ) : ( + {isLoading &&

    Loading records count...

    } + + {isSuccess && ( <> -

    {`${state.totalRows.toLocaleString()} ${ - state.totalRows === 0 || state.totalRows > 1 ? `records` : 'record' +

    {`${data.count.toLocaleString()} ${ + data.count === 0 || data.count > 1 ? `records` : 'record' }`}

    - {state.isLoading && } + {isLoadingRows && } )} + + {isError && ( +

    + Error fetching records count. Please refresh the page. +

    + )}
    ) } diff --git a/studio/components/grid/components/grid/Grid.tsx b/studio/components/grid/components/grid/Grid.tsx index 07aee22745b..a5194aa0c9f 100644 --- a/studio/components/grid/components/grid/Grid.tsx +++ b/studio/components/grid/components/grid/Grid.tsx @@ -6,6 +6,7 @@ import { GridProps, SupaRow } from '../../types' import { useDispatch, useTrackedState } from '../../store' import RowRenderer from './RowRenderer' import AwesomeDebouncePromise from 'awesome-debounce-promise' +import { useTableRowUpdateMutation } from 'data/table-rows/table-row-update-mutation' function rowKeyGetter(row: SupaRow) { return row?.idx ?? -1 @@ -13,13 +14,14 @@ function rowKeyGetter(row: SupaRow) { interface IGrid extends GridProps { rows: any[] + updateRow: (previousRow: any, updatedData: any) => void } // [Joshen] Just for visibility this is causing some hook errors in the browser export const Grid = memo( forwardRef( ( - { width, height, containerClass, gridClass, rowClass, rows }, + { width, height, containerClass, gridClass, rowClass, rows, updateRow }, ref: React.Ref | undefined ) => { const dispatch = useDispatch() @@ -41,18 +43,7 @@ export const Grid = memo( ) if (changedColumn) { - const { error } = state.rowService!.update( - rowData, - originRowData, - changedColumn, - (payload) => { - dispatch({ - type: 'EDIT_ROW', - payload, - }) - } - ) - if (error && onErrorFunc) onErrorFunc(error) + updateRow(originRowData, { [changedColumn]: rowData[changedColumn] }) } } diff --git a/studio/components/grid/components/header/Header.tsx b/studio/components/grid/components/header/Header.tsx index d839ced0cfd..8c869e456fd 100644 --- a/studio/components/grid/components/header/Header.tsx +++ b/studio/components/grid/components/header/Header.tsx @@ -1,17 +1,9 @@ -import { FC, useState, ReactNode } from 'react' -import { - Button, - IconDownload, - IconPlus, - IconX, - IconTrash, - Dropdown, - IconColumns, - IconChevronDown, -} from 'ui' import { saveAs } from 'file-saver' +import { useState, ReactNode } from 'react' +import { Button, IconDownload, IconX, IconTrash, Dropdown, IconChevronDown } from 'ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' import FilterDropdown from './filter' import SortPopover from './sort' import RefreshButton from './RefreshButton' @@ -19,14 +11,21 @@ import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmM import { Sort, Filter, SupaTable } from 'components/grid/types' import { exportRowsToCsv } from 'components/grid/utils' import { useDispatch, useTrackedState } from 'components/grid/store' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { useTableRowDeleteMutation } from 'data/table-rows/table-row-delete-mutation' +import { useTableRowDeleteAllMutation } from 'data/table-rows/table-row-delete-all-mutation' +import { useTableRowTruncateMutation } from 'data/table-rows/table-row-truncate-mutation' +import { useQueryClient } from '@tanstack/react-query' +import { useTableRowsCountQuery } from 'data/table-rows/table-rows-count-query' +import { useTableRowsQuery } from 'data/table-rows/table-rows-query' // [Joshen] CSV exports require this guard as a fail-safe if the table is // just too large for a browser to keep all the rows in memory before // exporting. Either that or export as multiple CSV sheets with max n rows each const MAX_EXPORT_ROW_COUNT = 500000 -interface HeaderProps { - table: Partial +export type HeaderProps = { + table: SupaTable sorts: Sort[] filters: Filter[] onAddColumn?: () => void @@ -34,56 +33,39 @@ interface HeaderProps { headerActions?: ReactNode } -const Header: FC = ({ - table, - sorts, - filters, - onAddColumn, - onAddRow, - headerActions, -}) => { +const Header = ({ table, sorts, filters, onAddColumn, onAddRow, headerActions }: HeaderProps) => { const state = useTrackedState() const { selectedRows } = state return (
    {selectedRows.size > 0 ? ( - + ) : ( - + )}
    {headerActions}
    ) } + export default Header -interface DefaultHeaderProps { - table: Partial - sorts: Sort[] - filters: Filter[] +type DefaultHeaderProps = { + table: SupaTable onAddColumn?: () => void onAddRow?: () => void } -const DefaultHeader: FC = ({ - table, - sorts, - filters, - onAddColumn, - onAddRow, -}) => { +const DefaultHeader = ({ table, onAddColumn, onAddRow }: DefaultHeaderProps) => { const canAddNew = onAddRow !== undefined || onAddColumn !== undefined + // [Joshen] Using this logic to block both column and row creation/update/delete + const canCreateColumns = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns') + return (
    - +
    @@ -91,71 +73,73 @@ const DefaultHeader: FC = ({ <>
    - -
    -
    -
    + {canCreateColumns && ( + +
    +
    +
    +
    + } + > +
    +

    Insert row

    +

    Insert a new row into {table.name}

    - } - > -
    -

    Insert row

    -

    Insert a new row into {table.name}

    -
    - , - ] - : []), - ...(onAddColumn !== undefined - ? [ - -
    -
    -
    + , + ] + : []), + ...(onAddColumn !== undefined + ? [ + +
    +
    +
    +
    + } + > +
    +

    Insert column

    +

    Insert a new column into {table.name}

    - } - > -
    -

    Insert column

    -

    Insert a new column into {table.name}

    -
    - , - ] - : []), - ]} - > - - + , + ] + : []), + ]} + > + + + )}
    )} @@ -163,18 +147,49 @@ const DefaultHeader: FC = ({ ) } -interface RowHeaderProps { +type RowHeaderProps = { + table: SupaTable sorts: Sort[] filters: Filter[] } -const RowHeader: FC = ({ sorts, filters }) => { +const RowHeader = ({ table, sorts, filters }: RowHeaderProps) => { + const queryClient = useQueryClient() const { ui } = useStore() const state = useTrackedState() const dispatch = useDispatch() - const [isExporting, setIsExporting] = useState(false) + const { project } = useProjectContext() + const { mutateAsync: deleteRows } = useTableRowDeleteMutation() + const { mutateAsync: deleteAllRows } = useTableRowDeleteAllMutation() + const { mutateAsync: truncateRows } = useTableRowTruncateMutation() - const { selectedRows, rows: allRows, editable, allRowsSelected, totalRows } = state + const { data } = useTableRowsQuery({ + queryKey: [table.schema, table.name], + projectRef: project?.ref, + connectionString: project?.connectionString, + table, + sorts, + filters, + page: state.page, + limit: state.rowsPerPage, + }) + + const allRows = data?.rows ?? [] + + const { data: countData } = useTableRowsCountQuery( + { + queryKey: [table?.schema, table?.name, 'count'], + projectRef: project?.ref, + connectionString: project?.connectionString, + table, + filters, + }, + { keepPreviousData: true } + ) + + const totalRows = countData?.count ?? 0 + + const { selectedRows, editable, allRowsSelected } = state const onSelectAllRows = () => { dispatch({ @@ -192,38 +207,60 @@ const RowHeader: FC = ({ sorts, filters }) => { }? This action cannot be undone.`, confirmText: `Delete ${numRows} rows`, onAsyncConfirm: async () => { + if (!project) return + if (allRowsSelected) { - const { error } = - filters.length === 0 - ? await state.rowService!.truncate() - : await state.rowService!.deleteAll(filters) - if (error) { - if (state.onError) state.onError(error) - } else { + try { + if (filters.length === 0) { + await truncateRows({ + projectRef: project.ref, + connectionString: project.connectionString, + table, + }) + } else { + await deleteAllRows({ + projectRef: project.ref, + connectionString: project.connectionString, + table, + filters, + }) + } + dispatch({ type: 'REMOVE_ALL_ROWS' }) dispatch({ type: 'SELECTED_ROWS_CHANGE', payload: { selectedRows: new Set() }, }) + } catch (error) { + if (state.onError) state.onError(error) } } else { const rowIdxs = Array.from(selectedRows) as number[] const rows = allRows.filter((x) => rowIdxs.includes(x.idx)) - const { error } = await state.rowService!.delete(rows) - if (error) { - if (state.onError) state.onError(error) - } else { + + try { + await deleteRows({ + projectRef: project?.ref, + connectionString: project?.connectionString, + table, + rows, + }) + dispatch({ type: 'REMOVE_ROWS', payload: { rowIdxs } }) dispatch({ type: 'SELECTED_ROWS_CHANGE', payload: { selectedRows: new Set() }, }) + } catch (error) { + if (state.onError) state.onError(error) } } }, }) } + const [isExporting, setIsExporting] = useState(false) + async function onRowsExportCSV() { setIsExporting(true) diff --git a/studio/components/grid/components/header/RefreshButton.tsx b/studio/components/grid/components/header/RefreshButton.tsx index cea80d66f2e..dea181a3de8 100644 --- a/studio/components/grid/components/header/RefreshButton.tsx +++ b/studio/components/grid/components/header/RefreshButton.tsx @@ -1,20 +1,23 @@ -import { FC, useState, useEffect } from 'react' -import { Button, IconRefreshCw, IconCheck } from 'ui' +import { useQueryClient, useIsFetching } from '@tanstack/react-query' +import { SupaTable } from 'components/grid/types' +import { sqlKeys } from 'data/sql/keys' +import { useParams } from 'hooks' +import { useEffect, useState } from 'react' +import { Button, IconCheck, IconRefreshCw } from 'ui' import { SupabaseGridQueue } from '../../constants' -import { Filter, Sort } from 'components/grid/types' -import { useDispatch, useTrackedState } from 'components/grid/store' -import { fetchCount, fetchPage } from 'components/grid/utils' -interface Props { - sorts: Sort[] - filters: Filter[] +export type RefreshButtonProps = { + table: SupaTable } -const RefreshButton: FC = ({ sorts, filters }) => { - const state = useTrackedState() - const dispatch = useDispatch() +const RefreshButton = ({ table }: RefreshButtonProps) => { + const { ref } = useParams() + const queryClient = useQueryClient() + + const queryKey = sqlKeys.query(ref, [table.schema, table.name]) + + const loading = useIsFetching({ queryKey }) > 0 - const [loading, setLoading] = useState(false) const [status, setStatus] = useState() useEffect(() => { @@ -40,10 +43,7 @@ const RefreshButton: FC = ({ sorts, filters }) => { }, []) async function onClick() { - setLoading(true) - await fetchCount(state, dispatch, filters) - await fetchPage(state, dispatch, sorts, filters) - setLoading(false) + await queryClient.invalidateQueries(queryKey) } return ( diff --git a/studio/components/grid/components/menu/ColumnMenu.tsx b/studio/components/grid/components/menu/ColumnMenu.tsx index d252aa58289..27d5f59b57a 100644 --- a/studio/components/grid/components/menu/ColumnMenu.tsx +++ b/studio/components/grid/components/menu/ColumnMenu.tsx @@ -10,7 +10,6 @@ import { IconLock, IconUnlock, } from 'ui' -import * as React from 'react' import { useDispatch, useTrackedState } from '../../store' type ColumnMenuProps = { diff --git a/studio/components/grid/components/menu/RowContextMenu.tsx b/studio/components/grid/components/menu/RowContextMenu.tsx index e0dd6c1da4b..1bdd6ccd19c 100644 --- a/studio/components/grid/components/menu/RowContextMenu.tsx +++ b/studio/components/grid/components/menu/RowContextMenu.tsx @@ -1,18 +1,25 @@ -import * as React from 'react' import { Menu, Item, ItemParams, PredicateParams } from 'react-contexify' import { IconTrash, IconClipboard, IconEdit } from 'ui' import { useDispatch, useTrackedState } from '../../store' import { formatClipboardValue, copyToClipboard } from '../../utils' +import { useTableRowDeleteMutation } from 'data/table-rows/table-row-delete-mutation' import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { SupaTable } from 'components/grid/types' export const ROW_CONTEXT_MENU_ID = 'row-context-menu-id' -type RowContextMenuProps = {} +export type RowContextMenuProps = { + table: SupaTable +} -const RowContextMenu: React.FC = ({}) => { +const RowContextMenu = ({ table }: RowContextMenuProps) => { const state = useTrackedState() const dispatch = useDispatch() + const { project } = useProjectContext() + const { mutateAsync: deleteRows } = useTableRowDeleteMutation() + function onDeleteRow(p: ItemParams) { confirmAlert({ title: 'Confirm to delete', @@ -21,17 +28,23 @@ const RowContextMenu: React.FC = ({}) => { const { props } = p const { rowIdx } = props const row = state.rows[rowIdx] - if (!row) return + if (!row || !project) return + + try { + await deleteRows({ + projectRef: project.ref, + connectionString: project.connectionString, + table, + rows: [row], + }) - const { error } = await state.rowService!.delete([row]) - if (error) { - if (state.onError) state.onError(error) - } else { dispatch({ type: 'REMOVE_ROWS', payload: { rowIdxs: [row.idx] } }) dispatch({ type: 'SELECTED_ROWS_CHANGE', payload: { selectedRows: new Set() }, }) + } catch (error) { + if (state.onError) state.onError(error) } }, }) diff --git a/studio/components/grid/types/grid.ts b/studio/components/grid/types/grid.ts index a036249a97d..f64fd05917a 100644 --- a/studio/components/grid/types/grid.ts +++ b/studio/components/grid/types/grid.ts @@ -15,7 +15,7 @@ export interface SupabaseGridProps { /** * database table swagger or table name */ - table: SupaTable | string + table: SupaTable /** * * run sql query @@ -64,7 +64,17 @@ export interface SupabaseGridProps { */ onEditRow?: (row: SupaRow) => void onError?: (error: any) => void + /** + * Toggle api preview panel open + */ + apiPreviewPanelOpen?: boolean + setApiPreviewPanelOpen?: () => void + /** + * Refresh the docs after a change is made to the table + */ + refreshDocs: () => void onExpandJSONEditor: (column: string, row: SupaRow) => void + updateTableRow: (previousRow: any, updatedData: any) => void } export interface SupabaseGridRef { diff --git a/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx b/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx index a982880a845..85462a7e4be 100644 --- a/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx +++ b/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx @@ -8,19 +8,18 @@ import TemplateEditor from './TemplateEditor' const EmailTemplates = observer(() => { return (
    -
    +
    diff --git a/studio/components/interfaces/Billing/Billing.constants.tsx b/studio/components/interfaces/Billing/Billing.constants.tsx index 547f5c5460b..f253e1bbf0d 100644 --- a/studio/components/interfaces/Billing/Billing.constants.tsx +++ b/studio/components/interfaces/Billing/Billing.constants.tsx @@ -1,4 +1,4 @@ -import { Badge, IconArchive, IconCode, IconDatabase, IconKey } from 'ui' +import { IconArchive, IconCode, IconDatabase, IconKey, IconZap } from 'ui' export const CANCELLATION_REASONS = [ 'Pricing', @@ -154,4 +154,36 @@ export const USAGE_BASED_PRODUCTS = [ }, ], }, + { + title: 'Realtime', + icon: , + features: [ + { + key: 'realtime_message_count', + attribute: 'total_realtime_message_count', + title: 'Realtime Messages', + units: 'absolute', + costPerUnit: 0.0000025, + tooltip: ( + + Billing is based on the total amount of messages throughout your billing period. + + ), + }, + + { + key: 'realtime_peak_connection', + attribute: 'total_realtime_peak_connection', + title: 'Realtime Concurrent Peak Connections', + units: 'absolute', + costPerUnit: 0.01, + tooltip: ( + + Billing is based on the maximum amount of concurrent peak connections throughout your + billing period. + + ), + }, + ], + }, ] diff --git a/studio/components/interfaces/Database/Backups/BackupsList.tsx b/studio/components/interfaces/Database/Backups/BackupsList.tsx index 7f4f41434c5..b444a8e913d 100644 --- a/studio/components/interfaces/Database/Backups/BackupsList.tsx +++ b/studio/components/interfaces/Database/Backups/BackupsList.tsx @@ -35,7 +35,7 @@ const BackupsList: FC = ({}) => { icon={} primaryText="Free Plan does not include project backups." projectRef={projectRef} - secondaryText="Please upgrade to the Pro plan for up to 7 days of scheduled backups." + secondaryText="Upgrade to the Pro plan for up to 7 days of scheduled backups." /> ) } diff --git a/studio/components/interfaces/Database/Backups/PITR/PITRNotice.tsx b/studio/components/interfaces/Database/Backups/PITR/PITRNotice.tsx index cd43b19d452..daab4106406 100644 --- a/studio/components/interfaces/Database/Backups/PITR/PITRNotice.tsx +++ b/studio/components/interfaces/Database/Backups/PITR/PITRNotice.tsx @@ -1,15 +1,22 @@ import Link from 'next/link' import { Button, IconCalendar } from 'ui' import { FormPanel } from 'components/ui/Forms' -import { useParams } from 'hooks' +import * as Tooltip from '@radix-ui/react-tooltip' +import { checkPermissions, useParams } from 'hooks' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import { getPITRRetentionDuration } from './PITR.utils' +import { PermissionAction } from '@supabase/shared-types/out/constants' const PITRNotice = ({}) => { const { ref: projectRef } = useParams() const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) const retentionPeriod = getPITRRetentionDuration(subscription?.addons ?? []) + const canUpdateSubscription = checkPermissions( + PermissionAction.BILLING_WRITE, + 'stripe.subscriptions' + ) + return ( { You can also increase your recovery retention period updating your PITR add-on - - - - - + + {!canUpdateSubscription && ( + + +
    + + You need additional permissions to amend subscriptions + +
    +
    + )} +
    } > diff --git a/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx b/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx index 4b47575c1d4..b634e1365c7 100644 --- a/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx +++ b/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx @@ -2,11 +2,13 @@ import dayjs from 'dayjs' import { FC } from 'react' import { observer } from 'mobx-react-lite' import { Button, IconAlertCircle } from 'ui' +import * as Tooltip from '@radix-ui/react-tooltip' -import { useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' import { Timezone } from './PITR.types' import { FormPanel } from 'components/ui/Forms' import TimezoneSelection from './TimezoneSelection' +import { PermissionAction } from '@supabase/shared-types/out/constants' interface Props { selectedTimezone: Timezone @@ -29,6 +31,11 @@ const PITRStatus: FC = ({ selectedTimezone, onUpdateTimezone, onSetConfig .tz(selectedTimezone?.utc[0]) .format('DD MMM YYYY, HH:mm:ss') + const canTriggerPhysicalBackup = checkPermissions( + PermissionAction.INFRA_EXECUTE, + 'queue_job.walg.prepare_restore' + ) + return ( <> = ({ selectedTimezone, onUpdateTimezone, onSetConfig You'll be able to pick the right date and time when you begin
    - + + + + + {!canTriggerPhysicalBackup && ( + + +
    + + You need additional permissions to trigger a PITR recovery + +
    +
    + )} +
    } > diff --git a/studio/components/interfaces/Database/Extensions/Extensions.constants.ts b/studio/components/interfaces/Database/Extensions/Extensions.constants.ts index 669b441d503..a725a810c92 100644 --- a/studio/components/interfaces/Database/Extensions/Extensions.constants.ts +++ b/studio/components/interfaces/Database/Extensions/Extensions.constants.ts @@ -14,4 +14,5 @@ export const HIDDEN_EXTENSIONS = [ 'supabase_vault', 'intagg', 'xml2', + 'pg_tle', ] diff --git a/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx b/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx index 46458d9a75d..df69111db50 100644 --- a/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx +++ b/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx @@ -60,6 +60,8 @@ const FunctionsList: FC = ({ title="Functions" ctaButtonLabel="Create a new function" onClickCta={() => createFunction()} + disabled={!canCreateFunctions} + disabledMessage="You need additional permissions to create functions" >

    diff --git a/studio/components/interfaces/Database/Roles/RolesList.tsx b/studio/components/interfaces/Database/Roles/RolesList.tsx index 956fb1af750..c5059c26e05 100644 --- a/studio/components/interfaces/Database/Roles/RolesList.tsx +++ b/studio/components/interfaces/Database/Roles/RolesList.tsx @@ -1,17 +1,18 @@ +import { partition } from 'lodash' import { FC, useState, useEffect } from 'react' import { observer } from 'mobx-react-lite' import * as Tooltip from '@radix-ui/react-tooltip' import { PostgresRole } from '@supabase/postgres-meta' import { Button, Input, IconPlus, IconSearch, IconX, Badge } from 'ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' import SparkBar from 'components/ui/SparkBar' import { FormHeader } from 'components/ui/Forms' import RoleRow from './RoleRow' -import DeleteRoleModal from './DeleteRoleModal' import { SUPABASE_ROLES } from './Roles.constants' -import { partition } from 'lodash' import CreateRolePanel from './CreateRolePanel' +import DeleteRoleModal from './DeleteRoleModal' interface Props { onSelectRole: (role: any) => void @@ -26,6 +27,8 @@ const RolesList: FC = ({}) => { const [isCreatingRole, setIsCreatingRole] = useState(false) const [selectedRoleToDelete, setSelectedRoleToDelete] = useState() + const canUpdateRoles = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'roles') + useEffect(() => { const getMaxConnectionLimit = async () => { const res = await meta.query('show max_connections') @@ -149,13 +152,31 @@ const RolesList: FC = ({}) => {

    - + + + + + {!canUpdateRoles && ( + + +
    + You need additional permissions to add a new role +
    +
    + )} +
    @@ -184,7 +205,12 @@ const RolesList: FC = ({}) => {
    )} {otherRoles.map((role: PostgresRole, i: number) => ( - + ))}
    diff --git a/studio/components/interfaces/Database/Tables/ColumnList.tsx b/studio/components/interfaces/Database/Tables/ColumnList.tsx index c5b668777fc..e53ed442863 100644 --- a/studio/components/interfaces/Database/Tables/ColumnList.tsx +++ b/studio/components/interfaces/Database/Tables/ColumnList.tsx @@ -1,11 +1,13 @@ import { FC, useState } from 'react' import { observer } from 'mobx-react-lite' +import * as Tooltip from '@radix-ui/react-tooltip' import { Input, Button, IconSearch, IconPlus, IconChevronLeft, IconEdit3, IconTrash } from 'ui' +import type { PostgresTable } from '@supabase/postgres-meta' +import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useStore } from 'hooks' +import { useStore, checkPermissions } from 'hooks' import Table from 'components/to-be-cleaned/Table' import NoSearchResults from 'components/to-be-cleaned/NoSearchResults' -import type { PostgresTable } from '@supabase/postgres-meta' interface Props { selectedTable: PostgresTable @@ -30,6 +32,7 @@ const ColumnList: FC = ({ : selectedTable.columns?.filter((column: any) => column.name.includes(filterString))) ?? [] const isLocked = meta.excludedSchemas.includes(selectedTable.schema ?? '') + const canUpdateColumns = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns') return ( <> @@ -58,9 +61,32 @@ const ColumnList: FC = ({
    {!isLocked && (
    - + + + + + {!canUpdateColumns && ( + + +
    + + You need additional permissions to create columns + +
    +
    + )} +
    )}
    @@ -103,20 +129,59 @@ const ColumnList: FC = ({
    -
    diff --git a/studio/components/interfaces/Database/Tables/TableList.tsx b/studio/components/interfaces/Database/Tables/TableList.tsx index de856582937..b058f9ce6f4 100644 --- a/studio/components/interfaces/Database/Tables/TableList.tsx +++ b/studio/components/interfaces/Database/Tables/TableList.tsx @@ -12,10 +12,11 @@ import { IconLock, IconCheck, } from 'ui' - import { partition } from 'lodash' +import * as Tooltip from '@radix-ui/react-tooltip' +import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useStore } from 'hooks' +import { useStore, checkPermissions } from 'hooks' import Table from 'components/to-be-cleaned/Table' import NoSearchResults from 'components/to-be-cleaned/NoSearchResults' import type { PostgresTable, PostgresSchema } from '@supabase/postgres-meta' @@ -38,8 +39,8 @@ const TableList: FC = ({ onOpenTable = () => {}, }) => { const { meta } = useStore() - const [filterString, setFilterString] = useState('') + const canUpdateTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') const schemas: PostgresSchema[] = meta.schemas.list() const [protectedSchemas, openSchemas] = partition(schemas, (schema) => @@ -120,9 +121,32 @@ const TableList: FC = ({
    {!isLocked && (
    - + + + + + {!canUpdateTables && ( + + +
    + + You need additional permissions to create tables + +
    +
    + )} +
    )}
    @@ -183,7 +207,62 @@ const TableList: FC = ({ > {x.columns.length} columns - + + + +
    + + You need additional permissions to add wrappers + +
    +
    + + ) + } return ( { + setIsGeneratingTypes(true) + const res = await get(`${API_ADMIN_URL}/projects/${ref}/types/typescript`) + + if (!res.error) { + let element = document.createElement('a') + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(res.types)) + element.setAttribute('download', 'supabase.ts') + element.style.display = 'none' + document.body.appendChild(element) + element.click() + document.body.removeChild(element) + ui.setNotification({ + category: 'success', + message: `Successfully generated types! File is being downloaded`, + }) + } else { + ui.setNotification({ + category: 'error', + message: `Failed to generate types: ${res.error.message}`, + error: res.error, + }) + } + + setIsGeneratingTypes(false) + } + return ( + <> +

    + Generating types + + + + + +

    +
    +
    +

    + Supabase APIs are generated from your database, which means that we can use database + introspection to generate type-safe API definitions. +

    +

    + You can generate types from your database either through the{' '} + + Supabase CLI + + , or by downloading the types file via the button on the right and importing it in your + application within src/index.ts. +

    +
    +
    +
    +

    + {selectedLang === 'js' && ( + + )} +

    +

    + Remember to re-generate and download this file as you make changes to your tables. +

    +
    + + +
    +
    + + ) +} + +const localSnippets = { + cliLogin: () => ({ + title: 'Login via the CLI with your Personal Access Token', + bash: { + code: ` +npx supabase login +`, + }, + }), + generateTypes: (ref: string) => ({ + title: 'Generate types', + bash: { + code: ` +npx supabase gen types typescript --project-id "${ref}" --schema public > types/supabase.ts +`, + }, + }), +} diff --git a/studio/components/interfaces/Docs/LangSelector.tsx b/studio/components/interfaces/Docs/LangSelector.tsx new file mode 100644 index 00000000000..a0e05272874 --- /dev/null +++ b/studio/components/interfaces/Docs/LangSelector.tsx @@ -0,0 +1,110 @@ +import { FC } from 'react' +import { Button, Dropdown, IconKey } from 'ui' +import { checkPermissions } from 'hooks' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { showApiKey } from 'components/interfaces/Docs/Docs.types' + +const DEFAULT_KEY = { name: 'hide', key: 'SUPABASE_KEY' } + +interface Props { + selectedLang: string + setSelectedLang: (selectedLang: string) => void + showApiKey: showApiKey + setShowApiKey: (showApiKey: showApiKey) => void + apiKey: string | undefined + autoApiService: any +} + +const LangSelector: FC = ({ + selectedLang, + setSelectedLang, + showApiKey, + setShowApiKey, + apiKey, + autoApiService, +}) => { + const canReadServiceKey = checkPermissions( + PermissionAction.READ, + 'service_api_keys.service_role_key' + ) + + return ( +
    +
    + + + {selectedLang == 'bash' && ( +
    +
    + + Project API key : +
    + + setShowApiKey(DEFAULT_KEY)}> + hide + + {apiKey && ( + + setShowApiKey({ + key: apiKey, + name: 'anon (public)', + }) + } + > + anon (public) + + )} + {canReadServiceKey && ( + + setShowApiKey({ + key: autoApiService.serviceApiKey, + name: 'service_role (secret)', + }) + } + > + service_role (secret) + + )} + + } + > + + +
    + )} +
    +
    + ) +} + +export default LangSelector diff --git a/studio/components/interfaces/Docs/ResourceContent.tsx b/studio/components/interfaces/Docs/ResourceContent.tsx index a37ed884081..21e6a3dc5fb 100644 --- a/studio/components/interfaces/Docs/ResourceContent.tsx +++ b/studio/components/interfaces/Docs/ResourceContent.tsx @@ -2,6 +2,7 @@ import Snippets from 'components/to-be-cleaned/Docs/Snippets' import CodeSnippet from 'components/to-be-cleaned/Docs/CodeSnippet' import Param from 'components/to-be-cleaned/Docs/Param' import Description from 'components/to-be-cleaned/Docs/Description' +import { IconTable } from 'ui' const ResourceContent = ({ autoApiService, @@ -29,12 +30,18 @@ const ResourceContent = ({ return ( <> -

    - {resourceId} +

    + + + + {resourceId}

    -
    +
    + {properties.map((x) => (
    -
    +
    -
    -
    +
    +
    -
    +
    ))} @@ -87,8 +94,8 @@ const ResourceContent = ({ To read rows in {resourceId}, use the select method.

    - - Learn more. + + Learn more

    @@ -120,8 +127,11 @@ const ResourceContent = ({

    Filtering

    Supabase provides a wide range of filters.

    - - Learn more. + + Learn more

    @@ -147,8 +157,8 @@ const ResourceContent = ({ insert will also return the replaced values for UPSERT.

    - - Learn more. + + Learn more

    @@ -183,8 +193,8 @@ const ResourceContent = ({ update will also return the replaced values for UPDATE.

    - - Learn more. + + Learn more

    @@ -207,8 +217,8 @@ const ResourceContent = ({ default, so remember to specify your filters!

    - - Learn more. + + Learn more

    @@ -230,8 +240,8 @@ const ResourceContent = ({ users depending on Row Level Security (RLS) policies.

    - - Learn more. + + Learn more

    @@ -264,21 +274,6 @@ const ResourceContent = ({ - <> -

    Much more

    -
    -
    -

    - These docs are a work in progress! See our{' '} - - docs - {' '} - for the additional functionality Supabase has to offer. -

    -
    -
    -
    - ) } diff --git a/studio/components/interfaces/Functions/EdgeFunctionDetails.tsx b/studio/components/interfaces/Functions/EdgeFunctionDetails.tsx new file mode 100644 index 00000000000..10c2fffa641 --- /dev/null +++ b/studio/components/interfaces/Functions/EdgeFunctionDetails.tsx @@ -0,0 +1,332 @@ +import dayjs from 'dayjs' +import { useRouter } from 'next/router' +import { FC, useState, useEffect } from 'react' +import * as Tooltip from '@radix-ui/react-tooltip' +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { + Alert, + IconGlobe, + IconTerminal, + IconMinimize2, + IconMaximize2, + IconCheck, + IconClipboard, + Button, + Modal, +} from 'ui' + +import { useStore, useParams, checkPermissions } from 'hooks' +import Panel from 'components/ui/Panel' +import CommandRender from './CommandRender' +import { useProjectApiQuery } from 'data/config/project-api-query' +import { useEdgeFunctionDeleteMutation } from 'data/edge-functions/edge-functions-delete-mutation' + +interface Props {} + +// [Joshen] Next - additional configs: Verify jwt + import maps + +const EdgeFunctionDetails: FC = () => { + const router = useRouter() + const { functions, ui } = useStore() + const { ref: projectRef, id } = useParams() + const [isCopied, setIsCopied] = useState(false) + const [showDeleteModal, setShowDeleteModal] = useState(false) + const [showInstructions, setShowInstructions] = useState(false) + const [selectedFunction, setSelectedFunction] = useState(null) + + const canDeleteEdgeFunction = checkPermissions(PermissionAction.FUNCTIONS_WRITE, '*') + const { mutateAsync: deleteEdgeFunction, isLoading: isDeleting } = useEdgeFunctionDeleteMutation() + + useEffect(() => { + setSelectedFunction(functions.byId(id)) + }, [functions.isLoaded, ui.selectedProject]) + + const { data: settings } = useProjectApiQuery({ projectRef }) + + // Get the API service + const apiService = settings?.autoApiService + const anonKey = apiService?.service_api_keys.find((x) => x.name === 'anon key') + ? apiService.defaultApiKey + : '[YOUR ANON KEY]' + + const endpoint = apiService?.app_config.endpoint ?? '' + const endpointSections = endpoint.split('.') + const functionsEndpoint = [ + ...endpointSections.slice(0, 1), + 'functions', + ...endpointSections.slice(1), + ].join('.') + const functionUrl = `${apiService?.protocol}://${functionsEndpoint}/${selectedFunction?.slug}` + + const onConfirmDelete = async () => { + if (!projectRef) return console.error('Project ref is required') + if (selectedFunction === undefined) return console.error('No edge function selected') + + try { + await deleteEdgeFunction({ projectRef, slug: selectedFunction.slug }) + ui.setNotification({ + category: 'success', + message: `Successfully deleted "${selectedFunction.name}"`, + }) + router.push(`/project/${projectRef}/functions`) + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to delete function: ${error.message}`, + }) + } + } + + const managementCommands: any = [ + { + command: `supabase functions deploy ${selectedFunction?.slug}`, + description: 'This will overwrite the deployed function with your new function', + jsx: () => { + return ( + <> + supabase functions deploy{' '} + {selectedFunction?.slug} + + ) + }, + comment: 'Deploy a new version', + }, + { + command: `supabase functions delete ${selectedFunction?.slug}`, + description: 'This will remove the function and all the logs associated with it', + jsx: () => { + return ( + <> + supabase functions delete{' '} + {selectedFunction?.slug} + + ) + }, + comment: 'Delete the function', + }, + ] + + const secretCommands: any = [ + { + command: `supabase secrets list`, + description: 'This will list all the secrets for your project', + jsx: () => { + return ( + <> + supabase secrets list + + ) + }, + comment: 'View all secrets', + }, + { + command: `supabase secrets set NAME1=VALUE1 NAME2=VALUE2`, + description: 'This will set secrets for your project', + jsx: () => { + return ( + <> + supabase secrets set NAME1=VALUE1 NAME2=VALUE2 + + ) + }, + comment: 'Set secrets for your project', + }, + { + command: `supabase secrets unset NAME1 NAME2 `, + description: 'This will delete secrets for your project', + jsx: () => { + return ( + <> + supabase secrets unset NAME1 NAME2 + + ) + }, + comment: 'Unset secrets for your project', + }, + ] + + const invokeCommands: any = [ + { + command: `curl -L -X POST '${functionUrl}' -H 'Authorization: Bearer ${ + anonKey ?? '[YOUR ANON KEY]' + }' --data '{"name":"Functions"}'`, + description: 'Invokes the hello function', + jsx: () => { + return ( + <> + curl -L -X POST 'https://{functionsEndpoint}/ + {selectedFunction?.slug}' -H 'Authorization: Bearer [YOUR ANON KEY]'{' '} + {`--data '{"name":"Functions"}'`} + + ) + }, + comment: 'Invoke your function', + }, + ] + + return ( + <> +
    +
    +
    +

    Function Name

    +

    {selectedFunction?.name}

    +
    + +
    +

    Endpoint URL

    +

    {functionUrl}

    + +
    + +
    +

    Created At

    +

    + {selectedFunction?.created_at && + dayjs(selectedFunction.created_at).format('dddd, MMMM D, YYYY h:mm A')} +

    +
    + +
    +

    Last Updated At

    +

    + {selectedFunction?.updated_at && + dayjs(selectedFunction.updated_at).format('dddd, MMMM D, YYYY h:mm A')} +

    +
    + +
    + Deployments +
    {selectedFunction?.version}
    +
    + +
    + Regions +
    +
    + + Earth +
    + All functions are deployed globally +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Command line access

    +
    +
    setShowInstructions(!showInstructions)}> + {showInstructions ? ( + + ) : ( + + )} +
    +
    + +
    Deployment management
    + +
    Invoke
    + +
    Secrets management
    + +
    + + Delete Edge Function

    }> + + +

    + Make sure you have made a backup if you want to restore your edge function +

    + + + + + {!canDeleteEdgeFunction && ( + + +
    + + You need additional permissions to delete an edge function + +
    +
    + )} +
    +
    +
    +
    +
    + + Confirm to delete {selectedFunction?.name}} + visible={showDeleteModal} + loading={isDeleting} + onCancel={() => setShowDeleteModal(false)} + onConfirm={onConfirmDelete} + > +
    + + + Ensure that you have made a backup if you want to restore your edge function + + +
    +
    + + ) +} + +export default EdgeFunctionDetails diff --git a/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx b/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx new file mode 100644 index 00000000000..0d9ce1f727b --- /dev/null +++ b/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx @@ -0,0 +1,103 @@ +import dayjs from 'dayjs' +import { FC, useState } from 'react' +import { useRouter } from 'next/router' +import { observer } from 'mobx-react-lite' +import { IconCheck, IconClipboard } from 'ui' +import * as Tooltip from '@radix-ui/react-tooltip' + +import { useParams, useStore } from 'hooks' +import Table from 'components/to-be-cleaned/Table' +import { EdgeFunctionsResponse } from 'data/edge-functions/edge-functions-query' + +interface Props { + function: EdgeFunctionsResponse +} + +const EdgeFunctionsListItem: FC = ({ function: item }) => { + const router = useRouter() + const { ui } = useStore() + const { ref } = useParams() + const [isCopied, setIsCopied] = useState(false) + + // get the .co or .net TLD from the restUrl + const restUrl = ui.selectedProject?.restUrl + const restUrlTld = new URL(restUrl as string).hostname.split('.').pop() + const functionUrl = `https://${ref}.functions.supabase.${restUrlTld}/${item.slug}` + + return ( + { + router.push(`/project/${ref}/functions/${item.id}/details`) + }} + > + +
    +

    {item.name}

    +
    +
    + +
    +

    {functionUrl}

    + +
    +
    + +

    {dayjs(item.created_at).format('DD MMM, YYYY HH:mm')}

    +
    + + + +
    +

    {dayjs(item.updated_at).fromNow()}

    +
    +
    + + +
    + + Last updated on {dayjs(item.updated_at).format('DD MMM, YYYY HH:mm')} + +
    +
    +
    +
    + +

    {item.version}

    +
    +
    + ) +} + +export default observer(EdgeFunctionsListItem) diff --git a/studio/components/interfaces/Functions/FunctionsListItem.tsx b/studio/components/interfaces/Functions/FunctionsListItem.tsx deleted file mode 100644 index f2b9045d417..00000000000 --- a/studio/components/interfaces/Functions/FunctionsListItem.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import dayjs from 'dayjs' -import { FC } from 'react' -import { useRouter } from 'next/router' -import { observer } from 'mobx-react-lite' - -import { useStore } from 'hooks' -import Table from 'components/to-be-cleaned/Table' - -interface Props { - function: any -} - -const FunctionsListItem: FC = ({ function: item }) => { - const { ui } = useStore() - const is_functionConfirmed = item.email_confirmed_at || item.phone_confirmed_at - - const router = useRouter() - const ref = ui?.selectedProject?.ref - - // get the .co or .net TLD from the restUrl - const restUrl = ui.selectedProject?.restUrl - const restUrlTld = new URL(restUrl as string).hostname.split('.').pop() - - return ( - { - router.push(`/project/${ref}/functions/${item.id}`) - }} - > - -
    - {item.name} -
    -
    - -
    - {`https://${ref}.functions.supabase.${restUrlTld}/${item.slug}`} - {`/${item.name}`} -
    -
    - - - {dayjs(item.created_at).format('DD MMM, YYYY HH:mm')} - - - - - {dayjs(item.updated_at).format('DD MMM, YYYY HH:mm')} - - - - v{item.version} - - - {item.status} - -
    - ) -} - -export default observer(FunctionsListItem) diff --git a/studio/components/interfaces/Functions/FunctionsNav.tsx b/studio/components/interfaces/Functions/FunctionsNav.tsx index dfe6bd5ab13..af24e28d29f 100644 --- a/studio/components/interfaces/Functions/FunctionsNav.tsx +++ b/studio/components/interfaces/Functions/FunctionsNav.tsx @@ -1,8 +1,7 @@ import { Tabs } from 'ui' import { useRouter } from 'next/router' -// @ts-ignore -const FunctionsNav = ({ item }) => { +const FunctionsNav = ({ item }: any) => { const router = useRouter() const activeRoute = router.pathname.split('/')[5] const { ref } = router.query @@ -17,9 +16,8 @@ const FunctionsNav = ({ item }) => { router.push(`/project/${ref}/functions/${item.id}/${e === 'metrics' ? '' : e}`) } > - - {/* */} + diff --git a/studio/components/interfaces/Functions/TerminalInstructions.tsx b/studio/components/interfaces/Functions/TerminalInstructions.tsx index f8f30b523ab..a5770812ef0 100644 --- a/studio/components/interfaces/Functions/TerminalInstructions.tsx +++ b/studio/components/interfaces/Functions/TerminalInstructions.tsx @@ -10,23 +10,20 @@ import { Commands } from './Functions.types' interface Props { closable?: boolean + removeBorder?: boolean } -const TerminalInstructions: FC = ({ closable = false }) => { +const TerminalInstructions: FC = ({ closable = false, removeBorder = false }) => { const router = useRouter() const { ref: projectRef } = useParams() - const [showInstructions, setShowInstructions] = useState(!closable) - const { data: tokens } = useAccessTokensQuery() + const { data: settings } = useProjectApiQuery({ projectRef }) - const { data: settings } = useProjectApiQuery({ - projectRef, - }) const apiService = settings?.autoApiService const anonKey = apiService?.service_api_keys.find((x) => x.name === 'anon key') ? apiService.defaultApiKey - : undefined + : '[YOUR ANON KEY]' const endpoint = settings?.autoApiService.app_config.endpoint ?? '' const endpointSections = endpoint.split('.') @@ -86,7 +83,9 @@ const TerminalInstructions: FC = ({ closable = false }) => { return (
    @@ -107,7 +106,7 @@ const TerminalInstructions: FC = ({ closable = false }) => {
    )}
    -
    +
    diff --git a/studio/components/interfaces/Functions/index.ts b/studio/components/interfaces/Functions/index.ts index b096ff5e604..003d5feaca6 100644 --- a/studio/components/interfaces/Functions/index.ts +++ b/studio/components/interfaces/Functions/index.ts @@ -1,5 +1,6 @@ -import FunctionsListItem from './FunctionsListItem' +import EdgeFunctionDetails from './EdgeFunctionDetails' +import EdgeFunctionsListItem from './EdgeFunctionsListItem' import FunctionsEmptyState from './FunctionsEmptyState' import TerminalInstructions from './TerminalInstructions' -export { FunctionsListItem, FunctionsEmptyState, TerminalInstructions } +export { EdgeFunctionDetails, EdgeFunctionsListItem, FunctionsEmptyState, TerminalInstructions } diff --git a/studio/components/interfaces/GraphQL/GraphiQL.tsx b/studio/components/interfaces/GraphQL/GraphiQL.tsx new file mode 100644 index 00000000000..9b8df44c33f --- /dev/null +++ b/studio/components/interfaces/GraphQL/GraphiQL.tsx @@ -0,0 +1,411 @@ +/* Based on https://github.com/graphql/graphiql/blob/main/packages/graphiql/src/components/GraphiQL.tsx */ + +import { + ChevronDownIcon, + ChevronUpIcon, + CopyIcon, + ExecuteButton, + GraphiQLProvider, + HeaderEditor, + MergeIcon, + PlusIcon, + PrettifyIcon, + QueryEditor, + ReloadIcon, + ResponseEditor, + Spinner, + Tab, + Tabs, + ToolbarButton, + Tooltip, + UnStyledButton, + useCopyQuery, + useDragResize, + useEditorContext, + useExecutionContext, + useMergeQuery, + usePluginContext, + usePrettifyEditors, + useSchemaContext, + useTheme, + VariableEditor, +} from '@graphiql/react' +import { Fetcher } from '@graphiql/toolkit' +import clsx from 'clsx' +import { useEffect, useState } from 'react' +import styles from './graphiql.module.css' + +export type GraphiQLProps = { + fetcher: Fetcher + theme?: 'dark' | 'light' + accessToken?: string +} + +const GraphiQL = ({ fetcher, theme = 'dark', accessToken }: GraphiQLProps) => { + // Ensure props are correct + if (typeof fetcher !== 'function') { + throw new TypeError( + 'The `GraphiQL` component requires a `fetcher` function to be passed as prop.' + ) + } + + return ( + + + + ) +} + +type GraphiQLInterfaceProps = { + theme: 'dark' | 'light' +} + +export const GraphiQLInterface = ({ theme }: GraphiQLInterfaceProps) => { + const editorContext = useEditorContext({ nonNull: true }) + const executionContext = useExecutionContext({ nonNull: true }) + const schemaContext = useSchemaContext({ nonNull: true }) + const pluginContext = usePluginContext() + + const copy = useCopyQuery() + const merge = useMergeQuery() + const prettify = usePrettifyEditors() + + const { setTheme } = useTheme() + useEffect(() => { + setTheme(theme) + }, [theme]) + + const PluginContent = pluginContext?.visiblePlugin?.content + + const pluginResize = useDragResize({ + defaultSizeRelation: 1 / 3, + direction: 'horizontal', + initiallyHidden: pluginContext?.visiblePlugin ? undefined : 'second', + onHiddenElementChange: (resizableElement) => { + if (resizableElement === 'first') { + pluginContext?.setVisiblePlugin(null) + } + }, + sizeThresholdSecond: 200, + storageKey: 'docExplorerFlex', + }) + const editorResize = useDragResize({ + direction: 'horizontal', + storageKey: 'editorFlex', + }) + const editorToolsResize = useDragResize({ + defaultSizeRelation: 3, + direction: 'vertical', + initiallyHidden: (() => { + return editorContext.initialVariables || editorContext.initialHeaders ? undefined : 'second' + })(), + sizeThresholdSecond: 60, + storageKey: 'secondaryEditorFlex', + }) + + const [activeSecondaryEditor, setActiveSecondaryEditor] = useState<'variables' | 'headers'>( + () => { + return !editorContext.initialVariables && editorContext.initialHeaders + ? 'headers' + : 'variables' + } + ) + + const toolbar = ( + <> + prettify()} label="Prettify query (Shift-Ctrl-P)"> + + merge()} label="Merge fragments into query (Shift-Ctrl-M)"> + + copy()} label="Copy query (Shift-Ctrl-C)"> + + + ) + + const onClickReference = () => { + if (pluginResize.hiddenElement === 'second') { + pluginResize.setHiddenElement(null) + } + } + + const hasSingleTab = editorContext.tabs.length === 1 + + return ( +
    +
    +
    +
    +
    + + {editorContext.tabs.length > 1 ? ( + <> + {editorContext.tabs.map((tab, index) => ( + + { + executionContext.stop() + editorContext.changeTab(index) + }} + > + {tab.title} + + { + if (editorContext.activeTabIndex === index) { + executionContext.stop() + } + editorContext.closeTab(index) + }} + /> + + ))} +
    + + editorContext.addTab()} + aria-label="Add tab" + > + + +
    + + ) : null} +
    +
    + {hasSingleTab ? ( +
    + + editorContext.addTab()} + aria-label="Add tab" + > + + +
    + ) : null} +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + + {toolbar} +
    +
    +
    +
    +
    +
    + { + if (editorToolsResize.hiddenElement === 'second') { + editorToolsResize.setHiddenElement(null) + } + setActiveSecondaryEditor('variables') + }} + > + Variables + + + { + if (editorToolsResize.hiddenElement === 'second') { + editorToolsResize.setHiddenElement(null) + } + setActiveSecondaryEditor('headers') + }} + > + Headers + +
    + + { + editorToolsResize.setHiddenElement( + editorToolsResize.hiddenElement === 'second' ? null : 'second' + ) + }} + aria-label={ + editorToolsResize.hiddenElement === 'second' + ? 'Show editor tools' + : 'Hide editor tools' + } + > + {editorToolsResize.hiddenElement === 'second' ? ( + + +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + {executionContext.isFetching ? : null} + +
    +
    +
    +
    +
    + +
    + {pluginContext?.visiblePlugin ? ( +
    + ) : null} +
    + +
    +
    {PluginContent ? : null}
    +
    +
    +
    +
    + {pluginContext?.plugins.map((plugin) => { + const isVisible = plugin === pluginContext.visiblePlugin + const label = `${isVisible ? 'Hide' : 'Show'} ${plugin.title}` + const Icon = plugin.icon + return ( + + { + if (isVisible) { + pluginContext.setVisiblePlugin(null) + pluginResize.setHiddenElement('second') + } else { + pluginContext.setVisiblePlugin(plugin) + pluginResize.setHiddenElement(null) + } + }} + aria-label={label} + > + + + ) + })} +
    +
    + + schemaContext.introspect()} + aria-label="Re-fetch GraphQL schema" + > + + +
    +
    +
    + ) +} + +export default GraphiQL diff --git a/studio/components/interfaces/GraphQL/graphiql.module.css b/studio/components/interfaces/GraphQL/graphiql.module.css new file mode 100644 index 00000000000..0dad329811d --- /dev/null +++ b/studio/components/interfaces/GraphQL/graphiql.module.css @@ -0,0 +1,63 @@ +.graphiqlContainer .graphiqlSessions { + margin: 0; + border-radius: 0; +} + +.graphiqlContainer .graphiqlSession { + padding: 0; +} + +.graphiqlContainer .graphiqlEditors { + border-radius: 0; +} + +.graphiqlContainer .graphiqlEditors:global(.full-height) { + margin-top: calc(0px - var(--session-header-height)); +} + +.graphiqlContainer .graphiqlQueryEditor { + @apply border-b border-scale-500; +} + +.graphiqlContainer .graphiqlSessionHeader { + background-color: hsl(var(--color-base)); + @apply border-b border-scale-500; +} + +.graphiqlContainer .graphiqlResponseSingleTab { + margin-top: calc(24px - var(--session-header-height)); +} + +.graphiqlContainer .graphiqlResponseMultiTab { + padding-top: 16px; +} + +.graphiqlContainer .graphiqlSidebar { + @apply border-l border-scale-500; +} + +.graphiqlHorizontalDragBar { + @apply border-l border-scale-500; +} + +.graphiqlAddTabWrapper { + padding: var(--px-12); + @apply z-10; +} + +/* Variables */ + +:global(body.graphiql-dark) .graphiqlContainer { + --color-base: 0, 0%, 11%; + --color-primary: 153, 50%, 50%; +} + +:global(body.graphiql-light) .graphiqlContainer { + --color-base: 210, 17%, 98%; + --color-primary: 153, 50%, 50%; +} + +.graphiqlContainer { + --font-family: theme(fontFamily.sans); + --font-family-mono: theme(fontFamily.mono); +} diff --git a/studio/components/interfaces/Home/Home.constants.ts b/studio/components/interfaces/Home/Home.constants.ts index 0bbcef3fcda..319aa46eb5d 100644 --- a/studio/components/interfaces/Home/Home.constants.ts +++ b/studio/components/interfaces/Home/Home.constants.ts @@ -75,7 +75,7 @@ export const EXAMPLE_PROJECTS = [ framework: 'NextJS', title: 'Next.js todo list app', description: 'NextJS todo list example', - url: 'https://github.com/supabase/examples/tree/main/supabase-js-v1/todo-list/nextjs-todo-list', + url: 'https://github.com/supabase/supabase/tree/master/examples/todo-list/nextjs-todo-list', }, { framework: 'React', diff --git a/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx b/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx index a920d38809e..9bd55624df0 100644 --- a/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx @@ -15,12 +15,11 @@ import { isInviteExpired } from '../Organization.utils' import { getRolesManagementPermissions } from './TeamSettings.utils' interface Props { - members: Member[] member: Member roles: Role[] } -const MemberActions: FC = ({ members, member, roles }) => { +const MemberActions: FC = ({ member, roles }) => { const { ui } = useStore() const { slug } = useParams() const { rolesRemovable } = getRolesManagementPermissions(roles) diff --git a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx index c54088028ed..925ddf81cc7 100644 --- a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx @@ -191,6 +191,7 @@ const MembersView: FC = ({ searchString, roles, members }) => { value={r.id} label={r.name} disabled={disableRoleEdit} + className="w-36" > {r.name} @@ -232,9 +233,7 @@ const MembersView: FC = ({ searchString, roles, members }) => { )} - {!memberIsUser && ( - - )} + {!memberIsUser && } diff --git a/studio/components/interfaces/Reports/ReportFilterBar.tsx b/studio/components/interfaces/Reports/ReportFilterBar.tsx new file mode 100644 index 00000000000..6b0dba60aca --- /dev/null +++ b/studio/components/interfaces/Reports/ReportFilterBar.tsx @@ -0,0 +1,296 @@ +import { FC, useState, ComponentProps } from 'react' +import { + Dropdown, + Popover, + Button, + IconPlus, + IconChevronDown, + Select, + Input, + IconX, + IconKey, + IconBox, + IconCode, + IconDatabase, + IconZapOff, +} from 'ui' +import DatePickers from '../Settings/Logs/Logs.DatePickers' +import { REPORTS_DATEPICKER_HELPERS } from './Reports.constants' +import { ReportFilterItem } from './Reports.types' + +interface Props { + filters: ReportFilterItem[] + onAddFilter: (filter: ReportFilterItem) => void + onRemoveFilters: (filters: ReportFilterItem[]) => void + onDatepickerChange: ComponentProps['onChange'] + datepickerTo?: string + datepickerFrom?: string + datepickerHelpers: typeof REPORTS_DATEPICKER_HELPERS +} + +const PRODUCT_FILTERS = [ + { + key: 'rest', + filterKey: 'request.path', + filterValue: '/rest', + label: 'REST', + description: 'Requests made to PostgREST', + icon: IconDatabase, + }, + { + key: 'auth', + filterKey: 'request.path', + filterValue: '/auth', + label: 'Auth', + description: 'Auth and authorization requests', + icon: IconKey, + }, + { + key: 'storage', + filterKey: 'request.path', + filterValue: '/storage', + label: 'Storage', + description: 'Storage asset requests', + icon: IconBox, + }, + { + key: 'realtime', + filterKey: 'request.path', + filterValue: '/realtime', + label: 'Realtime', + description: 'Realtime connection requests', + icon: IconZapOff, + }, + // TODO: support functions once union parsing is fixed + // { + // key: 'functions', + // filterKey: 'request.host', + // filterValue: '.functions.', + // label: 'Edge Functions', + // description: 'Edge function calls', + // icon: IconCode, + // }, + { + key: 'graphql', + filterKey: 'request.path', + filterValue: '/graphql', + label: 'GraphQL', + description: 'Requests made to pg_graphql', + icon: IconCode, + }, +] + +const ReportFilterBar: FC = ({ + filters, + onAddFilter, + onDatepickerChange, + datepickerTo = '', + datepickerFrom = '', + onRemoveFilters, + datepickerHelpers, +}) => { + const filterKeys = [ + 'request.path', + 'request.method', + 'request.search', + 'request.headers.x_client_info', + 'request.headers.user_agent', + 'response.status_code', + ] + const [showAdder, setShowAdder] = useState(false) + const [currentProductFilter, setCurrentProductFilter] = useState< + null | typeof PRODUCT_FILTERS[number] + >(null) + const [addFilterValues, setAddFilterValues] = useState({ + key: filterKeys[0], + compare: 'matches', + value: '', + }) + + const resetFilterValues = () => { + setAddFilterValues({ + key: filterKeys[0], + compare: 'matches', + value: '', + }) + } + + const handleProductFilterChange = async ( + nextProductFilter: null | typeof PRODUCT_FILTERS[number] + ) => { + const toRemove = PRODUCT_FILTERS.map( + (productFilter) => + ({ + key: productFilter.filterKey, + compare: 'matches', + value: productFilter.filterValue, + } as ReportFilterItem) + ) + onRemoveFilters(toRemove) + if (nextProductFilter) { + onAddFilter({ + key: nextProductFilter.filterKey, + compare: 'matches', + value: nextProductFilter.filterValue, + }) + } + setCurrentProductFilter(nextProductFilter) + } + + return ( +
    +
    + + + handleProductFilterChange(null)}> + All Requests + + + {PRODUCT_FILTERS.map((productFilter) => { + const Icon = productFilter.icon + return ( + handleProductFilterChange(productFilter)} + className="hover:bg-scale-600" + icon={} + > +

    + {productFilter.label} +

    +

    + {productFilter.description} +

    +
    + ) + })} + + } + > + +
    + {filters + .filter( + (filter) => + filter.value !== currentProductFilter?.filterValue || + filter.key !== currentProductFilter?.filterKey + ) + .map((filter) => ( +
    + {filter.key} {filter.compare} {filter.value} + +
    + ))} + +
    Add Filter
    + + +
    + } + open={showAdder} + onOpenChange={(openValue) => setShowAdder(openValue)} + overlay={ +
    + + + { + setAddFilterValues((prev) => ({ ...prev, value: e.target.value })) + }} + /> +
    + } + showClose + > + + +
    +
    + ) +} +export default ReportFilterBar diff --git a/studio/components/interfaces/Reports/ReportHeader.tsx b/studio/components/interfaces/Reports/ReportHeader.tsx new file mode 100644 index 00000000000..62b51c36035 --- /dev/null +++ b/studio/components/interfaces/Reports/ReportHeader.tsx @@ -0,0 +1,28 @@ +import { Button, IconRefreshCw } from 'ui' + +interface Props { + title: string + onRefresh: () => void + isLoading: boolean +} + +const ReportHeader: React.FC = ({ title, onRefresh, isLoading }) => ( +
    +

    {title}

    + +
    +) +export default ReportHeader diff --git a/studio/components/interfaces/Reports/ReportPadding.tsx b/studio/components/interfaces/Reports/ReportPadding.tsx new file mode 100644 index 00000000000..ece24f6f81e --- /dev/null +++ b/studio/components/interfaces/Reports/ReportPadding.tsx @@ -0,0 +1,9 @@ +/** + * Standardized padding and width layout for non-custom reports + */ +const ReportPadding: React.FC = ({ children }) => ( +
    + {children} +
    +) +export default ReportPadding diff --git a/studio/components/interfaces/Reports/ReportWidget.tsx b/studio/components/interfaces/Reports/ReportWidget.tsx index 2594c10c646..72e51182525 100644 --- a/studio/components/interfaces/Reports/ReportWidget.tsx +++ b/studio/components/interfaces/Reports/ReportWidget.tsx @@ -1,11 +1,11 @@ -import { Button, Dropdown, IconMoreVertical } from 'ui' -import LoadingOpacity from 'components/ui/LoadingOpacity' -import Panel from 'components/ui/Panel' -import { useRouter } from 'next/router' +import { NextRouter, useRouter } from 'next/router' +import * as Tooltip from '@radix-ui/react-tooltip' +import { Button, IconExternalLink, IconHelpCircle } from 'ui' + import { BaseReportParams } from './Reports.types' import { LogsEndpointParams } from '../Settings/Logs' -import * as Tooltip from '@radix-ui/react-tooltip' -import { IconHelpCircle } from '@supabase/ui' +import Panel from 'components/ui/Panel' +import LoadingOpacity from 'components/ui/LoadingOpacity' export interface ReportWidgetProps { data: T[] @@ -13,17 +13,25 @@ export interface ReportWidgetProps { description?: string tooltip?: string className?: string - renderer: (props: ReportWidgetProps) => React.ReactNode + renderer: (props: ReportWidgetRendererProps) => React.ReactNode params: BaseReportParams | LogsEndpointParams isLoading: boolean } +export interface ReportWidgetRendererProps extends ReportWidgetProps { + router: NextRouter + projectRef: string +} + const ReportWidget: React.FC = (props) => { const router = useRouter() const { ref } = router.query + const projectRef = ref as string + return ( = (props) => {
    - -

    {props.title}

    {' '} - {props?.tooltip && ( - - - - - - -
    - {props.tooltip} -
    -
    -
    - )} +

    {props.title}

    {' '} + {props?.tooltip && ( + + + + + + +
    + {props.tooltip} +
    +
    +
    + )}

    {props.description}

    - - { - router.push({ - pathname: `/project/${ref}/logs/explorer`, - query: { - q: props.params?.sql, - its: props.params.iso_timestamp_start, - ite: props.params.iso_timestamp_end, - }, - }) - }} - > - Open in Logs Explorer - - - } - side="bottom" - > - - + + +
    - {props.renderer(props)} + + {props.data === undefined ? null : props.renderer({ ...props, router, projectRef })} +
    ) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 3713345e5f5..025b39a3401 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -1,21 +1,25 @@ import dayjs from 'dayjs' import { DatetimeHelper } from '../Settings/Logs' -import { PresetConfig, Presets } from './Reports.types' +import { PresetConfig, Presets, ReportFilterItem } from './Reports.types' export const LAYOUT_COLUMN_COUNT = 24 export const REPORTS_DATEPICKER_HELPERS: DatetimeHelper[] = [ + { + text: 'Last 24 hours', + calcFrom: () => dayjs().subtract(1, 'day').startOf('day').toISOString(), + calcTo: () => '', + default: true, + }, { text: 'Last 7 days', calcFrom: () => dayjs().subtract(7, 'day').startOf('day').toISOString(), calcTo: () => '', - default: true, }, { text: 'Last 14 days', calcFrom: () => dayjs().subtract(7, 'day').startOf('day').toISOString(), calcTo: () => '', - default: true, }, { text: 'Last 30 days', @@ -29,216 +33,174 @@ export const DEFAULT_QUERY_PARAMS = { iso_timestamp_end: REPORTS_DATEPICKER_HELPERS[0].calcTo(), } +const generateRexepWhere = (filters: ReportFilterItem[], prepend = true) => { + if (filters.length === 0) return '' + const conditions = filters + .map((filter) => { + const splitKey = filter.key.split('.') + const normalizedKey = [splitKey[splitKey.length - 2], splitKey[splitKey.length - 1]].join('.') + if (filter.compare === 'matches') { + return `REGEXP_CONTAINS(${normalizedKey}, '${filter.value}')` + } else if (filter.compare === 'is') { + return `${normalizedKey} = ${filter.value}` + } + }) + .join(' AND ') + if (prepend) { + return 'WHERE ' + conditions + } else { + return conditions + } +} + export const PRESET_CONFIG: Record = { [Presets.API]: { title: 'API', queries: { - statusCodes: { + totalRequests: { queryType: 'logs', - sql: ` + sql: (filters) => ` select - timestamp_trunc(timestamp, hour) as timestamp, - r.status_code as status_code, - count(status_code) as count - FROM - edge_logs - CROSS JOIN UNNEST(metadata) AS m - CROSS JOIN UNNEST(m.response) AS r + cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp, + count(t.id) as count + 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 + ${generateRexepWhere(filters)} GROUP BY - timestamp, - status_code + timestamp ORDER BY timestamp ASC`, }, - errorRates: { + errorCounts: { queryType: 'logs', - sql: ` + sql: (filters) => ` select - timestamp_trunc(timestamp, hour) as timestamp, - count(timestamp) as count - FROM - edge_logs - LEFT JOIN UNNEST(metadata) AS f1 ON TRUE - LEFT JOIN UNNEST(f1.response) AS f3 ON TRUE + cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp, + count(t.id) as count + 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 WHERE - f3.status_code >= 400 + response.status_code >= 400 + ${generateRexepWhere(filters, false)} GROUP BY timestamp ORDER BY timestamp ASC `, }, - requestPaths: { + responseSpeed: { queryType: 'logs', - sql: ` + sql: (filters) => ` select - f2.path as path, - f2.search as query_params, - f2.method as method, - f3.status_code as status_code, - avg(f3.origin_time) as avg_origin_time, - sum(f3.origin_time) as sum, - APPROX_QUANTILES(f3.origin_time, 100) as quantiles, - count(timestamp) as count + cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp, + avg(response.origin_time) as avg, + APPROX_QUANTILES(response.origin_time, 100) as quantiles FROM - edge_logs - LEFT JOIN UNNEST(metadata) AS f1 ON TRUE - LEFT JOIN UNNEST(f1.request) AS f2 ON TRUE - LEFT JOIN UNNEST(f1.response) AS f3 ON TRUE + 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 + ${generateRexepWhere(filters)} GROUP BY - path, - query_params, - method, - status_code + timestamp ORDER BY - sum DESC, - count desc, - avg_origin_time DESC - LIMIT 50 + timestamp ASC `, }, - userAgents: { - queryType: 'logs', - sql: ` - select - h.user_agent as user_agent, - cf.asOrganization as request_source, - count(f.id) as count - from - edge_logs as f - cross join unnest(f.metadata) as m - cross join unnest(m.request) as r - cross join unnest(r.cf) as cf - cross join unnest(r.headers) as h - group by - user_agent, - request_source - order by - count desc - limit 12 - `, - }, - botScores: { - queryType: 'logs', - sql: ` - select - h.cf_connecting_ip as ip, - h.cf_ipcountry as country, - h.user_agent as user_agent, - r.path as path, - b.score as bot_score, - b.verifiedBot as bot_verified, - count(f.id) as count - from - edge_logs as f - cross join unnest(f.metadata) as m - cross join unnest(m.request) as r - cross join unnest(r.cf) as cf - cross join unnest(r.headers) as h - cross join unnest(cf.botManagement) as b - group by - ip, - country, - path, - user_agent, - bot_score, - bot_verified - order by - bot_score desc - limit 20 - `, - }, }, }, [Presets.AUTH]: { - title: 'Auth', + title: '', + queries: {}, + }, + [Presets.QUERY_PERFORMANCE]: { + title: 'Query performance', queries: { - bannedUsers: { + mostFrequentlyInvoked: { queryType: 'db', sql: (_params) => ` -select - count(distinct u.id) as count -from auth.users u -where u.banned_until is not null and u.banned_until > now() -limit 1 - `, +-- Most frequently called queries +-- A limit of 100 has been added below +select + auth.rolname, + statements.query, + statements.calls, + -- -- Postgres 13, 14, 15 + statements.total_exec_time + statements.total_plan_time as total_time, + statements.min_exec_time + statements.min_plan_time as min_time, + statements.max_exec_time + statements.max_plan_time as max_time, + statements.mean_exec_time + statements.mean_plan_time as mean_time, + -- -- Postgres <= 12 + -- total_time, + -- min_time, + -- max_time, + -- mean_time, + statements.rows / statements.calls as avg_rows + from pg_stat_statements as statements + inner join pg_authid as auth on statements.userid = auth.oid + order by + statements.calls desc + limit 10;`, }, - unverifiedUsers: { + mostTimeConsuming: { queryType: 'db', - sql: (params) => ` -select - date_trunc('day', u.created_at) as timestamp, - count(distinct u.id) as count -from auth.users u -where u.confirmed_at is null and '${params.iso_timestamp_start}' < u.created_at -group by timestamp -order by timestamp desc - `, + sql: (_params) => `-- A limit of 100 has been added below +select + auth.rolname, + statements.query, + statements.calls, + statements.total_exec_time + statements.total_plan_time as total_time, + to_char(((statements.total_exec_time + statements.total_plan_time)/sum(statements.total_exec_time + statements.total_plan_time) OVER()) * 100, 'FM90D0') || '%' AS prop_total_time + from pg_stat_statements as statements + inner join pg_authid as auth on statements.userid = auth.oid + order by + total_time desc + limit 10;`, }, - signUpProviders: { + slowestExecutionTime: { queryType: 'db', - sql: (params) => ` -select - sum(count(u.id)) over (order by date_trunc('day', u.created_at)) as count, - date_trunc('day', u.created_at) as timestamp, - i.provider as provider -from auth.users u -join auth.identities as i on u.id = i.user_id -where date_trunc('day', u.created_at) > '${params.iso_timestamp_start}' -group by timestamp, provider -order by timestamp desc - `, + sql: (_params) => `-- Slowest queries by max execution time +-- A limit of 100 has been added below +select + auth.rolname, + statements.query, + statements.calls, + -- -- Postgres 13, 14, 15 + statements.total_exec_time + statements.total_plan_time as total_time, + statements.min_exec_time + statements.min_plan_time as min_time, + statements.max_exec_time + statements.max_plan_time as max_time, + statements.mean_exec_time + statements.mean_plan_time as mean_time, + -- -- Postgres <= 12 + -- total_time, + -- min_time, + -- max_time, + -- mean_time, + statements.rows / statements.calls as avg_rows + from pg_stat_statements as statements + inner join pg_authid as auth on statements.userid = auth.oid + order by + max_time desc + limit 10`, }, - failedMigrations: { - queryType: 'logs', - sql: ` -select count(f.id) -from auth_logs -where json_value(f.event_message, "$.level") = "fatal" - and regexp_contains(f.event_message, "error executing migrations") -limit 1 -`, - }, - dailyActiveUsers: { - queryType: 'logs', - sql: ` -select - timestamp_trunc(timestamp, day) as timestamp, - count(distinct json_value(f.event_message, "$.user_id")) as count -from auth_logs f -where json_value(f.event_message, "$.action") = "login" -group by - timestamp -order by - timestamp desc -`, - }, - cumulativeUsers: { + queryHitRate: { queryType: 'db', - sql: (params) => ` - -select - date_trunc('day', u.created_at) as timestamp, - sum(count(u.id)) over (order by date_trunc('day', u.created_at)) as count -from auth.users as u -where u.confirmed_at is not null and date_trunc('day', u.created_at) > '${params.iso_timestamp_start}' -group by timestamp -order by timestamp desc - `, - }, - newUsers: { - queryType: 'db', - sql: (params) => ` -select -date_trunc('day', u.created_at) as timestamp, -count(u.id) as count -from auth.users as u -where u.confirmed_at is not null and date_trunc('day', u.created_at) > '${params.iso_timestamp_start}' -group by - timestamp -order by - timestamp desc -`, + sql: (_params) => `-- Cache and index hit rate +select + 'index hit rate' as name, + (sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read),0) as ratio + from pg_statio_user_indexes + union all + select + 'table hit rate' as name, + sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read),0) as ratio + from pg_statio_user_tables;`, }, }, }, diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 0cc81dd470e..201f62f712b 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -4,6 +4,7 @@ import { DEFAULT_QUERY_PARAMS } from './Reports.constants' export enum Presets { API = 'api', AUTH = 'auth', + QUERY_PERFORMANCE = 'query_performance', } export interface QueryDataBase { @@ -25,18 +26,15 @@ export type MetaQueryResponse = any & { error: ResponseError } export type BaseReportParams = typeof DEFAULT_QUERY_PARAMS & { sql?: string } & unknown export interface PresetConfig { title: string - queries: Record + queries: BaseQueries +} +export type BaseQueries = Record + +export interface ReportQuery { + queryType: 'db' | 'logs' + sql: (filters: ReportFilterItem[]) => string } -export interface DbQuery { - queryType: 'db' - sql: string | ((params: BaseReportParams) => string) -} - -export interface LogsQuery { - queryType: 'logs' - sql: string -} export interface StatusCodesDatum { timestamp: number status_code: number @@ -52,3 +50,9 @@ export interface PathsDatum { avg_origin_time: number quantiles: number[] } + +export interface ReportFilterItem { + key: string + value: string | number + compare: 'matches' | 'is' +} diff --git a/studio/components/interfaces/Reports/Reports.utils.tsx b/studio/components/interfaces/Reports/Reports.utils.tsx index 006ee0e17d5..52fec0b482a 100644 --- a/studio/components/interfaces/Reports/Reports.utils.tsx +++ b/studio/components/interfaces/Reports/Reports.utils.tsx @@ -1,12 +1,13 @@ -import { IconRefreshCw } from '@supabase/ui' +import { useStore } from 'hooks' import useDbQuery from 'hooks/analytics/useDbQuery' import useLogsQuery, { LogsQueryData, LogsQueryHandlers } from 'hooks/analytics/useLogsQuery' -import React from 'react' -import { Button } from 'ui' -import { DatePickerToFrom } from '../Settings/Logs' -import DatePickers from '../Settings/Logs/Logs.DatePickers' -import { DEFAULT_QUERY_PARAMS, REPORTS_DATEPICKER_HELPERS } from './Reports.constants' -import { DbQueryData, DbQueryHandler, PresetConfig } from './Reports.types' +import { + BaseQueries, + DbQueryData, + DbQueryHandler, + PresetConfig, + ReportQuery, +} from './Reports.types' /** * Converts a query params string to an object @@ -18,8 +19,12 @@ export const queryParamsToObject = (params: string) => { // generate hooks based on preset config type PresetHookResult = [LogsQueryData | DbQueryData, LogsQueryHandlers | DbQueryHandler] type PresetHooks = Record PresetHookResult> -export const hooksFactory = (projectRef: string, config: PresetConfig) => { - const hooks: PresetHooks = Object.entries(config.queries).reduce( + +export const queriesFactory = (queries: BaseQueries): PresetHooks => { + const { ui } = useStore() + const projectRef = ui.selectedProject?.ref ?? 'default' + + const hooks: PresetHooks = Object.entries(queries).reduce( (acc, [k, { sql, queryType }]) => { if (queryType === 'db') { return { @@ -29,11 +34,7 @@ export const hooksFactory = (projectRef: string, config: PresetConfig) => { } else { return { ...acc, - [k]: () => - useLogsQuery(projectRef, { - sql: sql as string, - ...DEFAULT_QUERY_PARAMS, - }), + [k]: () => useLogsQuery(projectRef), } } }, @@ -41,62 +42,3 @@ export const hooksFactory = (projectRef: string, config: PresetConfig) => { ) return hooks } - -export const usePresetReport = (hooks: PresetHookResult[]) => { - const data = hooks.map((h) => h[0]) - const handlers = hooks.map((h) => h[1]) - const handleRefresh = () => { - handlers.forEach((handler) => { - handler.runQuery() - }) - } - const handleDatepickerChange = ({ to, from }: DatePickerToFrom) => { - const newParams = { iso_timestamp_start: from || '', iso_timestamp_end: to || '' } - - handlers.forEach((handler) => { - if (handler.setParams) { - handler.setParams((prev) => ({ - ...prev, - ...newParams, - })) - } - }) - } - const isLoading = data.map((datum) => datum.isLoading).some((v) => v) - - const Layout: React.FC<{ title: string }> = ({ title, children }) => ( -
    -

    {title}

    -
    - - -
    - - {children} -
    - ) - return { - isLoading, - Layout, - handleRefresh, - handleDatepickerChange, - } -} diff --git a/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx b/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx index e6e8156d296..70a689b9eaa 100644 --- a/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx @@ -1,266 +1,76 @@ -import { Button, IconChevronRight } from 'ui' -import { jsonSyntaxHighlight } from 'components/interfaces/Settings/Logs/LogsFormatters' -import { BarChart } from 'components/to-be-cleaned/Charts/ChartRenderer' -import Table from 'components/to-be-cleaned/Table' -import { USAGE_COLORS } from 'components/ui/Charts/Charts.constants' -import StackedAreaChart from 'components/ui/Charts/StackedAreaChart' -import { useState } from 'react' -import { DATETIME_FORMAT } from '../Reports.constants' -import { PathsDatum, StatusCodesDatum } from '../Reports.types' -import { queryParamsToObject } from '../Reports.utils' import { ReportWidgetProps } from '../ReportWidget' +import BarChart from 'components/ui/Charts/BarChart' -export const renderUserAgents = ( +export const renderTotalRequests = ( props: ReportWidgetProps<{ - user_agent: string - request_source: string + timestamp: string count: number }> ) => { + const total = props.data.reduce((acc, datum) => { + return acc + datum.count + }, 0) return ( - - User Agents - Request Source - Count - - } - body={ - <> - {props.data.map((row, index) => { - return ( - - - {row.user_agent ? ( - row.user_agent - ) : ( - No user agent - )} - - {row.request_source} - - {row.count} - - - ) - })} - - } + ) } -export const renderBotScores = ( +export const renderErrorCounts = ( props: ReportWidgetProps<{ - ip: string - country: string - user_agent: string - path: string - bot_score: number - bot_verified: boolean + timestamp: string count: number }> ) => { + const total = props.data.reduce((acc, datum) => { + return acc + datum.count + }, 0) return ( -
    - IP (Country) - Request Path - User Agent - Bot Score - Is Verified Bot - Count - - } - body={ - <> - {props.data.map((row, index) => { - return ( - - - {row.ip} ({row.country}) - - {row.path} - {row.user_agent} - {row.bot_score} - {row.bot_verified ? 'Yes' : 'No'} - - {row.count} - - - ) - })} - - } + ) } -export const renderStatusCodesChart = (props: ReportWidgetProps) => ( - -) -export const renderErrorRateChart = (props: ReportWidgetProps) => { - return ( -
    - -
    - ) -} - -export const renderRequestsPathsTable = (props: ReportWidgetProps) => { - const transformedData = props.data.map((data: PathsDatum) => ({ - ...data, - p99_time: data.quantiles[98], +export const renderResponseSpeed = ( + props: ReportWidgetProps<{ + timestamp: string + avg: number + quantiles: number[] + }> +) => { + const transformedData = props.data.map((datum) => ({ + timestamp: datum.timestamp, + avg: datum.avg, + median: datum.quantiles[49], })) - const requestPathsSums = transformedData.map((v) => v.sum) as number[] - const requestPathsSumMax = Math.max(...requestPathsSums) - const requestPathsSumMin = Math.min(...requestPathsSums) - const requestPathsAvgs = transformedData.map((v) => v.avg_origin_time) as number[] - const requestPathsAvgMax = Math.max(...requestPathsAvgs) - const requestPathsAvgMin = Math.min(...requestPathsAvgs) - const requestPathsP99s = transformedData.map((v) => v.p99_time) as number[] - const requestPathsP99Max = Math.max(...requestPathsP99s) - const requestPathsP99Min = Math.min(...requestPathsP99s) - - const renderNumericCellWithColorScale = (value: number, percentage: number) => ( - = 80 - ? 'bg-orange-600' - : percentage > 60 - ? 'bg-orange-500' - : percentage > 40 - ? 'bg-yellow-500' - : percentage > 20 - ? 'bg-yellow-400' - : percentage > 10 - ? 'bg-yellow-200' - : 'bg-green-100' - } align-top text-xs`} - > - {Number(value).toFixed(1)} - - ) + const lastAvg = props.data[props.data.length - 1]?.avg return ( -
    - Path - Count - Avg. Time (ms) - p99 Time (ms) - Total Query Time - - } - body={ - <> - {transformedData.map((row, index) => { - const [show, setShow] = useState(false) - - const totalQueryTimePercentage = - ((row.sum - requestPathsSumMin) / requestPathsSumMax) * 100 - const avgTimePercentage = - ((row.avg_origin_time - requestPathsAvgMin) / requestPathsAvgMax) * 100 - const p99TimePercentage = - ((row.p99_time - requestPathsP99Min) / requestPathsP99Max) * 100 - return ( - <> - - - - - - {row.count} - - {renderNumericCellWithColorScale(row.avg_origin_time, avgTimePercentage)} - {renderNumericCellWithColorScale(row.p99_time, p99TimePercentage)} - -
    - - - - -
    -                      
    -
    -
    -
    - - ) - })} - - } + ) } diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx deleted file mode 100644 index f61c26a5674..00000000000 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { BarChart } from 'components/to-be-cleaned/Charts/ChartRenderer' -import StackedBarChart from 'components/ui/Charts/StackedBarChart' -import { DATETIME_FORMAT } from '../Reports.constants' -import { ReportWidgetProps } from '../ReportWidget' -import Statistic from '../Statistic' - -export const renderBannedUsers = ( - props: ReportWidgetProps<{ - count: number - }> -) => { - const count = props.data.length === 0 ? 0 : props?.data?.[0]?.count - return -} -export const renderUnverifiedUsers = ( - props: ReportWidgetProps<{ - timestamp: string - count: number - }> -) => { - const count = props.data.length === 0 ? 0 : props?.data?.[0]?.count - - return ( - = 3 ? props.data : null} - sparklineXAxis="timestamp" - sparklineYAxis="count" - /> - ) -} -export const renderSignUpProviders = ( - props: ReportWidgetProps<{ - timestamp: string - provider: string - count: number - }> -) => { - return ( - - ) -} -export const renderFailedMigrations = ( - props: ReportWidgetProps<{ - timestamp: string - count: number - }> -) => { - const count = props.data.length === 0 ? 0 : props?.data?.[0]?.count - return -} -export const renderCumulativeUsers = ( - props: ReportWidgetProps<{ - count: number - timestamp: string - }> -) => { - const total = props.data?.[0]?.count - return ( -
    - -
    - ) -} - -export const renderNewUsers = ( - props: ReportWidgetProps<{ - count: number - timestamp: string - }> -) => { - const sum = (props.data || []).reduce((acc, datum) => datum.count + acc, 0) - return ( -
    - -
    - ) -} - -export const renderDailyActiveUsers = ( - props: ReportWidgetProps<{ - count: number - timestamp: string - }> -) => { - const last = props.data?.[props.data.length - 1]?.count - - return ( -
    - -
    - ) -} diff --git a/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts b/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts index 2483efe39da..f1b8e5c4c85 100644 --- a/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts +++ b/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts @@ -969,4 +969,106 @@ GRANT ALL ON TABLE next_auth.verification_tokens TO postgres; GRANT ALL ON TABLE next_auth.verification_tokens TO service_role; `.trim(), }, + { + id: 16, + type: 'template', + title: 'Most frequently invoked', + description: 'Most frequently called queries in your database.', + sql: `-- Most frequently called queries + +-- A limit of 100 has been added below + +select + auth.rolname, + statements.query, + statements.calls, + -- -- Postgres 13, 14, 15 + statements.total_exec_time + statements.total_plan_time as total_time, + statements.min_exec_time + statements.min_plan_time as min_time, + statements.max_exec_time + statements.max_plan_time as max_time, + statements.mean_exec_time + statements.mean_plan_time as mean_time, + -- -- Postgres <= 12 + -- total_time, + -- min_time, + -- max_time, + -- mean_time, + statements.rows / statements.calls as avg_rows + + from pg_stat_statements as statements + inner join pg_authid as auth on statements.userid = auth.oid + order by + statements.calls desc + limit + 100;`, + }, + { + id: 17, + type: 'template', + title: 'Most time consuming', + description: 'Aggregate time spent on a query type.', + sql: `-- Most time consuming queries + +-- A limit of 100 has been added below + +select + auth.rolname, + statements.query, + statements.calls, + statements.total_exec_time + statements.total_plan_time as total_time, + to_char(((statements.total_exec_time + statements.total_plan_time)/sum(statements.total_exec_time + statements.total_plan_time) over()) * 100, 'FM90D0') || '%' as prop_total_time + from pg_stat_statements as statements + inner join pg_authid as auth on statements.userid = auth.oid + order by + total_time desc + limit + 100;`, + }, + { + id: 18, + type: 'template', + title: 'Slowest execution time', + description: 'Slowest queries based on max execution time.', + sql: `-- Slowest queries by max execution time + +-- A limit of 100 has been added below + +select + auth.rolname, + statements.query, + statements.calls, + -- -- Postgres 13, 14, 15 + statements.total_exec_time + statements.total_plan_time as total_time, + statements.min_exec_time + statements.min_plan_time as min_time, + statements.max_exec_time + statements.max_plan_time as max_time, + statements.mean_exec_time + statements.mean_plan_time as mean_time, + -- -- Postgres <= 12 + -- total_time, + -- min_time, + -- max_time, + -- mean_time, + statements.rows / statements.calls as avg_rows + from pg_stat_statements as statements + inner join pg_authid as auth on statements.userid = auth.oid + order by + max_time desc + limit + 100;`, + }, + { + id: 19, + type: 'template', + title: 'Hit rate', + description: 'See your cache and index hit rate.', + sql: `-- Cache and index hit rate + +select + 'index hit rate' as name, + (sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read),0) as ratio + from pg_statio_user_indexes + union all + select + 'table hit rate' as name, + sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read),0) as ratio + from pg_statio_user_tables;`, + } ] diff --git a/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx b/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx index 111a003258a..43ce15af121 100644 --- a/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx +++ b/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx @@ -1,6 +1,6 @@ import { FC, useState, useEffect, Fragment } from 'react' import { observer } from 'mobx-react-lite' -import { useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' import { pluckObjectFields } from 'lib/helpers' import Loading from 'components/ui/Loading' import Panel from 'components/ui/Panel' @@ -12,6 +12,7 @@ import ToggleField from 'components/to-be-cleaned/forms/ToggleField' import SchemaFormPanel from 'components/to-be-cleaned/forms/SchemaFormPanel' import { Input } from 'ui' import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { PermissionAction } from '@supabase/shared-types/out/constants' interface Props {} @@ -127,6 +128,11 @@ export const PgbouncerConfig: FC = observer( ({ projectRef, bouncerInfo, connectionInfo }) => { const { ui } = useStore() + const canUpdateConnectionPoolingConfiguration = checkPermissions( + PermissionAction.UPDATE, + 'projects' + ) + const [updates, setUpdates] = useState({ pgbouncer_enabled: bouncerInfo.pgbouncer_enabled, pool_mode: bouncerInfo.pool_mode || 'transaction', @@ -198,6 +204,8 @@ export const PgbouncerConfig: FC = observer( onChangeModel={(model: any) => setUpdates(model)} onSubmit={(model: any) => updateConfig(model)} onReset={() => setUpdates(bouncerInfo)} + disabled={!canUpdateConnectionPoolingConfiguration} + disabledMessage="You need additional permissions to update connection pooling settings" >
    diff --git a/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx b/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx index 7fa753067e7..b3c47c59119 100644 --- a/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx +++ b/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx @@ -1,8 +1,10 @@ import Link from 'next/link' -import { useState } from 'react' +import { FC, useState } from 'react' +import * as Tooltip from '@radix-ui/react-tooltip' import { Button, IconAlertCircle, IconExternalLink, IconGlobe, IconLock } from 'ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useParams } from 'hooks' +import { checkPermissions, useParams } from 'hooks' import Panel from 'components/ui/Panel' import { FormPanel, FormHeader } from 'components/ui/Forms' import ShimmeringLoader from 'components/ui/ShimmeringLoader' @@ -12,6 +14,62 @@ import DisallowAllModal from './DisallowAllModal' import AllowAllModal from './AllowAllModal' import { useNetworkRestrictionsQuery } from 'data/network-restrictions/network-restrictions-query' +const AllowAllAccessButton: FC<{ disabled: boolean; onClick: (value: boolean) => void }> = ({ + disabled, + onClick, +}) => ( + + + + + {disabled && ( + + +
    + + You need additional permissions to update network restrictions + +
    +
    + )} +
    +) + +const DisallowAllAccessButton: FC<{ disabled: boolean; onClick: (value: boolean) => void }> = ({ + disabled, + onClick, +}) => ( + + + + + {disabled && ( + + +
    + + You need additional permissions to update network restrictions + +
    +
    + )} +
    +) + const NetworkRestrictions = ({}) => { const { ref } = useParams() @@ -21,6 +79,8 @@ const NetworkRestrictions = ({}) => { const [selectedRestrictionToRemove, setSelectedRestrictionToRemove] = useState() const { data, isLoading } = useNetworkRestrictionsQuery({ projectRef: ref }) + const canUpdateNetworkRestrictions = checkPermissions(PermissionAction.UPDATE, 'projects') + const hasAccessToRestrictions = data?.entitlement === 'allowed' const restrictedIps = data?.config.dbAllowedCidrs ?? [] const restrictionStatus = data?.status ?? '' @@ -48,7 +108,31 @@ const NetworkRestrictions = ({}) => { - + + + + + {!canUpdateNetworkRestrictions && ( + + +
    + + You need additional permissions to update network restrictions + +
    +
    + )} +
    {isLoading ? ( @@ -75,12 +159,14 @@ const NetworkRestrictions = ({}) => {

    - - + +
    @@ -101,9 +187,10 @@ const NetworkRestrictions = ({}) => {
    - +
    ) : isDisallowedAll ? ( @@ -125,9 +212,10 @@ const NetworkRestrictions = ({}) => {
    - +
    ) : ( @@ -146,12 +234,14 @@ const NetworkRestrictions = ({}) => {

    - - + +
    {restrictedIps.map((ip) => { diff --git a/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx b/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx index 192c6137897..9f1f8596905 100644 --- a/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx +++ b/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx @@ -1,8 +1,10 @@ import Link from 'next/link' import { useState, useEffect } from 'react' -import { useParams, useStore, useFlag } from 'hooks' import * as Tooltip from '@radix-ui/react-tooltip' import { Button, IconDownload, Toggle, IconLoader, Alert } from 'ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { checkPermissions, useParams, useStore, useFlag } from 'hooks' import { FormHeader, FormPanel, @@ -27,6 +29,8 @@ const SSLConfiguration = () => { }) const { mutateAsync: updateSSLEnforcement } = useSSLEnforcementUpdateMutation() + const canUpdateSSLEnforcement = checkPermissions(PermissionAction.UPDATE, 'projects') + const hasAccessToSSLEnforcement = !sslEnforcementConfiguration?.isNotAllowed const env = process.env.NEXT_PUBLIC_ENVIRONMENT === 'prod' ? 'prod' : 'staging' const hasSSLCertificate = @@ -118,7 +122,7 @@ const SSLConfiguration = () => { )} diff --git a/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx b/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx index 9a4e472c322..331b7893170 100644 --- a/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx +++ b/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx @@ -48,8 +48,14 @@ const DatePickers: React.FC = ({ to, from, onChange, helpers }) => { <> {helpers.map((helper) => ( - - {helper.text} + + + {helper.text} + ))} diff --git a/studio/components/interfaces/Settings/Logs/Logs.constants.ts b/studio/components/interfaces/Settings/Logs/Logs.constants.ts index 1ad8909622c..e0e5c686ec2 100644 --- a/studio/components/interfaces/Settings/Logs/Logs.constants.ts +++ b/studio/components/interfaces/Settings/Logs/Logs.constants.ts @@ -1,7 +1,8 @@ import dayjs from 'dayjs' import { DatetimeHelper, FilterTableSet, LogTemplate } from '.' -export const LOGS_EXPLORER_DOCS_URL = "https://supabase.com/docs/guides/platform/logs#querying-with-the-logs-explorer" +export const LOGS_EXPLORER_DOCS_URL = + 'https://supabase.com/docs/guides/platform/logs#querying-with-the-logs-explorer' export const LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD = 4 @@ -200,6 +201,28 @@ limit 100 `, for: ['database'], }, + { + label: 'Storage Object Requests', + description: 'Number of requests done on Storage Objects', + mode: 'custom', + searchString: `select + r.method as http_verb, + r.path as filepath, + count(*) as num_requests + from edge_logs + cross join unnest(metadata) as m + cross join unnest(m.request) AS r + cross join unnest(r.headers) AS h + where + path like '%rest/v1/object%' + group by + r.path, r.method + order by + num_requests desc + limit 100 +`, + for: ['api'], + }, ] export const LOG_TYPE_LABEL_MAPPING: { [k: string]: string } = { @@ -254,19 +277,19 @@ export const SQL_FILTER_TEMPLATES: any = { }, auth_logs: { ..._SQL_FILTER_COMMON, - "severity.error": `REGEXP_CONTAINS(event_message, "level.{3}error|level.{3}fatal")`, - "severity.warning": `REGEXP_CONTAINS(event_message, "level.{3}warning")`, - "severity.info": `REGEXP_CONTAINS(event_message, "level.{3}info")`, - "status_code.server_error": `REGEXP_CONTAINS(event_message, "status.{3}5[0-9]{2}")`, - "status_code.client_error": `REGEXP_CONTAINS(event_message, "status.{3}4[0-9]{2}")`, - "status_code.redirection": `REGEXP_CONTAINS(event_message, "status.{3}3[0-9]{2}")`, - "status_code.success": `REGEXP_CONTAINS(event_message, "status.{3}2[0-9]{2}")`, - "endpoints.admin": `REGEXP_CONTAINS(event_message, "path.{3}/admin")`, - "endpoints.signup": `REGEXP_CONTAINS(event_message, "path.{3}/signup|path.{3}/invite|path.{3}/verify")`, - "endpoints.authentication": `REGEXP_CONTAINS(event_message, "path.{3}/token|path.{3}/authorize|path.{3}/callback|path.{3}/otp|path.{3}/magiclink")`, - "endpoints.recover": `REGEXP_CONTAINS(event_message, "path.{3}/recover")`, - "endpoints.user": `REGEXP_CONTAINS(event_message, "path.{3}/user")`, - "endpoints.logout": `REGEXP_CONTAINS(event_message, "path.{3}/logout")`, + 'severity.error': `REGEXP_CONTAINS(event_message, "level.{3}error|level.{3}fatal")`, + 'severity.warning': `REGEXP_CONTAINS(event_message, "level.{3}warning")`, + 'severity.info': `REGEXP_CONTAINS(event_message, "level.{3}info")`, + 'status_code.server_error': `REGEXP_CONTAINS(event_message, "status.{3}5[0-9]{2}")`, + 'status_code.client_error': `REGEXP_CONTAINS(event_message, "status.{3}4[0-9]{2}")`, + 'status_code.redirection': `REGEXP_CONTAINS(event_message, "status.{3}3[0-9]{2}")`, + 'status_code.success': `REGEXP_CONTAINS(event_message, "status.{3}2[0-9]{2}")`, + 'endpoints.admin': `REGEXP_CONTAINS(event_message, "path.{3}/admin")`, + 'endpoints.signup': `REGEXP_CONTAINS(event_message, "path.{3}/signup|path.{3}/invite|path.{3}/verify")`, + 'endpoints.authentication': `REGEXP_CONTAINS(event_message, "path.{3}/token|path.{3}/authorize|path.{3}/callback|path.{3}/otp|path.{3}/magiclink")`, + 'endpoints.recover': `REGEXP_CONTAINS(event_message, "path.{3}/recover")`, + 'endpoints.user': `REGEXP_CONTAINS(event_message, "path.{3}/user")`, + 'endpoints.logout': `REGEXP_CONTAINS(event_message, "path.{3}/logout")`, }, realtime_logs: { ..._SQL_FILTER_COMMON, @@ -279,7 +302,7 @@ export const SQL_FILTER_TEMPLATES: any = { }, pgbouncer_logs: { ..._SQL_FILTER_COMMON, - } + }, } export enum LogsTableName { @@ -290,8 +313,8 @@ export enum LogsTableName { AUTH = 'auth_logs', REALTIME = 'realtime_logs', STORAGE = 'storage_logs', - PGBOUNCER = "pgbouncer_logs", - POSTGREST = "postgrest_logs" + PGBOUNCER = 'pgbouncer_logs', + POSTGREST = 'postgrest_logs', } export const LOGS_TABLES = { @@ -303,7 +326,7 @@ export const LOGS_TABLES = { realtime: LogsTableName.REALTIME, storage: LogsTableName.STORAGE, postgrest: LogsTableName.POSTGREST, - pgbouncer: LogsTableName.PGBOUNCER + pgbouncer: LogsTableName.PGBOUNCER, } export const LOGS_SOURCE_DESCRIPTION = { @@ -565,7 +588,7 @@ export const FILTER_OPTIONS: FilterTableSet = { }, { key: 'recover', - label: "Password Recovery", + label: 'Password Recovery', description: 'Show all password recovery requests', }, { @@ -585,7 +608,7 @@ export const FILTER_OPTIONS: FilterTableSet = { }, ], }, - } + }, } export const LOGS_TAILWIND_CLASSES = { diff --git a/studio/components/interfaces/Settings/Logs/Logs.types.ts b/studio/components/interfaces/Settings/Logs/Logs.types.ts index cc10f35be77..b31bb16bf08 100644 --- a/studio/components/interfaces/Settings/Logs/Logs.types.ts +++ b/studio/components/interfaces/Settings/Logs/Logs.types.ts @@ -84,8 +84,8 @@ export type QueryType = | 'auth' | 'realtime' | 'storage' - | "pgbouncer" - | "postgrest" + | 'pgbouncer' + | 'postgrest' export type Mode = 'simple' | 'custom' @@ -134,4 +134,5 @@ export interface DatetimeHelper { calcTo: () => string calcFrom: () => string default?: boolean + disabled?: boolean } diff --git a/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx b/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx index d2b6cfb387c..d29400e1a1b 100644 --- a/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx +++ b/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx @@ -6,7 +6,6 @@ import { EXPLORER_DATEPICKER_HELPERS, LogsTableName, LogsWarning, - LOGS_EXPLORER_DOCS_URL, LOGS_SOURCE_DESCRIPTION, LogTemplate, } from '.' @@ -14,7 +13,6 @@ import DatePickers from './Logs.DatePickers' import Link from 'next/link' import React from 'react' import { checkPermissions, useStore } from 'hooks' -import { IconBookOpen } from '@supabase/ui' interface Props { templates?: LogTemplate[] @@ -132,11 +130,6 @@ const LogsQueryPanel: React.FC = ({
    - - - diff --git a/studio/components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars.tsx b/studio/components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars.tsx index a7d3134706e..c24fbe5a685 100644 --- a/studio/components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars.tsx +++ b/studio/components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars.tsx @@ -1,16 +1,29 @@ +import Link from 'next/link' import { FC, useEffect } from 'react' -import { Badge, Button, IconAlertCircle, IconInfo, Loading, IconExternalLink } from 'ui' +import { useRouter } from 'next/router' +import * as Tooltip from '@radix-ui/react-tooltip' +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { + Badge, + Button, + IconAlertCircle, + IconInfo, + Loading, + IconExternalLink, + Alert, + IconBookOpen, +} from 'ui' -import { useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' import { formatBytes } from 'lib/helpers' import { PRICING_TIER_PRODUCT_IDS, USAGE_APPROACHING_THRESHOLD } from 'lib/constants' import SparkBar from 'components/ui/SparkBar' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import InformationBox from 'components/ui/InformationBox' import { USAGE_BASED_PRODUCTS } from 'components/interfaces/Billing/Billing.constants' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { useProjectReadOnlyQuery } from 'data/config/project-read-only-query' import { ProjectUsageResponseUsageKeys, useProjectUsageQuery } from 'data/usage/project-usage-query' -import { useRouter } from 'next/router' -import * as Tooltip from '@radix-ui/react-tooltip' interface Props { projectRef?: string @@ -21,8 +34,18 @@ const ProjectUsage: FC = ({ projectRef }) => { const { data: usage, error, isLoading } = useProjectUsageQuery({ projectRef }) const router = useRouter() - const subscriptionTier = ui.selectedProject?.subscription_tier + const { project } = useProjectContext() + const { data: isReadOnlyMode } = useProjectReadOnlyQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + const canUpdateSubscription = checkPermissions( + PermissionAction.BILLING_WRITE, + 'stripe.subscriptions' + ) + + const subscriptionTier = ui.selectedProject?.subscription_tier const projectHasNoLimits = subscriptionTier === PRICING_TIER_PRODUCT_IDS.PAYG || subscriptionTier === PRICING_TIER_PRODUCT_IDS.TEAM || @@ -62,13 +85,12 @@ const ProjectUsage: FC = ({ projectRef }) => { const isPaidTier = subscriptionTier !== PRICING_TIER_PRODUCT_IDS.FREE - const featureFootnotes: Record = { - db_size: ( + const featureFootnotes: Record = { + db_size: isPaidTier ? (
    {usage?.disk_volume_size_gb && Disk Size: {usage.disk_volume_size_gb} GB} - - {isPaidTier && Auto-Scaling} + Auto-Scaling
    - ), + ) : null, } return ( @@ -127,7 +149,6 @@ const ProjectUsage: FC = ({ projectRef }) => {
    - {/* Line items */} {usage === undefined ? (
    @@ -148,14 +169,16 @@ const ProjectUsage: FC = ({ projectRef }) => { usageElement = (
    Not included in {planName} tier - + {canUpdateSubscription && ( + + )}
    ) } else if (showUsageExceedMessage) { @@ -253,6 +276,121 @@ const ProjectUsage: FC = ({ projectRef }) => {
    )}
    + + {isReadOnlyMode && product.title === 'Database' && ( +
    + +

    + {isPaidTier ? ( + <> + Your disk has reached 95% capacity and has entered{' '} + + read-only mode + + . + + ) : ( + <> + You have exceeded the 500mb Database size limit and your project is now + in{' '} + + read-only mode + + . + + )} +

    + {isPaidTier ? ( + <> +

    + For Pro and Enterprise projects,{' '} + + Disk Size expands automatically + {' '} + when it reaches 90% capacity, but can only occur once every six hours. + If the disk size has already expanded and then reaches 95% capacity + within 6 hours, then{' '} + + your disk will enter read-only mode + {' '} + until it can resize again after 6 hours. +

    +

    + If you require help or need your disk changed to read/write mode so you + can delete data,{' '} + + + you can contact the support team + + + . +

    + + ) : ( +

    + You can either{' '} + + reduce your disk usage below 500mb + {' '} + or{' '} + + + upgrade your project + + + . +

    + )} + + {!isPaidTier ? ( + + ) : ( + + )} +
    +
    + )}
    ) })} diff --git a/studio/components/interfaces/Settings/Vault/Keys/EncryptionKeysManagement.tsx b/studio/components/interfaces/Settings/Vault/Keys/EncryptionKeysManagement.tsx index 876805da8a1..63cac0d9820 100644 --- a/studio/components/interfaces/Settings/Vault/Keys/EncryptionKeysManagement.tsx +++ b/studio/components/interfaces/Settings/Vault/Keys/EncryptionKeysManagement.tsx @@ -1,6 +1,7 @@ import dayjs from 'dayjs' import Link from 'next/link' import { observer } from 'mobx-react-lite' +import * as Tooltip from '@radix-ui/react-tooltip' import { FC, Fragment, useEffect, useState } from 'react' import { Alert, @@ -11,13 +12,13 @@ import { Modal, Form, IconTrash, - Badge, IconKey, IconLoader, IconX, IconExternalLink, } from 'ui' -import { useParams, useStore } from 'hooks' +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { useParams, useStore, checkPermissions } from 'hooks' import Divider from 'components/ui/Divider' const DEFAULT_KEY_NAME = 'No description provided' @@ -34,6 +35,8 @@ const EncryptionKeysManagement: FC = ({}) => { const [selectedKeyToRemove, setSelectedKeyToRemove] = useState() const [isDeletingKey, setIsDeletingKey] = useState(false) + const canManageKeys = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') + useEffect(() => { if (id !== undefined) setSearchValue(id) }, [id]) @@ -143,9 +146,32 @@ const EncryptionKeysManagement: FC = ({}) => { - + + + + + {!canManageKeys && ( + + +
    + + You need additional permissions to add keys + +
    +
    + )} +
    @@ -178,12 +204,32 @@ const EncryptionKeysManagement: FC = ({}) => {

    Added on {dayjs(key.created).format('MMM D, YYYY')}

    - - + + + + + {!canManageSecrets && ( + + +
    + + You need additional permissions to add secrets + +
    +
    + )} +
    diff --git a/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx b/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx index 198e9a2a964..9996fc88488 100644 --- a/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx +++ b/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx @@ -1,21 +1,78 @@ import { FC } from 'react' import Link from 'next/link' -import { Button, IconAlertCircle, IconLock } from 'ui' +import { Button, IconAlertCircle, IconCode, IconLock } from 'ui' +import * as Tooltip from '@radix-ui/react-tooltip' import type { PostgresPolicy, PostgresTable } from '@supabase/postgres-meta' -import { useStore } from 'hooks' +import { useStore, checkPermissions } from 'hooks' +import { PermissionAction } from '@supabase/shared-types/out/constants' interface Props { table: PostgresTable + apiPreviewPanelOpen: boolean + setApiPreviewPanelOpen: (apiPreviewPanelOpen: boolean) => void + refreshDocs: () => void } -const GridHeaderActions: FC = ({ table }) => { +const GridHeaderActions: FC = ({ + table, + apiPreviewPanelOpen, + setApiPreviewPanelOpen, + refreshDocs, +}) => { const { ui, meta } = useStore() const projectRef = ui.selectedProject?.ref const policies = meta.policies.list((policy: PostgresPolicy) => policy.table_id === table.id) + const isReadOnly = + !checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') && + !checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns') + + function handlePreviewToggle() { + setApiPreviewPanelOpen(!apiPreviewPanelOpen) + refreshDocs() + } + + const RenderAPIPreviewToggle = () => { + return ( + + ) + } + return (
    + {isReadOnly && ( + + +
    + Viewing as read-only +
    +
    + + +
    + + You need additional permissions to manage your project's data + +
    +
    +
    + )} +
    + +
    + + + + + +
    - - {name && ( - <> - - - - - -
    {name}
    - - )} {item && } @@ -140,6 +161,24 @@ const FunctionsLayout: FC = ({ title, children }) => { )} + + setShowTerminalInstructions(false)} + header={

    Deploying an edge function to your project

    } + customFooter={ +
    + +
    + } + > +
    + +
    +
    ) } diff --git a/studio/components/layouts/ProjectLayout/ConnectingState.tsx b/studio/components/layouts/ProjectLayout/ConnectingState.tsx index a4124474256..4378343983b 100644 --- a/studio/components/layouts/ProjectLayout/ConnectingState.tsx +++ b/studio/components/layouts/ProjectLayout/ConnectingState.tsx @@ -37,9 +37,7 @@ const ConnectingState: FC = ({ project }) => { }, [project]) const testProjectConnection = async () => { - const result = await pingPostgrest(project.restUrl!, project.ref, { - kpsVersion: project.kpsVersion, - }) + const result = await pingPostgrest(project.ref, { kpsVersion: project.kpsVersion }) if (result) { clearInterval(checkProjectConnectionIntervalRef.current) app.onProjectPostgrestStatusUpdated(project.id, 'ONLINE') diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx index cf8789a80e2..90700cd6c35 100644 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx +++ b/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx @@ -11,19 +11,30 @@ import HelpPopover from './HelpPopover' import NotificationsPopover from './NotificationsPopover' import { getResourcesExceededLimits } from 'components/ui/OveragesBanner/OveragesBanner.utils' import { useProjectUsageQuery } from 'data/usage/project-usage-query' +import { useProjectReadOnlyQuery } from 'data/config/project-read-only-query' +import { Badge } from 'ui' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder = true }: any) => { const { ui } = useStore() const { selectedOrganization, selectedProject } = ui const { ref: projectRef } = useParams() + const { project } = useProjectContext() + + const { data: isReadOnlyMode } = useProjectReadOnlyQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) const { data: usage } = useProjectUsageQuery({ projectRef }) const resourcesExceededLimits = getResourcesExceededLimits(usage) + const projectHasNoLimits = ui.selectedProject?.subscription_tier === PRICING_TIER_PRODUCT_IDS.PAYG || ui.selectedProject?.subscription_tier === PRICING_TIER_PRODUCT_IDS.ENTERPRISE || ui.selectedProject?.subscription_tier === PRICING_TIER_PRODUCT_IDS.TEAM + const showOverUsageBadge = selectedProject?.subscription_tier !== undefined && !projectHasNoLimits && @@ -63,6 +74,18 @@ const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder = {/* Project Dropdown */} + {/* [Terry] Temporary until we figure out how we want to display this permanently */} + {/* context: https://www.notion.so/supabase/DB-Disk-Size-Free-tier-Read-only-Critical-f2b8937c13a149e3ac769fe5888f6db0*/} + {isReadOnlyMode && ( + + )} + {/* [Joshen TODO] Temporarily hidden until usage endpoint is sorted out */} {/* {showOverUsageBadge && (
    diff --git a/studio/components/layouts/ProjectLayout/ProjectContext.tsx b/studio/components/layouts/ProjectLayout/ProjectContext.tsx index 7be2501528c..0c7b2144f31 100644 --- a/studio/components/layouts/ProjectLayout/ProjectContext.tsx +++ b/studio/components/layouts/ProjectLayout/ProjectContext.tsx @@ -1,4 +1,4 @@ -import { useStore } from 'hooks' +import { useParams, useStore } from 'hooks' import { observer } from 'mobx-react-lite' import { createContext, PropsWithChildren, useContext, useMemo } from 'react' import { Project } from 'types' @@ -44,3 +44,9 @@ export const ProjectContextProvider = observer( return {children} } ) + +export const ProjectContextFromParamsProvider = ({ children }: PropsWithChildren<{}>) => { + const { ref: projectRef } = useParams() + + return {children} +} diff --git a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts index 8e6142aaef8..56dbac921b8 100644 --- a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts +++ b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts @@ -24,15 +24,10 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { ? [ { name: 'API', - key: 'api', - url: `/project/${ref}/reports/api`, - items: [], - }, - { - name: 'Auth', - key: 'auth', - url: `/project/${ref}/reports/auth`, + key: 'api-overview', + url: `/project/${ref}/reports/api-overview`, items: [], + label: 'NEW', }, ] : []), @@ -42,6 +37,13 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { url: `/project/${ref}/reports/database`, items: [], }, + { + name: 'Query Performance', + key: 'query-performance', + url: `/project/${ref}/reports/query-performance`, + items: [], + label: 'NEW', + }, ], }, ] diff --git a/studio/components/layouts/SignInLayout/SignInLayout.tsx b/studio/components/layouts/SignInLayout/SignInLayout.tsx index a9a88f48348..4732fc96179 100644 --- a/studio/components/layouts/SignInLayout/SignInLayout.tsx +++ b/studio/components/layouts/SignInLayout/SignInLayout.tsx @@ -118,7 +118,7 @@ const SignInLayout = ({
    -
    +

    {heading}

    {subheading}

    diff --git a/studio/components/layouts/StorageLayout/BucketRow.tsx b/studio/components/layouts/StorageLayout/BucketRow.tsx index f96a5d96b4c..79e9cf91ace 100644 --- a/studio/components/layouts/StorageLayout/BucketRow.tsx +++ b/studio/components/layouts/StorageLayout/BucketRow.tsx @@ -1,6 +1,8 @@ import { FC } from 'react' import { Badge, Dropdown, IconLoader, IconMoreVertical, IconTrash } from 'ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { checkPermissions } from 'hooks' import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' import { STORAGE_ROW_STATUS } from 'components/to-be-cleaned/Storage/Storage.constants' @@ -19,6 +21,8 @@ const BucketRow: FC = ({ onSelectDeleteBucket = () => {}, onSelectToggleBucketPublic = () => {}, }) => { + const canUpdateBuckets = checkPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*') + return ( = ({ action={ bucket.status === STORAGE_ROW_STATUS.LOADING ? ( - ) : bucket.status === STORAGE_ROW_STATUS.READY ? ( + ) : canUpdateBuckets && bucket.status === STORAGE_ROW_STATUS.READY ? ( = ({ title, children }) => { const { data: settings, isLoading } = useProjectApiQuery({ projectRef }) const apiService = settings?.autoApiService - const serviceKey = find(apiService?.service_api_keys ?? [], (key) => key.tags === 'service_role') - const canAccessStorage = !isLoading && apiService && serviceKey useEffect(() => { if (!isLoading && apiService) initializeStorageStore(apiService) @@ -51,15 +49,13 @@ const StorageLayout: FC = ({ title, children }) => { const initializeStorageStore = async (apiService: AutoApiService) => { if (apiService.endpoint) { - if (serviceKey) { - storageExplorerStore.initStore( - projectRef, - apiService.endpoint, - apiService.serviceApiKey, - apiService.protocol - ) - await storageExplorerStore.fetchBuckets() - } + storageExplorerStore.initStore( + projectRef, + apiService.endpoint, + apiService.serviceApiKey, + apiService.protocol + ) + await storageExplorerStore.fetchBuckets() } else { ui.setNotification({ category: 'error', @@ -99,15 +95,15 @@ const StorageLayout: FC = ({ title, children }) => { } } - if (!isLoading && !canAccessStorage) { - return ( - -
    - -
    -
    - ) - } + // if (!isLoading && !canAccessStorage) { + // return ( + // + //
    + // + //
    + //
    + // ) + // } return ( }> diff --git a/studio/components/layouts/StorageLayout/StorageMenu.tsx b/studio/components/layouts/StorageLayout/StorageMenu.tsx index 441e07c40a9..1d2a88de2fd 100644 --- a/studio/components/layouts/StorageLayout/StorageMenu.tsx +++ b/studio/components/layouts/StorageLayout/StorageMenu.tsx @@ -2,9 +2,11 @@ import { FC } from 'react' import Link from 'next/link' import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' -import { useParams } from 'hooks' +import * as Tooltip from '@radix-ui/react-tooltip' import { Button, Menu, IconLoader, Alert, IconEdit } from 'ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { checkPermissions, useParams } from 'hooks' import BucketRow from './BucketRow' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' @@ -13,6 +15,7 @@ interface Props {} const StorageMenu: FC = () => { const router = useRouter() const { ref, bucketId } = useParams() + const canCreateBuckets = checkPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*') const page = router.pathname.split('/')[4] as | undefined @@ -33,19 +36,39 @@ const StorageMenu: FC = () => { return (
    -
    - } - style={{ justifyContent: 'start' }} - onClick={openCreateBucketModal} - > - New bucket - + + +
    + } + disabled={!canCreateBuckets} + style={{ justifyContent: 'start' }} + onClick={openCreateBucketModal} + > + New bucket + + + {!canCreateBuckets && ( + + +
    + + You need additional permissions to create buckets + +
    +
    + )} +
    diff --git a/studio/components/to-be-cleaned/Charts/ChartRenderer.tsx b/studio/components/to-be-cleaned/Charts/ChartRenderer.tsx index 8329a32669a..88812c5307e 100644 --- a/studio/components/to-be-cleaned/Charts/ChartRenderer.tsx +++ b/studio/components/to-be-cleaned/Charts/ChartRenderer.tsx @@ -126,6 +126,9 @@ const Header = ({ ) } +/** + * @deprecated please use studio/components/ui/Charts/BarChart.tsx instead + */ export function BarChart({ data, attribute, @@ -267,4 +270,4 @@ export function BarChart({
    ) -} \ No newline at end of file +} diff --git a/studio/components/to-be-cleaned/Docs/CodeSnippet.js b/studio/components/to-be-cleaned/Docs/CodeSnippet.js index 02bd93d5df3..47acdeff5da 100644 --- a/studio/components/to-be-cleaned/Docs/CodeSnippet.js +++ b/studio/components/to-be-cleaned/Docs/CodeSnippet.js @@ -3,12 +3,12 @@ import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' const CodeSnippet = ({ selectedLang, snippet }) => { if (!snippet[selectedLang]) return null return ( - <> +

    {snippet.title}

    {snippet[selectedLang].code} - +
    ) } export default CodeSnippet diff --git a/studio/components/to-be-cleaned/Docs/Pages/Authentication.js b/studio/components/to-be-cleaned/Docs/Pages/Authentication.js index 4ad66d322f3..651fe8142e7 100644 --- a/studio/components/to-be-cleaned/Docs/Pages/Authentication.js +++ b/studio/components/to-be-cleaned/Docs/Pages/Authentication.js @@ -5,9 +5,13 @@ import CodeSnippet from '../CodeSnippet' export default function Authentication({ autoApiService, selectedLang, showApiKey }) { // [Joshen] ShowApiKey should really be a boolean, its confusing const defaultApiKey = - showApiKey !== 'SUPABASE_KEY' ? autoApiService.defaultApiKey : 'SUPABASE_CLIENT_API_KEY' + showApiKey !== 'SUPABASE_KEY' + ? autoApiService?.defaultApiKey ?? 'SUPABASE_CLIENT_API_KEY' + : 'SUPABASE_CLIENT_API_KEY' const serviceApiKey = - showApiKey !== 'SUPABASE_KEY' ? autoApiService.serviceApiKey : 'SUPABASE_SERVICE_KEY' + showApiKey !== 'SUPABASE_KEY' + ? autoApiService?.serviceApiKey ?? 'SUPABASE_SERVICE_KEY' + : 'SUPABASE_SERVICE_KEY' return ( <> diff --git a/studio/components/to-be-cleaned/Docs/Pages/Introduction.js b/studio/components/to-be-cleaned/Docs/Pages/Introduction.js deleted file mode 100644 index 30e24301034..00000000000 --- a/studio/components/to-be-cleaned/Docs/Pages/Introduction.js +++ /dev/null @@ -1,59 +0,0 @@ -import Snippets from '../Snippets' -import CodeSnippet from '../CodeSnippet' - -export default function Introduction({ autoApiService, selectedLang }) { - return ( - <> -

    Introduction

    -
    -
    -

    - This API provides an easy way to integrate with your Postgres database. The API - documentation below is specifically generated for your database. -

    -

    - This is an auto-generating API, so as you make changes to your database, this - documentation will change too. -

    -

    - Please note: if you make changes to a field (column) name or type, the API - interface for those fields will change correspondingly. Therefore, please make sure to - update your API implementation accordingly whenever you make changes to your Supabase - schema from the graphical interface. -

    -
    -
    - -

    API URL

    -
    -
    -

    The API URL for your project.

    -
    -
    - -
    -
    - -

    Client Libraries

    -
    -
    -

    Your API consists of both a RESTful interface and a Realtime interface.

    -

    - For interacting with the Realtime streams, we provide client libraries that handle the - websockets. -

    -
    -
    - - -
    -
    - - ) -} diff --git a/studio/components/to-be-cleaned/Docs/Pages/Introduction.tsx b/studio/components/to-be-cleaned/Docs/Pages/Introduction.tsx new file mode 100644 index 00000000000..61f132b1cf1 --- /dev/null +++ b/studio/components/to-be-cleaned/Docs/Pages/Introduction.tsx @@ -0,0 +1,112 @@ +import Snippets from '../Snippets' +import CodeSnippet from '../CodeSnippet' +import Image from 'next/image' +import { useStore } from 'hooks' +import { AutoApiService } from 'data/config/project-api-query' + +const libs = [ + { + name: 'Javascript', + url: 'https://supabase.com/docs/reference/javascript/introduction', + icon: 'javascript', + }, + { name: 'Flutter', url: 'https://supabase.com/docs/reference/dart/introduction', icon: 'dart' }, + { + name: 'Python', + url: 'https://supabase.com/docs/reference/python/introduction', + icon: 'python', + }, + { name: 'C#', url: 'https://supabase.com/docs/reference/csharp/introduction', icon: 'csharp' }, +] + +interface Props { + autoApiService: AutoApiService + selectedLang: string +} + +export default function Introduction({ autoApiService, selectedLang }: Props) { + const { ui } = useStore() + const { isDarkTheme } = ui + + return ( + <> +

    Introduction

    +
    +
    +

    + This API provides an easy way to integrate with your Postgres database. The API + documentation below is specifically generated for your database. +

    +

    + This is an auto-generating API, so as you make changes to your database, this + documentation will change too. +

    +

    + Note: if you make changes to a field (column) name or type, the API interface for + those fields will change correspondingly. Therefore, please make sure to update your API + implementation accordingly whenever you make changes to your Supabase schema from the + graphical interface. +

    + +
    +

    Read the reference documentation:

    +
    + {libs.map((lib) => ( + + {lib.name} + {lib.name} + + ))} +
    +
    +
    +
    + +

    API URL

    +
    +
    +

    The API URL for your project.

    +
    +
    + +
    +
    + +

    Client Libraries

    +
    +
    +

    Your API consists of both a RESTful interface and a Realtime interface.

    +

    + For interacting with the Realtime streams, we provide client libraries that handle the + websockets. +

    +
    +
    + + +
    +
    + + ) +} diff --git a/studio/components/to-be-cleaned/Docs/Pages/Tables/Introduction.tsx b/studio/components/to-be-cleaned/Docs/Pages/Tables/Introduction.tsx index 8ef26409da3..ba2441ad011 100644 --- a/studio/components/to-be-cleaned/Docs/Pages/Tables/Introduction.tsx +++ b/studio/components/to-be-cleaned/Docs/Pages/Tables/Introduction.tsx @@ -1,10 +1,8 @@ -import { FC, useState } from 'react' +import { FC } from 'react' import Link from 'next/link' -import { Button, IconDownload, IconExternalLink } from 'ui' -import { useParams, useStore } from 'hooks' -import { API_ADMIN_URL } from 'lib/constants' -import { get } from 'lib/common/fetch' +import { useParams } from 'hooks' import CodeSnippet from '../../CodeSnippet' +import GeneratingTypes from 'components/interfaces/Docs/GeneratingTypes' interface Props { selectedLang: string @@ -12,35 +10,6 @@ interface Props { const Introduction: FC = ({ selectedLang }) => { const { ref } = useParams() - const { ui } = useStore() - const [isGeneratingTypes, setIsGeneratingTypes] = useState(false) - - const generateTypes = async () => { - setIsGeneratingTypes(true) - const res = await get(`${API_ADMIN_URL}/projects/${ref}/types/typescript`) - - if (!res.error) { - let element = document.createElement('a') - element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(res.types)) - element.setAttribute('download', 'supabase.ts') - element.style.display = 'none' - document.body.appendChild(element) - element.click() - document.body.removeChild(element) - ui.setNotification({ - category: 'success', - message: `Successfully generated types! File is being downloaded`, - }) - } else { - ui.setNotification({ - category: 'error', - message: `Failed to generate types: ${res.error.message}`, - error: res.error, - }) - } - - setIsGeneratingTypes(false) - } return ( <> @@ -65,48 +34,7 @@ const Introduction: FC = ({ selectedLang }) => {
    -

    - Generating types - - - - - -

    -
    -
    -

    - Supabase APIs are generated from your database, which means that we can use database - introspection to generate type-safe API definitions. -

    -

    - You can generate types from your database either through the{' '} - Supabase CLI, - or by downloading the types file via the button on the right and importing it in your - application within src/index.ts. -

    -
    -
    - {selectedLang === 'js' && ( - - )} - - -
    -
    +

    GraphQL vs Supabase @@ -141,22 +69,6 @@ const Introduction: FC = ({ selectedLang }) => { } const localSnippets = { - cliLogin: () => ({ - title: 'Login via the CLI with your Personal Access Token', - bash: { - code: ` -npx supabase login -`, - }, - }), - generateTypes: (ref: string) => ({ - title: 'Generate types', - bash: { - code: ` -npx supabase gen types typescript --project-id "${ref}" --schema public > types/supabase.ts -`, - }, - }), withApollo: () => ({ title: 'With Apollo GraphQL', bash: { diff --git a/studio/components/to-be-cleaned/Docs/Param.js b/studio/components/to-be-cleaned/Docs/Param.js deleted file mode 100644 index 9abb36712f8..00000000000 --- a/studio/components/to-be-cleaned/Docs/Param.js +++ /dev/null @@ -1,35 +0,0 @@ -import { Badge } from 'ui' -import Description from './Description' - -const Param = ({ - name, - type, - format, - required, - description, - metadata = {}, - onDesciptionUpdated = () => {}, -}) => { - return ( - <> -
    - {name} - {required ? 'Required' : 'Optional'} -
    - {format && format != type && ( -
    - - {format} - {type} -
    - )} - {description !== false && ( - <> - - - - )} - - ) -} -export default Param diff --git a/studio/components/to-be-cleaned/Docs/Param.tsx b/studio/components/to-be-cleaned/Docs/Param.tsx new file mode 100644 index 00000000000..611a61e707a --- /dev/null +++ b/studio/components/to-be-cleaned/Docs/Param.tsx @@ -0,0 +1,97 @@ +import { FC } from 'react' +import { Badge, IconCode, IconDatabase } from 'ui' +import Description from './Description' + +function getColumnType(type: string, format: string) { + // json and jsonb both have type=undefined, so check format instead + if (type === undefined && (format === 'jsonb' || format === 'json')) return 'json' + + switch (type) { + case 'string': + return 'string' + case 'integer': + return 'number' + case 'json': + return 'json' + case 'number': + return 'number' + case 'boolean': + return 'boolean' + default: + return '' + } +} + +interface Props { + name: string + type: string + format: string + required: boolean + description: boolean + metadata?: any + onDesciptionUpdated?: () => void +} +const Param: FC = ({ + name, + type, + format, + required, + description, + metadata = {}, + onDesciptionUpdated = () => {}, +}) => { + return ( + <> +
    +
    +
    + + +
    + {name} +
    +
    +
    + + {required ? 'Required' : 'Optional'} +
    + {format && ( +
    +
    + +
    + + + + {getColumnType(type, format)} + + +
    +
    +
    + +
    + + + + {format} + + +
    +
    +
    + )} + {description !== false && ( +
    + + +
    + )} + + ) +} +export default Param diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/ColumnContextMenu.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/ColumnContextMenu.js deleted file mode 100644 index 45d5cb5ba0d..00000000000 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/ColumnContextMenu.js +++ /dev/null @@ -1,49 +0,0 @@ -import { Menu, Item, Separator, Submenu } from 'react-contexify' -import 'react-contexify/dist/ReactContexify.css' - -import { STORAGE_VIEWS, STORAGE_SORT_BY, STORAGE_SORT_BY_ORDER } from '../Storage.constants' - -const ColumnContextMenu = ({ - id = '', - onCreateNewFolder = () => {}, - onSelectAllItems = () => {}, - onSelectView = () => {}, - onSelectSort = () => {}, - onSelectSortByOrder = () => {}, -}) => { - return ( - - { - onCreateNewFolder(props.index) - }} - > - New folder - - - { - onSelectAllItems(props.index) - }} - > - Select all items - - - onSelectView(STORAGE_VIEWS.COLUMNS)}>As columns - onSelectView(STORAGE_VIEWS.LIST)}>As list - - - onSelectSort(STORAGE_SORT_BY.NAME)}>Name - onSelectSort(STORAGE_SORT_BY.CREATED_AT)}>Last created - onSelectSort(STORAGE_SORT_BY.UPDATED_AT)}>Last modified - onSelectSort(STORAGE_SORT_BY.LAST_ACCESSED_AT)}>Last accessed - - - onSelectSortByOrder(STORAGE_SORT_BY_ORDER.ASC)}>Ascending - onSelectSortByOrder(STORAGE_SORT_BY_ORDER.DESC)}>Descending - - - ) -} - -export default ColumnContextMenu diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/ColumnContextMenu.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/ColumnContextMenu.tsx new file mode 100644 index 00000000000..4f13edbf96b --- /dev/null +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/ColumnContextMenu.tsx @@ -0,0 +1,121 @@ +import { FC } from 'react' +import { compact, uniqBy } from 'lodash' +import { Menu, Item, Separator, Submenu } from 'react-contexify' +import 'react-contexify/dist/ReactContexify.css' + +import { + STORAGE_VIEWS, + STORAGE_SORT_BY, + STORAGE_SORT_BY_ORDER, + STORAGE_ROW_TYPES, +} from '../Storage.constants' +import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' +import { IconFolderPlus } from 'ui' + +interface Props { + id: string +} + +const ColumnContextMenu: FC = ({ id = '' }) => { + const storageExplorerStore = useStorageStore() + const { + columns, + selectedItems, + setSelectedItems, + setView, + setSortBy, + setSortByOrder, + addNewFolderPlaceholder, + } = storageExplorerStore + + const onSelectCreateFolder = (columnIndex = -1) => { + addNewFolderPlaceholder(columnIndex) + } + + const onSelectAllItemsInColumn = (columnIndex: number) => { + const columnFiles = columns[columnIndex].items + .filter((item: any) => item.type === STORAGE_ROW_TYPES.FILE) + .map((item: any) => { + return { ...item, columnIndex } + }) + const columnFilesId = compact(columnFiles.map((item: any) => item.id)) + const selectedItemsFromColumn = selectedItems.filter((item: any) => + columnFilesId.includes(item.id) + ) + + if (selectedItemsFromColumn.length === columnFiles.length) { + // Deselect all items from column + const updatedSelectedItems = selectedItems.filter( + (item: any) => !columnFilesId.includes(item.id) + ) + setSelectedItems(updatedSelectedItems) + } else { + // Select all items from column + const updatedSelectedItems = uniqBy(selectedItems.concat(columnFiles), 'id') + setSelectedItems(updatedSelectedItems) + } + } + + return ( + + onSelectCreateFolder(props.index)}> + + New folder + + + onSelectAllItemsInColumn(props.index)}> + Select all items + + + View +

    + } + > + setView(STORAGE_VIEWS.COLUMNS)}> + As columns + + setView(STORAGE_VIEWS.LIST)}> + As list + + + + Sort by +
    + } + > + setSortBy(STORAGE_SORT_BY.NAME)}> + Name + + setSortBy(STORAGE_SORT_BY.CREATED_AT)}> + Last created + + setSortBy(STORAGE_SORT_BY.UPDATED_AT)}> + Last modified + + setSortBy(STORAGE_SORT_BY.LAST_ACCESSED_AT)}> + Last accessed + + + + Sort by order +
    + } + > + setSortByOrder(STORAGE_SORT_BY_ORDER.ASC)}> + Ascending + + setSortByOrder(STORAGE_SORT_BY_ORDER.DESC)}> + Descending + + + + ) +} + +export default ColumnContextMenu diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js index 69e9a354f06..2b41ffda51a 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorer.js @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react' +import { useEffect, useRef } from 'react' import { observer } from 'mobx-react-lite' import { STORAGE_VIEWS, CONTEXT_MENU_KEYS } from '../Storage.constants' @@ -6,7 +6,6 @@ import ItemContextMenu from './ItemContextMenu' import FolderContextMenu from './FolderContextMenu' import ColumnContextMenu from './ColumnContextMenu' import FileExplorerColumn from './FileExplorerColumn' -import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' const FileExplorer = ({ view = STORAGE_VIEWS.COLUMNS, @@ -17,16 +16,9 @@ const FileExplorer = ({ onFilesUpload = () => {}, onSelectAllItemsInColumn = () => {}, onSelectColumnEmptySpace = () => {}, - onSelectCreateFolder = () => {}, - onChangeView = () => {}, - onChangeSortBy = () => {}, - onChangeSortByOrder = () => {}, onColumnLoadMore = () => {}, }) => { const fileExplorerRef = useRef(null) - const storageExplorerStore = useStorageStore() - - const { setSelectedItemToRename, setSelectedItemsToDelete } = storageExplorerStore useEffect(() => { if (fileExplorerRef) { @@ -42,20 +34,9 @@ const FileExplorer = ({ ref={fileExplorerRef} className="file-explorer flex flex-grow overflow-x-auto justify-between h-full w-full" > - + - setSelectedItemToRename(folder)} - onDeleteFolder={(folder) => setSelectedItemsToDelete([folder])} - /> + {view === STORAGE_VIEWS.COLUMNS ? (
    {columns.map((column, index) => ( diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.tsx index e4ab9f3e40e..d3b10ab5108 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.tsx @@ -18,6 +18,10 @@ import { IconChevronsUp, IconList, } from 'ui' +import * as Tooltip from '@radix-ui/react-tooltip' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { checkPermissions } from 'hooks' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' import { STORAGE_VIEWS, STORAGE_SORT_BY, STORAGE_SORT_BY_ORDER } from '../Storage.constants' @@ -137,6 +141,7 @@ const FileExplorerHeader: FC = ({ const breadcrumbs = columns.map((column: any) => column.name) const backDisabled = columns.length <= 1 + const canUpdateStorage = checkPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*') useEffect(() => { if (itemSearchString) setSearchString(itemSearchString) @@ -392,22 +397,60 @@ const FileExplorerHeader: FC = ({ {/* @ts-ignore */}
    - - + + + + + {!canUpdateStorage && ( + + +
    + + You need additional permissions to upload files + +
    +
    + )} +
    + + + + + {!canUpdateStorage && ( + + +
    + + You need additional permissions to create folders + +
    +
    + )} +
    {/* Search: Disabled for now */} diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx index b5bd5982c23..7eaf41c543b 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.tsx @@ -20,6 +20,9 @@ import { import SVG from 'react-inlinesvg' import * as Tooltip from '@radix-ui/react-tooltip' import { useContextMenu } from 'react-contexify' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { checkPermissions } from 'hooks' import { STORAGE_VIEWS, STORAGE_ROW_TYPES, @@ -68,6 +71,7 @@ const RowIcon = ({ view, status, fileType, mimeType }: any) => { } interface Props { + index: number item: any view: string columnIndex: number @@ -77,6 +81,7 @@ interface Props { } const FileExplorerRow: FC = ({ + index: itemIndex, item = {}, view = STORAGE_VIEWS.COLUMNS, columnIndex = 0, @@ -95,6 +100,7 @@ const FileExplorerRow: FC = ({ addNewFolder, renameFolder, renameFile, + selectedBucket, setSelectedItems, setSelectedItemsToDelete, setSelectedItemToRename, @@ -104,13 +110,16 @@ const FileExplorerRow: FC = ({ downloadFile, downloadFolder, copyFileURLToClipboard, + selectRangeItems, } = storageExplorerStore + const isPublic = selectedBucket.public const itemWithColumnIndex = { ...item, columnIndex } const isSelected = find(selectedItems, item) !== undefined const isOpened = openedFolders.length > columnIndex ? isEqual(openedFolders[columnIndex], item) : false const isPreviewed = !isEmpty(selectedFilePreview) && isEqual(selectedFilePreview.id, item.id) + const canUpdateFiles = checkPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*') const onSelectFile = async (columnIndex: number, file: any) => { popColumnAtIndex(columnIndex) @@ -127,11 +136,18 @@ const FileExplorerRow: FC = ({ await fetchFolderContents(folder.id, folder.name, columnIndex) } - const onCheckItem = (item: any) => { - if (find(selectedItems, item) === undefined) { - setSelectedItems(selectedItems.concat([item])) + const onCheckItem = (isShiftKeyHeld: boolean) => { + // Select a range if shift is held down + if (isShiftKeyHeld && selectedItems.length !== 0) { + selectRangeItems(columnIndex, itemIndex) + return + } + if (find(selectedItems, (item: any) => itemWithColumnIndex.id === item.id) !== undefined) { + setSelectedItems( + selectedItems.filter((selectedItem: any) => itemWithColumnIndex.id !== selectedItem.id) + ) } else { - setSelectedItems(selectedItems.filter((selectedItem: any) => item.id !== selectedItem.id)) + setSelectedItems([...selectedItems, itemWithColumnIndex]) } closeFilePreview() } @@ -187,77 +203,109 @@ const FileExplorerRow: FC = ({ const rowOptions = item.type === STORAGE_ROW_TYPES.FOLDER ? [ - { - name: 'Rename', - icon: , - onClick: () => setSelectedItemToRename(itemWithColumnIndex), - }, - { - name: 'Download', - icon: , - onClick: () => downloadFolder(itemWithColumnIndex), - }, - { name: 'Separator', icon: undefined, onClick: undefined }, - { - name: 'Delete', - icon: , - onClick: () => setSelectedItemsToDelete([itemWithColumnIndex]), - }, - ] - : [ - ...(!item.isCorrupted + ...(canUpdateFiles ? [ - { - name: 'Get URL', - icon: , - children: [ - { - name: 'Expire in 1 week', - onClick: async () => - await copyFileURLToClipboard(itemWithColumnIndex, URL_EXPIRY_DURATION.WEEK), - }, - { - name: 'Expire in 1 month', - onClick: async () => - await copyFileURLToClipboard( - itemWithColumnIndex, - URL_EXPIRY_DURATION.MONTH - ), - }, - { - name: 'Expire in 1 year', - onClick: async () => - await copyFileURLToClipboard(itemWithColumnIndex, URL_EXPIRY_DURATION.YEAR), - }, - { - name: 'Custom expiry', - onClick: async () => setSelectedFileCustomExpiry(itemWithColumnIndex), - }, - ], - }, { name: 'Rename', icon: , onClick: () => setSelectedItemToRename(itemWithColumnIndex), }, - { - name: 'Move', - icon: , - onClick: () => setSelectedItemsToMove([itemWithColumnIndex]), - }, - { - name: 'Download', - icon: , - onClick: async () => await downloadFile(itemWithColumnIndex), - }, - { name: 'Separator', icon: undefined, onClick: undefined }, ] : []), { - name: 'Delete', - icon: , - onClick: () => setSelectedItemsToDelete([itemWithColumnIndex]), + name: 'Download', + icon: , + onClick: () => downloadFolder(itemWithColumnIndex), }, + ...(canUpdateFiles + ? [ + { name: 'Separator', icon: undefined, onClick: undefined }, + { + name: 'Delete', + icon: , + onClick: () => setSelectedItemsToDelete([itemWithColumnIndex]), + }, + ] + : []), + ] + : [ + ...(!item.isCorrupted + ? [ + ...(isPublic + ? [ + { + name: 'Get URL', + icon: , + onClick: async () => await copyFileURLToClipboard(itemWithColumnIndex), + }, + ] + : [ + { + name: 'Get URL', + icon: , + children: [ + { + name: 'Expire in 1 week', + onClick: async () => + await copyFileURLToClipboard( + itemWithColumnIndex, + URL_EXPIRY_DURATION.WEEK + ), + }, + { + name: 'Expire in 1 month', + onClick: async () => + await copyFileURLToClipboard( + itemWithColumnIndex, + URL_EXPIRY_DURATION.MONTH + ), + }, + { + name: 'Expire in 1 year', + onClick: async () => + await copyFileURLToClipboard( + itemWithColumnIndex, + URL_EXPIRY_DURATION.YEAR + ), + }, + { + name: 'Custom expiry', + onClick: async () => setSelectedFileCustomExpiry(itemWithColumnIndex), + }, + ], + }, + ]), + ...(canUpdateFiles + ? [ + { + name: 'Rename', + icon: , + onClick: () => setSelectedItemToRename(itemWithColumnIndex), + }, + { + name: 'Move', + icon: , + onClick: () => setSelectedItemsToMove([itemWithColumnIndex]), + }, + { + name: 'Download', + icon: , + onClick: async () => await downloadFile(itemWithColumnIndex), + }, + { name: 'Separator', icon: undefined, onClick: undefined }, + ] + : []), + ] + : []), + ...(canUpdateFiles + ? [ + { + name: 'Delete', + icon: , + onClick: () => setSelectedItemsToDelete([itemWithColumnIndex]), + }, + ] + : []), ] const size = item.metadata ? formatBytes(item.metadata.size) : '-' @@ -343,7 +391,7 @@ const FileExplorerRow: FC = ({ checked={isSelected} onChange={(event) => { event.stopPropagation() - onCheckItem(itemWithColumnIndex) + onCheckItem((event.nativeEvent as KeyboardEvent).shiftKey) }} /> diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FolderContextMenu.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FolderContextMenu.js deleted file mode 100644 index 15ecfd3e311..00000000000 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FolderContextMenu.js +++ /dev/null @@ -1,29 +0,0 @@ -import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' -import { Menu, Item, Separator } from 'react-contexify' -import 'react-contexify/dist/ReactContexify.css' -import { IconEdit, IconDownload, IconTrash2 } from 'ui' - -const FolderContextMenu = ({ id = '', onRenameFolder = () => {}, onDeleteFolder = () => {} }) => { - const storageExplorerStore = useStorageStore() - const { downloadFolder } = storageExplorerStore - - return ( - - onRenameFolder(props.item)}> - - Rename - - downloadFolder(props.item)}> - - Download - - - onDeleteFolder(props.item)}> - - Delete - - - ) -} - -export default FolderContextMenu diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FolderContextMenu.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/FolderContextMenu.tsx new file mode 100644 index 00000000000..aedfa8e5c55 --- /dev/null +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FolderContextMenu.tsx @@ -0,0 +1,44 @@ +import { FC } from 'react' +import { Menu, Item, Separator } from 'react-contexify' +import 'react-contexify/dist/ReactContexify.css' +import { IconEdit, IconDownload, IconTrash2 } from 'ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { checkPermissions } from 'hooks' +import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' + +interface Props { + id: string +} + +const FolderContextMenu: FC = ({ id = '' }) => { + const storageExplorerStore = useStorageStore() + const { downloadFolder, setSelectedItemToRename, setSelectedItemsToDelete } = storageExplorerStore + const canUpdateFiles = checkPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*') + + return ( + + {canUpdateFiles && ( + setSelectedItemToRename(props.item)}> + + Rename + + )} + downloadFolder(props.item)}> + + Download + + {canUpdateFiles && ( + <> + + setSelectedItemsToDelete([props.item])}> + + Delete + + + )} + + ) +} + +export default FolderContextMenu diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.tsx similarity index 66% rename from studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js rename to studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.tsx index eb48047e271..e42be48fe5f 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/ItemContextMenu.tsx @@ -1,12 +1,19 @@ +import { FC } from 'react' import { observer } from 'mobx-react-lite' import { Menu, Item, Separator, Submenu } from 'react-contexify' import 'react-contexify/dist/ReactContexify.css' +import { PermissionAction } from '@supabase/shared-types/out/constants' import { IconClipboard, IconEdit, IconMove, IconDownload, IconTrash2, IconChevronRight } from 'ui' +import { checkPermissions } from 'hooks' import { URL_EXPIRY_DURATION } from '../Storage.constants' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' -const ItemContextMenu = ({ id = '' }) => { +interface Props { + id: string +} + +const ItemContextMenu: FC = ({ id = '' }) => { const storageExplorerStore = useStorageStore() const { downloadFile, @@ -18,12 +25,13 @@ const ItemContextMenu = ({ id = '' }) => { copyFileURLToClipboard, } = storageExplorerStore const isPublic = selectedBucket.public + const canUpdateFiles = checkPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*') - const onHandleClick = async (event, item, expiresIn) => { + const onHandleClick = async (event: any, item: any, expiresIn?: number) => { if (item.isCorrupted) return switch (event) { case 'copy': - if (expiresIn < 0) return setSelectedFileCustomExpiry(item) + if (expiresIn !== undefined && expiresIn < 0) return setSelectedFileCustomExpiry(item) else return await copyFileURLToClipboard(item, expiresIn) case 'rename': return setSelectedItemToRename(item) @@ -73,23 +81,27 @@ const ItemContextMenu = ({ id = '' }) => { )} - onHandleClick('rename', props.item)}> - - Rename - - onHandleClick('move', props.item)}> - - Move - - onHandleClick('download', props.item)}> - - Download - - - setSelectedItemsToDelete([props.item])}> - - Delete - + {canUpdateFiles && ( + <> + onHandleClick('rename', props.item)}> + + Rename + + onHandleClick('move', props.item)}> + + Move + + onHandleClick('download', props.item)}> + + Download + + + setSelectedItemsToDelete([props.item])}> + + Delete + + + )} ) } diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/MoveItemsModal.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/MoveItemsModal.tsx similarity index 83% rename from studio/components/to-be-cleaned/Storage/StorageExplorer/MoveItemsModal.js rename to studio/components/to-be-cleaned/Storage/StorageExplorer/MoveItemsModal.tsx index 0f85cf1fae2..709b4c5bcaf 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/MoveItemsModal.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/MoveItemsModal.tsx @@ -1,7 +1,15 @@ -import { useEffect, useState } from 'react' -import { Modal, Button, Input, Space } from 'ui' +import { FC, useEffect, useState } from 'react' +import { Modal, Button, Input } from 'ui' -const MoveItemsModal = ({ +interface Props { + bucketName: string + visible: boolean + selectedItemsToMove: any[] + onSelectCancel: () => void + onSelectMove: (path: string) => void +} + +const MoveItemsModal: FC = ({ bucketName = '', visible = false, selectedItemsToMove = [], @@ -21,14 +29,14 @@ const MoveItemsModal = ({ const title = multipleFiles ? `Moving ${selectedItemsToMove.length} items within ${bucketName}` : selectedItemsToMove.length === 1 - ? `Moving ${selectedItemsToMove[0].name} within ${bucketName}` + ? `Moving ${selectedItemsToMove[0]?.name} within ${bucketName}` : `` const description = `Enter the path to where you'd like to move the file${ multipleFiles ? 's' : '' } to.` - const onConfirmMove = (event) => { + const onConfirmMove = (event: any) => { if (event) { event.preventDefault() } diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx index 205e6fbbba6..2ba600a6f83 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/PreviewPane.tsx @@ -1,4 +1,3 @@ -import Link from 'next/link' import { isEmpty } from 'lodash' import { Button, @@ -14,6 +13,10 @@ import { import SVG from 'react-inlinesvg' import { formatBytes } from 'lib/helpers' import { Transition } from '@headlessui/react' +import * as Tooltip from '@radix-ui/react-tooltip' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { checkPermissions } from 'hooks' import { URL_EXPIRY_DURATION } from '../Storage.constants' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' @@ -93,6 +96,7 @@ const PreviewFile = ({ mimeType, previewUrl }: { mimeType: string; previewUrl: s const PreviewPane = () => { const storageExplorerStore = useStorageStore() const { + downloadFile, selectedBucket, selectedFilePreview: file, copyFileURLToClipboard, @@ -107,6 +111,7 @@ const PreviewPane = () => { const mimeType = file.metadata ? file.metadata.mimetype : null const createdAt = file.created_at ? new Date(file.created_at).toLocaleString() : 'Unknown' const updatedAt = file.updated_at ? new Date(file.updated_at).toLocaleString() : 'Unknown' + const canUpdateFiles = checkPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*') return ( <> @@ -176,17 +181,14 @@ const PreviewPane = () => { {/* Actions */}
    - - - - - + {selectedBucket.public ? (
    - + + + + + {!canUpdateFiles && ( + + +
    + + You need additional permissions to delete this file + +
    +
    + )} +
    diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js index b19374ff0ba..b31a2d8f792 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/StorageExplorer.js @@ -30,14 +30,10 @@ const StorageExplorer = observer(({ bucket }) => { selectedItemsToMove, clearSelectedItemsToMove, view, - setView, - setSortBy, - setSortByOrder, currentBucketName, openBucket, loadExplorerPreferences, - addNewFolderPlaceholder, fetchFolderContents, fetchMoreFolderContents, deleteFolder, @@ -116,10 +112,6 @@ const StorageExplorer = observer(({ bucket }) => { /** File manipulation methods */ - const onSelectCreateFolder = (columnIndex = -1) => { - addNewFolderPlaceholder(columnIndex) - } - const onFilesUpload = async (event, columnIndex = -1) => { event.persist() const items = event.target.files || event.dataTransfer.items @@ -158,12 +150,6 @@ const StorageExplorer = observer(({ bucket }) => { clearSelectedItems() } - const onChangeView = (view) => setView(view) - - const onChangeSortBy = (sortBy) => setSortBy(sortBy) - - const onChangeSortByOrder = (sortByOrder) => setSortByOrder(sortByOrder) - return (
    { onFilesUpload={onFilesUpload} onSelectAllItemsInColumn={onSelectAllItemsInColumn} onSelectColumnEmptySpace={onSelectColumnEmptySpace} - onSelectCreateFolder={onSelectCreateFolder} - onChangeView={onChangeView} - onChangeSortBy={onChangeSortBy} - onChangeSortByOrder={onChangeSortByOrder} onColumnLoadMore={(index, column) => fetchMoreFolderContents(index, column, itemSearchString) } diff --git a/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx index 4e05953129a..a604849edf0 100644 --- a/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx @@ -1,12 +1,14 @@ import { useState } from 'react' +import * as Tooltip from '@radix-ui/react-tooltip' import { Button, Form, IconClock, Input, Listbox } from 'ui' -import { useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query' import { useProjectStorageConfigUpdateUpdateMutation } from 'data/config/project-storage-config-update-mutation' import UpgradeToPro from 'components/ui/UpgradeToPro' import { convertFromBytes, convertToBytes } from './StorageSettings.utils' import { StorageSizeUnits, STORAGE_FILE_SIZE_LIMIT_MAX_BYTES } from './StorageSettings.constants' +import { PermissionAction } from '@supabase/shared-types/out/constants' export type StorageSettingsProps = { projectRef: string | undefined @@ -42,6 +44,8 @@ const StorageConfig = ({ config, projectRef }: any) => { const [selectedUnit, setSelectedUnit] = useState(unit) let initialValues = { fileSizeLimit: value, unformattedFileSizeLimit: fileSizeLimit } + const canUpdateStorageSettings = checkPermissions(PermissionAction.UPDATE, 'projects') + const formattedMaxSizeBytes = `${new Intl.NumberFormat('en-US').format( STORAGE_FILE_SIZE_LIMIT_MAX_BYTES )} bytes` @@ -138,7 +142,7 @@ const StorageConfig = ({ config, projectRef }: any) => { id="fileSizeLimit" name="fileSizeLimit" type="number" - disabled={isFreeTier} + disabled={isFreeTier || !canUpdateStorageSettings} step={1} onKeyPress={(event) => { if (event.charCode < 48 || event.charCode > 57) { @@ -184,13 +188,20 @@ const StorageConfig = ({ config, projectRef }: any) => { icon={} primaryText="Free Plan has a fixed upload file size limit of 50 MB." projectRef={projectRef} - secondaryText="Please upgrade to Pro plan for a configurable upload file size limit of up to 5 GB." + secondaryText="Upgrade to the Pro plan for a configurable upload file size limit of up to 5 GB." />
    )}
    -
    +
    + {!canUpdateStorageSettings ? ( +

    + You need additional permissions to update storage settings +

    + ) : ( +
    + )}
    diff --git a/studio/components/to-be-cleaned/forms/SchemaForm.js b/studio/components/to-be-cleaned/forms/SchemaForm.js index bc5e6ac20a0..fa65c7b1560 100644 --- a/studio/components/to-be-cleaned/forms/SchemaForm.js +++ b/studio/components/to-be-cleaned/forms/SchemaForm.js @@ -2,10 +2,19 @@ import { AutoForm } from 'uniforms-bootstrap4' import Ajv from 'ajv' import { JSONSchemaBridge } from 'uniforms-bridge-json-schema' -const SchemaForm = ({ schema, model, children, formRef, onSubmit, onChangeModel = () => {} }) => { +const SchemaForm = ({ + schema, + model, + disabled = false, + children, + formRef, + onSubmit, + onChangeModel = () => {}, +}) => { const validatedSchema = new JSONSchemaBridge(schema, createValidator(schema)) return (
    {title || ''}
    -
    - - -
    + + +
    + )} {message}
    { setHasChanged(true) if (onChangeModel) onChangeModel(model) diff --git a/studio/components/ui/Charts/AreaChart.stories.tsx b/studio/components/ui/Charts/AreaChart.stories.tsx index 599063c21ce..d7797fa7410 100644 --- a/studio/components/ui/Charts/AreaChart.stories.tsx +++ b/studio/components/ui/Charts/AreaChart.stories.tsx @@ -26,15 +26,21 @@ export const AreaChart = () => ( { title: 'No title', props: { title: undefined } }, { title: 'Minimal Header', props: { minimalHeader: true } }, { title: 'Minimal Highlighted Value', props: { minimalHeader: true, highlightedValue: 123 } }, - { title: 'Highlighted Value', props: { highlightedValue: 123, highlightedLabel: "some custom label" } }, - { title: 'Formatter - Auto 2 Precision for floats', props: { data: DATA.map(d=> ({...d, ram_usage: d.ram_usage + 0.1231233})) } }, + { + title: 'Highlighted Value', + props: { highlightedValue: 123, highlightedLabel: 'some custom label' }, + }, + { + title: 'Formatter - Auto 2 Precision for floats', + props: { data: DATA.map((d) => ({ ...d, ram_usage: d.ram_usage + 0.1231233 })) }, + }, { title: 'UTC Dates', props: { displayDateInUtc: true } }, { title: 'Custom date format', props: { customDateFormat: 'HH:mm' } }, { title: 'Value format', props: { format: 'c' } }, { title: 'Minimal Value format', props: { minimalHeader: true, format: 'c' } }, { title: 'No data', props: { data: [] } }, ].map(({ title, props }) => ( -
    +
    {title}
    diff --git a/studio/components/ui/Charts/AreaChart.tsx b/studio/components/ui/Charts/AreaChart.tsx index 90e783da40d..c917fe45e71 100644 --- a/studio/components/ui/Charts/AreaChart.tsx +++ b/studio/components/ui/Charts/AreaChart.tsx @@ -13,7 +13,7 @@ import ChartHeader from './ChartHeader' import { Datum, CommonChartProps } from './Charts.types' import utc from 'dayjs/plugin/utc' import ChartNoData from './NoDataPlaceholder' -import { numberFormatter } from './Charts.utils' +import { numberFormatter, useChartSize } from './Charts.utils' dayjs.extend(utc) export interface AreaChartProps extends CommonChartProps { @@ -36,13 +36,12 @@ const AreaChart: React.FC = ({ displayDateInUtc, minimalHeader, className = '', + size = 'normal', }) => { + const { Container } = useChartSize(size) const [focusDataIndex, setFocusDataIndex] = useState(null) - // For future reference: https://github.com/supabase/supabase/pull/5311#discussion_r800852828 - const chartHeight = 160 - - if (data.length === 0) return + if (data.length === 0) return const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value)) const resolvedHighlightedLabel = @@ -69,7 +68,7 @@ const AreaChart: React.FC = ({ highlightedLabel={resolvedHighlightedLabel} minimalHeader={minimalHeader} /> - + = ({ fill="url(#colorUv)" /> - + {data && (
    {dayjs(data[0][xAxisKey]).format(customDateFormat)} diff --git a/studio/components/ui/Charts/BarChart.stories.tsx b/studio/components/ui/Charts/BarChart.stories.tsx new file mode 100644 index 00000000000..53779c01763 --- /dev/null +++ b/studio/components/ui/Charts/BarChart.stories.tsx @@ -0,0 +1,56 @@ +import React from 'react' +import { ComponentMeta } from '@storybook/react' +import Chart, { BarChartProps } from './BarChart' +import dayjs from 'dayjs' + +export default { + title: 'Charts', +} as ComponentMeta + +const DATA = [290, 430, 649, 422, 321, 893, 111].map((value, index) => ({ + ram_usage: value, + timestamp: dayjs().subtract(index, 'day').toISOString(), +})) + +const PROPS: BarChartProps = { + title: 'Memory usage', + xAxisKey: 'timestamp', + yAxisKey: 'ram_usage', + data: DATA, +} + +export const BarChart = () => ( +
    + {[ + { title: 'Normal', props: {} }, + { title: 'No title', props: { title: undefined } }, + { title: 'Minimal Header', props: { minimalHeader: true } }, + { title: 'Minimal Highlighted Value', props: { minimalHeader: true, highlightedValue: 123 } }, + { + title: 'Highlighted Value', + props: { highlightedValue: 123, highlightedLabel: 'some custom label' }, + }, + { + title: 'Formatter - Auto 2 Precision for floats', + props: { data: DATA.map((d) => ({ ...d, ram_usage: d.ram_usage + 0.1231233 })) }, + }, + { title: 'UTC Dates', props: { displayDateInUtc: true } }, + { title: 'Custom date format', props: { customDateFormat: 'HH:mm' } }, + { title: 'Value format', props: { format: 'c' } }, + { title: 'Minimal Value format', props: { minimalHeader: true, format: 'c' } }, + { title: 'No data', props: { data: [] } }, + { title: 'onBarClick', props: { onBarClick: console.log } }, + ].map(({ title, props }) => ( +
    + {title} +
    + +
    +
    + ))} +
    +) + +const ExampleName: React.FC = ({ children }) => ( +

    {children}

    +) diff --git a/studio/components/ui/Charts/BarChart.tsx b/studio/components/ui/Charts/BarChart.tsx new file mode 100644 index 00000000000..7970e8f5a64 --- /dev/null +++ b/studio/components/ui/Charts/BarChart.tsx @@ -0,0 +1,132 @@ +import { useState } from 'react' +import { BarChart as RechartBarChart, XAxis, Tooltip, Bar, Cell, BarProps } from 'recharts' +import dayjs from 'dayjs' +import { CHART_COLORS, DateTimeFormats } from 'components/ui/Charts/Charts.constants' +import ChartHeader from './ChartHeader' +import { Datum, CommonChartProps } from './Charts.types' +import utc from 'dayjs/plugin/utc' +import ChartNoData from './NoDataPlaceholder' +import { numberFormatter, useChartSize } from './Charts.utils' +import { CategoricalChartProps } from 'recharts/types/chart/generateCategoricalChart' +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 = ({ + data, + yAxisKey, + xAxisKey, + format, + customDateFormat = DateTimeFormats.FULL, + title, + highlightedValue, + highlightedLabel, + displayDateInUtc, + minimalHeader, + className = '', + size = 'normal', + onBarClick, +}) => { + const { Container } = useChartSize(size) + const [focusDataIndex, setFocusDataIndex] = useState(null) + + if (data.length === 0) return + + const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value)) + const resolvedHighlightedLabel = + (focusDataIndex !== null && + data && + data[focusDataIndex] && + day(data[focusDataIndex][xAxisKey]).format(customDateFormat)) || + highlightedLabel + + const resolvedHighlightedValue = + (focusDataIndex !== null ? data[focusDataIndex]?.[yAxisKey] : null) || highlightedValue + + return ( +
    + + + { + if (e.activeTooltipIndex !== focusDataIndex) { + setFocusDataIndex(e.activeTooltipIndex) + } + }} + onMouseLeave={() => setFocusDataIndex(null)} + onClick={(tooltipData: any) => { + // receives tooltip data https://github.com/recharts/recharts/blob/2a3405ff64a0c050d2cf94c36f0beef738d9e9c2/src/chart/generateCategoricalChart.tsx + const datum = tooltipData?.activePayload?.[0]?.payload + if (onBarClick) onBarClick(datum, tooltipData) + }} + > + + null} /> + + {data?.map((_entry: Datum, index: any) => ( + + ))} + + + + {data && ( +
    + {dayjs(data[0][xAxisKey]).format(customDateFormat)} + {dayjs(data[data?.length - 1]?.[xAxisKey]).format(customDateFormat)} +
    + )} +
    + ) +} +export default BarChart diff --git a/studio/components/ui/Charts/ChartHeader.tsx b/studio/components/ui/Charts/ChartHeader.tsx index c4d841f6bac..ee515259815 100644 --- a/studio/components/ui/Charts/ChartHeader.tsx +++ b/studio/components/ui/Charts/ChartHeader.tsx @@ -33,7 +33,7 @@ const ChartHeader: React.FC = ({ if (minimalHeader) { return (
    - {chartTitle} + {title && chartTitle}
    {highlightedValue && highlighted} {label} @@ -44,7 +44,7 @@ const ChartHeader: React.FC = ({ return (
    - {chartTitle} + {title && chartTitle} {highlightedValue && highlighted} {label}
    diff --git a/studio/components/ui/Charts/Charts.types.tsx b/studio/components/ui/Charts/Charts.types.tsx index 3332fc441ae..6b5a1aa3650 100644 --- a/studio/components/ui/Charts/Charts.types.tsx +++ b/studio/components/ui/Charts/Charts.types.tsx @@ -14,7 +14,7 @@ export interface CommonChartProps title?: string className?: string isLoading?: boolean - size?: 'small' | 'normal' | 'large' + size?: 'tiny' | 'small' | 'normal' | 'large' } export interface StackedChartProps extends CommonChartProps { diff --git a/studio/components/ui/Charts/Charts.utils.tsx b/studio/components/ui/Charts/Charts.utils.tsx index ce988a2d86b..913f9965b8b 100644 --- a/studio/components/ui/Charts/Charts.utils.tsx +++ b/studio/components/ui/Charts/Charts.utils.tsx @@ -70,10 +70,16 @@ export const timestampFormatter = ( export const useChartSize = ( size: CommonChartProps['size'] = 'normal', sizeMap: { + tiny: number small: number normal: number large: number - } = { small: 120, normal: 160, large: 280 } + } = { + tiny: 76, + small: 96, + normal: 160, + large: 280, + } ) => { const minHeight = sizeMap[size] const Container: React.FC = useMemo( diff --git a/studio/components/ui/Charts/NoDataPlaceholder.tsx b/studio/components/ui/Charts/NoDataPlaceholder.tsx index 7fe1bcf96a0..6a7a7e29c72 100644 --- a/studio/components/ui/Charts/NoDataPlaceholder.tsx +++ b/studio/components/ui/Charts/NoDataPlaceholder.tsx @@ -1,4 +1,4 @@ -import { IconBarChart2 } from '@supabase/ui' +import { IconBarChart2 } from 'ui' interface Props { title?: string @@ -12,9 +12,10 @@ const NoDataPlaceholder: React.FC = ({ }) => (
    diff --git a/studio/components/ui/Charts/Sparkline.tsx b/studio/components/ui/Charts/Sparkline.tsx index c6c2bf1079b..7f36b90257c 100644 --- a/studio/components/ui/Charts/Sparkline.tsx +++ b/studio/components/ui/Charts/Sparkline.tsx @@ -8,6 +8,7 @@ interface Props extends CommonChartProps { } const Sparkline: React.FC = ({ size, data, xAxisKey, yAxisKey }) => { const { Container } = useChartSize(size, { + tiny: 30, small: 50, normal: 90, large: 140, diff --git a/studio/components/ui/Flag/FlagProvider.tsx b/studio/components/ui/Flag/FlagProvider.tsx index e521fec1a65..40d1ac7dae8 100644 --- a/studio/components/ui/Flag/FlagProvider.tsx +++ b/studio/components/ui/Flag/FlagProvider.tsx @@ -16,20 +16,12 @@ const FlagProvider: FC = ({ children }) => { const { Provider } = FlagContext const [store, setStore] = useState({}) - useEffect(() => { - // [Joshen] getFlags get triggered everytime the tab refocuses but this should be okay - // as per https://configcat.com/docs/sdk-reference/js/#polling-modes: - // The polling downloads the config.json at the set interval and are stored in the internal cache - // which subsequently all getValueAsync() calls are served from there - if (IS_PLATFORM) getFlags(profile) - }, [profile]) - const getFlags = async (user?: User) => { if (!client) { client = configcat.getClient( process.env.NEXT_PUBLIC_CONFIGCAT_SDK_KEY ?? '', configcat.PollingMode.AutoPoll, - { pollIntervalSeconds: 10 } + { pollIntervalSeconds: 600 } ) } @@ -44,6 +36,14 @@ const FlagProvider: FC = ({ children }) => { setStore(flagStore) } + useEffect(() => { + // [Joshen] getFlags get triggered everytime the tab refocuses but this should be okay + // as per https://configcat.com/docs/sdk-reference/js/#polling-modes: + // The polling downloads the config.json at the set interval and are stored in the internal cache + // which subsequently all getValueAsync() calls are served from there + if (IS_PLATFORM) getFlags(profile) + }, [profile]) + return {children} } diff --git a/studio/components/ui/LoadingOpacity.tsx b/studio/components/ui/LoadingOpacity.tsx index ff15c8b38b3..af867eb9758 100644 --- a/studio/components/ui/LoadingOpacity.tsx +++ b/studio/components/ui/LoadingOpacity.tsx @@ -1,11 +1,21 @@ import React from 'react' -const LoadingOpacity = ({ children, active }: { children?: React.ReactNode; active: boolean }) => { +const LoadingOpacity = ({ + children, + active, + className, +}: { + children?: React.ReactNode + active: boolean + className?: string +}) => { return (
    {children}
    diff --git a/studio/components/ui/Logs/LogsExplorerHeader.tsx b/studio/components/ui/Logs/LogsExplorerHeader.tsx index 450ffa6a7a1..923f56a0eca 100644 --- a/studio/components/ui/Logs/LogsExplorerHeader.tsx +++ b/studio/components/ui/Logs/LogsExplorerHeader.tsx @@ -1,11 +1,14 @@ -import { IconList } from 'ui' +import Link from 'next/link' +import { FC } from 'react' +import { Button, IconExternalLink, IconList } from 'ui' +import { LOGS_EXPLORER_DOCS_URL } from 'components/interfaces/Settings/Logs' interface Props { subtitle?: string } -const LogsExplorerHeader: React.FC = ({ subtitle }) => ( -
    +const LogsExplorerHeader: FC = ({ subtitle }) => ( +
    Logs Explorer - {subtitle && {subtitle}} + {subtitle && {subtitle}}
    + + + + +
    ) export default LogsExplorerHeader diff --git a/studio/components/ui/Panel.tsx b/studio/components/ui/Panel.tsx index 60584e05f1b..da3ab4d1975 100644 --- a/studio/components/ui/Panel.tsx +++ b/studio/components/ui/Panel.tsx @@ -11,6 +11,7 @@ interface Props { noMargin?: boolean title?: JSX.Element | false wrapWithLoading?: boolean + noHideOverflow?: boolean } function Panel(props: Props) { let headerClasses: string[] = [] @@ -31,7 +32,7 @@ function Panel(props: Props) { const content = (
    diff --git a/studio/components/ui/ProductMenu/ProductMenuItem.tsx b/studio/components/ui/ProductMenu/ProductMenuItem.tsx index ca2fdcf51a7..2b8dedb6ec3 100644 --- a/studio/components/ui/ProductMenu/ProductMenuItem.tsx +++ b/studio/components/ui/ProductMenu/ProductMenuItem.tsx @@ -16,6 +16,32 @@ interface Props { label?: string } +const Label = ({ label }: { label: string }) => { + const color = + label.toLowerCase() === 'new' ? 'text-brand-900 bg-brand-500' : 'text-amber-900 bg-amber-500' + + return ( + + {label} + + ) +} + const ProductMenuItem: FC = ({ name = '', isActive, @@ -34,7 +60,7 @@ const ProductMenuItem: FC = ({
    {name}{' '} {label !== undefined && ( diff --git a/studio/components/ui/UpgradeToPro.tsx b/studio/components/ui/UpgradeToPro.tsx index ca7b1b549b6..ccee10b1295 100644 --- a/studio/components/ui/UpgradeToPro.tsx +++ b/studio/components/ui/UpgradeToPro.tsx @@ -2,8 +2,11 @@ import { Button } from 'ui' import { observer } from 'mobx-react-lite' import Link from 'next/link' import { FC, ReactNode } from 'react' -import { useStore } from 'hooks' +import * as Tooltip from '@radix-ui/react-tooltip' + +import { checkPermissions, useStore, useFlag, useParams } from 'hooks' import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' +import { PermissionAction } from '@supabase/shared-types/out/constants' interface Props { icon?: ReactNode @@ -14,8 +17,16 @@ interface Props { const UpgradeToPro: FC = ({ icon, primaryText, projectRef, secondaryText }) => { const { ui } = useStore() + const { ref } = useParams() const tier = ui.selectedProject?.subscription_tier + const canUpdateSubscription = checkPermissions( + PermissionAction.BILLING_WRITE, + 'stripe.subscriptions' + ) + const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate') + const isEnterprise = tier === PRICING_TIER_PRODUCT_IDS.ENTERPRISE + return (
    = ({ icon, primaryText, projectRef, secondaryText

    {secondaryText}

    - - - - - + + {!canUpdateSubscription || projectUpdateDisabled ? ( + + +
    + + {projectUpdateDisabled ? ( + <> + Subscription changes are currently disabled. +
    + Our engineers are working on a fix. + + ) : !canUpdateSubscription ? ( + 'You need additional permissions to amend subscriptions' + ) : ( + '' + )} +
    +
    +
    + ) : ( + <> + )} +
    diff --git a/studio/data/__templates/resource-query.ts b/studio/data/__templates/resource-query.ts index c348077547f..6d278c48ceb 100644 --- a/studio/data/__templates/resource-query.ts +++ b/studio/data/__templates/resource-query.ts @@ -47,6 +47,18 @@ export const useResourceQuery = ( } ) +/** + * useResourcePrefetch is used for prefetching data. For example, starting a query loading before a page is navigated to. + * + * @example + * const prefetch = useResourcePrefetch({ projectRef, id }) + * + * return ( + * prefetch()}> + * Start loading on hover + * + * ) + */ export const useResourcePrefetch = ({ projectRef, id }: ResourceVariables) => { const client = useQueryClient() diff --git a/studio/data/__templates/resources-query.ts b/studio/data/__templates/resources-query.ts index 9ff95df37f7..b9b1ae01a68 100644 --- a/studio/data/__templates/resources-query.ts +++ b/studio/data/__templates/resources-query.ts @@ -40,6 +40,18 @@ export const useResourcesQuery = ( { enabled: enabled && typeof projectRef !== 'undefined', ...options } ) +/** + * useResourcesPrefetch is used for prefetching data. For example, starting a query loading before a page is navigated to. + * + * @example + * const prefetch = useResourcesPrefetch({ projectRef }) + * + * return ( + * prefetch()}> + * Start loading on hover + * + * ) + */ export const useResourcesPrefetch = ({ projectRef }: ResourcesVariables) => { const client = useQueryClient() diff --git a/studio/data/auth/keys.ts b/studio/data/auth/keys.ts new file mode 100644 index 00000000000..052713f52ff --- /dev/null +++ b/studio/data/auth/keys.ts @@ -0,0 +1,3 @@ +export const authKeys = { + accessToken: () => ['access-token'] as const, +} diff --git a/studio/data/auth/session-access-token-query.ts b/studio/data/auth/session-access-token-query.ts new file mode 100644 index 00000000000..46068987ff2 --- /dev/null +++ b/studio/data/auth/session-access-token-query.ts @@ -0,0 +1,35 @@ +import { useCallback } from 'react' +import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' + +import { authKeys } from './keys' +import { auth } from 'lib/gotrue' + +export async function getSessionAccessToken() { + // ignore if server-side + if (typeof window === 'undefined') return '' + const { + data: { session }, + } = await auth.getSession() + return session?.access_token +} + +export type SessionAccessTokenData = Awaited> +export type SessionAccessTokenError = unknown + +export const useSessionAccessTokenQuery = ({ + enabled = true, + ...options +}: UseQueryOptions = {}) => + useQuery( + authKeys.accessToken(), + () => getSessionAccessToken(), + options + ) + +export const useSessionAccessTokenPrefetch = () => { + const client = useQueryClient() + return useCallback( + () => client.prefetchQuery(authKeys.accessToken(), () => getSessionAccessToken()), + [] + ) +} diff --git a/studio/data/config/project-read-only-query.ts b/studio/data/config/project-read-only-query.ts new file mode 100644 index 00000000000..23312f2f67e --- /dev/null +++ b/studio/data/config/project-read-only-query.ts @@ -0,0 +1,56 @@ +import { UseQueryOptions } from '@tanstack/react-query' +import { ExecuteSqlData, useExecuteSqlPrefetch, useExecuteSqlQuery } from '../sql/execute-sql-query' + +// TODO: temporary solution to check if project is in read only mode +// until we get an api endpoint for this + +export const getProjectReadOnlySql = () => { + const sql = /* SQL */ ` + show default_transaction_read_only; + ` + + return sql +} + +export type ProjectReadOnlyVariables = { + projectRef?: string + connectionString?: string +} + +export type ProjectReadOnlyData = boolean +export type ProjectReadOnlyError = unknown + +export const useProjectReadOnlyQuery = ( + { projectRef, connectionString }: ProjectReadOnlyVariables, + options: Omit< + UseQueryOptions, + 'select' + > = {} +) => + useExecuteSqlQuery( + { + projectRef, + connectionString, + sql: getProjectReadOnlySql(), + queryKey: ['project-read-only'], + }, + { + select(data) { + return data.result[0]?.default_transaction_read_only === 'on' + }, + enabled: typeof projectRef !== 'undefined' && typeof connectionString !== 'undefined', + ...options, + } + ) + +export const useProjectReadOnlyPrefetch = ({ + projectRef, + connectionString, +}: ProjectReadOnlyVariables) => { + return useExecuteSqlPrefetch({ + projectRef, + connectionString, + sql: getProjectReadOnlySql(), + queryKey: ['project-read-only'], + }) +} diff --git a/studio/data/docs/keys.ts b/studio/data/docs/keys.ts index b0f66362945..266c1512311 100644 --- a/studio/data/docs/keys.ts +++ b/studio/data/docs/keys.ts @@ -1,7 +1,3 @@ export const docsKeys = { - jsonSchema: ( - projectRef: string | undefined, - swaggerUrl: string | undefined, - apiKey: string | undefined - ) => ['projects', projectRef, 'docs', { swaggerUrl, apiKey }] as const, + jsonSchema: (projectRef: string | undefined) => ['projects', projectRef, 'docs'] as const, } diff --git a/studio/data/docs/project-json-schema-query.ts b/studio/data/docs/project-json-schema-query.ts index f37f6dd55cb..1155f7c14a6 100644 --- a/studio/data/docs/project-json-schema-query.ts +++ b/studio/data/docs/project-json-schema-query.ts @@ -1,41 +1,26 @@ 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 { docsKeys } from './keys' export type ProjectJsonSchemaVariables = { projectRef?: string - swaggerUrl?: string - apiKey?: string } export type ProjectJsonSchemaResponse = any export async function getProjectJsonSchema( - { projectRef, swaggerUrl, apiKey }: ProjectJsonSchemaVariables, + { projectRef }: ProjectJsonSchemaVariables, signal?: AbortSignal ) { if (!projectRef) { throw new Error('projectRef is required') } - if (!swaggerUrl) { - throw new Error('id is required') - } - - let headers: { - [key: string]: string | undefined - } = { apikey: apiKey } - if ((apiKey?.length ?? 0) > 40) headers['Authorization'] = `Bearer ${apiKey}` - - const response = await get(swaggerUrl, { - signal, - headers, - credentials: 'omit', - }) - if (response.error) { - throw response.error - } + const url = `${API_URL}/projects/${projectRef}/api/rest` + const response = await get(url, { signal }) + if (response.error) throw response.error return response as ProjectJsonSchemaResponse } @@ -43,33 +28,29 @@ export type ProjectJsonSchemaData = Awaited( - { projectRef, swaggerUrl, apiKey }: ProjectJsonSchemaVariables, + { projectRef }: ProjectJsonSchemaVariables, { enabled = true, ...options }: UseQueryOptions = {} ) => useQuery( - docsKeys.jsonSchema(projectRef, swaggerUrl, apiKey), - ({ signal }) => getProjectJsonSchema({ projectRef, swaggerUrl, apiKey }, signal), + docsKeys.jsonSchema(projectRef), + ({ signal }) => getProjectJsonSchema({ projectRef }, signal), { - enabled: enabled && typeof projectRef !== 'undefined' && typeof swaggerUrl !== 'undefined', + enabled: enabled && typeof projectRef !== 'undefined', ...options, } ) -export const useProjectJsonSchemaPrefetch = ({ - projectRef, - swaggerUrl, - apiKey, -}: ProjectJsonSchemaVariables) => { +export const useProjectJsonSchemaPrefetch = ({ projectRef }: ProjectJsonSchemaVariables) => { const client = useQueryClient() return useCallback(() => { - if (projectRef && swaggerUrl) { - client.prefetchQuery(docsKeys.jsonSchema(projectRef, swaggerUrl, apiKey), ({ signal }) => - getProjectJsonSchema({ projectRef, swaggerUrl, apiKey }, signal) + if (projectRef) { + client.prefetchQuery(docsKeys.jsonSchema(projectRef), ({ signal }) => + getProjectJsonSchema({ projectRef }, signal) ) } - }, [projectRef, swaggerUrl]) + }, [projectRef]) } diff --git a/studio/data/edge-functions/edge-functions-delete-mutation.ts b/studio/data/edge-functions/edge-functions-delete-mutation.ts new file mode 100644 index 00000000000..d642758cd9c --- /dev/null +++ b/studio/data/edge-functions/edge-functions-delete-mutation.ts @@ -0,0 +1,46 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { delete_ } from 'lib/common/fetch' +import { API_ADMIN_URL } from 'lib/constants' +import { edgeFunctionsKeys } from './keys' + +export type EdgeFunctionsDeleteVariables = { + projectRef: string + slug: string +} + +export async function deleteEdgeFunction({ projectRef, slug }: EdgeFunctionsDeleteVariables) { + if (!projectRef) throw new Error('projectRef is required') + + const response = await delete_(`${API_ADMIN_URL}/projects/${projectRef}/functions/${slug}`, {}) + if (response.error) { + throw response.error + } + + return response +} + +type EdgeFunctionsDeleteData = Awaited> + +export const useEdgeFunctionDeleteMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => deleteEdgeFunction(vars), + { + async onSuccess(data, variables, context) { + const { projectRef } = variables + await queryClient.invalidateQueries(edgeFunctionsKeys.list(projectRef), { + refetchType: 'all', + }) + await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/data/edge-functions/edge-functions-query.ts b/studio/data/edge-functions/edge-functions-query.ts new file mode 100644 index 00000000000..28bc3f8c649 --- /dev/null +++ b/studio/data/edge-functions/edge-functions-query.ts @@ -0,0 +1,57 @@ +import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' +import { get } from 'lib/common/fetch' +import { API_ADMIN_URL } from 'lib/constants' +import { useCallback } from 'react' +import { edgeFunctionsKeys } from './keys' + +export type EdgeFunctionsVariables = { projectRef?: string } + +export type EdgeFunctionsResponse = { + id: string + name: string + slug: string + status: string + version: number + created_at: number + updated_at: number + verify_jwt: boolean + import_map: boolean +} + +export async function getEdgeFunctions( + { projectRef }: EdgeFunctionsVariables, + signal?: AbortSignal +) { + if (!projectRef) throw new Error('projectRef is required') + + const response = await get(`${API_ADMIN_URL}/projects/${projectRef}/functions`, { + signal, + }) + if (response.error) throw response.error + return response as EdgeFunctionsResponse[] +} + +export type EdgeFunctionsData = Awaited> +export type EdgeFunctionsError = unknown + +export const useEdgeFunctionsQuery = ( + { projectRef }: EdgeFunctionsVariables, + { enabled = true, ...options }: UseQueryOptions = {} +) => + useQuery( + edgeFunctionsKeys.list(projectRef), + ({ signal }) => getEdgeFunctions({ projectRef }, signal), + { enabled: enabled && typeof projectRef !== 'undefined', ...options } + ) + +export const useEdgeFunctionsPrefetch = ({ projectRef }: EdgeFunctionsVariables) => { + const client = useQueryClient() + + return useCallback(() => { + if (projectRef) { + client.prefetchQuery(edgeFunctionsKeys.list(projectRef), ({ signal }) => + getEdgeFunctions({ projectRef }, signal) + ) + } + }, [projectRef]) +} diff --git a/studio/data/edge-functions/keys.ts b/studio/data/edge-functions/keys.ts new file mode 100644 index 00000000000..dacd8d4a8dd --- /dev/null +++ b/studio/data/edge-functions/keys.ts @@ -0,0 +1,3 @@ +export const edgeFunctionsKeys = { + list: (projectRef: string | undefined) => ['projects', projectRef, 'edge-functions'] as const, +} diff --git a/studio/data/permissions/permissions-query.ts b/studio/data/permissions/permissions-query.ts index 45d73e0d0a5..0dff6750374 100644 --- a/studio/data/permissions/permissions-query.ts +++ b/studio/data/permissions/permissions-query.ts @@ -21,10 +21,9 @@ export async function getPermissions(signal?: AbortSignal) { export type PermissionsData = Awaited> export type PermissionsError = unknown -export const usePermissionsQuery = ({ - enabled = true, - ...options -}: UseQueryOptions = {}) => +export const usePermissionsQuery = ( + options: UseQueryOptions = {} +) => useQuery( permissionKeys.list(), ({ signal }) => getPermissions(signal), diff --git a/studio/data/table-rows/table-row-create-mutation.ts b/studio/data/table-rows/table-row-create-mutation.ts new file mode 100644 index 00000000000..0e8737f36f2 --- /dev/null +++ b/studio/data/table-rows/table-row-create-mutation.ts @@ -0,0 +1,63 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { Query, SupaTable } from 'components/grid' +import { executeSql } from 'data/sql/execute-sql-query' +import { sqlKeys } from 'data/sql/keys' + +export type TableRowCreateVariables = { + projectRef: string + connectionString?: string + table: SupaTable + payload: any + enumArrayColumns: string[] +} + +export function getTableRowCreateSql({ + table, + payload, + enumArrayColumns, +}: Pick) { + return new Query() + .from(table.name, table.schema ?? undefined) + .insert([payload], { returning: true, enumArrayColumns }) + .toSql() +} + +export async function createTableRow({ + projectRef, + connectionString, + table, + payload, + enumArrayColumns, +}: TableRowCreateVariables) { + const sql = getTableRowCreateSql({ table, payload, enumArrayColumns }) + + const { result } = await executeSql({ projectRef, connectionString, sql }) + + return result +} + +type TableRowCreateData = Awaited> + +export const useTableRowCreateMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => createTableRow(vars), + { + async onSuccess(data, variables, context) { + const { projectRef, table } = variables + + await queryClient.invalidateQueries(sqlKeys.query(projectRef, [table.schema, table.name])) + + await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/data/table-rows/table-row-delete-all-mutation.ts b/studio/data/table-rows/table-row-delete-all-mutation.ts new file mode 100644 index 00000000000..995851dbb15 --- /dev/null +++ b/studio/data/table-rows/table-row-delete-all-mutation.ts @@ -0,0 +1,70 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { Filter, Query, SupaTable } from 'components/grid' +import { executeSql } from 'data/sql/execute-sql-query' +import { sqlKeys } from 'data/sql/keys' +import { formatFilterValue } from './utils' + +export type TableRowDeleteAllVariables = { + projectRef: string + connectionString?: string + table: SupaTable + filters: Filter[] +} + +export function getTableRowDeleteAllSql({ + table, + filters, +}: Pick) { + let queryChains = new Query().from(table.name, table.schema ?? undefined).delete() + + filters + .filter((x) => x.value && x.value !== '') + .forEach((x) => { + const value = formatFilterValue(table, x) + queryChains = queryChains.filter(x.column, x.operator, value) + }) + + return queryChains.toSql() +} + +export async function deleteAllTableRow({ + projectRef, + connectionString, + table, + filters, +}: TableRowDeleteAllVariables) { + const sql = getTableRowDeleteAllSql({ table, filters }) + + const { result } = await executeSql({ projectRef, connectionString, sql }) + + return result +} + +type TableRowDeleteAllData = Awaited> + +/** + * For deleting all rows based on a given filter + */ +export const useTableRowDeleteAllMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => deleteAllTableRow(vars), + { + async onSuccess(data, variables, context) { + const { projectRef, table } = variables + + await queryClient.invalidateQueries(sqlKeys.query(projectRef, [table.schema, table.name])) + + await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/data/table-rows/table-row-delete-mutation.ts b/studio/data/table-rows/table-row-delete-mutation.ts new file mode 100644 index 00000000000..ec660bf90e2 --- /dev/null +++ b/studio/data/table-rows/table-row-delete-mutation.ts @@ -0,0 +1,70 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { Query, SupaRow, SupaTable } from 'components/grid' +import { executeSql } from 'data/sql/execute-sql-query' +import { sqlKeys } from 'data/sql/keys' +import { getPrimaryKeys } from './utils' + +export type TableRowDeleteVariables = { + projectRef: string + connectionString?: string + table: SupaTable + rows: SupaRow[] +} + +export function getTableRowDeleteSql({ + table, + rows, +}: Pick) { + const { primaryKeys, error } = getPrimaryKeys({ table }) + if (error) { + throw error + } + + let queryChains = new Query().from(table.name, table.schema ?? undefined).delete() + + primaryKeys!.forEach((key) => { + const primaryKeyValues = rows.map((x) => x[key]) + queryChains = queryChains.filter(key, 'in', primaryKeyValues) + }) + + return queryChains.toSql() +} + +export async function deleteTableRow({ + projectRef, + connectionString, + table, + rows, +}: TableRowDeleteVariables) { + const sql = getTableRowDeleteSql({ table, rows }) + + const { result } = await executeSql({ projectRef, connectionString, sql }) + + return result +} + +type TableRowDeleteData = Awaited> + +export const useTableRowDeleteMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => deleteTableRow(vars), + { + async onSuccess(data, variables, context) { + const { projectRef, table } = variables + + await queryClient.invalidateQueries(sqlKeys.query(projectRef, [table.schema, table.name])) + + await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/data/table-rows/table-row-truncate-mutation.ts b/studio/data/table-rows/table-row-truncate-mutation.ts new file mode 100644 index 00000000000..5093c0199c2 --- /dev/null +++ b/studio/data/table-rows/table-row-truncate-mutation.ts @@ -0,0 +1,54 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { Query, SupaTable } from 'components/grid' +import { executeSql } from 'data/sql/execute-sql-query' +import { sqlKeys } from 'data/sql/keys' + +export type TableRowTruncateVariables = { + projectRef: string + connectionString?: string + table: SupaTable +} + +export function getTableRowTruncateSql({ table }: Pick) { + let queryChains = new Query().from(table.name, table.schema ?? undefined).truncate() + + return queryChains.toSql() +} + +export async function truncateTableRow({ + projectRef, + connectionString, + table, +}: TableRowTruncateVariables) { + const sql = getTableRowTruncateSql({ table }) + + const { result } = await executeSql({ projectRef, connectionString, sql }) + + return result +} + +type TableRowTruncateData = Awaited> + +export const useTableRowTruncateMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => truncateTableRow(vars), + { + async onSuccess(data, variables, context) { + const { projectRef, table } = variables + + await queryClient.invalidateQueries(sqlKeys.query(projectRef, [table.schema, table.name])) + + await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/data/table-rows/table-row-update-mutation.ts b/studio/data/table-rows/table-row-update-mutation.ts new file mode 100644 index 00000000000..71f896656a8 --- /dev/null +++ b/studio/data/table-rows/table-row-update-mutation.ts @@ -0,0 +1,67 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { Query, SupaTable } from 'components/grid' +import { executeSql } from 'data/sql/execute-sql-query' +import { sqlKeys } from 'data/sql/keys' + +export type TableRowUpdateVariables = { + projectRef: string + connectionString?: string + table: SupaTable + configuration: { identifiers: any } + payload: any + enumArrayColumns: string[] +} + +export function getTableRowUpdateSql({ + table, + configuration, + payload, + enumArrayColumns, +}: Pick) { + return new Query() + .from(table.name, table.schema ?? undefined) + .update(payload, { returning: true, enumArrayColumns }) + .match(configuration.identifiers) + .toSql() +} + +export async function updateTableRow({ + projectRef, + connectionString, + table, + payload, + configuration, + enumArrayColumns, +}: TableRowUpdateVariables) { + const sql = getTableRowUpdateSql({ table, configuration, payload, enumArrayColumns }) + + const { result } = await executeSql({ projectRef, connectionString, sql }) + + return result +} + +type TableRowUpdateData = Awaited> + +export const useTableRowUpdateMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => updateTableRow(vars), + { + async onSuccess(data, variables, context) { + const { projectRef, table } = variables + + await queryClient.invalidateQueries(sqlKeys.query(projectRef, [table.schema, table.name])) + + await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/data/table-rows/table-rows-count-query.ts b/studio/data/table-rows/table-rows-count-query.ts new file mode 100644 index 00000000000..bb3e504c667 --- /dev/null +++ b/studio/data/table-rows/table-rows-count-query.ts @@ -0,0 +1,97 @@ +import { QueryKey, UseQueryOptions } from '@tanstack/react-query' +import { Filter, Query, SupaTable } from 'components/grid' +import { ExecuteSqlData, useExecuteSqlPrefetch, useExecuteSqlQuery } from '../sql/execute-sql-query' +import { formatFilterValue } from './utils' + +type GetTableRowsCountArgs = { + table?: SupaTable + filters?: Filter[] +} + +export const getTableRowsCountSqlQuery = ({ table, filters = [] }: GetTableRowsCountArgs) => { + const query = new Query() + + if (!table) { + return `` + } + + let queryChains = query.from(table.name, table.schema ?? undefined).count() + filters + .filter((x) => x.value && x.value != '') + .forEach((x) => { + const value = formatFilterValue(table, x) + queryChains = queryChains.filter(x.column, x.operator, value) + }) + + const sql = queryChains.toSql() + + return sql +} + +export type TableRowsCount = { + count: number +} + +export type TableRowsCountVariables = GetTableRowsCountArgs & { + projectRef?: string + connectionString?: string + queryKey?: QueryKey +} + +export type TableRowsCountData = TableRowsCount +export type TableRowsCountError = unknown + +export const useTableRowsCountQuery = ( + { projectRef, connectionString, queryKey, table, ...args }: TableRowsCountVariables, + options: UseQueryOptions = {} +) => + useExecuteSqlQuery( + { + projectRef, + connectionString, + sql: getTableRowsCountSqlQuery({ table, ...args }), + queryKey: [ + ...(queryKey ?? []), + { table: { name: table?.name, schema: table?.schema }, ...args }, + ], + }, + { + select(data) { + return { + count: data.result[0].count, + } as TData + }, + enabled: typeof projectRef !== 'undefined' && typeof table !== 'undefined', + ...options, + } + ) + +/** + * useTableRowsCountPrefetch is used for prefetching the table rows count. For example, starting a query loading before a page is navigated to. + * + * @example + * const prefetch = useTableRowsCountPrefetch({ ...args }) + * + * return ( + * prefetch()}> + * Start loading on hover + * + * ) + */ +export const useTableRowsCountPrefetch = ({ + projectRef, + connectionString, + queryKey, + table, + ...args +}: TableRowsCountVariables) => { + return useExecuteSqlPrefetch({ + projectRef, + connectionString, + sql: getTableRowsCountSqlQuery({ table, ...args }), + queryKey: [ + ...(queryKey ?? []), + { table: { name: table?.name, schema: table?.schema }, ...args }, + ], + }) +} diff --git a/studio/data/table-rows/table-rows-query.ts b/studio/data/table-rows/table-rows-query.ts new file mode 100644 index 00000000000..edbf5155767 --- /dev/null +++ b/studio/data/table-rows/table-rows-query.ts @@ -0,0 +1,128 @@ +import { QueryKey, UseQueryOptions } from '@tanstack/react-query' +import { Filter, Query, Sort, SupaRow, SupaTable } from 'components/grid' +import { ExecuteSqlData, useExecuteSqlPrefetch, useExecuteSqlQuery } from '../sql/execute-sql-query' +import { getPagination } from '../utils/pagination' +import { formatFilterValue } from './utils' + +type GetTableRowsArgs = { + table?: SupaTable + filters?: Filter[] + sorts?: Sort[] + limit?: number + page?: number +} + +export const getTableRowsSqlQuery = ({ + table, + filters = [], + sorts = [], + page, + limit, +}: GetTableRowsArgs) => { + const query = new Query() + + if (!table) { + return `` + } + + const enumArrayColumns = table.columns + .filter((column) => { + return (column?.enum ?? []).length > 0 && column.dataType.toLowerCase() === 'array' + }) + .map((column) => column.name) + + let queryChains = + enumArrayColumns.length > 0 + ? query + .from(table.name, table.schema ?? undefined) + .select(`*,${enumArrayColumns.map((x) => `"${x}"::text[]`).join(',')}`) + : query.from(table.name, table.schema ?? undefined).select() + + filters + .filter((x) => x.value && x.value != '') + .forEach((x) => { + const value = formatFilterValue(table, x) + queryChains = queryChains.filter(x.column, x.operator, value) + }) + sorts.forEach((x) => { + queryChains = queryChains.order(x.column, x.ascending, x.nullsFirst) + }) + + // getPagination is expecting to start from 0 + const { from, to } = getPagination((page ?? 1) - 1, limit) + const sql = queryChains.range(from, to).toSql() + + return sql +} + +export type TableRows = { + rows: SupaRow[] +} + +export type TableRowsVariables = GetTableRowsArgs & { + projectRef?: string + connectionString?: string + queryKey?: QueryKey +} + +export type TableRowsData = TableRows +export type TableRowsError = unknown + +export const useTableRowsQuery = ( + { projectRef, connectionString, queryKey, table, ...args }: TableRowsVariables, + options: UseQueryOptions = {} +) => + useExecuteSqlQuery( + { + projectRef, + connectionString, + sql: getTableRowsSqlQuery({ table, ...args }), + queryKey: [ + ...(queryKey ?? []), + { table: { name: table?.name, schema: table?.schema }, ...args }, + ], + }, + { + select(data) { + const rows = data.result.map((x: any, index: number) => { + return { idx: index, ...x } as SupaRow + }) + + return { + rows, + } as TData + }, + enabled: typeof projectRef !== 'undefined' && typeof table !== 'undefined', + ...options, + } + ) + +/** + * useTableRowsPrefetch is used for prefetching table rows. For example, starting a query loading before a page is navigated to. + * + * @example + * const prefetch = useTableRowsPrefetch({ projectRef }) + * + * return ( + * prefetch()}> + * Start loading on hover + * + * ) + */ +export const useTableRowsPrefetch = ({ + projectRef, + connectionString, + queryKey, + table, + ...args +}: TableRowsVariables) => { + return useExecuteSqlPrefetch({ + projectRef, + connectionString, + sql: getTableRowsSqlQuery({ table, ...args }), + queryKey: [ + ...(queryKey ?? []), + { table: { name: table?.name, schema: table?.schema }, ...args }, + ], + }) +} diff --git a/studio/data/table-rows/utils.ts b/studio/data/table-rows/utils.ts new file mode 100644 index 00000000000..b12295252d7 --- /dev/null +++ b/studio/data/table-rows/utils.ts @@ -0,0 +1,28 @@ +import { Filter, ServiceError, SupaTable } from 'components/grid' +import { ERROR_PRIMARY_KEY_NOTFOUND } from 'components/grid/constants' +import { isNumericalColumn } from 'components/grid/utils' + +/** + * temporary fix until we implement a better filter UI + * which validate input value base on the column type + */ +export function formatFilterValue(table: SupaTable, filter: Filter) { + const column = table.columns.find((x) => x.name == filter.column) + if (column && isNumericalColumn(column.format)) { + const numberValue = Number(filter.value) + if (Number.isNaN(numberValue)) return filter.value + else return Number(filter.value) + } + return filter.value +} + +export function getPrimaryKeys({ table }: { table: SupaTable }): { + primaryKeys?: string[] + error?: ServiceError +} { + const pkColumns = table.columns.filter((x) => x.isPrimaryKey) + if (!pkColumns || pkColumns.length == 0) { + return { error: { message: ERROR_PRIMARY_KEY_NOTFOUND } } + } + return { primaryKeys: pkColumns.map((x) => x.name) } +} diff --git a/studio/data/utils/pagination.ts b/studio/data/utils/pagination.ts new file mode 100644 index 00000000000..ea72c3e45d6 --- /dev/null +++ b/studio/data/utils/pagination.ts @@ -0,0 +1,11 @@ +/* Utils for use in queries / mutations */ + +export const DEFAULT_PAGE_SIZE = 100 + +export function getPagination(page?: number, size: number = DEFAULT_PAGE_SIZE) { + const limit = size + const from = page ? page * limit : 0 + const to = page ? from + size - 1 : size - 1 + + return { from, to } +} diff --git a/studio/hooks/analytics/useDbQuery.tsx b/studio/hooks/analytics/useDbQuery.tsx index 0130ca85ed9..1fab5b86eea 100644 --- a/studio/hooks/analytics/useDbQuery.tsx +++ b/studio/hooks/analytics/useDbQuery.tsx @@ -5,16 +5,19 @@ import { DbQueryData, DbQueryHandler, MetaQueryResponse, - DbQuery, + ReportQuery, } from 'components/interfaces/Reports/Reports.types' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { executeSql } from 'data/sql/execute-sql-query' -type UseDbQuery = (sql: DbQuery['sql'], params?: BaseReportParams) => [DbQueryData, DbQueryHandler] +type UseDbQuery = ( + sql: ReportQuery['sql'], + params?: BaseReportParams +) => [DbQueryData, DbQueryHandler] const useDbQuery: UseDbQuery = (sql, params = DEFAULT_QUERY_PARAMS) => { const { project } = useProjectContext() - const resolvedSql = typeof sql === 'function' ? sql(params) : sql + const resolvedSql = typeof sql === 'function' ? sql([]) : sql const { data, diff --git a/studio/hooks/misc/withAuth.tsx b/studio/hooks/misc/withAuth.tsx index b41734cb1c8..d2cdc5d94fd 100644 --- a/studio/hooks/misc/withAuth.tsx +++ b/studio/hooks/misc/withAuth.tsx @@ -50,6 +50,7 @@ export function withAuth( }) usePermissionsQuery({ + enabled: IS_PLATFORM, onSuccess(permissions) { ui.setPermissions(permissions) }, diff --git a/studio/lib/common/fetch/base.ts b/studio/lib/common/fetch/base.ts index 45597dc3588..4116526bf6b 100644 --- a/studio/lib/common/fetch/base.ts +++ b/studio/lib/common/fetch/base.ts @@ -12,6 +12,9 @@ export async function handleResponse( response: Response, requestId: string ): Promise> { + const contentType = response.headers.get('Content-Type') + if (contentType === 'application/octet-stream') return response as any + try { const resTxt = await response.text() try { diff --git a/studio/lib/common/fetch/post.ts b/studio/lib/common/fetch/post.ts index c71f86a14b6..ce29bada3c6 100644 --- a/studio/lib/common/fetch/post.ts +++ b/studio/lib/common/fetch/post.ts @@ -9,7 +9,7 @@ export async function post( ): Promise> { const requestId = uuidv4() try { - const { headers: optionHeaders, ...otherOptions } = options ?? {} + const { headers: optionHeaders, abortSignal, ...otherOptions } = options ?? {} const headers = await constructHeaders(requestId, optionHeaders) const response = await fetch(url, { method: 'POST', @@ -17,6 +17,7 @@ export async function post( referrerPolicy: 'no-referrer-when-downgrade', headers, ...otherOptions, + signal: abortSignal, }) if (!response.ok) return handleResponseError(response, requestId) return handleResponse(response, requestId) diff --git a/studio/lib/pingPostgrest.ts b/studio/lib/pingPostgrest.ts index 623775327d8..42919651b3f 100644 --- a/studio/lib/pingPostgrest.ts +++ b/studio/lib/pingPostgrest.ts @@ -2,6 +2,8 @@ import semver from 'semver' import { headWithTimeout, getWithTimeout } from './common/fetch' import { API_URL } from './constants' +const DEFAULT_TIMEOUT_MILLISECONDS = 2000 + /** * Ping Postgrest for health check. Default timeout in 2s. * @@ -15,7 +17,6 @@ import { API_URL } from './constants' * @return true if ping is successful else false */ async function pingPostgrest( - restUrl: string, projectRef: string, options?: { kpsVersion?: string @@ -23,8 +24,6 @@ async function pingPostgrest( } ) { if (projectRef === undefined) return false - const apiKey = await getApiKey(projectRef, options?.timeout) - if (apiKey === undefined) return false const { kpsVersion, timeout } = options ?? {} const healthCheckApiEnable = semver.gte( @@ -33,35 +32,21 @@ async function pingPostgrest( semver.coerce('kps-v3.8.6') ) if (healthCheckApiEnable) { - const healthCheckUrl = `${restUrl.replace('/rest/', '/rest-admin/')}live` - return pingHealthCheckApi(healthCheckUrl, apiKey, timeout) + return pingHealthCheckApi(projectRef, timeout) + } else { + return pingOpenApi(projectRef, timeout) } - - return pingOpenApi(restUrl, apiKey, timeout) } + export default pingPostgrest -const getApiKey = async ( - projectRef: string, - timeout = DEFAULT_TIMEOUT_MILLISECONDS -): Promise => { - const response = await getWithTimeout(`${API_URL}/props/project/${projectRef}/api`, { timeout }) - if (response.error || response.autoApiService.service_api_keys.length === 0) return undefined - return response.autoApiService.defaultApiKey -} - -const DEFAULT_TIMEOUT_MILLISECONDS = 2000 - /** * Send a HEAD request to postgrest OpenAPI. * * @return true if there's no error else false */ -async function pingOpenApi(url: string, apikey: string, timeout?: number) { - const headers = { apikey, Authorization: `Bearer ${apikey}` } - const { error } = await headWithTimeout(url, [], { - headers, - credentials: 'omit', +async function pingOpenApi(ref: string, timeout?: number) { + const { error } = await headWithTimeout(`${API_URL}/projects/${ref}/api/rest`, [], { timeout: timeout ?? DEFAULT_TIMEOUT_MILLISECONDS, }) return error === undefined @@ -72,11 +57,8 @@ async function pingOpenApi(url: string, apikey: string, timeout?: number) { * * @return true if there's no error else false */ -async function pingHealthCheckApi(url: string, apikey: string, timeout?: number) { - const headers = { apikey } - const { error } = await getWithTimeout(url, { - headers, - credentials: 'omit', +async function pingHealthCheckApi(ref: string, timeout?: number) { + const { error } = await getWithTimeout(`${API_URL}/projects/${ref}/live`, { timeout: timeout ?? DEFAULT_TIMEOUT_MILLISECONDS, }) return error === undefined diff --git a/studio/localStores/storageExplorer/StorageExplorerStore.js b/studio/localStores/storageExplorer/StorageExplorerStore.js index 77d26ceda79..57533c66f99 100644 --- a/studio/localStores/storageExplorer/StorageExplorerStore.js +++ b/studio/localStores/storageExplorer/StorageExplorerStore.js @@ -1,13 +1,14 @@ import toast from 'react-hot-toast' import { createContext, useContext } from 'react' import { makeAutoObservable } from 'mobx' -import { find, compact, isEqual, isNil, has, some, chunk, get, uniq } from 'lodash' +import { find, compact, isEqual, has, some, chunk, uniq, uniqBy, findIndex } from 'lodash' import { BlobReader, BlobWriter, ZipWriter } from '@zip.js/zip.js' import { createClient } from '@supabase/supabase-js' import { useStore } from 'hooks' import { copyToClipboard } from 'lib/helpers' -import { IS_PLATFORM } from 'lib/constants' +import { API_URL, IS_PLATFORM } from 'lib/constants' +import { get, patch, post, delete_ } from 'lib/common/fetch' import { PROJECT_ENDPOINT_PROTOCOL } from 'pages/api/constants' import { STORAGE_VIEWS, @@ -71,6 +72,8 @@ class StorageExplorerStore { /* Supabase client */ supabaseClient = null + /* [Joshen] Move towards using API */ + endpoint = '' /* FE to toggle page level modals */ showCreateBucketModal = false @@ -99,7 +102,8 @@ class StorageExplorerStore { initStore(projectRef, url, serviceKey, protocol = PROJECT_ENDPOINT_PROTOCOL) { this.projectRef = projectRef - this.initializeSupabaseClient(serviceKey, url, protocol) + this.endpoint = `${API_URL}/storage/${projectRef}` + if (serviceKey !== undefined) this.initializeSupabaseClient(serviceKey, url, protocol) } /* Methods which are commonly used + For better readability */ @@ -353,9 +357,9 @@ class StorageExplorerStore { .upload(formattedPathToEmptyPlaceholderFile, new File([], EMPTY_FOLDER_PLACEHOLDER_FILE_NAME)) if (pathToFolder.length > 0) { - await this.supabaseClient.storage - .from(this.selectedBucket.name) - .remove([`${pathToFolder}/${EMPTY_FOLDER_PLACEHOLDER_FILE_NAME}`]) + await delete_(`${this.endpoint}/buckets/${this.selectedBucket.id}/objects`, { + paths: [`${pathToFolder}/${EMPTY_FOLDER_PLACEHOLDER_FILE_NAME}`], + }) } } @@ -373,8 +377,8 @@ class StorageExplorerStore { this.selectedFilePreview = { ...file, previewUrl: 'loading' } const cachedPreview = find(this.filePreviewCache, { id: file.id }) - const fetchedAt = get(cachedPreview, ['fetchedAt'], null) - const expiresIn = get(cachedPreview, ['expiresIn'], null) + const fetchedAt = cachedPreview?.fetchedAt ?? null + const expiresIn = cachedPreview?.expiresIn ?? null const existsInCache = fetchedAt !== null && expiresIn !== null const isExpired = existsInCache ? fetchedAt + expiresIn * 1000 < Date.now() : true @@ -454,28 +458,16 @@ class StorageExplorerStore { /* Bucket CRUD */ createBucket = async (bucketName, isPublic = false) => { - if (isNil(this.supabaseClient)) { - return this.ui.setNotification({ - message: 'Failed to initialize supabase client, try refreshing your browser.', - category: 'error', - }) - } - - const { data, error } = await this.supabaseClient.storage.createBucket(bucketName, { - public: isPublic, - }) - if (error) { - this.ui.setNotification({ - message: error.message, - category: 'error', - }) + const res = await post(`${this.endpoint}/buckets`, { id: bucketName, public: isPublic }) + if (res.error) { + this.ui.setNotification({ category: 'error', message: res.error.message }) this.closeCreateBucketModal() return undefined + } else { + await this.fetchBuckets() + this.closeCreateBucketModal() + return res } - - await this.fetchBuckets() - this.closeCreateBucketModal() - return data } openBucket = async (bucket) => { @@ -488,10 +480,10 @@ class StorageExplorerStore { } fetchBuckets = async () => { - const { data: buckets, error } = await this.supabaseClient.storage.listBuckets() - if (error) return this.ui.setNotification({ message: error.message, category: 'error' }) + const res = await get(`${this.endpoint}/buckets`) + if (res.error) return this.ui.setNotification({ category: 'error', message: res.error.message }) - const formattedBuckets = buckets.map((bucket) => { + const formattedBuckets = res.map((bucket) => { return { ...bucket, type: STORAGE_ROW_TYPES.BUCKET, status: STORAGE_ROW_STATUS.READY } }) this.buckets = formattedBuckets @@ -503,15 +495,15 @@ class StorageExplorerStore { // hence delete bucket and empty bucket are coupled tightly here const { id, name: bucketName } = bucket - const { error: emptyBucketError } = await this.supabaseClient.storage.emptyBucket(id) - if (emptyBucketError) { - this.ui.setNotification({ message: emptyBucketError.message, category: 'error' }) + const emptyBucketRes = await post(`${this.endpoint}/buckets/${id}/empty`, {}) + if (emptyBucketRes.error) { + this.ui.setNotification({ category: 'error', message: emptyBucketRes.error.message }) return false } - const { error: deleteBucketError } = await this.supabaseClient.storage.deleteBucket(id) - if (deleteBucketError) { - this.ui.setNotification({ message: deleteBucketError.message, category: 'error' }) + const deleteBucketRes = await delete_(`${this.endpoint}/buckets/${id}`) + if (deleteBucketRes.error) { + this.ui.setNotification({ category: 'error', message: deleteBucketRes.error.message }) return false } @@ -527,13 +519,9 @@ class StorageExplorerStore { } toggleBucketPublic = async (bucket) => { - const { name: bucketName } = bucket - - const { data, error } = await this.supabaseClient.storage.updateBucket(bucketName, { - public: !bucket.public, - }) - if (error) { - this.ui.setNotification({ message: error.message, category: 'error' }) + const res = await patch(`${this.endpoint}/buckets/${bucket.id}`, { public: !bucket.public }) + if (res.error) { + this.ui.setNotification({ category: 'error', message: res.error.message }) return this.closeToggleBucketPublicModal() } @@ -619,7 +607,7 @@ class StorageExplorerStore { // If we're uploading a folder which name already exists in the same folder that we're uploading to // We sanitize the folder name and let all file uploads through. (This is only via drag drop) - const topLevelFolders = get(this.columns, [derivedColumnIndex, 'items'], []) + const topLevelFolders = (this.columns?.[derivedColumnIndex]?.items ?? []) .filter((item) => !item.id) .map((item) => item.name) const formattedFilesToUpload = filesToUpload.map((file) => { @@ -666,7 +654,7 @@ class StorageExplorerStore { const fileOptions = { cacheControl: '3600' } const metadata = { mimetype: file.type, size: file.size } - const isWithinFolder = get(file, ['path'], '').split('/').length > 1 + const isWithinFolder = (file?.path ?? '').split('/').length > 1 const fileName = !isWithinFolder ? this.sanitizeNameForDuplicateInColumn(file.name, autofix) : file.name @@ -740,9 +728,9 @@ class StorageExplorerStore { }, Promise.resolve()) if (numberOfFilesUploadedSuccess > 0) { - await this.supabaseClient.storage - .from(this.selectedBucket.name) - .remove([`${pathToFile}/${EMPTY_FOLDER_PLACEHOLDER_FILE_NAME}`]) + await delete_(`${this.endpoint}/buckets/${this.selectedBucket.id}/objects`, { + paths: [`${pathToFile}/${EMPTY_FOLDER_PLACEHOLDER_FILE_NAME}`], + }) } await this.refetchAllOpenedFolders() @@ -810,16 +798,13 @@ class StorageExplorerStore { const toPath = newPathToFile.length > 0 ? `${formattedNewPathToFile}/${item.name}` : item.name - const { error } = await this.supabaseClient.storage - .from(this.selectedBucket.name) - .move(fromPath, toPath) - - if (error) { + const res = await post(`${this.endpoint}/buckets/${this.selectedBucket.id}/objects/move`, { + from: fromPath, + to: toPath, + }) + if (res.error) { numberOfFilesMovedFail += 1 - this.ui.setNotification({ - message: error.message, - category: 'error', - }) + this.ui.setNotification({ category: 'error', message: res.error.message }) } }) ) @@ -857,23 +842,20 @@ class StorageExplorerStore { const formattedPathToFile = pathToFile.length > 0 ? `${pathToFile}/${fileName}` : fileName if (this.selectedBucket.public) { - const { data, error } = await this.supabaseClient.storage - .from(this.selectedBucket.name) - .getPublicUrl(formattedPathToFile) - - if (!error) { - return data.publicUrl - } + const res = await post( + `${this.endpoint}/buckets/${this.selectedBucket.id}/objects/public-url`, + { path: formattedPathToFile } + ) + if (!res.error) return res.publicUrl + else console.error('Failed to fetch public file preview', res.error.message) + } else { + const res = await post(`${this.endpoint}/buckets/${this.selectedBucket.id}/objects/sign`, { + path: formattedPathToFile, + expiresIn: expiresIn || DEFAULT_EXPIRY, + }) + if (!res.error) return res.signedUrl + else console.error('Failed to fetch signed url preview', res.error.message) } - - const { data, error } = await this.supabaseClient.storage - .from(this.selectedBucket.name) - .createSignedUrl(formattedPathToFile, expiresIn || DEFAULT_EXPIRY) - - if (!error) { - return data.signedUrl - } - return null } @@ -907,7 +889,9 @@ class StorageExplorerStore { // batch BATCH_SIZE prefixes per request const batches = chunk(prefixes, BATCH_SIZE).map((batch) => () => { progress = progress + batch.length / prefixes.length - return this.supabaseClient.storage.from(this.selectedBucket.name).remove(batch) + return delete_(`${this.endpoint}/buckets/${this.selectedBucket.name}/objects`, { + paths: batch, + }) }) // make BATCH_SIZE requests at the same time @@ -973,17 +957,18 @@ class StorageExplorerStore { const fileMimeType = file.metadata?.mimetype ?? null return () => { return new Promise(async (resolve) => { - const { data, error } = await this.supabaseClient.storage - .from(this.selectedBucket.name) - .download(`${file.prefix}/${file.name}`) - + const res = await post( + `${this.endpoint}/buckets/${this.selectedBucket.id}/objects/download`, + { path: `${file.prefix}/${file.name}` } + ) progress = progress + 1 / files.length - if (!error) { + if (!res.error) { + const blob = await res.blob() resolve({ name: file.name, prefix: file.prefix, - blob: new Blob([data], { type: fileMimeType }), + blob: new Blob([blob], { type: fileMimeType }), }) } else { resolve(false) @@ -1095,7 +1080,7 @@ class StorageExplorerStore { downloadFile = async (file, showToast = true, returnBlob = false) => { const fileName = file.name - const fileMimeType = get(file, ['metadata', 'mimetype'], null) + const fileMimeType = file?.metadata?.mimetype ?? null const toastId = showToast ? this.ui.setNotification({ category: 'loading', message: `Retrieving ${fileName}...` }) @@ -1106,13 +1091,12 @@ class StorageExplorerStore { .map((folder) => folder.name) .join('/') const formattedPathToFile = pathToFile.length > 0 ? `${pathToFile}/${fileName}` : fileName + const res = await post(`${this.endpoint}/buckets/${this.selectedBucket.id}/objects/download`, { + path: formattedPathToFile, + }) - const { data, error } = await this.supabaseClient.storage - .from(this.selectedBucket.name) - .download(formattedPathToFile) - - if (!error) { - const blob = data + if (!res.error) { + const blob = await res.blob() const newBlob = new Blob([blob], { type: fileMimeType }) if (returnBlob) return { name: fileName, blob: newBlob } @@ -1136,7 +1120,7 @@ class StorageExplorerStore { } else { if (toastId) { this.ui.setNotification({ - error, + error: error, id: toastId, category: 'error', message: `Failed to download ${fileName}`, @@ -1157,15 +1141,14 @@ class StorageExplorerStore { const fromPath = pathToFile.length > 0 ? `${pathToFile}/${originalName}` : originalName const toPath = pathToFile.length > 0 ? `${pathToFile}/${newName}` : newName - const { error } = await this.supabaseClient.storage - .from(this.selectedBucket.name) - .move(fromPath, toPath) - if (error) { - this.ui.setNotification({ - message: error.message, - type: 'error', - }) + const res = await post(`${this.endpoint}/buckets/${this.selectedBucket.id}/objects/move`, { + from: fromPath, + to: toPath, + }) + + if (res.error) { + this.ui.setNotification({ category: 'error', message: res.error.message }) } await this.refetchAllOpenedFolders() @@ -1195,16 +1178,16 @@ class StorageExplorerStore { search: searchString, sortBy: { column: this.sortBy, order: this.sortByOrder }, } - const parameters = { signal: this.abortController.signal } - - const { data: items, error } = await this.supabaseClient.storage - .from(this.selectedBucket.name) - .list(prefix, options, parameters) + const res = await post( + `${this.endpoint}/buckets/${this.selectedBucket.id}/objects/list`, + { path: prefix, options }, + { abortSignal: this.abortController.signal } + ) this.updateRowStatus(folderName, STORAGE_ROW_STATUS.READY, index) - if (!error) { - const formattedItems = this.formatFolderItems(items) + if (!res.error) { + const formattedItems = this.formatFolderItems(res) this.pushColumnAtIndex( { id: folderId || folderName, @@ -1228,22 +1211,23 @@ class StorageExplorerStore { search: searchString, sortBy: { column: this.sortBy, order: this.sortByOrder }, } - const parameters = { signal: this.abortController.signal } - const { data: items, error } = await this.supabaseClient.storage - .from(this.selectedBucket.name) - .list(prefix, options, parameters) + const res = await post( + `${this.endpoint}/buckets/${this.selectedBucket.id}/objects/list`, + { path: prefix, options }, + { abortSignal: this.abortController.signal } + ) - if (!error) { + if (!res.error) { // Add items to column - const formattedItems = this.formatFolderItems(items) + const formattedItems = this.formatFolderItems(res) this.columns = this.columns.map((col, idx) => { if (idx === index) { return { ...col, items: col.items.concat(formattedItems), isLoadingMoreItems: false, - hasMoreItems: items.length === LIMIT, + hasMoreItems: res.length === LIMIT, } } return col @@ -1268,15 +1252,12 @@ class StorageExplorerStore { sortBy: { column: this.sortBy, order: this.sortByOrder }, } - const { data: items, error } = await this.supabaseClient.storage - .from(this.selectedBucket.name) - .list(prefix, options) - - if (error) { - console.error('Error at fetchFoldersByPath:', error) - } - - return items + const res = await post(`${this.endpoint}/buckets/${this.selectedBucket.id}/objects/list`, { + path: prefix, + options, + }) + if (res.error) console.error('Error at fetchFoldersByPath:', error) + return res }) ) @@ -1314,10 +1295,11 @@ class StorageExplorerStore { // Check parent folder if its empty, if yes, reinstate .emptyFolderPlaceholder // Used when deleting folder or deleting files validateParentFolderEmpty = async (parentFolderPrefix) => { - const { data: items, error } = await this.supabaseClient.storage - .from(this.selectedBucket.name) - .list(parentFolderPrefix, this.DEFAULT_OPTIONS) - if (!error && items.length === 0) { + const res = await post(`${this.endpoint}/buckets/${this.selectedBucket.id}/objects/list`, { + path: parentFolderPrefix, + options: this.DEFAULT_OPTIONS, + }) + if (!res.error && res.length === 0) { const prefixToPlaceholder = `${parentFolderPrefix}/${EMPTY_FOLDER_PLACEHOLDER_FILE_NAME}` await this.supabaseClient.storage .from(this.selectedBucket.name) @@ -1396,10 +1378,14 @@ class StorageExplorerStore { return () => { return new Promise(async (resolve) => { progress = progress + 1 / files.length - const { error } = await this.supabaseClient.storage - .from(this.selectedBucket.name) - .move(fromPath, toPath) - if (error) { + const res = await post( + `${this.endpoint}/buckets/${this.selectedBucket.name}/objects/move`, + { + from: fromPath, + to: toPath, + } + ) + if (res.error) { hasErrors = true this.ui.setNotification({ category: 'error', @@ -1487,12 +1473,13 @@ class StorageExplorerStore { let folderContents = [] for (;;) { - const { data } = await this.supabaseClient.storage - .from(this.selectedBucket.name) - .list(formattedPathToFolder, options) - folderContents = folderContents.concat(data) + const res = await post(`${this.endpoint}/buckets/${this.selectedBucket.name}/objects/list`, { + path: formattedPathToFolder, + options, + }) + folderContents = folderContents.concat(res) options.offset += options.limit - if ((data || []).length < options.limit) { + if ((res || []).length < options.limit) { break } } @@ -1690,6 +1677,40 @@ class StorageExplorerStore { this.sortByOrder = sortByOrder } } + + selectRangeItems = (columnIndex, toItemIndex) => { + const columnItems = this.columns[columnIndex].items + const toItem = columnItems[toItemIndex] + const selectedItemIds = this.selectedItems.map((item) => item.id) + const lastSelectedItemId = selectedItemIds[selectedItemIds.length - 1] + const lastSelectedItemIndex = findIndex(columnItems, { id: lastSelectedItemId }) + + // Get the start and end index of the range to select + const start = Math.min(toItemIndex, lastSelectedItemIndex) + const end = Math.max(toItemIndex, lastSelectedItemIndex) + + // Get the range to select and reverse the order if necessary + const rangeToSelect = columnItems + .slice(start, end + 1) + // we need `columnIndex` in all item of `selectedItems` + .map((item) => ({ ...item, columnIndex })) + if (toItemIndex < lastSelectedItemIndex) { + rangeToSelect.reverse() + } + + if (selectedItemIds.includes(toItem.id)) { + const rangeToDeselectIds = rangeToSelect.map((item) => item.id) + // Deselect all items within the selection range + this.setSelectedItems( + this.selectedItems.filter( + (item) => item.id === toItem.id || !rangeToDeselectIds.includes(item.id) + ) + ) + } else { + // Select items within the range + this.setSelectedItems(uniqBy(this.selectedItems.concat(rangeToSelect), 'id')) + } + } } export default StorageExplorerStore diff --git a/studio/package.json b/studio/package.json index db300011bdd..c51eda86ca4 100644 --- a/studio/package.json +++ b/studio/package.json @@ -15,6 +15,8 @@ "prettier:write": "prettier --write ." }, "dependencies": { + "@graphiql/react": "^0.15.0", + "@graphiql/toolkit": "^0.8.0", "@hcaptcha/react-hcaptcha": "^1.4.4", "@headlessui/react": "^1.4.1", "@monaco-editor/react": "^4.3.1", @@ -28,7 +30,7 @@ "@stripe/stripe-js": "^1.25.0", "@supabase/react-data-grid": "^7.1.0-beta.7", "@supabase/shared-types": "^0.1.29", - "@supabase/supabase-js": "^2.1.1", + "@supabase/supabase-js": "^2.8.0", "@tanstack/react-query": "^4.22.0", "@tanstack/react-query-devtools": "^4.22.0", "@zip.js/zip.js": "^2.6.61", @@ -37,12 +39,15 @@ "awesome-debounce-promise": "^2.1.0", "blueimp-md5": "^2.19.0", "clipboard": "^2.0.8", + "clsx": "^1.2.1", "common": "*", "config": "*", "configcat-js": "^7.0.0", "dayjs": "^1.11.0", "file-saver": "^2.0.5", "generate-password": "^1.7.0", + "graphql": "^16.6.0", + "graphql-ws": "^5.11.3", "html-to-image": "^1.10.8", "immutability-helper": "^3.1.1", "ip-address": "^8.1.0", @@ -101,7 +106,7 @@ "@storybook/addon-links": "^6.4.19", "@storybook/react": "^6.4.19", "@storybook/testing-library": "^0.0.9", - "@supabase/postgres-meta": "^0.60.2", + "@supabase/postgres-meta": "^0.60.7", "@tailwindcss/typography": "^0.5.2", "@testing-library/dom": "^8.19.0", "@testing-library/react": "^12.1.5", diff --git a/studio/pages/_app.tsx b/studio/pages/_app.tsx index d97675d69ef..9f03a99259e 100644 --- a/studio/pages/_app.tsx +++ b/studio/pages/_app.tsx @@ -10,12 +10,13 @@ import 'styles/contextMenu.scss' import 'styles/react-data-grid-logs.scss' import 'styles/date-picker.scss' import 'styles/grid.scss' -import 'styles/users-table.scss' +import 'styles/graphiql-base.scss' import dayjs from 'dayjs' import customParseFormat from 'dayjs/plugin/customParseFormat' import timezone from 'dayjs/plugin/timezone' import utc from 'dayjs/plugin/utc' +import relativeTime from 'dayjs/plugin/relativeTime' // @ts-ignore import Prism from 'prism-react-renderer/prism' @@ -44,6 +45,7 @@ import useAutoAuthRedirect from 'hooks/misc/useAutoAuthRedirect' dayjs.extend(customParseFormat) dayjs.extend(utc) dayjs.extend(timezone) +dayjs.extend(relativeTime) dart(Prism) diff --git a/studio/pages/project/[ref]/api/graphiql.tsx b/studio/pages/project/[ref]/api/graphiql.tsx new file mode 100644 index 00000000000..6fe1b70347c --- /dev/null +++ b/studio/pages/project/[ref]/api/graphiql.tsx @@ -0,0 +1,63 @@ +import { useEffect, useMemo } from 'react' +import { observer } from 'mobx-react-lite' +import { createGraphiQLFetcher } from '@graphiql/toolkit' + +import { NextPageWithLayout } from 'types' +import { useParams, useStore } from 'hooks' +import { API_URL } from 'lib/constants' +import ExtensionCard from 'components/interfaces/Database/Extensions/ExtensionCard' +import GraphiQL from 'components/interfaces/GraphQL/GraphiQL' +import { DocsLayout } from 'components/layouts' +import Connecting from 'components/ui/Loading/Loading' +import { useSessionAccessTokenQuery } from 'data/auth/session-access-token-query' + +const GraphiQLPage: NextPageWithLayout = () => { + const { ref: projectRef } = useParams() + const { ui, meta } = useStore() + + const isExtensionsLoading = meta.extensions.isLoading + const pgGraphqlExtension = meta.extensions.byId('pg_graphql') + const { data: accessToken } = useSessionAccessTokenQuery() + + useEffect(() => { + if (ui.selectedProject?.ref) { + // Schemas may be needed when enabling the GraphQL extension + meta.schemas.load() + meta.extensions.load() + } + }, [ui.selectedProject?.ref]) + + const graphqlUrl = `${API_URL}/projects/${projectRef}/api/graphql` + + const fetcher = useMemo(() => { + return createGraphiQLFetcher({ url: graphqlUrl, fetch }) + }, [graphqlUrl]) + + if (!accessToken || (isExtensionsLoading && !pgGraphqlExtension)) { + return + } + + if (pgGraphqlExtension?.installed_version === null) { + return ( +
    +
    +
    +

    Enable the GraphQL Extension

    +

    + Toggle the switch below to enable the GraphQL extension. You can then use the GraphQL + API with your Supabase Database. +

    +
    + + +
    +
    + ) + } + + return +} + +GraphiQLPage.getLayout = (page) => {page} + +export default observer(GraphiQLPage) diff --git a/studio/pages/project/[ref]/api/index.tsx b/studio/pages/project/[ref]/api/index.tsx index 82bed5db772..3ceb55c3383 100644 --- a/studio/pages/project/[ref]/api/index.tsx +++ b/studio/pages/project/[ref]/api/index.tsx @@ -1,5 +1,4 @@ import { useRouter } from 'next/router' -import { Button, Dropdown, IconKey } from 'ui' import { FC, createContext, useContext, useEffect, useState } from 'react' import { observer, useLocalObservable } from 'mobx-react-lite' @@ -11,6 +10,7 @@ import { DocsLayout } from 'components/layouts' import { GeneralContent, ResourceContent, RpcContent } from 'components/interfaces/Docs' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useProjectJsonSchemaQuery } from 'data/docs/project-json-schema-query' +import LangSelector from 'components/interfaces/Docs/LangSelector' const PageContext = createContext(null) @@ -77,22 +77,12 @@ const DocView: FC = observer(({}) => { const anonKey = apiService?.service_api_keys.find((x) => x.name === 'anon key') ? apiService.defaultApiKey : undefined - const swaggerUrl = data?.autoApiService?.restUrl - - const canReadServiceKey = checkPermissions( - PermissionAction.READ, - 'service_api_keys.service_role_key' - ) const { data: jsonSchema, error: jsonSchemaError, refetch, - } = useProjectJsonSchemaQuery({ - projectRef, - swaggerUrl, - apiKey: anonKey, - }) + } = useProjectJsonSchemaQuery({ projectRef }) useEffect(() => { PageState.setJsonSchema(jsonSchema) @@ -104,7 +94,7 @@ const DocView: FC = observer(({}) => { if (error || jsonSchemaError) return ( -
    +

    Error connecting to API

    {`${error || jsonSchemaError}`}

    @@ -113,7 +103,7 @@ const DocView: FC = observer(({}) => { ) if (!data || !jsonSchema || !PageState.jsonSchema) return ( -
    +

    Building docs ...

    ) @@ -129,81 +119,17 @@ const DocView: FC = observer(({}) => { const PAGE_KEY: any = resource || rpc || page || 'index' return ( -
    +
    -
    -
    -
    - - - {selectedLang == 'bash' && ( -
    -
    - - Project API key : -
    - - setShowApiKey(DEFAULT_KEY)}> - hide - - - setShowApiKey({ - key: anonKey, - name: 'anon (public)', - }) - } - > - anon (public) - - {canReadServiceKey && ( - - setShowApiKey({ - key: autoApiService.serviceApiKey, - name: 'service_role (secret)', - }) - } - > - service_role (secret) - - )} - - } - > - - -
    - )} -
    -
    +
    +
    {resource ? ( diff --git a/studio/pages/project/[ref]/database/backups/pitr.tsx b/studio/pages/project/[ref]/database/backups/pitr.tsx index 5f3eb4aadad..694e62f97c7 100644 --- a/studio/pages/project/[ref]/database/backups/pitr.tsx +++ b/studio/pages/project/[ref]/database/backups/pitr.tsx @@ -65,7 +65,7 @@ const PITR = () => { primaryText="Point in time recovery is a Pro plan add-on." secondaryText={ tier === PRICING_TIER_PRODUCT_IDS.FREE - ? 'Please upgrade to the Pro plan with the PITR add-on selected to enable point in time recovery for your project.' + ? 'Upgrade to the Pro plan with the PITR add-on selected to enable point in time recovery for your project.' : 'Please enable the add-on to enable point in time recovery for your project.' } /> diff --git a/studio/pages/project/[ref]/database/wrappers/[id].tsx b/studio/pages/project/[ref]/database/wrappers/[id].tsx index a5abeb5df61..bf197056957 100644 --- a/studio/pages/project/[ref]/database/wrappers/[id].tsx +++ b/studio/pages/project/[ref]/database/wrappers/[id].tsx @@ -9,6 +9,7 @@ import { EditWrapper } from 'components/interfaces/Database' const DatabaseWrappersNew: NextPageWithLayout = () => { const canReadWrappers = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'wrappers') + if (!canReadWrappers) { return } diff --git a/studio/pages/project/[ref]/database/wrappers/index.tsx b/studio/pages/project/[ref]/database/wrappers/index.tsx index ead5fba1e25..d8df5feae34 100644 --- a/studio/pages/project/[ref]/database/wrappers/index.tsx +++ b/studio/pages/project/[ref]/database/wrappers/index.tsx @@ -9,7 +9,7 @@ import NoPermission from 'components/ui/NoPermission' import { FormsContainer } from 'components/ui/Forms' const DatabaseWrappers: NextPageWithLayout = () => { - const canReadWrappers = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'wrappers') + const canReadWrappers = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'tables') if (!canReadWrappers) { return } diff --git a/studio/pages/project/[ref]/database/wrappers/new.tsx b/studio/pages/project/[ref]/database/wrappers/new.tsx index 4db0245131f..225b4051123 100644 --- a/studio/pages/project/[ref]/database/wrappers/new.tsx +++ b/studio/pages/project/[ref]/database/wrappers/new.tsx @@ -8,9 +8,9 @@ import NoPermission from 'components/ui/NoPermission' import { CreateWrapper } from 'components/interfaces/Database' const DatabaseWrappersNew: NextPageWithLayout = () => { - const canReadWrappers = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'wrappers') - if (!canReadWrappers) { - return + const canCreateWrappers = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') + if (!canCreateWrappers) { + return } return diff --git a/studio/pages/project/[ref]/editor/[id].tsx b/studio/pages/project/[ref]/editor/[id].tsx index c0f9c541c85..a6dc7cab4d3 100644 --- a/studio/pages/project/[ref]/editor/[id].tsx +++ b/studio/pages/project/[ref]/editor/[id].tsx @@ -1,5 +1,4 @@ import { useEffect, useState } from 'react' -import { NextPage } from 'next' import Link from 'next/link' import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' @@ -12,13 +11,13 @@ import { Dictionary } from 'components/grid' import { TableEditorLayout } from 'components/layouts' import { TableGridEditor } from 'components/interfaces' import ConfirmationModal from 'components/ui/ConfirmationModal' -import { SchemaView } from 'types' +import { NextPageWithLayout, SchemaView } from 'types' import { JsonEditValue } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types' -import { tryParseJson } from 'lib/helpers' +import { ProjectContextFromParamsProvider } from 'components/layouts/ProjectLayout/ProjectContext' -const TableEditorPage: NextPage = () => { +const TableEditorPage: NextPageWithLayout = () => { const router = useRouter() - const { id } = useParams() + const { id, ref: projectRef } = useParams() const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] }) const { meta, ui } = useStore() @@ -37,7 +36,6 @@ const TableEditorPage: NextPage = () => { const [selectedTableToEdit, setSelectedTableToEdit] = useState() const [selectedValueForJsonEdit, setSelectedValueForJsonEdit] = useState() - const projectRef = ui.selectedProject?.ref const tables: PostgresTable[] = meta.tables.list() const views: SchemaView[] = meta.views.list() const foreignTables: Partial[] = meta.foreignTables.list() @@ -322,4 +320,8 @@ const TableEditorPage: NextPage = () => { ) } +TableEditorPage.getLayout = (page) => ( + {page} +) + export default withAuth(observer(TableEditorPage)) diff --git a/studio/pages/project/[ref]/editor/index.tsx b/studio/pages/project/[ref]/editor/index.tsx index 74762a22dac..f6e8d064eb4 100644 --- a/studio/pages/project/[ref]/editor/index.tsx +++ b/studio/pages/project/[ref]/editor/index.tsx @@ -1,16 +1,17 @@ import { useState } from 'react' -import { NextPage } from 'next' import router from 'next/router' import { observer } from 'mobx-react-lite' import { isUndefined } from 'lodash' import type { PostgresTable } from '@supabase/postgres-meta' +import { NextPageWithLayout } from 'types' import { useStore, withAuth } from 'hooks' import { TableEditorLayout } from 'components/layouts' import { EmptyState, SidePanelEditor } from 'components/interfaces/TableGridEditor' import ConfirmationModal from 'components/ui/ConfirmationModal' +import { ProjectContextFromParamsProvider } from 'components/layouts/ProjectLayout/ProjectContext' -const Editor: NextPage = () => { +const TableEditorPage: NextPageWithLayout = () => { const { meta, ui } = useStore() const projectRef = ui.selectedProject?.ref const [sidePanelKey, setSidePanelKey] = useState<'row' | 'column' | 'table'>() @@ -99,4 +100,8 @@ const Editor: NextPage = () => { ) } -export default withAuth(observer(Editor)) +TableEditorPage.getLayout = (page) => ( + {page} +) + +export default withAuth(observer(TableEditorPage)) diff --git a/studio/pages/project/[ref]/functions/[id]/details.tsx b/studio/pages/project/[ref]/functions/[id]/details.tsx index 613833e4f31..debcd5ebe99 100644 --- a/studio/pages/project/[ref]/functions/[id]/details.tsx +++ b/studio/pages/project/[ref]/functions/[id]/details.tsx @@ -1,244 +1,21 @@ -import dayjs from 'dayjs' -import { useEffect, useState } from 'react' import { observer } from 'mobx-react-lite' -import { IconGlobe, IconTerminal } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' import { NextPageWithLayout } from 'types' -import { checkPermissions, useParams, useStore } from 'hooks' -import { useProjectApiQuery } from 'data/config/project-api-query' +import { checkPermissions, useParams } from 'hooks' import FunctionsLayout from 'components/layouts/FunctionsLayout' -import CommandRender from 'components/interfaces/Functions/CommandRender' import NoPermission from 'components/ui/NoPermission' +import { EdgeFunctionDetails } from 'components/interfaces/Functions' const PageLayout: NextPageWithLayout = () => { - const { ref: projectRef, id } = useParams() - - const { functions, ui } = useStore() - - const [selectedFunction, setSelectedFunction] = useState(null) - - useEffect(() => { - setSelectedFunction(functions.byId(id)) - }, [functions.isLoaded, ui.selectedProject]) - - const { data: settings } = useProjectApiQuery({ - projectRef, - }) - - // get the .co or .net TLD from the restUrl - const restUrl = ui.selectedProject?.restUrl - const restUrlTld = new URL(restUrl as string).hostname.split('.').pop() - - const managementCommands: any = [ - { - command: `supabase functions deploy ${selectedFunction?.slug}`, - description: 'This will overwrite the deployed function with your new function', - jsx: () => { - return ( - <> - supabase functions deploy{' '} - {selectedFunction?.slug} - - ) - }, - comment: 'Deploy a new version', - }, - { - command: `supabase functions delete ${selectedFunction?.slug}`, - description: 'This will remove the function and all the logs associated with it', - jsx: () => { - return ( - <> - supabase functions delete{' '} - {selectedFunction?.slug} - - ) - }, - comment: 'Delete the function', - }, - ] - - const secretCommands: any = [ - { - command: `supabase secrets list`, - description: 'This will list all the secrets for your project', - jsx: () => { - return ( - <> - supabase secrets list - - ) - }, - comment: 'View all secrets', - }, - { - command: `supabase secrets set NAME1=VALUE1 NAME2=VALUE2`, - description: 'This will set secrets for your project', - jsx: () => { - return ( - <> - supabase secrets set NAME1=VALUE1 NAME2=VALUE2 - - ) - }, - comment: 'Set secrets for your project', - }, - { - command: `supabase secrets unset NAME1 NAME2 `, - description: 'This will delete secrets for your project', - jsx: () => { - return ( - <> - supabase secrets unset NAME1 NAME2 - - ) - }, - comment: 'Unset secrets for your project', - }, - ] - - // Get the API service - const apiService = settings?.autoApiService - const anonKey = apiService?.service_api_keys.find((x) => x.name === 'anon key') - ? apiService.defaultApiKey - : undefined - - const endpoint = apiService?.app_config.endpoint ?? '' - const endpointSections = endpoint.split('.') - const functionsEndpoint = [ - ...endpointSections.slice(0, 1), - 'functions', - ...endpointSections.slice(1), - ].join('.') - - const invokeCommands: any = [ - { - command: `curl -L -X POST 'https://${projectRef}.functions.supabase.${restUrlTld}/${ - selectedFunction?.slug - }' -H 'Authorization: Bearer ${anonKey ?? '[YOUR ANON KEY]'}' --data '{"name":"Functions"}'`, - description: 'Invokes the hello function', - jsx: () => { - return ( - <> - curl -L -X POST 'https://{functionsEndpoint}/ - {selectedFunction?.slug}' -H 'Authorization: Bearer [YOUR ANON KEY]'{' '} - {`--data '{"name":"Functions"}'`} - - ) - }, - comment: 'Invoke your function', - }, - ] + const { id } = useParams() const canReadFunction = checkPermissions(PermissionAction.FUNCTIONS_READ, id as string) if (!canReadFunction) { return } - return ( -
    -
    -
    -
    -
    - Function Name -
    {selectedFunction?.name}
    -
    - -
    - Status -
    -
    -
    - {selectedFunction?.status} -
    - - - - -
    -
    -
    -
    -
    -
    - -
    - Endpoint URL -
    - {`https://${projectRef}.functions.supabase.co/${selectedFunction?.slug}`} -
    -
    - -
    - Created At -
    - {selectedFunction?.created_at && - dayjs(selectedFunction.created_at).format('dddd, MMMM D, YYYY h:mm A')} -
    -
    - -
    - Updated At -
    - {selectedFunction?.updated_at && - dayjs(selectedFunction.updated_at).format('dddd, MMMM D, YYYY h:mm A')} -
    -
    - -
    - Version -
    v{selectedFunction?.version}
    -
    - -
    - Regions -
    -
    - - Earth -
    - All functions are deployed globally -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -

    Command line access

    -
    - -
    Deployment management
    - -
    Invoke
    - -
    Secrets management
    - -
    -
    -
    - ) + return } PageLayout.getLayout = (page) => {page} diff --git a/studio/pages/project/[ref]/functions/index.tsx b/studio/pages/project/[ref]/functions/index.tsx index 0532afd7a8f..e78b41e0996 100644 --- a/studio/pages/project/[ref]/functions/index.tsx +++ b/studio/pages/project/[ref]/functions/index.tsx @@ -1,59 +1,53 @@ import { observer } from 'mobx-react-lite' +import { useParams } from 'hooks' import { NextPageWithLayout } from 'types' -import { useStore } from 'hooks' -import FunctionsLayout from 'components/layouts/FunctionsLayout' - import Table from 'components/to-be-cleaned/Table' -import { Function } from 'components/interfaces/Functions/Functions.types' -import { - FunctionsListItem, - FunctionsEmptyState, - TerminalInstructions, -} from 'components/interfaces/Functions' - -const EdgeFunctionsList = ({ functions }: { functions: Function[] }) => { - return ( - <> -
    -
    - {`${functions.length} function${ - functions.length > 1 ? 's' : '' - } deployed`} -
    -
    - - Name - URL - Created - Last updated - Version - Status - - } - body={ - <> - {functions.length > 0 && - functions.map((item: any) => )} - - } - /> - - - - ) -} +import FunctionsLayout from 'components/layouts/FunctionsLayout' +import { EdgeFunctionsListItem, FunctionsEmptyState } from 'components/interfaces/Functions' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query' const PageLayout: NextPageWithLayout = () => { - const { functions } = useStore() - const hasFunctions = functions.list().length > 0 + const { ref } = useParams() + const { isLoading, data: functions } = useEdgeFunctionsQuery({ projectRef: ref }) + + const hasFunctions = (functions ?? []).length > 0 + + if (isLoading || functions === undefined) { + return ( +
    + + + +
    + ) + } return hasFunctions ? ( -
    - - +
    +
    + {`${functions.length} function${ + functions.length > 1 ? 's' : '' + } deployed`} +
    +
    + Name + URL + Created + Last updated + Deployments + + } + body={ + <> + {functions.length > 0 && + functions.map((item: any) => )} + + } + /> ) : ( diff --git a/studio/pages/project/[ref]/logs/explorer/saved.tsx b/studio/pages/project/[ref]/logs/explorer/saved.tsx index 3d49b345d0c..eebeafa3e6a 100644 --- a/studio/pages/project/[ref]/logs/explorer/saved.tsx +++ b/studio/pages/project/[ref]/logs/explorer/saved.tsx @@ -27,8 +27,8 @@ export const LogsSavedPage: NextPageWithLayout = () => { return (
    -
    - {saved.length > 0 && ( + {saved.length > 0 && ( +
    { ))} /> - )} - + + )} {saved.length === 0 && (
    diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api-overview.tsx new file mode 100644 index 00000000000..00c45d7888b --- /dev/null +++ b/studio/pages/project/[ref]/reports/api-overview.tsx @@ -0,0 +1,169 @@ +import { observer } from 'mobx-react-lite' +import { NextPageWithLayout } from 'types' +import { ReportFilterItem } from 'components/interfaces/Reports/Reports.types' +import { ReportsLayout } from 'components/layouts' +import { + PRESET_CONFIG, + REPORTS_DATEPICKER_HELPERS, +} from 'components/interfaces/Reports/Reports.constants' +import ReportWidget from 'components/interfaces/Reports/ReportWidget' +import { queriesFactory } from 'components/interfaces/Reports/Reports.utils' +import { + renderTotalRequests, + renderErrorCounts, + renderResponseSpeed, +} from 'components/interfaces/Reports/renderers/ApiRenderers' +import { useState, useEffect } from 'react' +import ReportHeader from 'components/interfaces/Reports/ReportHeader' +import { DatePickerToFrom, LogsEndpointParams } from 'components/interfaces/Settings/Logs' +import ReportFilterBar from 'components/interfaces/Reports/ReportFilterBar' +import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' +import { useParams } from 'hooks' +import { isEqual } from 'lodash' +import ShimmerLine from 'components/ui/ShimmerLine' +import ReportPadding from "components/interfaces/Reports/ReportPadding" + +export const ApiReport: NextPageWithLayout = () => { + const { ref: projectRef } = useParams() + const report = useApiReport() + + const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) + const tier = subscription?.tier + + const handleDatepickerChange = ({ from, to }: DatePickerToFrom) => { + report.mergeParams({ + iso_timestamp_start: from || '', + iso_timestamp_end: to || '', + }) + } + + return ( + + +
    + ({ + ...helper, + disabled: (index > 0 && tier?.key === 'FREE') || (index > 1 && tier?.key !== 'PRO'), + }))} + /> +
    + +
    +
    + + + + +
    + ) +} + +// hook to fetch data +const useApiReport = () => { + const queryHooks = queriesFactory( + PRESET_CONFIG.api.queries + ) + const totalRequests = queryHooks.totalRequests() + const errorCounts = queryHooks.errorCounts() + const responseSpeed = queryHooks.responseSpeed() + const activeHooks = [totalRequests, errorCounts, responseSpeed] + const [filters, setFilters] = useState([]) + const addFilter = (filter: ReportFilterItem) => { + // use a deep equal when comparing objects. + if (filters.some((f) => isEqual(f, filter))) return + setFilters((prev) => + [...prev, filter].sort((a, b) => { + const keyA = a.key.toLowerCase() + const keyB = b.key.toLowerCase() + if (keyA < keyB) { + return -1 + } + if (keyA > keyB) { + return 1 + } + return 0 + }) + ) + } + const removeFilter = (filter: ReportFilterItem) => removeFilters([filter]) + const removeFilters = (toRemove: ReportFilterItem[]) => { + setFilters((prev) => { + return prev.filter((f) => !toRemove.find((r) => isEqual(f, r))) + }) + } + + useEffect(() => { + // update sql for each query + if (totalRequests[1].changeQuery) { + totalRequests[1].changeQuery(PRESET_CONFIG.api.queries.totalRequests.sql(filters)) + } + if (errorCounts[1].changeQuery) { + errorCounts[1].changeQuery(PRESET_CONFIG.api.queries.errorCounts.sql(filters)) + } + if (responseSpeed[1].changeQuery) { + responseSpeed[1].changeQuery(PRESET_CONFIG.api.queries.responseSpeed.sql(filters)) + } + }, [JSON.stringify(filters)]) + + const handleRefresh = async () => { + activeHooks.forEach(([_hookData, hookHandler]) => { + hookHandler.runQuery() + }) + } + const handleSetParams = (params: Partial) => { + activeHooks.forEach(([_hookData, hookHandler]) => { + hookHandler.setParams?.((prev: LogsEndpointParams) => ({ ...prev, ...params })) + }) + } + const isLoading = activeHooks.some(([hookData]) => hookData.isLoading) + return { + data: { + totalRequests: totalRequests[0].logData, + errorCounts: errorCounts[0].logData, + responseSpeed: responseSpeed[0].logData, + }, + params: { + totalRequests: totalRequests[0].params, + errorCounts: errorCounts[0].params, + responseSpeed: responseSpeed[0].params, + }, + mergeParams: handleSetParams, + filters, + addFilter, + removeFilter, + removeFilters, + isLoading, + refresh: handleRefresh, + } +} + +ApiReport.getLayout = (page) => {page} + +export default observer(ApiReport) diff --git a/studio/pages/project/[ref]/reports/api.tsx b/studio/pages/project/[ref]/reports/api.tsx deleted file mode 100644 index 31f799665c7..00000000000 --- a/studio/pages/project/[ref]/reports/api.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { observer } from 'mobx-react-lite' -import { useRouter } from 'next/router' - -import { NextPageWithLayout } from 'types' -import { Presets } from 'components/interfaces/Reports/Reports.types' -import { ReportsLayout } from 'components/layouts' -import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' -import ReportWidget from 'components/interfaces/Reports/ReportWidget' -import { hooksFactory, usePresetReport } from 'components/interfaces/Reports/Reports.utils' -import { - renderErrorRateChart, - renderRequestsPathsTable, - renderStatusCodesChart, - renderBotScores, - renderUserAgents, -} from 'components/interfaces/Reports/renderers/ApiRenderers' - -export const ApiReport: NextPageWithLayout = () => { - const router = useRouter() - const { ref } = router.query - const config = PRESET_CONFIG[Presets.API] - const hooks = hooksFactory(ref as string, config) - const statusCodes = hooks.statusCodes() - const errorRates = hooks.errorRates() - const requestPaths = hooks.requestPaths() - const userAgents = hooks.userAgents() - const botScores = hooks.botScores() - const { isLoading, Layout } = usePresetReport([ - statusCodes, - errorRates, - requestPaths, - userAgents, - botScores, - ]) - return ( - -
    - - - - -

    Bots

    - - - -
    -
    - ) -} - -ApiReport.getLayout = (page) => {page} - -export default observer(ApiReport) diff --git a/studio/pages/project/[ref]/reports/auth.tsx b/studio/pages/project/[ref]/reports/auth.tsx deleted file mode 100644 index 6e0419aedb6..00000000000 --- a/studio/pages/project/[ref]/reports/auth.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { observer } from 'mobx-react-lite' -import { useRouter } from 'next/router' - -import { NextPageWithLayout } from 'types' -import { Presets } from 'components/interfaces/Reports/Reports.types' -import { ReportsLayout } from 'components/layouts' -import { hooksFactory, usePresetReport } from 'components/interfaces/Reports/Reports.utils' -import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' -import ReportWidget from 'components/interfaces/Reports/ReportWidget' -import { - renderDailyActiveUsers, - renderNewUsers, - renderCumulativeUsers, - renderFailedMigrations, - renderSignUpProviders, - renderUnverifiedUsers, - renderBannedUsers, -} from 'components/interfaces/Reports/renderers/AuthRenderers' - -export const AuthReport: NextPageWithLayout = () => { - const router = useRouter() - const { ref } = router.query - const config = PRESET_CONFIG[Presets.AUTH] - const hooks = hooksFactory(ref as string, config) - const dailyActiveUsers = hooks.dailyActiveUsers() - const newUsers = hooks.newUsers() - const cumulativeUsers = hooks.cumulativeUsers() - const failedMigrations = hooks.failedMigrations() - const signUpProviders = hooks.signUpProviders() - const unverifiedUsers = hooks.unverifiedUsers() - const bannedUsers = hooks.bannedUsers() - - const { isLoading, Layout } = usePresetReport([ - dailyActiveUsers, - newUsers, - cumulativeUsers, - failedMigrations, - signUpProviders, - unverifiedUsers, - ]) - return ( - -
    -

    User Activity

    - - - - - - - - - - -

    Sever Health

    - -
    -
    - ) -} - -AuthReport.getLayout = (page) => {page} - -export default observer(AuthReport) diff --git a/studio/pages/project/[ref]/reports/database.tsx b/studio/pages/project/[ref]/reports/database.tsx index 20ba14361d2..51ff8dfb946 100644 --- a/studio/pages/project/[ref]/reports/database.tsx +++ b/studio/pages/project/[ref]/reports/database.tsx @@ -103,24 +103,25 @@ const DatabaseUsage: FC = () => { />
    -
    -
    - {usage?.disk_volume_size_gb && ( - Disk Size: {usage.disk_volume_size_gb} GB - )} + {isPaidTier && ( +
    +
    + {usage?.disk_volume_size_gb && ( + Disk Size: {usage.disk_volume_size_gb} GB + )} + Auto-Scaling +
    - {isPaidTier && Auto-Scaling} +
    - - -
    + )}
    diff --git a/studio/pages/project/[ref]/reports/query-performance.tsx b/studio/pages/project/[ref]/reports/query-performance.tsx new file mode 100644 index 00000000000..2fcbea23763 --- /dev/null +++ b/studio/pages/project/[ref]/reports/query-performance.tsx @@ -0,0 +1,389 @@ +import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' +import { Presets } from 'components/interfaces/Reports/Reports.types' +import { queriesFactory } from 'components/interfaces/Reports/Reports.utils' +import { ReportsLayout } from 'components/layouts' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import Table from 'components/to-be-cleaned/Table' +import CopyButton from 'components/ui/CopyButton' +import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog' +import { executeSql } from 'data/sql/execute-sql-query' +import { useFlag } from 'hooks' +import { observer } from 'mobx-react-lite' +import { useState } from 'react' +import ReactMarkdown from 'react-markdown' +import { NextPageWithLayout } from 'types' +import { Accordion, Button, IconAlertCircle, IconCheckCircle, Tabs } from 'ui' +import ReportPadding from 'components/interfaces/Reports/ReportPadding' +import ReportHeader from 'components/interfaces/Reports/ReportHeader' + +const QueryPerformanceReport: NextPageWithLayout = () => { + const { project } = useProjectContext() + const [showResetgPgStatStatements, setShowResetgPgStatStatements] = useState(false) + const tableIndexEfficiencyEnabled = useFlag('tableIndexEfficiency') + const config = PRESET_CONFIG[Presets.QUERY_PERFORMANCE] + const hooks = queriesFactory(config.queries) + const mostFrequentlyInvoked = hooks.mostFrequentlyInvoked() + const mostTimeConsuming = hooks.mostTimeConsuming() + const slowestExecutionTime = hooks.slowestExecutionTime() + const queryHitRate = hooks.queryHitRate() + + const isLoading = [ + mostFrequentlyInvoked[0].isLoading, + mostTimeConsuming[0].isLoading, + slowestExecutionTime[0].isLoading, + queryHitRate[0].isLoading, + ].every((value) => value) + + const handleRefresh = async () => { + mostFrequentlyInvoked[1].runQuery() + mostTimeConsuming[1].runQuery() + slowestExecutionTime[1].runQuery() + queryHitRate[1].runQuery() + } + + const checkAlert = ( +
    + +
    + ) + const warnAlert = ( +
    + +
    + ) + const dangerAlert = ( +
    + +
    + ) + + const indexHitRate = queryHitRate[0]?.data?.[0]?.ratio + const tableHitRate = queryHitRate[0]?.data?.[1]?.ratio + const showIndexWarning = + indexHitRate && tableHitRate && (indexHitRate <= 0.99 || tableHitRate <= 0.99) + + const HeaderText = `Identify the queries that consume the most time and database resources. + + It relies on the \`pg_stat_statements\` table. Read more about [examining query performance]((https://supabase.com/docs/guides/platform/performance#examining-query-performance)). + + Consider resetting the analysis after optimizing any queries. + +` + + const TimeConsumingHelperText = `This table lists queries ordered by their cumulative total execution time. + + It displays the total time a query has spent running and the proportion of total execution time the query has consumed. +` + + const MostFrequentHelperText = `This table lists queries in order of their execution count, providing insights into the most frequently executed queries. + + Pay attention to queries with high max_time or mean_time values that are called frequently, as they may benefit from optimization. +` + + const SlowestExecutionHelperText = `This table lists queries ordered by their maximum execution time. It shows outliers with high execution times. Queries with long execution times may benefit from optimization. + + Look for queries with high or mean execution times. These are often good candidates for optimization. +` + + const panelClassNames = 'text-sm max-w-none flex flex-col gap-8 py-4' + const helperTextClassNames = 'prose text-sm max-w-2xl text-scale-1000' + + return ( + + + {tableIndexEfficiencyEnabled && ( + + + Index Efficiency + {showIndexWarning ? warnAlert : checkAlert} +
    + } + id="1" + className="flex flex-row gap-8" + > + {!isLoading && queryHitRate && ( +
    +
    +
    + Index Hit Rate +
    + {indexHitRate >= 0.99 + ? checkAlert + : indexHitRate >= 0.95 + ? warnAlert + : dangerAlert} +
    + + {(queryHitRate[0]?.data![0]?.ratio * 100).toFixed(2)} + + % +
    +
    +
    + +
    + {queryHitRate[0]?.data![1]?.name == 'table hit rate' && 'Table Hit Rate'} +
    + {tableHitRate >= 0.99 + ? checkAlert + : tableHitRate >= 0.95 + ? warnAlert + : dangerAlert} +
    + + {(queryHitRate[0]?.data![1]?.ratio * 100).toFixed(2)} + + % +
    +
    +
    +
    +
    +

    + For best performance, ensure that the cache hit rate ratios above 99%.
    {' '} + Consider upgrading to an instance with more memory if the ratios dip below 95%. +

    +
    +
    + )} + + + )} + + + +
    + +
    + + setShowResetgPgStatStatements(false)} + onSelectConfirm={async () => { + try { + await executeSql({ + projectRef: project?.ref, + connectionString: project?.connectionString, + sql: `SELECT pg_stat_statements_reset();`, + }) + setShowResetgPgStatStatements(false) + handleRefresh() + } catch (error) { + console.error(error) + } + }} + /> + +
    + + +
    + +
    +
    + Role + Time Consumed + Calls + Total Time + Query + + } + body={ + !isLoading && mostTimeConsuming && mostTimeConsuming[0]?.data ? ( + mostTimeConsuming[0].data!.map((item, i) => { + return ( + + + {item.rolname} + + + {item.prop_total_time} + + + {item.calls} + + + {item.total_time.toFixed(2)}ms + + +

    {item.query}

    + +
    +
    + ) + }) + ) : ( + <> + ) + } + /> + + + + +
    + +
    +
    + {/* source */} + Role + Avg. Roles + Calls + Max Time + Mean Time + Min Time + Total Time + Query + + } + body={ + !isLoading && mostFrequentlyInvoked && mostFrequentlyInvoked[0]?.data ? ( + mostFrequentlyInvoked[0].data!.map((item, i) => { + return ( + + + {item.rolname} + + + {item.avg_rows} + + + {item.calls} + + + {item.max_time.toFixed(2)}ms + + + {item.mean_time.toFixed(2)}ms + + + {item.min_time.toFixed(2)}ms + + + {item.total_time.toFixed(2)}ms + + +

    {item.query}

    + +
    +
    + ) + }) + ) : ( + <> + ) + } + /> + + + + +
    + +
    +
    + Role + Avg Rows + Calls + Max Time + Mean Time + Min Time + Total Time + Query + + } + body={ + !isLoading && slowestExecutionTime && slowestExecutionTime[0]?.data ? ( + slowestExecutionTime[0].data!.map((item, i) => { + return ( + + + {item.rolname} + + + {item.avg_rows} + + + {item.calls} + + + {item.max_time.toFixed(2)}ms + + + {item.mean_time.toFixed(2)}ms + + + {item.min_time.toFixed(2)}ms + + + {item.total_time.toFixed(2)}ms + + +

    {item.query}

    + +
    +
    + ) + }) + ) : ( + <> + ) + } + /> + + + + + + + ) +} + +const QueryActions = ({ sql, className }: { sql: string; className: string }) => { + if (sql.includes('insufficient privilege')) return null + + return ( +
    + +
    + ) +} + +QueryPerformanceReport.getLayout = (page) => ( + {page} +) + +export default observer(QueryPerformanceReport) diff --git a/studio/pages/project/[ref]/settings/billing/subscription.tsx b/studio/pages/project/[ref]/settings/billing/subscription.tsx index 750af452736..a47a62ad90d 100644 --- a/studio/pages/project/[ref]/settings/billing/subscription.tsx +++ b/studio/pages/project/[ref]/settings/billing/subscription.tsx @@ -36,7 +36,6 @@ interface SettingsProps { const Settings: FC = ({ project }) => { const { ui } = useStore() - const projectTier = ui.selectedProject?.subscription_tier const { data: subscription, diff --git a/studio/pages/project/[ref]/storage/buckets/index.tsx b/studio/pages/project/[ref]/storage/buckets/index.tsx index 80ca79f9f03..396a914ba2a 100644 --- a/studio/pages/project/[ref]/storage/buckets/index.tsx +++ b/studio/pages/project/[ref]/storage/buckets/index.tsx @@ -3,21 +3,21 @@ import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' import { API_URL } from 'lib/constants' -import { useFlag, useStore } from 'hooks' +import { useFlag, useParams, useStore } from 'hooks' import { post } from 'lib/common/fetch' import { PROJECT_STATUS } from 'lib/constants' import { StorageLayout } from 'components/layouts' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' import { NextPageWithLayout } from 'types' +import { useProjectApiQuery } from 'data/config/project-api-query' +import { find } from 'lodash' /** * PageLayout is used to setup layout - as usual it will requires inject global store */ const PageLayout: NextPageWithLayout = ({}) => { - const router = useRouter() - const { ref } = router.query - + const { ref } = useParams() const { ui } = useStore() const project = ui.selectedProject @@ -26,6 +26,11 @@ const PageLayout: NextPageWithLayout = ({}) => { const kpsEnabled = useFlag('initWithKps') + const { data: settings, isLoading } = useProjectApiQuery({ projectRef: ref }) + const apiService = settings?.autoApiService + const serviceKey = find(apiService?.service_api_keys ?? [], (key) => key.tags === 'service_role') + const canAccessStorage = !isLoading && apiService && serviceKey !== undefined + useEffect(() => { if (project && project.status === PROJECT_STATUS.INACTIVE) { post(`${API_URL}/projects/${ref}/restore`, { kps_enabled: kpsEnabled }) @@ -42,6 +47,8 @@ const PageLayout: NextPageWithLayout = ({}) => { infoButtonLabel="About storage" infoButtonUrl="https://supabase.com/docs/guides/storage" onClickCta={openCreateBucketModal} + disabled={!canAccessStorage} + disabledMessage="You need additional permissions to create buckets" >

    Create buckets to store and serve any type of digital content. diff --git a/studio/public/img/graphql.svg b/studio/public/img/graphql.svg new file mode 100644 index 00000000000..8060db470ea --- /dev/null +++ b/studio/public/img/graphql.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/studio/public/img/icons/reference-auth-light.svg b/studio/public/img/icons/reference-auth-light.svg new file mode 100644 index 00000000000..3995c790842 --- /dev/null +++ b/studio/public/img/icons/reference-auth-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/studio/public/img/icons/reference-csharp-light.svg b/studio/public/img/icons/reference-csharp-light.svg new file mode 100644 index 00000000000..782f87bcc40 --- /dev/null +++ b/studio/public/img/icons/reference-csharp-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/studio/public/img/icons/reference-csharp.svg b/studio/public/img/icons/reference-csharp.svg new file mode 100644 index 00000000000..40d0375079d --- /dev/null +++ b/studio/public/img/icons/reference-csharp.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/public/img/icons/reference-dart-light.svg b/studio/public/img/icons/reference-dart-light.svg new file mode 100644 index 00000000000..178f51a5b88 --- /dev/null +++ b/studio/public/img/icons/reference-dart-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/public/img/icons/reference-dart.svg b/studio/public/img/icons/reference-dart.svg new file mode 100644 index 00000000000..33f9ad24a27 --- /dev/null +++ b/studio/public/img/icons/reference-dart.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/public/img/icons/reference-javascript-light.svg b/studio/public/img/icons/reference-javascript-light.svg new file mode 100644 index 00000000000..a11845dff91 --- /dev/null +++ b/studio/public/img/icons/reference-javascript-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/public/img/icons/reference-javascript.svg b/studio/public/img/icons/reference-javascript.svg new file mode 100644 index 00000000000..f8ce1de0403 --- /dev/null +++ b/studio/public/img/icons/reference-javascript.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/public/img/icons/reference-python-light.svg b/studio/public/img/icons/reference-python-light.svg new file mode 100644 index 00000000000..1ac57aae572 --- /dev/null +++ b/studio/public/img/icons/reference-python-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/public/img/icons/reference-python.svg b/studio/public/img/icons/reference-python.svg new file mode 100644 index 00000000000..4ca064b0b73 --- /dev/null +++ b/studio/public/img/icons/reference-python.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/studio/sentry.client.config.js b/studio/sentry.client.config.js index 456f62c88a0..23e597a09cb 100644 --- a/studio/sentry.client.config.js +++ b/studio/sentry.client.config.js @@ -39,6 +39,9 @@ Sentry.init({ // Safe to ignore since it's not a user-facing issue + we've not received any user feedback/report about it // Ref: https://github.com/supabase/supabase/pull/9729 'The provided `href` (/org/[slug]/general) value is missing query values (slug)', + 'The provided `href` (/org/[slug]/team) value is missing query values (slug)', + 'The provided `href` (/org/[slug]/billing) value is missing query values (slug)', + 'The provided `href` (/org/[slug]/invoices) value is missing query values (slug)', ], beforeSend: (event) => filterConsoleErrors(event), }) diff --git a/studio/stores/UiStore.ts b/studio/stores/UiStore.ts index 411eecbdd74..74202e1623e 100644 --- a/studio/stores/UiStore.ts +++ b/studio/stores/UiStore.ts @@ -128,8 +128,8 @@ export default class UiStore implements IUiStore { } setTheme(theme: 'dark' | 'light') { + document.body.classList.replace(this.theme, theme) this.theme = theme - document.body.className = theme } onThemeOptionChange(themeOption: 'dark' | 'light' | 'system') { diff --git a/studio/stores/app/ProjectStore.ts b/studio/stores/app/ProjectStore.ts index d6f13efb066..d43814c29a6 100644 --- a/studio/stores/app/ProjectStore.ts +++ b/studio/stores/app/ProjectStore.ts @@ -68,9 +68,7 @@ export default class ProjectStore extends PostgresMetaInterface { async pingPostgrest(project: Project): Promise<'ONLINE' | 'OFFLINE' | undefined> { if (project.status === PROJECT_STATUS.ACTIVE_HEALTHY && project.restUrl) { - const success = await pingPostgrest(project.restUrl, project.ref, { - kpsVersion: project.kpsVersion, - }) + const success = await pingPostgrest(project.ref, { kpsVersion: project.kpsVersion }) return success ? 'ONLINE' : 'OFFLINE' } return undefined diff --git a/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx b/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx index aa06d1771c3..c5ffa57f386 100644 --- a/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx +++ b/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx @@ -191,7 +191,7 @@ const PROVIDER_PHONE = { SMS_TEMPLATE: { title: 'SMS Message', type: 'string', - description: 'To format the OPT code use `{{ .Code }}`', + description: 'To format the OTP code use `{{ .Code }}`', }, }, validationSchema: object().shape({ diff --git a/studio/stores/authConfig/schema/AuthProviders/AuthTemplatesValidation.tsx b/studio/stores/authConfig/schema/AuthProviders/AuthTemplatesValidation.tsx index 526e91022a9..101d126e95b 100644 --- a/studio/stores/authConfig/schema/AuthProviders/AuthTemplatesValidation.tsx +++ b/studio/stores/authConfig/schema/AuthProviders/AuthTemplatesValidation.tsx @@ -18,9 +18,11 @@ export const CONFIRMATION: FormSchema = { descriptionOptional: 'HTML body of your email', type: 'code', description: ` -- \`{{ .ConfirmationURL }}\` : URL to confirm the message +- \`{{ .ConfirmationURL }}\` : URL to confirm the e-mail address for the new account - \`{{ .Token }}\` : The 6-digit numeric email OTP - \`{{ .TokenHash }}\` : The hashed token used in the URL +- \`{{ .SiteURL }}\` : The URL of the site +- \`{{ .Email }}\` : The users email address `, }, }, @@ -49,9 +51,11 @@ export const INVITE: FormSchema = { descriptionOptional: 'HTML body of your email', type: 'code', description: ` -- \`{{ .ConfirmationURL }}\` : URL to confirm the message +- \`{{ .ConfirmationURL }}\` : URL to accept the invitation to create an account - \`{{ .Token }}\` : The 6-digit numeric email OTP - \`{{ .TokenHash }}\` : The hashed token used in the URL +- \`{{ .SiteURL }}\` : The URL of the site +- \`{{ .Email }}\` : The users email address `, }, }, @@ -80,9 +84,11 @@ export const MAGIC_LINK: FormSchema = { descriptionOptional: 'HTML body of your email', type: 'code', description: ` -- \`{{ .ConfirmationURL }}\` : URL to confirm the message +- \`{{ .ConfirmationURL }}\` : URL for a one-time login to the user's account - \`{{ .Token }}\` : The 6-digit numeric email OTP - \`{{ .TokenHash }}\` : The hashed token used in the URL +- \`{{ .SiteURL }}\` : The URL of the site +- \`{{ .Email }}\` : The users email address `, }, }, @@ -114,6 +120,9 @@ export const EMAIL_CHANGE: FormSchema = { - \`{{ .ConfirmationURL }}\` : URL to confirm the email change - \`{{ .Token }}\` : The 6-digit numeric email OTP - \`{{ .TokenHash }}\` : The hashed token used in the URL +- \`{{ .SiteURL }}\` : The URL of the site +- \`{{ .Email }}\` : The original users email address +- \`{{ .NewEmail }\` : The users new email address `, }, }, @@ -145,6 +154,8 @@ export const RECOVERY: FormSchema = { - \`{{ .ConfirmationURL }}\` : URL to confirm the password reset - \`{{ .Token }}\` : The 6-digit numeric email OTP - \`{{ .TokenHash }}\` : The hashed token used in the URL +- \`{{ .SiteURL }}\` : The URL of the site +- \`{{ .Email }}\` : The users email address `, }, }, diff --git a/studio/stores/common/PostgresMetaInterface.ts b/studio/stores/common/PostgresMetaInterface.ts index 09f605b5acc..bd51fd319d5 100644 --- a/studio/stores/common/PostgresMetaInterface.ts +++ b/studio/stores/common/PostgresMetaInterface.ts @@ -117,6 +117,7 @@ export default class PostgresMetaInterface implements IPostgresMetaInterface< } } + // [Joshen] Only used for tables and views for now async loadBySchema(schema: string) { let { LOADING, ERROR, LOADED } = this.STATES try { @@ -132,7 +133,14 @@ export default class PostgresMetaInterface implements IPostgresMetaInterface< const data = response as T[] const formattedData = keyBy(data, this.identifier) - this.data = { ...this.data, ...formattedData } + // Purge existing data that belongs to given schema, otherwise + // stale data will persist + const purgedData = Object.keys(this.data) + .map((identifier: any) => this.data[identifier]) + .filter((item: any) => item.schema !== schema) + const formattedPurgedData = keyBy(purgedData, this.identifier) + + this.data = { ...formattedPurgedData, ...formattedData } this.setState(LOADED) return data diff --git a/studio/stores/pgmeta/MetaStore.ts b/studio/stores/pgmeta/MetaStore.ts index 3d0a73e40d0..a24c5f365f0 100644 --- a/studio/stores/pgmeta/MetaStore.ts +++ b/studio/stores/pgmeta/MetaStore.ts @@ -183,7 +183,7 @@ export default class MetaStore implements IMetaStore { this.openApi = new OpenApiStore( this.rootStore, - `${API_URL}/props/project/${this.projectRef}/api` + `${API_URL}/projects/${this.projectRef}/api/rest` ) this.tables = new TableStore(this.rootStore, `${this.baseUrl}/tables`, this.headers) this.columns = new ColumnStore(this.rootStore, `${this.baseUrl}/columns`, this.headers) @@ -895,7 +895,7 @@ export default class MetaStore implements IMetaStore { this.headers['x-connection-encrypted'] = connectionString } - this.openApi.setUrl(`${API_URL}/props/project/${this.projectRef}/api`) + this.openApi.setUrl(`${API_URL}/projects/${this.projectRef}/api/rest`) this.openApi.setHeaders(this.headers) this.tables.setUrl(`${this.baseUrl}/tables`) diff --git a/studio/stores/pgmeta/OpenApiStore.ts b/studio/stores/pgmeta/OpenApiStore.ts index 0b8f35150d2..c66a2095989 100644 --- a/studio/stores/pgmeta/OpenApiStore.ts +++ b/studio/stores/pgmeta/OpenApiStore.ts @@ -52,17 +52,7 @@ export default class OpenApiStore implements IOpenApiStore { } async fetchData() { - const headers = { 'Content-Type': 'application/json', ...this.headers } - const projectConfig = await get(this.url, { headers }) - if (projectConfig.error) throw projectConfig.error - - const apiKey = projectConfig.autoApiService?.defaultApiKey - const restApiUrl = projectConfig.autoApiService?.restUrl - - const response = await get(`${restApiUrl}?apikey=${apiKey}`, { - headers: { ...headers, apiKey: apiKey, Authorization: `Bearer ${apiKey}` }, - credentials: 'omit', - }) + const response = await get(this.url) if (response.error) throw response.error const tables = response.definitions diff --git a/studio/styles/graphiql-base.scss b/studio/styles/graphiql-base.scss new file mode 100644 index 00000000000..462a89f1823 --- /dev/null +++ b/studio/styles/graphiql-base.scss @@ -0,0 +1,303 @@ +/* GraphiQL Base styles from https://github.com/graphql/graphiql/blob/main/packages/graphiql/src/style.css with font face directives removed */ + +.graphiql-container *{box-sizing:border-box}.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal{--color-primary: 320, 95%, 43%;--color-secondary: 242, 51%, 61%;--color-tertiary: 188, 100%, 36%;--color-info: 208, 100%, 46%;--color-success: 158, 60%, 42%;--color-warning: 36, 100%, 41%;--color-error: 13, 93%, 58%;--color-neutral: 219, 28%, 32%;--color-base: 219, 28%, 100%;--alpha-secondary: .76;--alpha-tertiary: .5;--alpha-background-heavy: .15;--alpha-background-medium: .1;--alpha-background-light: .07;--font-family: "Roboto", sans-serif;--font-family-mono: "Fira Code", monospace;--font-size-hint:.75rem;--font-size-inline-code:.8125rem;--font-size-body:.9375rem;--font-size-h4:1.125rem;--font-size-h3:1.375rem;--font-size-h2:1.8125rem;--font-weight-regular: 400;--font-weight-medium: 500;--line-height: 1.5;--px-2: 2px;--px-4: 4px;--px-6: 6px;--px-8: 8px;--px-10: 10px;--px-12: 12px;--px-16: 16px;--px-20: 20px;--px-24: 24px;--border-radius-2: 2px;--border-radius-4: 4px;--border-radius-8: 8px;--border-radius-12: 12px;--popover-box-shadow: 0px 6px 20px rgba(59, 76, 106, .13), 0px 1.34018px 4.46726px rgba(59, 76, 106, .0774939), 0px .399006px 1.33002px rgba(59, 76, 106, .0525061);--popover-border: none;--sidebar-width: 60px;--toolbar-width: 40px;--session-header-height: 51px}@media (prefers-color-scheme: dark){body:not(.graphiql-light) .graphiql-container,body:not(.graphiql-light) .CodeMirror-info,body:not(.graphiql-light) .CodeMirror-lint-tooltip,body:not(.graphiql-light) reach-portal{--color-primary: 338, 100%, 67%;--color-secondary: 243, 100%, 77%;--color-tertiary: 188, 100%, 44%;--color-info: 208, 100%, 72%;--color-success: 158, 100%, 42%;--color-warning: 30, 100%, 80%;--color-error: 13, 100%, 58%;--color-neutral: 219, 29%, 78%;--color-base: 219, 29%, 18%;--popover-box-shadow: none;--popover-border: 1px solid hsl(var(--color-neutral))}}body.graphiql-dark .graphiql-container,body.graphiql-dark .CodeMirror-info,body.graphiql-dark .CodeMirror-lint-tooltip,body.graphiql-dark reach-portal{--color-primary: 338, 100%, 67%;--color-secondary: 243, 100%, 77%;--color-tertiary: 188, 100%, 44%;--color-info: 208, 100%, 72%;--color-success: 158, 100%, 42%;--color-warning: 30, 100%, 80%;--color-error: 13, 100%, 58%;--color-neutral: 219, 29%, 78%;--color-base: 219, 29%, 18%;--popover-box-shadow: none;--popover-border: 1px solid hsl(var(--color-neutral))}:is(.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal),:is(.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal):is(button){color:hsla(var(--color-neutral),1);font-family:var(--font-family);font-size:var(--font-size-body);font-weight:var(----font-weight-regular);line-height:var(--line-height)}:is(.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal) input{color:hsla(var(--color-neutral),1);font-family:var(--font-family);font-size:var(--font-size-caption)}:is(.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal) input::placeholder{color:hsla(var(--color-neutral),var(--alpha-secondary))}:is(.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal) a{color:hsl(var(--color-primary))}:is(.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal) a:focus{outline:hsl(var(--color-primary)) auto 1px}.graphiql-un-styled,button.graphiql-un-styled{all:unset;border-radius:var(--border-radius-4);cursor:pointer}:is(.graphiql-un-styled,button.graphiql-un-styled):hover{background-color:hsla(var(--color-neutral),var(--alpha-background-light))}:is(.graphiql-un-styled,button.graphiql-un-styled):active{background-color:hsla(var(--color-neutral),var(--alpha-background-medium))}:is(.graphiql-un-styled,button.graphiql-un-styled):focus{outline:hsla(var(--color-neutral),var(--alpha-background-heavy)) auto 1px}.graphiql-button,button.graphiql-button{background-color:hsla(var(--color-neutral),var(--alpha-background-light));border:none;border-radius:var(--border-radius-4);color:hsla(var(--color-neutral),1);cursor:pointer;font-size:var(--font-size-body);padding:var(--px-8) var(--px-12)}:is(.graphiql-button,button.graphiql-button):hover,:is(.graphiql-button,button.graphiql-button):active{background-color:hsla(var(--color-neutral),var(--alpha-background-medium))}:is(.graphiql-button,button.graphiql-button):focus{outline:hsla(var(--color-neutral),var(--alpha-background-heavy)) auto 1px}.graphiql-button-success:is(.graphiql-button,button.graphiql-button){background-color:hsla(var(--color-success),var(--alpha-background-heavy))}.graphiql-button-error:is(.graphiql-button,button.graphiql-button){background-color:hsla(var(--color-error),var(--alpha-background-heavy))}.graphiql-button-group{background-color:hsla(var(--color-neutral),var(--alpha-background-light));border-radius:calc(var(--border-radius-4) + var(--px-4));display:flex;padding:var(--px-4)}.graphiql-button-group>button.graphiql-button{background-color:transparent}.graphiql-button-group>button.graphiql-button:hover{background-color:hsla(var(--color-neutral),var(--alpha-background-light))}.graphiql-button-group>button.graphiql-button.active{background-color:hsl(var(--color-base));cursor:default}.graphiql-button-group>*+*{margin-left:var(--px-8)}:root{--reach-dialog: 1}[data-reach-dialog-overlay]{background:hsla(0,0%,0%,.33);position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto}[data-reach-dialog-content]{width:50vw;margin:10vh auto;background:white;padding:2rem;outline:none}[data-reach-dialog-overlay]{align-items:center;background-color:hsla(var(--color-neutral),var(--alpha-background-heavy));display:flex;justify-content:center;z-index:10}[data-reach-dialog-content]{background-color:hsl(var(--color-base));border:var(--popover-border);border-radius:var(--border-radius-12);box-shadow:var(--popover-box-shadow);margin:0;max-height:80vh;max-width:80vw;overflow:auto;padding:0;width:unset}.graphiql-dialog-close>svg{color:hsla(var(--color-neutral),var(--alpha-secondary));display:block;height:var(--px-12);padding:var(--px-12);width:var(--px-12)}:root{--reach-listbox: 1}[data-reach-listbox-popover]{display:block;position:absolute;min-width:-moz-fit-content;min-width:-webkit-min-content;min-width:min-content;padding:.25rem 0;background:hsl(0,0%,100%);outline:none;border:solid 1px hsla(0,0%,0%,.25)}[data-reach-listbox-popover]:focus-within{box-shadow:0 0 4px Highlight;outline:-webkit-focus-ring-color auto 4px}[data-reach-listbox-popover][hidden]{display:none}[data-reach-listbox-list]{margin:0;padding:0;list-style:none}[data-reach-listbox-list]:focus{box-shadow:none;outline:none}[data-reach-listbox-option]{display:block;margin:0;padding:.25rem .5rem;white-space:nowrap;user-select:none}[data-reach-listbox-option][data-current-nav]{background:hsl(211,81%,46%);color:#fff}[data-reach-listbox-option][data-current-selected]{font-weight:bolder}[data-reach-listbox-option][data-current-selected][data-confirming]{animation:flash .1s;animation-iteration-count:1}[data-reach-listbox-option][aria-disabled=true]{opacity:.5}[data-reach-listbox-button]{display:inline-flex;align-items:center;justify-content:space-between;padding:1px 10px 2px;border:1px solid;border-color:rgb(216,216,216) rgb(209,209,209) rgb(186,186,186);cursor:default;user-select:none}[data-reach-listbox-button][aria-disabled=true]{opacity:.5}[data-reach-listbox-arrow]{margin-left:.5rem;display:block;font-size:.5em}[data-reach-listbox-group-label]{display:block;margin:0;padding:.25rem .5rem;white-space:nowrap;user-select:none;font-weight:bolder}@keyframes flash{0%{background:hsla(211,81%,36%,1);color:#fff;opacity:1}50%{opacity:.5;background:inherit;color:inherit}to{background:hsla(211,81%,36%,1);color:#fff;opacity:1}}:root{--reach-menu-button: 1}[data-reach-menu]{position:relative}[data-reach-menu-popover]{display:block;position:absolute}[data-reach-menu-popover][hidden]{display:none}[data-reach-menu-list],[data-reach-menu-items]{display:block;white-space:nowrap;border:solid 1px hsla(0,0%,0%,.25);background:hsla(0,100%,100%,.99);outline:none;padding:1rem 0;font-size:85%}[data-reach-menu-item]{display:block;user-select:none}[data-reach-menu-item]{cursor:pointer;display:block;color:inherit;font:inherit;text-decoration:initial;padding:5px 20px}[data-reach-menu-item][data-selected]{background:hsl(211,81%,36%);color:#fff;outline:none}[data-reach-menu-item][aria-disabled]{opacity:.5;cursor:not-allowed}[data-reach-listbox-popover],[data-reach-menu-list]{background-color:hsl(var(--color-base));border:var(--popover-border);border-radius:var(--border-radius-8);box-shadow:var(--popover-box-shadow);font-size:inherit;max-width:250px;padding:var(--px-4)}[data-reach-listbox-option],[data-reach-menu-item]{border-radius:var(--border-radius-4);font-size:inherit;margin:var(--px-4);overflow:hidden;padding:var(--px-6) var(--px-8);text-overflow:ellipsis;white-space:nowrap}[data-reach-listbox-option][data-selected],[data-reach-menu-item][data-selected],[data-reach-listbox-option][data-current-nav],[data-reach-menu-item][data-current-nav],[data-reach-listbox-option]:hover,[data-reach-menu-item]:hover{background-color:hsla(var(--color-neutral),var(--alpha-background-light));color:inherit}[data-reach-listbox-option]:not(:first-child),[data-reach-menu-item]:not(:first-child){margin-top:0}[data-reach-listbox-button]{border:none;cursor:pointer;padding:0}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) blockquote{margin-left:0;margin-right:0;padding-left:var(--px-8)}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) code,:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) pre{border-radius:var(--border-radius-4);font-family:var(--font-family-mono);font-size:var(--font-size-inline-code)}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) code{padding:var(--px-2)}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) pre{overflow:auto;padding:var(--px-6) var(--px-8)}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) pre code{background-color:initial;border-radius:0;padding:0}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) ol,:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) ul{padding-left:var(--px-16)}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) ol{list-style-type:decimal}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) ul{list-style-type:disc}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) img{border-radius:var(--border-radius-4);max-height:120px;max-width:100%}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation)>:first-child{margin-top:0}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation)>:last-child{margin-bottom:0}:is(.graphiql-markdown-description,.CodeMirror-hint-information-description,.CodeMirror-info .info-description) a{color:hsl(var(--color-primary));text-decoration:none}:is(.graphiql-markdown-description,.CodeMirror-hint-information-description,.CodeMirror-info .info-description) a:hover{text-decoration:underline}:is(.graphiql-markdown-description,.CodeMirror-hint-information-description,.CodeMirror-info .info-description) blockquote{border-left:1.5px solid hsla(var(--color-neutral),var(--alpha-tertiary))}:is(.graphiql-markdown-description,.CodeMirror-hint-information-description,.CodeMirror-info .info-description) code,:is(.graphiql-markdown-description,.CodeMirror-hint-information-description,.CodeMirror-info .info-description) pre{background-color:hsla(var(--color-neutral),var(--alpha-background-light));color:hsla(var(--color-neutral),1)}:is(.graphiql-markdown-description,.CodeMirror-hint-information-description,.CodeMirror-info .info-description)>*{margin:var(--px-12) 0}:is(.graphiql-markdown-deprecation,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-deprecation) a{color:hsl(var(--color-warning));text-decoration:underline}:is(.graphiql-markdown-deprecation,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-deprecation) blockquote{border-left:1.5px solid hsl(var(--color-warning))}:is(.graphiql-markdown-deprecation,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-deprecation) code,:is(.graphiql-markdown-deprecation,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-deprecation) pre{background-color:hsla(var(--color-warning),var(--alpha-background-heavy))}:is(.graphiql-markdown-deprecation,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-deprecation)>*{margin:var(--px-8) 0}.graphiql-markdown-preview>:not(:first-child){display:none}.CodeMirror-hint-information-deprecation,.CodeMirror-info .info-deprecation{background-color:hsla(var(--color-warning),var(--alpha-background-light));border:1px solid hsl(var(--color-warning));border-radius:var(--border-radius-4);color:hsl(var(--color-warning));margin-top:var(--px-12);padding:var(--px-6) var(--px-8)}.CodeMirror-hint-information-deprecation-label,.CodeMirror-info .info-deprecation-label{font-size:var(--font-size-hint);font-weight:var(--font-weight-medium)}.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-deprecation-reason{margin-top:var(--px-6)}.graphiql-spinner{height:56px;margin:auto;margin-top:var(--px-16);width:56px}.graphiql-spinner:after{animation:rotation .8s linear 0s infinite;border:4px solid transparent;border-radius:100%;border-top:4px solid hsla(var(--color-neutral),var(--alpha-tertiary));content:"";display:inline-block;height:46px;vertical-align:middle;width:46px}@keyframes rotation{0%{transform:rotate(0)}to{transform:rotate(360deg)}}:root{--reach-tooltip: 1}[data-reach-tooltip]{z-index:1;pointer-events:none;position:absolute;padding:.25em .5em;box-shadow:2px 2px 10px #0000001a;white-space:nowrap;font-size:85%;background:#f0f0f0;color:#444;border:solid 1px #ccc}[data-reach-tooltip]{background:hsl(var(--color-base));border:var(--popover-border);border-radius:var(--border-radius-4);box-shadow:var(--popover-box-shadow);color:hsl(var(--color-neutral));font-size:inherit;padding:var(--px-4) var(--px-6)}.graphiql-tabs{display:flex;overflow-x:auto;padding:var(--px-12)}.graphiql-tabs>:not(:first-child){margin-left:var(--px-12)}.graphiql-tab{align-items:stretch;border-radius:var(--border-radius-8);color:hsla(var(--color-neutral),var(--alpha-secondary));display:flex}.graphiql-tab>button.graphiql-tab-close{visibility:hidden}.graphiql-tab.graphiql-tab-active>button.graphiql-tab-close,.graphiql-tab:hover>button.graphiql-tab-close,.graphiql-tab:focus-within>button.graphiql-tab-close{visibility:unset}.graphiql-tab.graphiql-tab-active{background-color:hsla(var(--color-neutral),var(--alpha-background-heavy));color:hsla(var(--color-neutral),1)}button.graphiql-tab-button{padding:var(--px-4) 0 var(--px-4) var(--px-8)}button.graphiql-tab-close{align-items:center;display:flex;padding:var(--px-4) var(--px-8)}button.graphiql-tab-close>svg{height:var(--px-8);width:var(--px-8)}.graphiql-history-header{font-size:var(--font-size-h2);font-weight:var(--font-weight-medium)}.graphiql-history-items{margin:var(--px-16) 0 0;list-style:none;padding:0}.graphiql-history-item{border-radius:var(--border-radius-4);color:hsla(var(--color-neutral),var(--alpha-secondary));display:flex;font-size:var(--font-size-inline-code);font-family:var(--font-family-mono);height:34px}.graphiql-history-item:hover{color:hsla(var(--color-neutral),1);background-color:hsla(var(--color-neutral),var(--alpha-background-light))}.graphiql-history-item:not(:first-child){margin-top:var(--px-4)}.graphiql-history-item.editable{background-color:hsla(var(--color-primary),var(--alpha-background-medium))}.graphiql-history-item.editable>input{background:transparent;border:none;flex:1;margin:0;outline:none;padding:0 var(--px-10);width:100%}.graphiql-history-item.editable>input::placeholder{color:hsla(var(--color-neutral),var(--alpha-secondary))}.graphiql-history-item.editable>button{color:hsl(var(--color-primary));padding:0 var(--px-10)}.graphiql-history-item.editable>button:active{background-color:hsla(var(--color-primary),var(--alpha-background-heavy))}.graphiql-history-item.editable>button:focus{outline:hsl(var(--color-primary)) auto 1px}.graphiql-history-item.editable>button>svg{display:block}button.graphiql-history-item-label{flex:1;padding:var(--px-8) var(--px-10);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}button.graphiql-history-item-action{align-items:center;color:hsla(var(--color-neutral),var(--alpha-secondary));display:flex;padding:var(--px-8) var(--px-6)}button.graphiql-history-item-action:hover{color:hsla(var(--color-neutral),1)}button.graphiql-history-item-action>svg{height:14px;width:14px}.graphiql-history-item-spacer{height:var(--px-16)}.graphiql-doc-explorer-default-value{color:hsl(var(--color-success))}a.graphiql-doc-explorer-type-name{color:hsl(var(--color-warning));text-decoration:none}a.graphiql-doc-explorer-type-name:hover{text-decoration:underline}a.graphiql-doc-explorer-type-name:focus{outline:hsl(var(--color-warning)) auto 1px}.graphiql-doc-explorer-argument>*+*{margin-top:var(--px-12)}.graphiql-doc-explorer-argument-name{color:hsl(var(--color-secondary))}.graphiql-doc-explorer-argument-deprecation{background-color:hsla(var(--color-warning),var(--alpha-background-light));border:1px solid hsl(var(--color-warning));border-radius:var(--border-radius-4);color:hsl(var(--color-warning));padding:var(--px-8)}.graphiql-doc-explorer-argument-deprecation-label{font-size:var(--font-size-hint);font-weight:var(--font-weight-medium)}.graphiql-doc-explorer-deprecation{background-color:hsla(var(--color-warning),var(--alpha-background-light));border:1px solid hsl(var(--color-warning));border-radius:var(--px-4);color:hsl(var(--color-warning));padding:var(--px-8)}.graphiql-doc-explorer-deprecation-label{font-size:var(--font-size-hint);font-weight:var(--font-weight-medium)}.graphiql-doc-explorer-directive{color:hsl(var(--color-secondary))}.graphiql-doc-explorer-section-title{align-items:center;display:flex;font-size:var(--font-size-hint);font-weight:var(--font-weight-medium);line-height:1}.graphiql-doc-explorer-section-title>svg{height:var(--px-16);margin-right:var(--px-8);width:var(--px-16)}.graphiql-doc-explorer-section-content{margin-left:var(--px-8);margin-top:var(--px-16)}.graphiql-doc-explorer-section-content>*+*{margin-top:var(--px-16)}.graphiql-doc-explorer-root-type{color:hsl(var(--color-info))}:root{--reach-combobox: 1}[data-reach-combobox-popover]{border:solid 1px hsla(0,0%,0%,.25);background:hsla(0,100%,100%,.99);font-size:85%}[data-reach-combobox-list]{list-style:none;margin:0;padding:0;user-select:none}[data-reach-combobox-option]{cursor:pointer;margin:0;padding:.25rem .5rem}[data-reach-combobox-option][aria-selected=true]{background:hsl(211,10%,95%)}[data-reach-combobox-option]:hover{background:hsl(211,10%,92%)}[data-reach-combobox-option][aria-selected=true]:hover{background:hsl(211,10%,90%)}[data-suggested-value]{font-weight:700}[data-reach-combobox]{color:hsla(var(--color-neutral),var(--alpha-secondary))}[data-reach-combobox]:not([data-state="idle"]){border:var(--popover-border);border-radius:var(--border-radius-4);box-shadow:var(--popover-box-shadow);color:hsla(var(--color-neutral),1)}[data-reach-combobox]:not([data-state="idle"]) .graphiql-doc-explorer-search-input{background:hsl(var(--color-base));border-bottom-left-radius:0;border-bottom-right-radius:0}.graphiql-doc-explorer-search-input{align-items:center;background-color:hsla(var(--color-neutral),var(--alpha-background-light));border-radius:var(--border-radius-4);display:flex;padding:var(--px-8) var(--px-12)}[data-reach-combobox-input]{border:none;background-color:transparent;margin-left:var(--px-4);width:100%}[data-reach-combobox-input]:focus{outline:none}[data-reach-combobox-popover]{background-color:hsl(var(--color-base));border:none;border-bottom-left-radius:var(--border-radius-4);border-bottom-right-radius:var(--border-radius-4);border-top:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));max-height:400px;overflow-y:auto;position:relative}[data-reach-combobox-list]{font-size:var(--font-size-body);padding:var(--px-4)}[data-reach-combobox-option]{border-radius:var(--border-radius-4);color:hsla(var(--color-neutral),var(--alpha-secondary));overflow-x:hidden;padding:var(--px-8) var(--px-12);text-overflow:ellipsis;white-space:nowrap}[data-reach-combobox-option][data-highlighted]{background-color:hsla(var(--color-neutral),var(--alpha-background-light))}[data-reach-combobox-option]:hover{background-color:hsla(var(--color-neutral),var(--alpha-background-medium))}[data-reach-combobox-option][data-highlighted]:hover{background-color:hsla(var(--color-neutral),var(--alpha-background-heavy))}[data-reach-combobox-option]+[data-reach-combobox-option]{margin-top:var(--px-4)}.graphiql-doc-explorer-search-type{color:hsl(var(--color-info))}.graphiql-doc-explorer-search-field{color:hsl(var(--color-warning))}.graphiql-doc-explorer-search-argument{color:hsl(var(--color-secondary))}.graphiql-doc-explorer-search-divider{color:hsla(var(--color-neutral),var(--alpha-secondary));font-size:var(--font-size-hint);font-weight:var(--font-weight-medium);margin-top:var(--px-8);padding:var(--px-8) var(--px-12)}.graphiql-doc-explorer-search-empty{color:hsla(var(--color-neutral),var(--alpha-secondary));padding:var(--px-8) var(--px-12)}a.graphiql-doc-explorer-field-name{color:hsl(var(--color-info));text-decoration:none}a.graphiql-doc-explorer-field-name:hover{text-decoration:underline}a.graphiql-doc-explorer-field-name:focus{outline:hsl(var(--color-info)) auto 1px}.graphiql-doc-explorer-item>:not(:first-child){margin-top:var(--px-12)}.graphiql-doc-explorer-argument-multiple{margin-left:var(--px-8)}.graphiql-doc-explorer-enum-value{color:hsl(var(--color-info))}.graphiql-doc-explorer-header{display:flex;justify-content:space-between;position:relative}.graphiql-doc-explorer-header:focus-within .graphiql-doc-explorer-title{visibility:hidden}.graphiql-doc-explorer-header:focus-within .graphiql-doc-explorer-back:not(:focus){color:transparent}.graphiql-doc-explorer-header-content{display:flex;flex-direction:column;min-width:0}.graphiql-doc-explorer-search{height:100%;position:absolute;right:0;top:0}.graphiql-doc-explorer-search:focus-within{left:0}.graphiql-doc-explorer-search [data-reach-combobox-input]{height:24px;width:4ch}.graphiql-doc-explorer-search [data-reach-combobox-input]:focus{width:100%}a.graphiql-doc-explorer-back{align-items:center;color:hsla(var(--color-neutral),var(--alpha-secondary));display:flex;text-decoration:none}a.graphiql-doc-explorer-back:hover{text-decoration:underline}a.graphiql-doc-explorer-back:focus{outline:hsla(var(--color-neutral),var(--alpha-secondary)) auto 1px}a.graphiql-doc-explorer-back:focus+.graphiql-doc-explorer-title{visibility:unset}a.graphiql-doc-explorer-back>svg{height:var(--px-8);margin-right:var(--px-8);width:var(--px-8)}.graphiql-doc-explorer-title{font-weight:var(--font-weight-medium);font-size:var(--font-size-h2);overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.graphiql-doc-explorer-title:not(:first-child){font-size:var(--font-size-h3);margin-top:var(--px-8)}.graphiql-doc-explorer-content>*{color:hsla(var(--color-neutral),var(--alpha-secondary));margin-top:var(--px-20)}.graphiql-doc-explorer-error{background-color:hsla(var(--color-error),var(--alpha-background-heavy));border:1px solid hsl(var(--color-error));border-radius:var(--border-radius-8);color:hsl(var(--color-error));padding:var(--px-8) var(--px-12)}.CodeMirror{font-family:monospace;height:300px;color:#000;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{padding:0 4px}.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid black;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-fat-cursor .CodeMirror-line::selection,.cm-fat-cursor .CodeMirror-line>span::selection,.cm-fat-cursor .CodeMirror-line>span>span::selection{background:transparent}.cm-fat-cursor .CodeMirror-line::-moz-selection,.cm-fat-cursor .CodeMirror-line>span::-moz-selection,.cm-fat-cursor .CodeMirror-line>span>span::-moz-selection{background:transparent}.cm-fat-cursor{caret-color:transparent}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{position:absolute;left:0;right:0;top:-50px;bottom:0;overflow:hidden}.CodeMirror-ruler{border-left:1px solid #ccc;top:0;bottom:0;position:absolute}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3,.cm-s-default .cm-type{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-s-default .cm-error,.cm-invalidchar{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:white}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-50px;margin-right:-50px;padding-bottom:50px;height:100%;outline:none;position:relative;z-index:0}.CodeMirror-sizer{position:relative;border-right:50px solid transparent}.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{position:absolute;z-index:6;display:none;outline:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-50px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:none!important;border:none!important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:transparent;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual}.CodeMirror-wrap pre.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;padding:.1px}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:none}.CodeMirror-scroll,.CodeMirror-sizer,.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute;pointer-events:none}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}div.CodeMirror-dragcursors,.CodeMirror-focused div.CodeMirror-cursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background-color:#ffa;background-color:#ff06}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:""}span.CodeMirror-selectedtext{background:none}.CodeMirror{height:100%;position:absolute;width:100%}.CodeMirror{font-family:var(--font-family-mono)}.CodeMirror,.CodeMirror-gutters{background:none;background-color:var(--editor-background, hsl(var(--color-base)))}.CodeMirror-linenumber{padding:0}.CodeMirror-gutters{border:none}.cm-s-graphiql{color:hsla(var(--color-neutral),var(--alpha-tertiary))}.cm-s-graphiql .cm-keyword{color:hsl(var(--color-primary))}.cm-s-graphiql .cm-def{color:hsl(var(--color-tertiary))}.cm-s-graphiql .cm-punctuation{color:hsla(var(--color-neutral),var(--alpha-tertiary))}.cm-s-graphiql .cm-variable{color:hsl(var(--color-secondary))}.cm-s-graphiql .cm-atom{color:hsl(var(--color-tertiary))}.cm-s-graphiql .cm-number{color:hsl(var(--color-success))}.cm-s-graphiql .cm-string{color:hsl(var(--color-warning))}.cm-s-graphiql .cm-builtin{color:hsl(var(--color-success))}.cm-s-graphiql .cm-string-2{color:hsl(var(--color-secondary))}.cm-s-graphiql .cm-attribute,.cm-s-graphiql .cm-meta{color:hsl(var(--color-tertiary))}.cm-s-graphiql .cm-property{color:hsl(var(--color-info))}.cm-s-graphiql .cm-qualifier{color:hsl(var(--color-secondary))}.cm-s-graphiql .cm-comment{color:hsla(var(--color-neutral),var(--alpha-secondary))}.cm-s-graphiql .cm-ws{color:hsla(var(--color-neutral),var(--alpha-tertiary))}.cm-s-graphiql .cm-invalidchar{color:hsl(var(--color-error))}.cm-s-graphiql .CodeMirror-cursor{border-left:2px solid hsla(var(--color-neutral),var(--alpha-secondary))}.cm-s-graphiql .CodeMirror-linenumber{color:hsla(var(--color-neutral),var(--alpha-tertiary))}div.CodeMirror span.CodeMirror-matchingbracket,div.CodeMirror span.CodeMirror-nonmatchingbracket{color:hsl(var(--color-warning))}.CodeMirror-selected,.CodeMirror-focused .CodeMirror-selected{background:hsla(var(--color-neutral),var(--alpha-background-heavy))}.CodeMirror-dialog{background:inherit;color:inherit;left:0;right:0;overflow:hidden;padding:var(--px-2) var(--px-6);position:absolute;z-index:6}.CodeMirror-dialog-top{border-bottom:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));padding-bottom:var(--px-12);top:0}.CodeMirror-dialog-bottom{border-top:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));bottom:0;padding-top:var(--px-12)}.CodeMirror-search-hint{display:none}.CodeMirror-dialog input{border:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));border-radius:var(--border-radius-4);padding:var(--px-4)}.CodeMirror-dialog input:focus{outline:hsl(var(--color-primary)) solid 2px}.cm-searching{background-color:hsla(var(--color-warning),var(--alpha-background-light));padding-bottom:1.5px;padding-top:.5px}.CodeMirror-foldmarker{color:#00f;text-shadow:#b9f 1px 1px 2px,#b9f -1px -1px 2px,#b9f 1px -1px 2px,#b9f -1px 1px 2px;font-family:arial;line-height:.3;cursor:pointer}.CodeMirror-foldgutter{width:.7em}.CodeMirror-foldgutter-open,.CodeMirror-foldgutter-folded{cursor:pointer}.CodeMirror-foldgutter-open:after{content:"\25be"}.CodeMirror-foldgutter-folded:after{content:"\25b8"}.CodeMirror-foldgutter{width:var(--px-12)}.CodeMirror-foldmarker{background-color:hsl(var(--color-info));border-radius:var(--border-radius-4);color:hsl(var(--color-base));font-family:inherit;margin:0 var(--px-4);padding:0 var(--px-8);text-shadow:none}.CodeMirror-foldgutter-open,.CodeMirror-foldgutter-folded{color:hsla(var(--color-neutral),var(--alpha-tertiary))}.CodeMirror-foldgutter-open:after,.CodeMirror-foldgutter-folded:after{margin:0 var(--px-2)}.graphiql-editor{height:100%;position:relative;width:100%}.graphiql-editor.hidden{left:-9999px;position:absolute;top:-9999px;visibility:hidden}.CodeMirror-lint-markers{width:16px}.CodeMirror-lint-tooltip{background-color:#ffd;border:1px solid black;border-radius:4px;color:#000;font-family:monospace;font-size:10pt;overflow:hidden;padding:2px 5px;position:fixed;white-space:pre;white-space:pre-wrap;z-index:100;max-width:600px;opacity:0;transition:opacity .4s;-moz-transition:opacity .4s;-webkit-transition:opacity .4s;-o-transition:opacity .4s;-ms-transition:opacity .4s}.CodeMirror-lint-mark{background-position:left bottom;background-repeat:repeat-x}.CodeMirror-lint-mark-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=)}.CodeMirror-lint-mark-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==)}.CodeMirror-lint-marker{background-position:center center;background-repeat:no-repeat;cursor:pointer;display:inline-block;height:16px;width:16px;vertical-align:middle;position:relative}.CodeMirror-lint-message{padding-left:18px;background-position:top left;background-repeat:no-repeat}.CodeMirror-lint-marker-warning,.CodeMirror-lint-message-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=)}.CodeMirror-lint-marker-error,.CodeMirror-lint-message-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=)}.CodeMirror-lint-marker-multiple{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:right bottom;width:100%;height:100%}.CodeMirror-lint-line-error{background-color:#b74c5114}.CodeMirror-lint-line-warning{background-color:#ffd3001a}.CodeMirror-lint-mark-error,.CodeMirror-lint-mark-warning{background-repeat:repeat-x;background-size:10px 3px;background-position:0 95%}.cm-s-graphiql .CodeMirror-lint-mark-error{color:hsl(var(--color-error))}.CodeMirror-lint-mark-error{background-image:linear-gradient(45deg,transparent 65%,hsl(var(--color-error)) 80%,transparent 90%),linear-gradient(135deg,transparent 5%,hsl(var(--color-error)) 15%,transparent 25%),linear-gradient(135deg,transparent 45%,hsl(var(--color-error)) 55%,transparent 65%),linear-gradient(45deg,transparent 25%,hsl(var(--color-error)) 35%,transparent 50%)}.cm-s-graphiql .CodeMirror-lint-mark-warning{color:hsl(var(--color-warning))}.CodeMirror-lint-mark-warning{background-image:linear-gradient(45deg,transparent 65%,hsl(var(--color-warning)) 80%,transparent 90%),linear-gradient(135deg,transparent 5%,hsl(var(--color-warning)) 15%,transparent 25%),linear-gradient(135deg,transparent 45%,hsl(var(--color-warning)) 55%,transparent 65%),linear-gradient(45deg,transparent 25%,hsl(var(--color-warning)) 35%,transparent 50%)}.CodeMirror-lint-tooltip{background-color:hsl(var(--color-base));border:var(--popover-border);border-radius:var(--border-radius-8);box-shadow:var(--popover-box-shadow);font-size:var(--font-size-body);font-family:var(--font-family);max-width:600px;overflow:hidden;padding:var(--px-12)}.CodeMirror-lint-message-error,.CodeMirror-lint-message-warning{background-image:none;padding:0}.CodeMirror-lint-message-error{color:hsl(var(--color-error))}.CodeMirror-lint-message-warning{color:hsl(var(--color-warning))}.CodeMirror-hints{position:absolute;z-index:10;overflow:hidden;list-style:none;margin:0;padding:2px;-webkit-box-shadow:2px 3px 5px rgba(0,0,0,.2);-moz-box-shadow:2px 3px 5px rgba(0,0,0,.2);box-shadow:2px 3px 5px #0003;border-radius:3px;border:1px solid silver;background:white;font-size:90%;font-family:monospace;max-height:20em;overflow-y:auto}.CodeMirror-hint{margin:0;padding:0 4px;border-radius:2px;white-space:pre;color:#000;cursor:pointer}li.CodeMirror-hint-active{background:#08f;color:#fff}.CodeMirror-hints{background:hsl(var(--color-base));border:var(--popover-border);border-radius:var(--border-radius-8);box-shadow:var(--popover-box-shadow);display:grid;font-family:var(--font-family);font-size:var(--font-size-body);grid-template-columns:auto fit-content(300px);max-height:264px;padding:0}.CodeMirror-hint{border-radius:var(--border-radius-4);color:hsla(var(--color-neutral),var(--alpha-secondary));grid-column:1 / 2;margin:var(--px-4);padding:var(--px-6) var(--px-8)!important}.CodeMirror-hint:not(:first-child){margin-top:0}li.CodeMirror-hint-active{background:hsla(var(--color-primary),var(--alpha-background-medium));color:hsl(var(--color-primary))}.CodeMirror-hint-information{border-left:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));grid-column:2 / 3;grid-row:1 / 99999;max-height:264px;overflow:auto;padding:var(--px-12)}.CodeMirror-hint-information-header{display:flex;align-items:baseline}.CodeMirror-hint-information-field-name{font-size:var(--font-size-h4);font-weight:var(--font-weight-medium)}.CodeMirror-hint-information-type-name-pill{border:1px solid hsla(var(--color-neutral),var(--alpha-tertiary));border-radius:var(--border-radius-4);color:hsla(var(--color-neutral),var(--alpha-secondary));margin-left:var(--px-6);padding:var(--px-4)}.CodeMirror-hint-information-type-name{color:inherit;text-decoration:none}.CodeMirror-hint-information-type-name:hover{text-decoration:underline dotted}.CodeMirror-hint-information-description{color:hsla(var(--color-neutral),var(--alpha-secondary));margin-top:var(--px-12)}.CodeMirror-info{background-color:hsl(var(--color-base));border:var(--popover-border);border-radius:var(--border-radius-8);box-shadow:var(--popover-box-shadow);color:hsla(var(--color-neutral),1);max-height:300px;max-width:400px;opacity:0;overflow:auto;padding:var(--px-12);position:fixed;transition:opacity .15s;z-index:10}.CodeMirror-info a{color:inherit;text-decoration:none}.CodeMirror-info a:hover{text-decoration:underline dotted}.CodeMirror-info .CodeMirror-info-header{display:flex;align-items:baseline}.CodeMirror-info .CodeMirror-info-header>.type-name,.CodeMirror-info .CodeMirror-info-header>.field-name,.CodeMirror-info .CodeMirror-info-header>.arg-name,.CodeMirror-info .CodeMirror-info-header>.directive-name,.CodeMirror-info .CodeMirror-info-header>.enum-value{font-size:var(--font-size-h4);font-weight:var(--font-weight-medium)}.CodeMirror-info .type-name-pill{border:1px solid hsla(var(--color-neutral),var(--alpha-tertiary));border-radius:var(--border-radius-4);color:hsla(var(--color-neutral),var(--alpha-secondary));margin-left:var(--px-6);padding:var(--px-4)}.CodeMirror-info .info-description{color:hsla(var(--color-neutral),var(--alpha-secondary));margin-top:var(--px-12);overflow:hidden}.CodeMirror-jump-token{text-decoration:underline dotted;cursor:pointer}.auto-inserted-leaf.cm-property{animation-duration:6s;animation-name:insertionFade;border-radius:var(--border-radius-4);padding:var(--px-2)}@keyframes insertionFade{0%,to{background-color:none}15%,85%{background-color:hsla(var(--color-warning),var(--alpha-background-light))}}button.graphiql-toolbar-button{display:flex;align-items:center;justify-content:center;height:var(--toolbar-width);width:var(--toolbar-width)}button.graphiql-toolbar-button.error{background:hsla(var(--color-error),var(--alpha-background-heavy))}.graphiql-execute-button-wrapper{position:relative}button.graphiql-execute-button{background-color:hsl(var(--color-primary));border:none;border-radius:var(--border-radius-8);cursor:pointer;height:var(--toolbar-width);padding:0;width:var(--toolbar-width)}button.graphiql-execute-button:hover{background-color:hsla(var(--color-primary),.9)}button.graphiql-execute-button:active{background-color:hsla(var(--color-primary),.8)}button.graphiql-execute-button:focus{outline:hsla(var(--color-primary),.8) auto 1px}button.graphiql-execute-button>svg{color:#fff;display:block;height:var(--px-16);margin:auto;width:var(--px-16)}.graphiql-toolbar-listbox,button.graphiql-toolbar-menu{display:block;height:var(--toolbar-width);width:var(--toolbar-width)} + +/* Everything */ +.graphiql-container { + background-color: hsl(var(--color-base)); + display: flex; + height: 100%; + margin: 0; + overflow: hidden; + width: 100%; +} +/* The sidebar */ +.graphiql-container .graphiql-sidebar { + display: flex; + flex-direction: column; + justify-content: space-between; + padding: var(--px-8); + width: var(--sidebar-width); +} +.graphiql-container .graphiql-sidebar .graphiql-sidebar-section { + display: flex; + flex-direction: column; + gap: var(--px-8); +} +.graphiql-container .graphiql-sidebar button { + display: flex; + align-items: center; + justify-content: center; + color: hsla(var(--color-neutral), var(--alpha-secondary)); + height: calc(var(--sidebar-width) - (2 * var(--px-8))); + width: calc(var(--sidebar-width) - (2 * var(--px-8))); +} +.graphiql-container .graphiql-sidebar button.active { + color: hsla(var(--color-neutral), 1); +} +.graphiql-container .graphiql-sidebar button:not(:first-child) { + margin-top: var(--px-4); +} +.graphiql-container .graphiql-sidebar button > svg { + height: var(--px-20); + width: var(--px-20); +} +/* The main content, i.e. everything except the sidebar */ +.graphiql-container .graphiql-main { + display: flex; + flex: 1; + min-width: 0; +} +/* The current session and tabs */ +.graphiql-container .graphiql-sessions { + background-color: hsla(var(--color-neutral), var(--alpha-background-light)); + /* Adding the 8px of padding to the inner border radius of the query editor */ + border-radius: calc(var(--border-radius-12) + var(--px-8)); + display: flex; + flex-direction: column; + flex: 1; + max-height: 100%; + margin: var(--px-16); + margin-left: 0; + min-width: 0; +} +/* The session header containing tabs and the logo */ +.graphiql-container .graphiql-session-header { + align-items: center; + display: flex; + justify-content: space-between; + height: var(--session-header-height); +} +/* The button to add a new tab */ +button.graphiql-tab-add { + height: 100%; + padding: 0 var(--px-4); +} +button.graphiql-tab-add > svg { + color: hsla(var(--color-neutral), var(--alpha-secondary)); + display: block; + height: var(--px-16); + width: var(--px-16); +} +.graphiql-add-tab-wrapper { + padding: var(--px-12) 0; +} +/* The right-hand-side of the session header */ +.graphiql-container .graphiql-session-header-right { + align-items: stretch; + display: flex; +} +/* The GraphiQL logo */ +.graphiql-container .graphiql-logo { + color: hsla(var(--color-neutral), var(--alpha-secondary)); + font-size: var(--font-size-h4); + font-weight: var(--font-weight-medium); + padding: var(--px-12) var(--px-16); +} +/* Undo default link styling for the default GraphiQL logo link */ +.graphiql-container .graphiql-logo .graphiql-logo-link { + color: hsla(var(--color-neutral), var(--alpha-secondary)); + text-decoration: none; +} +/* The editor of the session */ +.graphiql-container .graphiql-session { + display: flex; + flex: 1; + padding: 0 var(--px-8) var(--px-8); +} +/* All editors (query, variable, headers) */ +.graphiql-container .graphiql-editors { + background-color: hsl(var(--color-base)); + border-radius: calc(var(--border-radius-12)); + box-shadow: var(--popover-box-shadow); + display: flex; + flex: 1; + flex-direction: column; +} +.graphiql-container .graphiql-editors.full-height { + margin-top: calc(var(--px-8) - var(--session-header-height)); +} +/* The query editor and the toolbar */ +.graphiql-container .graphiql-query-editor { + border-bottom: 1px solid + hsla(var(--color-neutral), var(--alpha-background-heavy)); + display: flex; + flex: 1; + padding: var(--px-16); +} +/* The query editor */ +.graphiql-container .graphiql-query-editor-wrapper { + display: flex; + flex: 1; +} +/* The vertical toolbar next to the query editor */ +.graphiql-container .graphiql-toolbar { + margin-left: var(--px-16); + width: var(--toolbar-width); +} +.graphiql-container .graphiql-toolbar > * + * { + margin-top: var(--px-8); +} +/* The toolbar icons */ +.graphiql-toolbar-icon { + color: hsla(var(--color-neutral), var(--alpha-tertiary)); + display: block; + height: calc(var(--toolbar-width) - (var(--px-8) * 2)); + width: calc(var(--toolbar-width) - (var(--px-8) * 2)); +} +/* The tab bar for editor tools */ +.graphiql-container .graphiql-editor-tools { + align-items: center; + cursor: row-resize; + display: flex; + justify-content: space-between; + padding: var(--px-8); +} +.graphiql-container .graphiql-editor-tools button { + color: hsla(var(--color-neutral), var(--alpha-secondary)); +} +.graphiql-container .graphiql-editor-tools button.active { + color: hsla(var(--color-neutral), 1); +} +/* The tab buttons to switch between editor tools */ +.graphiql-container .graphiql-editor-tools-tabs { + cursor: auto; + display: flex; +} +.graphiql-container .graphiql-editor-tools-tabs > button { + padding: var(--px-8) var(--px-12); +} +.graphiql-container .graphiql-editor-tools-tabs > button + button { + margin-left: var(--px-8); +} +/* An editor tool, e.g. variable or header editor */ +.graphiql-container .graphiql-editor-tool { + flex: 1; + padding: var(--px-16); +} +/** + * The way CodeMirror editors are styled they overflow their containing + * element. For some OS-browser-combinations this might cause overlap issues, + * setting the position of this to `relative` makes sure this element will + * always be on top of any editors. + */ +.graphiql-container .graphiql-toolbar, +.graphiql-container .graphiql-editor-tools, +.graphiql-container .graphiql-editor-tool { + position: relative; +} +/* The response view */ +.graphiql-container .graphiql-response { + --editor-background: transparent; + display: flex; + flex: 1; + flex-direction: column; + position: relative; +} +/* The results editor wrapping container */ +.graphiql-container .graphiql-response .result-window { + position: relative; + flex: 1; +} +/* The footer below the response view */ +.graphiql-container .graphiql-footer { + border-top: 1px solid + hsla(var(--color-neutral), var(--alpha-background-heavy)); +} +/* The plugin container */ +.graphiql-container .graphiql-plugin { + border-left: 1px solid + hsla(var(--color-neutral), var(--alpha-background-heavy)); + flex: 1; + overflow-y: auto; + padding: var(--px-16); +} +/* Generic drag bar for horizontal resizing */ +.graphiql-container .graphiql-horizontal-drag-bar { + width: var(--px-12); + cursor: col-resize; +} +.graphiql-container .graphiql-horizontal-drag-bar:hover::after { + border: var(--px-2) solid + hsla(var(--color-neutral), var(--alpha-background-heavy)); + border-radius: var(--border-radius-2); + content: ''; + display: block; + height: 25%; + margin: 0 auto; + position: relative; + /* (100% - 25%) / 2 = 37.5% */ + top: 37.5%; + width: 0; +} +.graphiql-container .graphiql-chevron-icon { + color: hsla(var(--color-neutral), var(--alpha-tertiary)); + display: block; + height: var(--px-12); + margin: var(--px-12); + width: var(--px-12); +} +/* Generic spin animation */ +.graphiql-spin { + animation: spin 0.8s linear 0s infinite; +} +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} +/* The header of the settings dialog */ +reach-portal .graphiql-dialog-header { + align-items: center; + display: flex; + justify-content: space-between; + padding: var(--px-24); +} +/* The title of the settings dialog */ +reach-portal .graphiql-dialog-title { + font-size: var(--font-size-h3); + font-weight: var(--font-weight-medium); +} +/* A section inside the settings dialog */ +reach-portal .graphiql-dialog-section { + align-items: center; + border-top: 1px solid + hsla(var(--color-neutral), var(--alpha-background-heavy)); + display: flex; + justify-content: space-between; + padding: var(--px-24); +} +reach-portal .graphiql-dialog-section > :not(:first-child) { + margin-left: var(--px-24); +} +/* The section title in the settings dialog */ +reach-portal .graphiql-dialog-section-title { + font-size: var(--font-size-h4); + font-weight: var(--font-weight-medium); +} +/* The section caption in the settings dialog */ +reach-portal .graphiql-dialog-section-caption { + color: hsla(var(--color-neutral), var(--alpha-secondary)); +} +reach-portal .graphiql-table { + border-collapse: collapse; + width: 100%; +} +reach-portal .graphiql-table :is(th, td) { + border: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy)); + padding: var(--px-8) var(--px-12); +} +/* A single key the short-key dialog */ +reach-portal .graphiql-key { + background-color: hsla(var(--color-neutral), var(--alpha-background-medium)); + border-radius: var(--border-radius-4); + padding: var(--px-4); +} +/* Avoid showing native tooltips for icons with titles */ +.graphiql-container svg { + pointer-events: none; +} + diff --git a/studio/styles/ui.scss b/studio/styles/ui.scss index e927eb48782..891b2a0bb7d 100644 --- a/studio/styles/ui.scss +++ b/studio/styles/ui.scss @@ -224,10 +224,40 @@ @apply border-b flex border-scale-500; } + &.Docs--table-editor .doc-section--introduction { + .text { + width: 85%; + } + } + + &.Docs--table-editor .doc-section--introduction { + .text { + width: 85%; + } + } + + &.Docs--table-editor .doc-section--client-libraries { + flex-direction: column; + margin-bottom: 40px; + + .text, + .code { + width: 100%; + max-width: none; + } + .code { + margin-bottom: 20px; + } + } + .doc-section article:first-child { @apply prose prose-docs prose-sm max-w-none; } + &.Docs--table-editor .doc-section__table-name { + @apply mt-12; + } + .text { @apply p-4 px-6 w-1/2; max-width: 50%; @@ -239,7 +269,7 @@ a { @apply transition; } - + a:hover { @apply text-brand-1000; } @@ -270,6 +300,9 @@ @apply text-brand-900; } } + .codeblock-container + .codeblock-container { + @apply mt-12; + } } ::-moz-selection { @@ -339,4 +372,44 @@ label { @apply mt-1; } -} \ No newline at end of file +} + +.thin-scrollbars { + scrollbar-width: thin; + scrollbar-color: var(--colors-slate9) var(--colors-slate1); + + .dark & { + scrollbar-color: var(--colors-slate10) var(--colors-slate3o); + } + + ::-webkit-scrollbar { + background: var(--colors-slate2); + height: 3px; + width: 0px; + border-radius: 0; + + .dark & { + background: var(--colors-slate6); + } + } + + ::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px var(--colors-slate6); + -webkit-border-radius: 5px; + border-radius: 5px; + + .dark & { + -webkit-box-shadow: inset 0 0 6px var(--colors-slate8); + } + } + + ::-webkit-scrollbar-thumb { + -webkit-border-radius: 5px; + border-radius: 5px; + background: var(--colors-slate9); + + .dark & { + -webkit-box-shadow: inset 0 0 6px var(--colors-slate12); + } + } +} diff --git a/studio/styles/users-table.scss b/studio/styles/users-table.scss index 5bd24e6d6e5..8b137891791 100644 --- a/studio/styles/users-table.scss +++ b/studio/styles/users-table.scss @@ -1,39 +1 @@ -.users-table-container { - scrollbar-width: thin; - scrollbar-color: var(--colors-slate9) var(--colors-slate1); - .dark & { - scrollbar-color: var(--colors-slate10) var(--colors-slate3o); - } - - ::-webkit-scrollbar { - background: var(--colors-slate2); - height: 3px; - width: 0px; - border-radius: 0; - - .dark & { - background: var(--colors-slate6); - } - } - - ::-webkit-scrollbar-track { - -webkit-box-shadow: inset 0 0 6px var(--colors-slate6); - -webkit-border-radius: 5px; - border-radius: 5px; - - .dark & { - -webkit-box-shadow: inset 0 0 6px var(--colors-slate8); - } - } - - ::-webkit-scrollbar-thumb { - -webkit-border-radius: 5px; - border-radius: 5px; - background: var(--colors-slate9); - - .dark & { - -webkit-box-shadow: inset 0 0 6px var(--colors-slate12); - } - } -} diff --git a/studio/tailwind.config.js b/studio/tailwind.config.js index 21148b6013e..42d8f6c8553 100644 --- a/studio/tailwind.config.js +++ b/studio/tailwind.config.js @@ -73,6 +73,125 @@ module.exports = ui({ 'code::after': { content: '""', }, + '--tw-prose-body': theme('colors.scale[1100]'), + '--tw-prose-headings': theme('colors.scale[1200]'), + '--tw-prose-lead': theme('colors.scale[1100]'), + '--tw-prose-links': theme('colors.scale[1100]'), + '--tw-prose-bold': theme('colors.scale[1100]'), + '--tw-prose-counters': theme('colors.scale[1100]'), + '--tw-prose-bullets': theme('colors.scale[900]'), + '--tw-prose-hr': theme('colors.scale[500]'), + '--tw-prose-quotes': theme('colors.scale[1100]'), + '--tw-prose-quote-borders': theme('colors.scale[500]'), + '--tw-prose-captions': theme('colors.scale[700]'), + '--tw-prose-code': theme('colors.scale[1200]'), + '--tw-prose-pre-code': theme('colors.scale[900]'), + '--tw-prose-pre-bg': theme('colors.scale[400]'), + '--tw-prose-th-borders': theme('colors.scale[500]'), + '--tw-prose-td-borders': theme('colors.scale[200]'), + '--tw-prose-invert-body': theme('colors.scale[200]'), + '--tw-prose-invert-headings': theme('colors.white'), + '--tw-prose-invert-lead': theme('colors.scale[500]'), + '--tw-prose-invert-links': theme('colors.white'), + '--tw-prose-invert-bold': theme('colors.white'), + '--tw-prose-invert-counters': theme('colors.scale[400]'), + '--tw-prose-invert-bullets': theme('colors.scale[600]'), + '--tw-prose-invert-hr': theme('colors.scale[700]'), + '--tw-prose-invert-quotes': theme('colors.scale[100]'), + '--tw-prose-invert-quote-borders': theme('colors.scale[700]'), + '--tw-prose-invert-captions': theme('colors.scale[400]'), + // the following are typography overrides + // examples can be seen here —> https://github.com/tailwindlabs/tailwindcss-typography/blob/master/src/styles.js + // reset all header font weights + 'h1, h2, h3, h4, h5': { + fontWeight: '400', + }, + h2: { + fontWeight: '400', + }, + p: { + fontWeight: '400', + }, + pre: { + background: 'none', + padding: 0, + marginBottom: '32px', + }, + ul: { + listStyleType: 'none', + paddingLeft: '1rem', + }, + 'ul li': { + position: 'relative', + }, + 'ul li::before': { + position: 'absolute', + top: '0.75rem', + left: '-1rem', + height: '0.125rem', + width: '0.5rem', + borderRadius: '0.25rem', + backgroundColor: 'var(--colors-scale7)', + content: '""', + }, + ol: { + paddingLeft: '1rem', + counterReset: 'item', + listStyleType: 'none', + }, + 'ol li': { display: 'block', position: 'relative', paddingLeft: '1rem' }, + 'ol li::before': { + position: 'absolute', + top: '0.25rem', + left: '-1rem', + height: '1.2rem', + width: '1.2rem', + borderRadius: '0.25rem', + backgroundColor: 'var(--colors-scale3)', + border: '1px solid var(--colors-scale5)', + content: 'counter(item) " "', + counterIncrement: 'item', + fontSize: '12px', + color: 'var(--colors-scale9)', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + + 'p img': { + border: '1px solid var(--colors-scale4)', + borderRadius: '4px', + overflow: 'hidden', + }, + iframe: { + border: '1px solid ' + theme('borderColor.DEFAULT'), + borderRadius: theme('borderRadius.lg'), + }, + td: { + borderBottom: '1px solid ' + theme('colors.scale[400]'), + }, + code: { + fontWeight: '400', + padding: '0.2rem 0.4rem', + backgroundColor: theme('colors.scale[400]'), + border: '1px solid ' + theme('colors.scale[500]'), + borderRadius: theme('borderRadius.lg'), + wordBreak: 'break-all', + }, + a: { + position: 'relative', + transition: 'color 0.3s ease-in-out', + paddingBottom: '2px', + fontWeight: '400', + color: 'var(--colors-scale12)', + textDecorationLine: 'underline', + textDecorationColor: 'var(--colors-brand7)', + textDecorationThickness: '1px', + textUnderlineOffset: '4px', + }, + 'a:hover': { + textDecorationColor: 'var(--colors-brand9)', + }, }, }, docs: { diff --git a/studio/tests/components/Reports/ReportWidget.test.js b/studio/tests/components/Reports/ReportWidget.test.js index 96cb6d6f26e..b657ec2d897 100644 --- a/studio/tests/components/Reports/ReportWidget.test.js +++ b/studio/tests/components/Reports/ReportWidget.test.js @@ -47,7 +47,4 @@ test('static elements', async () => { render( 'something'} />) await screen.findByText(/Some chart/) await screen.findByText(/something/) - const moreBtn = await screen.findByTitle(/Actions\.\.\./) - clickDropdown(moreBtn) - await screen.findByText(/Open in Logs Explorer/) }) diff --git a/studio/tests/pages/projects/reports/api-report.test.js b/studio/tests/pages/projects/reports/api-report.test.js new file mode 100644 index 00000000000..92cb474d077 --- /dev/null +++ b/studio/tests/pages/projects/reports/api-report.test.js @@ -0,0 +1,28 @@ +import { get } from 'lib/common/fetch' +import { render } from '../../../helpers' +import { waitFor, screen } from '@testing-library/react' +import { ApiReport } from 'pages/project/[ref]/reports/api-overview' +import userEvent from '@testing-library/user-event' + +beforeEach(() => { + // reset mocks between tests + get.mockReset() + get.mockImplementation(async (_url) => [{ data: [] }]) +}) + +test(`static elements`, async () => { + render() + await screen.findByText('Total Requests') + await screen.findByText('Response Errors') + await screen.findByText('Response Speed') + await screen.findByText(/Last 24 hours/) + await screen.findByText(/Custom/) +}) + +test('refresh button', async () => { + render() + await waitFor(() => expect(get).toBeCalled()) + get.mockReset() + userEvent.click(await screen.findByText(/Refresh/)) + await waitFor(() => expect(get).toBeCalled()) +}) diff --git a/studio/tests/pages/projects/reports/product-reports.test.js b/studio/tests/pages/projects/reports/product-reports.test.js deleted file mode 100644 index eefe4718b71..00000000000 --- a/studio/tests/pages/projects/reports/product-reports.test.js +++ /dev/null @@ -1,47 +0,0 @@ -import dayjs from 'dayjs' -import utc from 'dayjs/plugin/utc' -dayjs.extend(utc) - -import { get, post } from 'lib/common/fetch' - -import { useRouter } from 'next/router' -import { useParams } from 'hooks' -import { render } from '../../../helpers' -import { fireEvent, waitFor, screen } from '@testing-library/react' -import { ApiReport } from 'pages/project/[ref]/reports/api' -import { AuthReport } from 'pages/project/[ref]/reports/auth' -import userEvent from '@testing-library/user-event' - -beforeEach(() => { - // reset mocks between tests - get.mockReset() - post.mockReset() - useRouter.mockReset() - useRouter.mockReturnValue({ - query: { ref: '123' }, - }) - useParams.mockReturnValue({ ref: '123' }) - get.mockImplementation(async (url) => { - return [{ data: [] }] - }) - get.mockResolvedValue([]) - post.mockResolvedValue([]) -}) - -describe.each([ - { Page: ApiReport, contains: ['API'] }, - { Page: AuthReport, contains: ['Auth'] }, -])('$Page rendering', ({ Page, contains }) => { - test(`contains ${contains}`, async () => { - render() - - contains.forEach((word) => { - expect(screen.findByText(word)).resolves.toBeTruthy() - }) - }) - test('static elements', async () => { - render() - await screen.findByText(/Last 7 days/) - await screen.findAllByText(/Refresh/) - }) -}) diff --git a/supabase/functions/og-images/component/Docs.tsx b/supabase/functions/og-images/component/Docs.tsx index 5293a1b4bc2..4ccd9209502 100644 --- a/supabase/functions/og-images/component/Docs.tsx +++ b/supabase/functions/og-images/component/Docs.tsx @@ -36,13 +36,13 @@ const Docs = (props: Props) => {

    - {type} + {type.substring(0, 1).toUpperCase() + title.substring(1)} )}
    -

    {!title ? 'Supabase' : title.substring(0, 1).toUpperCase() + title.substring(1)}

    +

    {!title ? 'Supabase' : title}

    {description}