diff --git a/.github/workflows/avoid-typos.yml b/.github/workflows/avoid-typos.yml index cd8d982f4aa..1cbe64238ac 100644 --- a/.github/workflows/avoid-typos.yml +++ b/.github/workflows/avoid-typos.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code. - uses: actions/checkout@v1 + uses: actions/checkout@v3 - name: misspell uses: reviewdog/action-misspell@v1 with: diff --git a/.github/workflows/og_images.yml b/.github/workflows/og_images.yml index 2e1461461d2..c3cc6568e73 100644 --- a/.github/workflows/og_images.yml +++ b/.github/workflows/og_images.yml @@ -3,9 +3,9 @@ name: Deploy OG Images on: push: branches: - - main + - master paths: - - 'supabase/functions/og-images' + - 'supabase/functions/og-images/**' workflow_dispatch: jobs: @@ -18,11 +18,11 @@ jobs: steps: - name: Check out repo - - uses: actions/checkout@v3 + uses: actions/checkout@v3 - name: Setup the Supabase CLI - - uses: supabase/setup-cli@v1 + uses: supabase/setup-cli@v1 with: - version: 1.0.0 + version: latest - - 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 43305fc1aed..aef4d8f166d 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -10,15 +10,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repo - uses: actions/checkout@v2 - # disable this check while we migrate to monorepo + uses: actions/checkout@v3 - name: Run prettier - uses: EPMatt/reviewdog-action-prettier@v1 - with: - github_token: ${{ secrets.github_token }} - reporter: github-pr-review - level: warning - command: npx prettier -c 'apps/**/*.{js,jsx,ts,tsx,css,md,json}' + run: |- + npx prettier -c 'apps/**/*.{js,jsx,ts,tsx,css,md,json}' + # [Joshen] Temp disable the following due to an issue + # https://github.com/EPMatt/reviewdog-action-prettier/issues/34 + # uses: EPMatt/reviewdog-action-prettier@v1 + # with: + # github_token: ${{ secrets.github_token }} + # reporter: github-pr-review + # level: warning + # command: npx prettier -c 'apps/**/*.{js,jsx,ts,tsx,css,md,json}' # run: |- # function run_check() { @@ -38,11 +41,25 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Run prettier - uses: EPMatt/reviewdog-action-prettier@v1 - with: - github_token: ${{ secrets.github_token }} - reporter: github-pr-review - level: warning - command: npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,json}' + run: |- + npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,json}' + # uses: EPMatt/reviewdog-action-prettier@v1 + # with: + # github_token: ${{ secrets.github_token }} + # 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/.github/workflows/studio-build.yml b/.github/workflows/studio-build.yml deleted file mode 100644 index 35d1f97b8b1..00000000000 --- a/.github/workflows/studio-build.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Studio Build - -on: - pull_request: - branches: - - master - - studio - paths: - - 'studio/**' - - 'package-lock.json' -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [16.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - name: Install deps - run: npm ci - working-directory: ./ - - name: Run build - run: npx turbo run build --filter=studio - working-directory: ./ diff --git a/.github/workflows/studio-tests.yml b/.github/workflows/studio-tests.yml index 72a84549568..11fda1e766c 100644 --- a/.github/workflows/studio-tests.yml +++ b/.github/workflows/studio-tests.yml @@ -24,9 +24,9 @@ jobs: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: 'npm' diff --git a/.gitignore b/.gitignore index f536bae18d7..8e245433c33 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ node_modules out .docz tmp +.swp coverage allure-results @@ -119,4 +120,6 @@ typings/ **/supabase/.branches **/supabase/.temp -apps/new-docs/* \ No newline at end of file +apps/new-docs/* + +gcloud.json \ No newline at end of file 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/DEVELOPERS.md b/DEVELOPERS.md index 0281b1b0bd7..e88f3cb89f0 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -28,7 +28,7 @@ You need to install and configure the following dependencies on your machine to - [Git](http://git-scm.com/) - [Node.js v16.x (LTS)](http://nodejs.org) -- [npm](https://www.npmjs.com/) version 7+ or [Yarn](https://yarnpkg.com/) +- [npm](https://www.npmjs.com/) version 8.x.x or [Yarn](https://yarnpkg.com/) ## Local development @@ -57,7 +57,7 @@ To contribute code to [Supabase](https://supabase.com), you must fork the [Supab [Supabase](https://supabase.com) uses [Turborepo](https://turborepo.org/docs) to manage and run this monorepo. -1. Install the dependences in the root of the repo. +1. Install the dependencies in the root of the repo. ```sh npm install # install dependencies @@ -113,7 +113,7 @@ Following the changes to the [Supabase docs](https://supabase.com/blog/new-supab - Inside of `apps/docs` create a `.env.local` file with the following: `NEXT_PUBLIC_NEW_DOCS=true` -Now when you run a local devlopment docs server you will see the new docs site. +Now when you run a local development docs server you will see the new docs site. --- @@ -136,3 +136,9 @@ Create a new entry in the [`redirects.js`](https://github.com/supabase/supabase/ ## Community channels Stuck somewhere? Have any questions? Join the [Discord Community Server](https://discord.supabase.com/) or the [Github Discussions](https://github.com/supabase/supabase/discussions). We are here to help! + +## Contributors + + + contributors + diff --git a/README.md b/README.md index 0ae54fc8d0b..f3506927e0a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- - + +

--- @@ -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/SECURITY.md b/SECURITY.md index b257313bf5d..65dd29a7e19 120000 --- a/SECURITY.md +++ b/SECURITY.md @@ -1 +1 @@ -apps/reference/static/.well-known/security.txt +apps/docs/public/.well-known/security.txt \ No newline at end of file diff --git a/about/.gitignore b/about/.gitignore deleted file mode 100644 index 9b744063823..00000000000 --- a/about/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -build -.docusaurus diff --git a/about/README.md b/about/README.md deleted file mode 100644 index faaa71bd879..00000000000 --- a/about/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Supabase Docs - -### Contributing to the specs - -- `cd /web/spec/` -- run `make` which will pull the latest TSDoc with your updates -- add your comments to `supabase.yml` | `postgrest.yml` | `gotrue.yml` | `realtime.yml` -- run `npm run gen:supabase` inside the `/spec` folder which will generate the Docusaurus pages -- run `npm run build` to make sure the build is working -- submit your PR diff --git a/about/docs/careers.mdx b/about/docs/careers.mdx deleted file mode 100644 index c2afe678804..00000000000 --- a/about/docs/careers.mdx +++ /dev/null @@ -1,7 +0,0 @@ ---- -id: careers -title: Work at Supabase -description: We're hiring. Start your career at Supabase. ---- - -[Browse open roles](https://boards.greenhouse.io/supabase). diff --git a/about/docs/careers/_snippets/about.mdx b/about/docs/careers/_snippets/about.mdx deleted file mode 100644 index d86aaefe294..00000000000 --- a/about/docs/careers/_snippets/about.mdx +++ /dev/null @@ -1,3 +0,0 @@ -Supabase is an open source Firebase alternative. We're backed by Y Combinator, Mozilla, Coatue, and a bunch of [amazing developers](https://supabase.com/blog/angels-of-supabase). - -Supabase is a platform which makes it incredibly easy to build _and_ scale your projects. diff --git a/about/docs/careers/_snippets/apply.mdx b/about/docs/careers/_snippets/apply.mdx deleted file mode 100644 index aaedbe92954..00000000000 --- a/about/docs/careers/_snippets/apply.mdx +++ /dev/null @@ -1 +0,0 @@ -Apply for this role using our [online form](https://boards.greenhouse.io/supabase). \ No newline at end of file diff --git a/about/docs/careers/_snippets/hiring-freeze.mdx b/about/docs/careers/_snippets/hiring-freeze.mdx deleted file mode 100644 index 89ce1edca27..00000000000 --- a/about/docs/careers/_snippets/hiring-freeze.mdx +++ /dev/null @@ -1,3 +0,0 @@ -:::note -We're currently on a Hiring Freeze and only open to very specific roles. You're still welcome to apply, but we might take a while to respond! -::: \ No newline at end of file diff --git a/about/docs/careers/_snippets/process.mdx b/about/docs/careers/_snippets/process.mdx deleted file mode 100644 index 8dd01678a82..00000000000 --- a/about/docs/careers/_snippets/process.mdx +++ /dev/null @@ -1,11 +0,0 @@ - -- We hire in batches, but accept applications on a rolling basis, so now is always a good time to apply :) -- The entire process is fully remote and all communication will happen over email or via video chat. -- Once you've submitted your application, the team will review your submission, and may reach out for a short screening interview over video call. -- If you pass the screen you will be invited to up to four follow up interviews. The calls: - - usually take between 20-45 minutes each depending on the interviewer - - are all 1:1 - - will be with both founders, a member of either the growth or engineering team (depending on the role), and usually one other person from your immediate team or function. -- Once the interviews are over, the team will meet to discuss several roles and candidates and may: - - ask one or two follow-up questions over email or a quick call - - go directly to making an offer \ No newline at end of file diff --git a/about/docs/careers/_snippets/roles.mdx b/about/docs/careers/_snippets/roles.mdx deleted file mode 100644 index 756d3256227..00000000000 --- a/about/docs/careers/_snippets/roles.mdx +++ /dev/null @@ -1,91 +0,0 @@ - - -
-

Ex-Founders

-

Create your own role.

-
-
- - -
-

Data Engineers

-

Develop insights with the Growth Team.

-
-
- - -
-

Growth Marketer

-

Get our tools in front of the next billion developers.

-
-
- - -
-

Auth Engineers

-

Develop and Maintain our Auth Products.

-
-
- - -
-

Postgres Experts

-

Go deep on Postgres internals and extensions.

-
-
- - -
-

Technical Writers

-

Take the lead on our internal and external written communications

-
-
- - -
-

Cloud Platform Engineers and SRE's

-

Build our cloud platform.

-
-
- - -
-

Developer Advocate

-

Get more developers interested in Postgres and Supabase.

-
-
- - -
-

Support Engineers

-

Build internal tooling, automated tests, and support processes.

-
-
- - -
-

Full-stack Engineers

-

Build our Dashboard, tools, tests, and infra.

-
-
- - -
-

Graphic / Web Designers

-

Build our brand and visual communication.

-
-
- - -
-

Front End Developers

-

Build our Dashboard, tools, tests, and client libraries.

-
-
- - -
-

Technical recruiter

-

Take the lead on our technical recruitment.

-
-
diff --git a/about/docs/careers/_snippets/team.mdx b/about/docs/careers/_snippets/team.mdx deleted file mode 100644 index ebfb940626f..00000000000 --- a/about/docs/careers/_snippets/team.mdx +++ /dev/null @@ -1,9 +0,0 @@ - - -- 100% remote. Work anywhere in the world. No location-based adjustment to your salary. -- Autonomous work. We work collaboratively on projects, but you set your own pace. -- We're a startup. It's unstructured. -- Collectively founded more than a dozen venture-backed companies. -- More than 10 different nationalities. -- We deeply believe in the efficacy of collaborative open source. We support existing communities and tools, rather than building "yet another xx". -- We "dogfood" everything. If you use it in your project, we use it in Supabase. \ No newline at end of file diff --git a/about/docs/careers/auth-engineers.mdx b/about/docs/careers/auth-engineers.mdx deleted file mode 100644 index 949128eb262..00000000000 --- a/about/docs/careers/auth-engineers.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -id: careers-auth-engineers -slug: auth-engineers -title: Auth Engineers. -description: Supabase is hiring Auth Engineers ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -- [Auth](https://supabase.com/auth) is one of the most popular products in the Supabase stack -- Be a Maintainer of our Open Source Auth repos written in [golang](https://github.com/supabase/gotrue) and [typescript](https://github.com/supabase/gotrue-js) -- Be a key decision maker in the Supabase Auth team -- Expand our auth offering and work with our users to prioritize the product roadmap -- Previous working experience of Authorization and Authentication is a must -- Experience working with OAuth, SSO, SAML etc. -- Advise our users on how best to make use of Gotrue and Postgres RLS policies -- Help grow the Auth team - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/data-engineers.mdx b/about/docs/careers/data-engineers.mdx deleted file mode 100644 index c488f1cc8e6..00000000000 --- a/about/docs/careers/data-engineers.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -id: careers-data-engineers -slug: data-engineers -title: Data Engineers. -description: Supabase is hiring Data Engineers ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -- Be the business intelligence star player at Supabase by helping dig deep into the data we are generating. Deep knowledge of SQL and relevant tooling is a must. -- Help customer success & sales team by working to identify and monitor: - - Usage Insights - - Find and segment cohorts based around account level data, behavior, traffic sources, and measure their subsequent behaviors - - Find and monitor product qualified leads based around account and behavior characteristics - - Customer health segmentation into actionable cohorts (e.g. healthy, churn, at risk) -- Help identify opportunities high touch, low touch, & tech touch customer success & sales approaches -- Help sales & customer success identify opportunity for unscheduled outreach -- Working with internal tooling team to identify and implement solutions for growth, sales, customer success teams needs -- Help customer success team with data to prepare for client quarterly business reviews -- Help team put human and automated procedures in place based around data insights -- Monitoring and measurement of feature usage - maintaining any data pipelines or queries as we continue to update and add features -- Assisting with setup and measurement of growth experiments - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/developer-advocate.mdx b/about/docs/careers/developer-advocate.mdx deleted file mode 100644 index fdbaf08c1df..00000000000 --- a/about/docs/careers/developer-advocate.mdx +++ /dev/null @@ -1,66 +0,0 @@ ---- -id: careers-developer-advocate -slug: developer-advocate -title: Developer Advocate (US Timezone). -description: Supabase is hiring Developer Advocates and Community Managers ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -Supabase is looking for a Developer Advocate in US timezone. Supabase is a fully remote company and this is a remote role, that being said, we have some specific timezone and location coverage needs, so candidates who can operate in US timezone and can easily reach events within the US will be preferred. - -## Responsibilities - -- Attend conferences, meetups, and other events in person and in US (time). -- Develop and deliver engaging talks and educational materials. -- Write articles and create video tutorials -- Represent Supabase online via social media (Twitter, TikTok) and live coding streaming on a regular schedule. -- Work with the rest of the team, and the marketing team to develop swag and memes. -- Build examples using Supabase and other frameworks. -- Improve our documentation. -- Aid in the development of our client libraries. -- Respond to GitHub Issues and Discussions. -- Aid in developing the SupaSquad community program - -For the right candidate, additional responsibilities may include: - -- Working with the Founders to construct and execute on a hiring plan to grow the Developer Relations team. - -## Required skills - -- 3+ Years Experience as a Software Engineer. -- Fluent in spoken and written English. -- Familiar with popular JavaScript frameworks (React, Vue, Svelte, Node.js) -- Experience working with GitHub source code management, and CI/CD tools. - -## Nice to have - -- Prior DevRel experience not required but need to show some background and desire for the outlined responsibilities. -- Fluency in another spoken language. -- Familiar with Postgres, Golang, or mobile app development is a plus. - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/developer-success.mdx b/about/docs/careers/developer-success.mdx deleted file mode 100644 index e7be5de18a2..00000000000 --- a/about/docs/careers/developer-success.mdx +++ /dev/null @@ -1,58 +0,0 @@ ---- -id: careers-developer-success -slug: developer-success -title: Developer Success Engineer -description: Supabase is hiring Developer Success Engineers ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -Supabase is hiring Developer Success Engineers. Help us build the easiest database platform in the world. - -### Responsibilities - -- Focused on unblocking developers, backlog of issues. -- Priority focus on Enterprise customers. -- Help high-value customers with implementation and monitoring. -- Build internal tools for support team. -- Produce tests and documentation to mitigate recurring support tickets. -- Triage/clean/close issues on any of our repos. -- Work with the community on all of our channels: GitHub, Discord, and Email. - -## About you - -- 2+ years experience in engineering or a highly technical support role. -- You must love remote work. -- You possess exceptional communication skills. -- Availability to work within a weekend and on-call rota. - -## Bonus if you: - -- Are a Polyglot developer, with some skill in Node.js/JavaScript. -- Have PostgreSQL experience. - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/founders.mdx b/about/docs/careers/founders.mdx deleted file mode 100644 index d974fb046c9..00000000000 --- a/about/docs/careers/founders.mdx +++ /dev/null @@ -1,40 +0,0 @@ ---- -id: careers-founders -slug: founders -title: Ex-Founders. -description: Supabase is hiring ex-Founders ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -The Supabase team is a group of founders who previously scaled businesses to millions in revenue, hundreds of employees, and worked in a multitude of industries: -fintech, healthcare & medtech, crypto, insurance, agritech, marketplaces, and of course - open source dev tools. - -We recognize the value of the founder mindset. -If you're a founder and you're looking for an opportunity to work with other founders, tell us how you can help Supabase succeed and we'll give you the resources to make it happen. - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/front-end-developers.mdx b/about/docs/careers/front-end-developers.mdx deleted file mode 100644 index 3f190eaef65..00000000000 --- a/about/docs/careers/front-end-developers.mdx +++ /dev/null @@ -1,83 +0,0 @@ ---- -id: careers-front-end -slug: front-end-developers -title: Front End Developers -description: Supabase is hiring Front End Developers ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -We're looking for a Senior Front End Developer to join our team to help change the way developers build their applications. - -You'll be primarily responsible for growing our NextJS app to a comprehensive development stack, including our backend API, user authentication and expanding our React UI. - -You'll be an indispensable part of team, by working very closely with our front end team, designers, founders and backend engineers; you'll be directly contributing to technical choices, building a team culture and co authoring internal processes for a growing team. We want to expand our development team rapidly throughout 2022 and want you to be at the centre of growing and nurturing this talent. - -This is an opportunity to truly take ownership of a web platform, defining how it evolves, and how we can can scale our users, team, product and processes. - -## What we need - -You will have a background of working and leading developers, building trust across a team and continually evolving and improving standards and processes. - -You are a strong web developer, having worked with front end JS frameworks in production, ideally at high user scale, and for user facing applications. - -You care about attention to detail, the quality and robustness of code you write, but also about collaborating with others. Reviewing fairly and honestly other team member's work and helping them become the best they can be. - -You've worked in multi disciplinary teams before, working with designers, developers, marketeers, customer support and sales staff. You communicate problems at high level and can effectively convey your assumptions, issues and progress to others well and honestly. - -## Responsibilities - -- Build our dashboard, iterating existing features and building great new features. -- Take ownership of the dashboard front end stack, planning and iterating on it based on a roadmap that you have an active voice in moulding. -- Build tools within the Supabase ecosystem, occasionally dog fooding our own work. -- Interact with the wider developer community with blog posts, social media, discord; speaking at events and interacting with customers on a daily basis. -- Build out the test suite for the Dashboard. -- Improve the speed and code quality of our Dashboard. - -## Must have skills - -- 5+ years experience in web development (JavaScript, HTML5, CSS3, SCSS/LESS). -- Have an understanding of modern JS (ES6 etc). -- Experience with Node. -- Experience with React (We use NextJS everywhere). -- Experience with state management (Mobx, Redux etc). -- Experience of any kind of React UI Components. -- Willingness to work in an agile environment. -- Willingness to evolve and shape the development tools/stack. -- Willing to train and mentor junior developers. -- Fluent in the English language. - -## Nice to have skills - -- Opinions and ideas on how to align teams up to work really effectively. -- Experience working on large projects, and the code and contribution complexities that come with it. -- Has used tailwind. -- Web accessibility experience. -- Experience with PostgreSQL. -- Experience with design software (Figma, Sketch). - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/full-stack.mdx b/about/docs/careers/full-stack.mdx deleted file mode 100644 index 660c4aee36c..00000000000 --- a/about/docs/careers/full-stack.mdx +++ /dev/null @@ -1,53 +0,0 @@ ---- -id: careers-full-stack -slug: full-stack-developers -title: Full Stack Developers. -description: Supabase is hiring Full Stack Developers ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -Supabase is hiring Full Stack Developers. - -### Responsibilities - -- Build our Dashboard. -- Build tools within the Supabase ecosystem. -- Build example applications. -- Write blog posts. -- Respond to GitHub Discussions. -- Write tests. - -### Preferred skills - -- Typescript -- Elixir/Erlang -- Go Lang -- React / HTML / CSS / Next.js -- Tailwind - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/graphic-web-designers.mdx b/about/docs/careers/graphic-web-designers.mdx deleted file mode 100644 index 3652de4fbd9..00000000000 --- a/about/docs/careers/graphic-web-designers.mdx +++ /dev/null @@ -1,82 +0,0 @@ ---- -id: careers-graphic-web-designer -slug: graphic-web-designers -title: Graphic / Web Designers -description: Supabase is hiring Designers ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -We're looking for Graphic / Web Designers to join our team to help change the way developers build their applications. - -You'll be primarily responsible for growing our marketing and brand collateral across the entire Supabase ecosystem. - -You'll be an indispensable part of team, by working very closely with our sales, marketing, front end team, founders and backend engineers; you'll be directly contributing to brand decisions, building a team culture and co authoring internal processes for a growing team. We want to expand our marketing efforts rapidly throughout 2022 and want you to be at the centre of growing and nurturing our brand, while also nurturing new talent that come on board. - -This is an opportunity to truly take ownership of a fast growing developer tools outward facing visual communication, defining how it evolves, and how we can can scale it with our new products and new types of users. - -## What we need - -You will have a background of working with developers, building trust across a team and continually evolving and improving standards and processes. - -You are a strong designer, with an extreme attention to detail. You are someone who strives to make visual communication the best it can be where others would give up. - -You care about the quality and robustness of the designs you create while also collaborating with others in a healthy way. Reviewing fairly and honestly other team member's work and helping them become the best they can be. - -You've worked in multi disciplinary teams before, working with other designers, developers, marketeers, customer support and sales staff. You communicate problems at high level and can effectively convey your assumptions, issues and progress to others well and honestly. - -## Responsibilities - -- Maintain a design library for Supabase brand, covering web assets, social media, video assets, icons. -- Help grow and maintain a shared library in Figma for UI components -- Design and maintain document templates / presentation templates, while also working with the sales/growth team to realize the best format for co-collaboration. -- Work with the design and front end team to improve our visual language, create brand guidelines and help maintain a consistent supabase visual voice across all our mediums. -- Deliver clear and well annotated mockups for marketing websites, documentation websites, blog posts and web applications -- Make the best Swag in the world - -## Must have skills - -- You can make what is complicated visually simple and easy to understand. -- You can jump between departments easily and communicate clearly so that the best solutions can be realised -- Understands how to design for power tools (dashboards - denser, contextual elements appear when needed, multiple states of visual information), vs marketing (landing pages - spacious, eye pleasing, sales driven, all elements visible) -- You are familiar with all modern design software, from figma/sketch to adobe suite -- You can deliver quickly, even in situations were there is little time for preparing. - -## Experience - -- At least 3 years professional experience as a Designer, working freelance/agency. Preferably with experience in a start up environment, Developer tool / SASS product. -- Fluent in English language. -- Willingness to work in an agile environment - -## Nice to have skills - -- Prior knowledge of databases (postgres) -- You are also an engineer, (ie, You can build what you design) -- Interest in training and mentoring others -- web skills, such as HTML, JS, CSS, React, NextJS etc - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/marketing.mdx b/about/docs/careers/marketing.mdx deleted file mode 100644 index 11673773649..00000000000 --- a/about/docs/careers/marketing.mdx +++ /dev/null @@ -1,67 +0,0 @@ ---- -id: careers-growth-marketer -slug: growth-marketer -title: Growth Marketer. -description: Supabase is hiring a Growth Marketer ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -Supabase is looking for a growth marketer to help continue to raise awareness of Supabase within the developer community. This role will require working across the entire customer lifecycle helping shape programs that drive awareness, activation, engagement, and expansion. In this role you will play a key role in establishing and operating the marketing and growth strategies for supabase by working cross-functionally with our engineering and growth teams to ensure we have the best possible marketing strategy and execution. - -The ideal candidate has experience helping grow products which enable developers. You should be both strategic and highly execution focussed. - -**Some of the things you will be doing:** - -- Planning and executing the Supabase marketing strategy -- Designing and executing on industry specific marketing campaigns -- Help promote and amplify all things Supabase: events, hackathons, our DevRel team profiles, launch week -- Outbound to co-ordinate and schedule events & conferences that we can participate in -- Outbound to find channels for content distribution (written, video, podcasts, interviews etc) -- Outbound to find partnership opportunities with code schools & 3rd parties for content co-creation -- Understand whats driving our user acquisition funnel and assets required to increase activation -- Monitor and measure our entire portfolio of assets and communicating the results of our marketing efforts internally so we understand what is most effective -- Create enterprise grade marketing materials that we can start to distribute from our own website and across other channels such as LinkedIn and DZone -- Find interesting case study opportunities and help turn them into content we can distribute across different segments -- Liaise and work with our incredible DevRel team to help them identify opportunities and amplify their efforts -- work with our amazing team of community writers to help find topics to write about -- Scope out and action our SEO strategy -- Help running email campaigns -- Community management - -## Skills and Experience - -- Building marketing funnels and running outbound campaigns -- Creating and managing social media campaigns -- Marketing to software developers & developer tooling space -- Marketing collateral aimed at enterprise and cloud native customers -- Metrics driven & an experimental mindset -- Marketing automation and CRM tools -- Experience with analytics platforms -- SEO experience - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/postgres-experts.mdx b/about/docs/careers/postgres-experts.mdx deleted file mode 100644 index 1bfe33646ce..00000000000 --- a/about/docs/careers/postgres-experts.mdx +++ /dev/null @@ -1,44 +0,0 @@ ---- -id: careers-postgres-experts -slug: postgres-experts -title: Postgres Experts. -description: Supabase is hiring Postgres Experts ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -Supabase is hiring Postgres Experts. - -### Responsibilities - -- Contribute to the Postgres Core. -- Work on Postgres open source tooling. -- Help engineer Postgres Reliability and observability into our cloud platform. -- Develop multi-tenant solutions for thousands of databases. -- Debug database issues and provide customer support if required. - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/sre.mdx b/about/docs/careers/sre.mdx deleted file mode 100644 index e46ccdf7ba3..00000000000 --- a/about/docs/careers/sre.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -id: careers-sre -slug: sre -title: Cloud Platform Engineers and SREs. -description: Supabase is hiring Cloud Platform Engineers and SREs ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -Supabase is hiring Cloud Platform Engineers and Site Reliability Engineers. - -### Responsibilities - -- Help build the Supabase Cloud offering. -- Improve our system architecture to reduce costs while balancing security and performance. -- Work on designing and tracking metrics for platform uptime. -- Increase observability into our system by capturing relevant metrics and logs. -- Implement and maintain intrusion detection, automated remediation and patch management systems. -- Design CI/CD systems to speed up deploys with proper change and release management processes. -- Work on our SOC2 and GDPR compliance initiatives. - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/support.mdx b/about/docs/careers/support.mdx deleted file mode 100644 index 9fe75084508..00000000000 --- a/about/docs/careers/support.mdx +++ /dev/null @@ -1,70 +0,0 @@ ---- -id: careers-support -slug: support-and-qa -title: Support Engineers. -description: Supabase is hiring Support Engineers ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -Supabase is hiring Developer Support Engineers. - -### Responsibilities - -- Providing initial responses to all support cases. -- Triaging support cases. Including directing users to previous answers, code examples, documentation, or escalating to specific technical or business team members. -- Monitoring GitHub issues, discussions, twitter, email, and other feedback channels. -- Creating test cases and improving test coverage, if you have experience with testing. - -For the right candidate, additional responsibilities may include: - -- Establishing a workflow to reduce 'time to response' and 'time to fix' that can scale to multiple team members. -- Identifying where internal tooling might be developed or obtained to improve support efficiency. -- Establishing key support metrics and identifying how best to measure them. -- Working with the Founders to construct and execute on a hiring plan to grow the support team. - -### Requirements - -- 1+ year tech experience. -- Familiar with popular JavaScript frameworks (React, Vue, Svelte) and Node.js. -- Experience working with GitHub source code management, and CI/CD tools. - -## Nice to have - -- Experience writing tests -- Familiar with Postgres and Golang is a plus. - -## Timezones - -- We're looking for global coverage, in particular the US, European and APAC timezone. - -## Four day work week - -This role is optionally a four-day workweek if you are interested in working from Friday to Monday. We can also discuss a shorter (pro-rated) week. -In particular we would like to hire people who would be willing to work on weekends (or at least one of the days (Saturday or Sunday). - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/technical-recruiter.mdx b/about/docs/careers/technical-recruiter.mdx deleted file mode 100644 index 1769a491c28..00000000000 --- a/about/docs/careers/technical-recruiter.mdx +++ /dev/null @@ -1,59 +0,0 @@ ---- -id: careers-technical-recruiter -slug: technical-recruiter -title: Technical Recruiter -description: Supabase is hiring Technical Recruiters ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -#### Key Responsibilities: - -- Be accountable for implementing inclusive recruiting practices and finding ways to engage more under-represented talent for technical roles -- Ability to understand and communicate the business' current technical challenges to entice candidates who are driven by solving interesting problems -- Accountable to starting a candidate search project from scratch through requirement gathering, job posting, sourcing, networking, previous applicants follow-ups etc - -#### Requirements: - -- Minimum 2+ years technical recruiting experience across the full hiring process from sourcing to the offer stage -- Technical knowledge of different coding languages/technical stacks/terminology -- You have expertise in filling Engineering, Infrastructure, Data Science, and other technical roles. -- Self motivated Technical Recruiter that enjoys working in a fun, fast paced environment! -- A solid understanding of what makes a great candidate experience -- You enjoy reaching out to others and learning what drives them in their career -- You are not afraid to own all parts of an operation in a remote setting -- Ability to communicate effectively and effortlessly with candidates and internal stakeholders of all career levels -- Excellent interpersonal skills, resourcefulness, and follow-up - -#### Candidate Nice to Haves - -- A continuous improvement mindset with an iterative approach to making things better. Whether it's your interview technique, pipeline optimization or anything else that you discover. -- Fluency with Greenhouse or other Applicant Tracking Systems, as well as various sourcing platforms (e.g., LinkedIn, GitHub, Work at a Startup, etc) -- You're Comfortable coordinating all candidate touch points -- You can plan out batch days & other projects to drive excellence for our candidate experience and quality of interviews - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/careers/technical-writers.mdx b/about/docs/careers/technical-writers.mdx deleted file mode 100644 index cbe0215eee0..00000000000 --- a/about/docs/careers/technical-writers.mdx +++ /dev/null @@ -1,62 +0,0 @@ ---- -id: careers-technical-writers -slug: technical-writers -title: Technical Writers. -description: Supabase is hiring Technical Writers ---- - -import Roles from '@site/docs/careers/_snippets/roles.mdx' -import About from '@site/docs/careers/_snippets/about.mdx' -import Team from '@site/docs/careers/_snippets/team.mdx' -import Apply from '@site/docs/careers/_snippets/apply.mdx' -import Process from '@site/docs/careers/_snippets/process.mdx' - -## About the role - -Do you know what amazing documentation looks like? - -Do you have experience building for the web? - -Are you an engaging writer with an eye for detail? - -Responsibilities includes: - -- Blog posts for the Supabase website -- Helping other team members communicate their wins with edits and suggestions -- Writing internal docs such as: documenting processes, how-to guides etc. -- Writing and Organizing our documentation, and generally setting the direction on how they are structured -- Helping with enterprise orientated marketing collateral: thought leadership, white papers, case studies -- Working directly with the Founders, Growth, and Dev Rel teams on strategy - -Must Have: - -- Intermediate understanding of Postgres -- Full stack development experience -- Fluent in English -- Track record of producing content for a developer audience -- Enjoys working online remotely with minimal supervision - -Nice to Have: - -- Understanding of SEO -- Previous experience with SaaS content marketing - -## About Supabase - -{' '} - -## About the team - -{' '} - -## Apply - -{' '} - -## Process - -{' '} - -## All roles - -{' '} diff --git a/about/docs/handbook.mdx b/about/docs/handbook.mdx deleted file mode 100755 index fcd81e9ad8a..00000000000 --- a/about/docs/handbook.mdx +++ /dev/null @@ -1,10 +0,0 @@ ---- -id: handbook -title: Handbook -description: 'What is the Supabase Handbook?' -slug: /handbook ---- - -## What is this? - -This is the internal Supabase guide. It is a work in progress. diff --git a/about/docs/handbook/contributing.mdx b/about/docs/handbook/contributing.mdx deleted file mode 100644 index f2c45557326..00000000000 --- a/about/docs/handbook/contributing.mdx +++ /dev/null @@ -1,25 +0,0 @@ ---- -id: contributing -title: Contributing -description: Want to help? ---- - -## How to contribute - -Supabase has many [open source repos](https://github.com/orgs/supabase/repositories). Some of the more popular ones are: - - [Supabase](https://github.com/supabase/supabase) - Our Dashboard, Websites, and example apps - - [Supabase-js](https://github.com/supabase/supabase-js) - A clientside library written in Typescript - - [Gotrue](https://github.com/supabase/gotrue) - Our auth server, written in Go - - [Realtime](https://github.com/supabase/realtime) - A server for streaming changes from Postgres, written in Elixir - - [browse all](https://github.com/orgs/supabase/repositories) - -Want to contribute? Why not jump into our GitHub repo and: - -- [Sponsor Supabase](https://github.com/sponsors/supabase). -- [Answer Discussions](https://github.com/supabase/supabase/discussions). -- Submit an issue to one of our [repos](https://github.com/orgs/supabase/repositories) -- Report a performance issue or a part of the documentation that you find confusing. -- Create a pull request in one of our [repos](https://github.com/orgs/supabase/repositories) -- [Translate our Readme](https://github.com/supabase/supabase/issues/1341). -- Try our products and give feedback. -- Spread the word if you like what we are doing. \ No newline at end of file diff --git a/about/docs/handbook/supasquad.mdx b/about/docs/handbook/supasquad.mdx deleted file mode 100644 index 28ac655629a..00000000000 --- a/about/docs/handbook/supasquad.mdx +++ /dev/null @@ -1,89 +0,0 @@ ---- -id: supasquad -title: SupaSquad -description: Supabase Advocate program ---- - -The SupaSquad is an official Supabase advocate program where community members help build and manage the Supabase community. - -- Official recognition in the Supabase community. -- Direct connection to the Supabase team. -- Help steer the Supabase community. - -![Supabase SupaSquad](/img/supabase-squad.png) - -## Requirements - -As a member of the Squad, you choose the approach where you'll provide the most value. -You can help in one of five ways: - -### Maintainer - -Help maintain Supabase repositories. This includes building client libraries, managing issues, and fixing bugs. - -### Expert - -Answer user questions on GitHub [Discussions](https://github.com/supabase/supabase/discussions), [Discord](https://discord.supabase.com), and various other social platforms. - -### Advocate - -Spread the word on social channels and help to answer Supabase-related questions in the broader community and social channels. - -### Builder - -Build Supabase examples, blog about them, and them add to the [SupaSquad GitHub Org](https://github.com/supasquad). - -### Author - -Write guest blog posts, create documentation, and help Supabase global expansion through translation. - -### Moderator - -Help us maintain the community guidelines in our GitHub and Community-led communities such as [Discord](https://discord.supabase.com), [Reddit](https://reddit.com/r/Supabase/), -[StackOverflow](https://stackoverflow.com/questions/tagged/supabase) etc. - -## Benefits for SupaSquad members - -- Access to a Supabase Discord channel providing direct communication with the team, Discord badges, and elevated privileges. -- Special AMA sessions with members of the Supabase team. -- Monthly DevRel committee call with industry-leading Developer Advocates (many of whom are [angel investors](https://supabase.com/blog/angels-of-supabase)), where you can learn from the best. -- We'll help you build your audience by promoting content via the Supabase social channels. -- Early access to new features (and the opportunity to provide feedback to the team!). -- Free credits that you can use for Squad efforts. -- Direct access to members of the Supabase team for questions, suggestions, etc. -- Help shape the future of the program. -- Exclusive Supabase Team swag drops usually exclusively reserved to the Supabase core team. - -## How to join - -1. Apply to join the program using [this form](https://airtable.com/shr0FtLqLfhpuEya8). -2. We will start taking interviewing members from `1 September 2021`. -3. Entry is capped to 20 founding members so we can grow the community at a healthy pace and provide a great experience to all new users. -4. Membership is valid for one year, and can be renewed every year. - -## FAQs - -
- - Why are you only admitting 20 new members? - - The entire Supabase team is only 20 people, so as you can imagine adding another 20 people sounds like - a lot to us! We wish we could admit everyone who wanted to join. But we also want to make sure everyone - who joins the Squad has an awesome experience. In the future we will probably expand the intake to - include a monthly quota. -
-
- - What is expected? - - Mostly just enthusiasm. If you are interested in Open Source and want to get involved, the SupaSquad - program is a great channel. You'll be given opportunities to contribute to the community in whatever - ways match your skillset. -
-
- - What if I become too busy to contribute? - - No worries! The program isn't a job. It's just an opportunity to build your skillset and audience within - the Supabase ecosystem. -
diff --git a/about/docusaurus.config.js b/about/docusaurus.config.js deleted file mode 100755 index 6a1985531be..00000000000 --- a/about/docusaurus.config.js +++ /dev/null @@ -1,239 +0,0 @@ -module.exports = { - title: 'Supabase', - tagline: 'Build in a weekend. Scale to millions.', - url: 'https://supabase.com', - baseUrl: '/', - favicon: '/favicon.ico', - organizationName: 'supabase', // Usually your GitHub org/user name. - projectName: 'supabase', // Usually your repo name. - themeConfig: { - forceDarkMode: true, - darkMode: true, - colorMode: { - // "light" | "dark" - defaultMode: 'dark', - - // Hides the switch in the navbar - // Useful if you want to support a single color mode - disableSwitch: false, - - // Should we use the prefers-color-scheme media-query, - // using user system preferences, instead of the hardcoded defaultMode - respectPrefersColorScheme: false, - - // Dark/light switch icon options - switchConfig: { - // Icon for the switch while in dark mode - darkIcon: ' ', - darkIconStyle: { - marginTop: '1px', - }, - lightIcon: ' ', - lightIconStyle: { - marginTop: '1px', - }, - }, - }, - sidebarCollapsible: true, - algolia: { - apiKey: '766d56f13dd1e82f43253559b7c86636', - indexName: 'supabase', - }, - image: '/img/supabase-og-image.png', // used for meta tag, in particular og:image and twitter:image - metaImage: '/img/supabase-og-image.png', - navbar: { - // classNames: 'shadow--md', - // title: 'supabase', - hideOnScroll: true, - logo: { - alt: 'Supabase', - src: '/supabase-light.svg', - srcDark: '/supabase-dark.svg', - }, - items: [ - { - href: 'https://github.com/supabase/supabase', - className: 'navbar-item-github', - position: 'right', - }, - { - href: 'https://twitter.com/supabase', - className: 'navbar-item-twitter', - position: 'right', - }, - { - label: 'Careers', - to: 'https://boards.greenhouse.io/supabase', - activeBasePath: '/careers', - position: 'left', - }, - { - label: 'Handbook', - to: '/handbook', - activeBaseRegex: '/handbook', - position: 'left', - }, - ], - }, - prism: { - defaultLanguage: 'js', - additionalLanguages: ['dart'], - plugins: ['line-numbers', 'show-language'], - theme: require('@kiwicopple/prism-react-renderer/themes/vsDark'), - darkTheme: require('@kiwicopple/prism-react-renderer/themes/vsDark'), - }, - footer: { - links: [ - { - title: 'Company', - items: [ - { - label: 'Blog', - to: 'https://supabase.com/blog', - }, - { - label: 'Humans.txt', - to: 'https://supabase.com/humans.txt', - }, - { - label: 'Lawyers.txt', - to: 'https://supabase.com/lawyers.txt', - }, - ], - }, - { - title: 'Team', - items: [ - { - label: 'Handbook', - href: '/handbook', - }, - ], - }, - { - title: 'Community', - items: [ - { - label: 'GitHub', - href: 'https://github.com/supabase/supabase', - }, - { - label: 'Twitter', - href: 'https://twitter.com/supabase', - }, - { - label: 'DevTo', - href: 'https://dev.to/supabase', - }, - { - label: 'Discord', - href: 'https://discord.supabase.com', - }, - { - label: 'RSS', - href: 'https://supabase.com/rss.xml', - }, - ], - }, - { - title: 'Resources', - items: [ - { - label: 'Brand Assets', - to: 'https://supabase.com/brand-assets', - }, - ], - }, - ], - copyright: `Copyright © ${new Date().getFullYear()} Supabase.`, - }, - }, - presets: [ - [ - '@docusaurus/preset-classic', - { - docs: { - sidebarPath: require.resolve('./sidebars.js'), - routeBasePath: '/', - }, - theme: { - customCss: require.resolve('./src/css/custom.css'), - }, - blog: false, - }, - ], - ], - plugins: [ - // [ - // '@docusaurus/plugin-content-docs', - // { - // id: 'supabase-client', // for first plugin-content-docs with "resources/" path - // // homePageId: "doc2", - // path: './ref/supabase', // Path to data on filesystem, relative to site dir. - // routeBasePath: 'ref/supabase', // URL Route. - // include: ['**/*.md', '**/*.mdx'], - // sidebarPath: require.resolve('./sidebar_spec_supabase.js'), - // // disableVersioning: true, // if not set with versions, throw: Identifier 'React' has already been declared - // }, - // ], - // [ - // '@docusaurus/plugin-content-docs', - // { - // id: 'postgrest-client', // for first plugin-content-docs with "resources/" path - // // homePageId: "doc2", - // path: './ref/postgrest', // Path to data on filesystem, relative to site dir. - // routeBasePath: 'ref/postgrest', // URL Route. - // include: ['**/*.md', '**/*.mdx'], - // sidebarPath: require.resolve('./sidebar_spec_postgrest.js'), - // // disableVersioning: true, // if not set with versions, throw: Identifier 'React' has already been declared - // }, - // ], - // [ - // '@docusaurus/plugin-content-docs', - // { - // id: 'gotrue-client', // for first plugin-content-docs with "resources/" path - // // homePageId: "doc2", - // path: './ref/gotrue', // Path to data on filesystem, relative to site dir. - // routeBasePath: 'ref/gotrue', // URL Route. - // include: ['**/*.md', '**/*.mdx'], - // sidebarPath: require.resolve('./sidebar_spec_gotrue.js'), - // // disableVersioning: true, // if not set with versions, throw: Identifier 'React' has already been declared - // }, - // ], - // [ - // '@docusaurus/plugin-content-docs', - // { - // id: 'realtime-client', // for first plugin-content-docs with "resources/" path - // // homePageId: "doc2", - // path: './ref/realtime', // Path to data on filesystem, relative to site dir. - // routeBasePath: 'ref/realtime', // URL Route. - // include: ['**/*.md', '**/*.mdx'], - // sidebarPath: require.resolve('./sidebar_spec_realtime.js'), - // // disableVersioning: true, // if not set with versions, throw: Identifier 'React' has already been declared - // }, - // ], - // [ - // '@docusaurus/plugin-content-docs', - // { - // id: 'realtime-server', // for first plugin-content-docs with "resources/" path - // path: './tools/realtime', // Path to data on filesystem, relative to site dir. - // routeBasePath: 'docs/realtime', // URL Route. - // include: ['**/*.md', '**/*.mdx'], - // sidebarPath: require.resolve('./sidebar_realtime_server.js'), - // // disableVersioning: true, // if not set with versions, throw: Identifier 'React' has already been declared - // }, - // ], - // [ - // '@docusaurus/plugin-content-docs', - // { - // id: 'postgrest', // for first plugin-content-docs with "resources/" path - // // homePageId: "doc2", - // path: './ref/postgrest', // Path to data on filesystem, relative to site dir. - // routeBasePath: 'ref/postgrest', // URL Route. - // include: ['**/*.md', '**/*.mdx'], - // sidebarPath: require.resolve('./sidebar_spec_postgrest.js'), - // // disableVersioning: true, // if not set with versions, throw: Identifier 'React' has already been declared - // }, - // ], - ], -} diff --git a/about/package-lock.json b/about/package-lock.json deleted file mode 100644 index 4ec443b7240..00000000000 --- a/about/package-lock.json +++ /dev/null @@ -1,15098 +0,0 @@ -{ - "name": "supabase", - "version": "0.0.3", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@algolia/cache-browser-local-storage": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.5.1.tgz", - "integrity": "sha512-TAQHRHaCUAR0bNhUHG0CnO6FTx3EMPwZQrjPuNS6kHvCQ/H8dVD0sLsHyM8C7U4j33xPQCWi9TBnSx8cYXNmNw==", - "requires": { - "@algolia/cache-common": "4.5.1" - } - }, - "@algolia/cache-common": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.5.1.tgz", - "integrity": "sha512-Sux+pcedQi9sfScIiQdl6pEaTVl712qM9OblvDhnaeF1v6lf4jyTlRTiBLP7YBLuvO1Yo54W3maf03kmz9PVhA==" - }, - "@algolia/cache-in-memory": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.5.1.tgz", - "integrity": "sha512-fzwAtBFwveuG+E5T/namChEIvdVl0DoV3djV1C078b/JpO5+DeAwuXIJGYbyl950u170n5NEYuIwYG+R6h4lJQ==", - "requires": { - "@algolia/cache-common": "4.5.1" - } - }, - "@algolia/client-account": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.5.1.tgz", - "integrity": "sha512-2WFEaI7Zf4ljnBsSAS4e+YylZ5glovm78xFg4E1JKA8PE6M+TeIgUY6HO2ouLh2dqQKxc9UfdAT1Loo/dha2iQ==", - "requires": { - "@algolia/client-common": "4.5.1", - "@algolia/client-search": "4.5.1", - "@algolia/transporter": "4.5.1" - } - }, - "@algolia/client-analytics": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.5.1.tgz", - "integrity": "sha512-bTmZUU8zhZMWBeGEQ/TVqLoL3OOT0benU0HtS3iOnQURwb+AOCv3RsgZvkj2djp+M24Q6P8/L34uBJMmCurbLg==", - "requires": { - "@algolia/client-common": "4.5.1", - "@algolia/client-search": "4.5.1", - "@algolia/requester-common": "4.5.1", - "@algolia/transporter": "4.5.1" - } - }, - "@algolia/client-common": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.5.1.tgz", - "integrity": "sha512-5CpIf8IK1hke7q+N4e+A4TWdFXVJ5Qwyaa0xS84DrDO8HQ7vfYbDvG1oYa9hVEtGn6c3WVKPAvuWynK+fXQQCA==", - "requires": { - "@algolia/requester-common": "4.5.1", - "@algolia/transporter": "4.5.1" - } - }, - "@algolia/client-recommendation": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/client-recommendation/-/client-recommendation-4.5.1.tgz", - "integrity": "sha512-GiFrNSImoEBUQICjFBEoxPGzrjWji8PY9GeMg2CNvOYcRQ0Xt0Y36v9GN53NLjvB7QdQ2FlE1Cuv/PLUfS/aQQ==", - "requires": { - "@algolia/client-common": "4.5.1", - "@algolia/requester-common": "4.5.1", - "@algolia/transporter": "4.5.1" - } - }, - "@algolia/client-search": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.5.1.tgz", - "integrity": "sha512-wjuOTte9Auo9Cg4fL0709PjeJ9rXFh4okYUrOt/2SWqQid6DSdZOp+BtyaHKV3E94sj+SlmMxkMUacYluYg5zA==", - "requires": { - "@algolia/client-common": "4.5.1", - "@algolia/requester-common": "4.5.1", - "@algolia/transporter": "4.5.1" - } - }, - "@algolia/logger-common": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.5.1.tgz", - "integrity": "sha512-ZoVnGriinlLHlkvn5K7djOUn1/1IeTjU8rDzOJ3t06T+2hQytgJghaX7rSwKIeH4CjWMy61w8jLisuGJRBOEeg==" - }, - "@algolia/logger-console": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.5.1.tgz", - "integrity": "sha512-1qa7K18+uAgxyWuguayaDS5ViiZFcOjI3J5ACBb0i/n7RsXUo149lP6mwmx6TIU7s135hT0f0TCqnvfMvN1ilA==", - "requires": { - "@algolia/logger-common": "4.5.1" - } - }, - "@algolia/requester-browser-xhr": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.5.1.tgz", - "integrity": "sha512-tsQz+9pZw9dwPm/wMvZDpsWFZgmghLjXi4c3O4rfwoP/Ikum5fhle5fiR14yb4Lw4WlOQ1AJIHJvrg1qLIG8hQ==", - "requires": { - "@algolia/requester-common": "4.5.1" - } - }, - "@algolia/requester-common": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.5.1.tgz", - "integrity": "sha512-bPCiLvhHKXaka7f5FLtheChToz0yHVhvza64naFJRRh/3kC0nvyrvQ0ogjiydiSrGIfdNDyyTVfKGdk4gS5gyA==" - }, - "@algolia/requester-node-http": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.5.1.tgz", - "integrity": "sha512-BfFc2h9eQOKu1gGs3DtQO7GrVZW/rxUgpJVLja4UVQyGplJyTCrFgkTyfl+8rb3MkNgA/S2LNo7cKNSPfpqeAQ==", - "requires": { - "@algolia/requester-common": "4.5.1" - } - }, - "@algolia/transporter": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.5.1.tgz", - "integrity": "sha512-asPDNToDAPhH0tM6qKGTn1l0wTlNUbekpa1ifZ6v+qhSjo3VdqGyp+2VeciJOBW/wVHXh3HUbAcycvLERRlCLg==", - "requires": { - "@algolia/cache-common": "4.5.1", - "@algolia/logger-common": "4.5.1", - "@algolia/requester-common": "4.5.1" - } - }, - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/compat-data": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.1.tgz", - "integrity": "sha512-725AQupWJZ8ba0jbKceeFblZTY90McUBWMwHhkFQ9q1zKPJ95GUktljFcgcsIVwRnTnRKlcYzfiNImg5G9m6ZQ==" - }, - "@babel/core": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", - "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.1", - "@babel/parser": "^7.12.3", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "@babel/generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", - "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", - "requires": { - "@babel/types": "^7.12.1", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", - "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", - "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", - "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-builder-react-jsx": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz", - "integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-builder-react-jsx-experimental": { - "version": "7.12.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.12.4.tgz", - "integrity": "sha512-AjEa0jrQqNk7eDQOo0pTfUOwQBMF+xVqrausQwT9/rTKy0g04ggFNaJpaE09IQMn9yExluigWMJcj0WC7bq+Og==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-module-imports": "^7.12.1", - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.1.tgz", - "integrity": "sha512-jtBEif7jsPwP27GPHs06v4WBV0KrE8a/P7n0N0sSvHn2hwUCYnolP/CLmz51IzAW4NlN+HuoBtb9QcwnRo9F/g==", - "requires": { - "@babel/compat-data": "^7.12.1", - "@babel/helper-validator-option": "^7.12.1", - "browserslist": "^4.12.0", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", - "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.1.tgz", - "integrity": "sha512-rsZ4LGvFTZnzdNZR5HZdmJVuXK8834R5QkF3WvcnBhrlVtF0HSIUC6zbreL9MgjTywhKokn8RIYRiq99+DLAxA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-regex": "^7.10.4", - "regexpu-core": "^4.7.1" - } - }, - "@babel/helper-define-map": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", - "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.5", - "lodash": "^4.17.19" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", - "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", - "requires": { - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz", - "integrity": "sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ==", - "requires": { - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-module-imports": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.1.tgz", - "integrity": "sha512-ZeC1TlMSvikvJNy1v/wPIazCu3NdOwgYZLIkmIyAsGhqkNpiDoQQRmaCK8YP4Pq3GPTLPV9WXaPCJKvx06JxKA==", - "requires": { - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-module-transforms": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", - "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", - "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-simple-access": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/helper-validator-identifier": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "lodash": "^4.17.19" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" - }, - "@babel/helper-regex": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", - "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", - "requires": { - "lodash": "^4.17.19" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", - "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-wrap-function": "^7.10.4", - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-replace-supers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz", - "integrity": "sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw==", - "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-simple-access": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", - "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", - "requires": { - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", - "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", - "requires": { - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" - }, - "@babel/helper-validator-option": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz", - "integrity": "sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==" - }, - "@babel/helper-wrap-function": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", - "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helpers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.1.tgz", - "integrity": "sha512-9JoDSBGoWtmbay98efmT2+mySkwjzeFeAL9BuWNoVQpkPFQF8SIIFUfY5os9u8wVzglzoiPRSW7cuJmBDUt43g==", - "requires": { - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1" - } - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "@babel/parser": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz", - "integrity": "sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==" - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", - "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1", - "@babel/plugin-syntax-async-generators": "^7.8.0" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", - "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", - "integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", - "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", - "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.0" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", - "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", - "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.1.tgz", - "integrity": "sha512-MR7Ok+Af3OhNTCxYVjJZHS0t97ydnJZt/DbR4WISO39iDnhiD8XHrY12xuSJ90FFEGjir0Fzyyn7g/zY6hxbxA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", - "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.12.1" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", - "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.1.tgz", - "integrity": "sha512-c2uRpY6WzaVDzynVY9liyykS+kVU+WRZPMPYpkelXH8KBt1oXoI89kPbZKKG/jDT5UK92FTW2fZkZaJhdiBabw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", - "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", - "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", - "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", - "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", - "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.1.tgz", - "integrity": "sha512-UZNEcCY+4Dp9yYRCAHrHDU+9ZXLYaY9MgBXSRLkB9WjYFRR6quJBumfVrEkUxrePPBwFcpWfNKXqVRQQtm7mMA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", - "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", - "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", - "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", - "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz", - "integrity": "sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", - "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-define-map": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", - "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", - "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", - "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", - "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", - "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", - "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", - "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", - "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", - "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", - "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", - "requires": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", - "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", - "requires": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-simple-access": "^7.12.1", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", - "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", - "requires": { - "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-identifier": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", - "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", - "requires": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", - "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", - "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", - "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", - "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", - "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-react-constant-elements": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.12.1.tgz", - "integrity": "sha512-KOHd0tIRLoER+J+8f9DblZDa1fLGPwaaN1DI1TVHuQFOpjHV22C3CUB3obeC4fexHY9nx+fH0hQNvLFFfA1mxA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz", - "integrity": "sha512-cAzB+UzBIrekfYxyLlFqf/OagTvHLcVBb5vpouzkYkBclRPraiygVnafvAoipErZLI8ANv8Ecn6E/m5qPXD26w==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.1.tgz", - "integrity": "sha512-RmKejwnT0T0QzQUzcbP5p1VWlpnP8QHtdhEtLG55ZDQnJNalbF3eeDyu3dnGKvGzFIQiBzFhBYTwvv435p9Xpw==", - "requires": { - "@babel/helper-builder-react-jsx": "^7.10.4", - "@babel/helper-builder-react-jsx-experimental": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-jsx": "^7.12.1" - } - }, - "@babel/plugin-transform-react-jsx-development": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.1.tgz", - "integrity": "sha512-IilcGWdN1yNgEGOrB96jbTplRh+V2Pz1EoEwsKsHfX1a/L40cUYuD71Zepa7C+ujv7kJIxnDftWeZbKNEqZjCQ==", - "requires": { - "@babel/helper-builder-react-jsx-experimental": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-jsx": "^7.12.1" - } - }, - "@babel/plugin-transform-react-jsx-self": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.12.1.tgz", - "integrity": "sha512-FbpL0ieNWiiBB5tCldX17EtXgmzeEZjFrix72rQYeq9X6nUK38HCaxexzVQrZWXanxKJPKVVIU37gFjEQYkPkA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.12.1.tgz", - "integrity": "sha512-keQ5kBfjJNRc6zZN1/nVHCd6LLIHq4aUKcVnvE/2l+ZZROSbqoiGFRtT5t3Is89XJxBQaP7NLZX2jgGHdZvvFQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-react-pure-annotations": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz", - "integrity": "sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", - "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", - "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz", - "integrity": "sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg==", - "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "resolve": "^1.8.1", - "semver": "^5.5.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", - "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", - "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.1.tgz", - "integrity": "sha512-CiUgKQ3AGVk7kveIaPEET1jNDhZZEl1RPMWdTBE1799bdz++SwqDHStmxfCtDfBhQgCl38YRiSnrMuUMZIWSUQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-regex": "^7.10.4" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", - "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz", - "integrity": "sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.1.tgz", - "integrity": "sha512-VrsBByqAIntM+EYMqSm59SiMEf7qkmI9dqMt6RbD/wlwueWmYcI0FFK5Fj47pP6DRZm+3teXjosKlwcZJ5lIMw==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-typescript": "^7.12.1" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", - "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", - "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/preset-env": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.1.tgz", - "integrity": "sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg==", - "requires": { - "@babel/compat-data": "^7.12.1", - "@babel/helper-compilation-targets": "^7.12.1", - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-option": "^7.12.1", - "@babel/plugin-proposal-async-generator-functions": "^7.12.1", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-proposal-dynamic-import": "^7.12.1", - "@babel/plugin-proposal-export-namespace-from": "^7.12.1", - "@babel/plugin-proposal-json-strings": "^7.12.1", - "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", - "@babel/plugin-proposal-numeric-separator": "^7.12.1", - "@babel/plugin-proposal-object-rest-spread": "^7.12.1", - "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.12.1", - "@babel/plugin-proposal-private-methods": "^7.12.1", - "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.12.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.12.1", - "@babel/plugin-transform-arrow-functions": "^7.12.1", - "@babel/plugin-transform-async-to-generator": "^7.12.1", - "@babel/plugin-transform-block-scoped-functions": "^7.12.1", - "@babel/plugin-transform-block-scoping": "^7.12.1", - "@babel/plugin-transform-classes": "^7.12.1", - "@babel/plugin-transform-computed-properties": "^7.12.1", - "@babel/plugin-transform-destructuring": "^7.12.1", - "@babel/plugin-transform-dotall-regex": "^7.12.1", - "@babel/plugin-transform-duplicate-keys": "^7.12.1", - "@babel/plugin-transform-exponentiation-operator": "^7.12.1", - "@babel/plugin-transform-for-of": "^7.12.1", - "@babel/plugin-transform-function-name": "^7.12.1", - "@babel/plugin-transform-literals": "^7.12.1", - "@babel/plugin-transform-member-expression-literals": "^7.12.1", - "@babel/plugin-transform-modules-amd": "^7.12.1", - "@babel/plugin-transform-modules-commonjs": "^7.12.1", - "@babel/plugin-transform-modules-systemjs": "^7.12.1", - "@babel/plugin-transform-modules-umd": "^7.12.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", - "@babel/plugin-transform-new-target": "^7.12.1", - "@babel/plugin-transform-object-super": "^7.12.1", - "@babel/plugin-transform-parameters": "^7.12.1", - "@babel/plugin-transform-property-literals": "^7.12.1", - "@babel/plugin-transform-regenerator": "^7.12.1", - "@babel/plugin-transform-reserved-words": "^7.12.1", - "@babel/plugin-transform-shorthand-properties": "^7.12.1", - "@babel/plugin-transform-spread": "^7.12.1", - "@babel/plugin-transform-sticky-regex": "^7.12.1", - "@babel/plugin-transform-template-literals": "^7.12.1", - "@babel/plugin-transform-typeof-symbol": "^7.12.1", - "@babel/plugin-transform-unicode-escapes": "^7.12.1", - "@babel/plugin-transform-unicode-regex": "^7.12.1", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.12.1", - "core-js-compat": "^3.6.2", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-react": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.12.1.tgz", - "integrity": "sha512-euCExymHCi0qB9u5fKw7rvlw7AZSjw/NaB9h7EkdTt5+yHRrXdiRTh7fkG3uBPpJg82CqLfp1LHLqWGSCrab+g==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-transform-react-display-name": "^7.12.1", - "@babel/plugin-transform-react-jsx": "^7.12.1", - "@babel/plugin-transform-react-jsx-development": "^7.12.1", - "@babel/plugin-transform-react-jsx-self": "^7.12.1", - "@babel/plugin-transform-react-jsx-source": "^7.12.1", - "@babel/plugin-transform-react-pure-annotations": "^7.12.1" - } - }, - "@babel/preset-typescript": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.12.1.tgz", - "integrity": "sha512-hNK/DhmoJPsksdHuI/RVrcEws7GN5eamhi28JkO52MqIxU8Z0QpmiSOQxZHWOHV7I3P4UjHV97ay4TcamMA6Kw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-transform-typescript": "^7.12.1" - } - }, - "@babel/runtime": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz", - "integrity": "sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/runtime-corejs3": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.1.tgz", - "integrity": "sha512-umhPIcMrlBZ2aTWlWjUseW9LjQKxi1dpFlQS8DzsxB//5K+u6GLTC/JliPKHsd5kJVPIU6X/Hy0YvWOYPcMxBw==", - "requires": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/traverse": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.1.tgz", - "integrity": "sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.12.1", - "@babel/types": "^7.12.1", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "@babel/types": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.1.tgz", - "integrity": "sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "@braintree/sanitize-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-3.1.0.tgz", - "integrity": "sha512-GcIY79elgB+azP74j8vqkiXz8xLFfIzbQJdlwOPisgbKT00tviJQuEghOXSMVxJ00HoYJbGswr4kcllUc4xCcg==" - }, - "@csstools/convert-colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", - "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==" - }, - "@docsearch/css": { - "version": "1.0.0-alpha.28", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-1.0.0-alpha.28.tgz", - "integrity": "sha512-1AhRzVdAkrWwhaxTX6/R7SnFHz8yLz1W8I/AldlTrfbNvZs9INk1FZiEFTJdgHaP68nhgQNWSGlQiDiI3y2RYg==" - }, - "@docsearch/react": { - "version": "1.0.0-alpha.28", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-1.0.0-alpha.28.tgz", - "integrity": "sha512-XjJOnCBXn+UZmtuDmgzlVIHnnvh6yHVwG4aFq8AXN6xJEIX3f180FvGaowFWAxgdtHplJxFGux0Xx4piHqBzIw==", - "requires": { - "@docsearch/css": "^1.0.0-alpha.28", - "@francoischalifour/autocomplete-core": "^1.0.0-alpha.28", - "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.28", - "algoliasearch": "^4.0.0" - } - }, - "@docusaurus/core": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.0.0-alpha.66.tgz", - "integrity": "sha512-9HKqObYoyArpzSTIDguyUXm7z54bpV3dSWSc0PbKGu0Us6zP1TiOuhRDX1diFsKyvjNy7VbCe8XH8LJIdKi5dQ==", - "requires": { - "@babel/core": "^7.9.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.1", - "@babel/plugin-proposal-optional-chaining": "^7.10.3", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.9.0", - "@babel/preset-env": "^7.9.0", - "@babel/preset-react": "^7.9.4", - "@babel/preset-typescript": "^7.9.0", - "@babel/runtime": "^7.9.2", - "@babel/runtime-corejs3": "^7.10.4", - "@docusaurus/types": "2.0.0-alpha.66", - "@docusaurus/utils": "2.0.0-alpha.66", - "@docusaurus/utils-validation": "2.0.0-alpha.66", - "@endiliey/static-site-generator-webpack-plugin": "^4.0.0", - "@hapi/joi": "^17.1.1", - "@svgr/webpack": "^5.4.0", - "babel-loader": "^8.1.0", - "babel-plugin-dynamic-import-node": "^2.3.0", - "boxen": "^4.2.0", - "cache-loader": "^4.1.0", - "chalk": "^3.0.0", - "chokidar": "^3.3.0", - "commander": "^4.0.1", - "copy-webpack-plugin": "^6.0.3", - "core-js": "^2.6.5", - "css-loader": "^3.4.2", - "del": "^5.1.0", - "detect-port": "^1.3.0", - "eta": "^1.1.1", - "express": "^4.17.1", - "file-loader": "^6.0.0", - "fs-extra": "^8.1.0", - "globby": "^10.0.1", - "html-minifier-terser": "^5.0.5", - "html-tags": "^3.1.0", - "html-webpack-plugin": "^4.0.4", - "import-fresh": "^3.2.1", - "inquirer": "^7.2.0", - "is-root": "^2.1.0", - "leven": "^3.1.0", - "lodash": "^4.5.2", - "lodash.flatmap": "^4.5.0", - "lodash.has": "^4.5.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "mini-css-extract-plugin": "^0.8.0", - "nprogress": "^0.2.0", - "null-loader": "^3.0.0", - "optimize-css-assets-webpack-plugin": "^5.0.3", - "pnp-webpack-plugin": "^1.6.4", - "postcss-loader": "^3.0.0", - "postcss-preset-env": "^6.7.0", - "react-dev-utils": "^10.2.1", - "react-helmet": "^6.0.0-beta", - "react-loadable": "^5.5.0", - "react-loadable-ssr-addon": "^0.3.0", - "react-router": "^5.1.2", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.1.2", - "resolve-pathname": "^3.0.0", - "semver": "^6.3.0", - "serve-handler": "^6.1.3", - "shelljs": "^0.8.4", - "std-env": "^2.2.1", - "terser-webpack-plugin": "^4.1.0", - "update-notifier": "^4.1.0", - "url-loader": "^4.1.0", - "wait-file": "^1.0.5", - "webpack": "^4.44.1", - "webpack-bundle-analyzer": "^3.6.1", - "webpack-dev-server": "^3.11.0", - "webpack-merge": "^4.2.2", - "webpackbar": "^4.0.0" - } - }, - "@docusaurus/mdx-loader": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-alpha.66.tgz", - "integrity": "sha512-IvtrTNeAaynEGgfCbC4CeBgO76Mu76cGogBGv8a84bYeyCOtlxOJoH6JHkJ7T/v5D6lM16qzwx5oqesZ0kZuzA==", - "requires": { - "@babel/parser": "^7.9.4", - "@babel/traverse": "^7.9.0", - "@docusaurus/core": "2.0.0-alpha.66", - "@docusaurus/utils": "2.0.0-alpha.66", - "@mdx-js/mdx": "^1.5.8", - "@mdx-js/react": "^1.5.8", - "escape-html": "^1.0.3", - "file-loader": "^6.0.0", - "fs-extra": "^8.1.0", - "github-slugger": "^1.3.0", - "gray-matter": "^4.0.2", - "loader-utils": "^1.2.3", - "mdast-util-to-string": "^1.1.0", - "remark-emoji": "^2.1.0", - "stringify-object": "^3.3.0", - "unist-util-visit": "^2.0.2", - "url-loader": "^4.1.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "@docusaurus/plugin-content-blog": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-alpha.66.tgz", - "integrity": "sha512-voK5ZUZcUn5blIDakYNKQ42wPMZLfrZnvEJuwh/8S/W1oNbPN935NBu9vL23fHEmp9L2MGykAdaCmev0Su04yQ==", - "requires": { - "@docusaurus/core": "2.0.0-alpha.66", - "@docusaurus/mdx-loader": "2.0.0-alpha.66", - "@docusaurus/types": "2.0.0-alpha.66", - "@docusaurus/utils": "2.0.0-alpha.66", - "@docusaurus/utils-validation": "2.0.0-alpha.66", - "@hapi/joi": "^17.1.1", - "chalk": "^3.0.0", - "feed": "^4.1.0", - "fs-extra": "^8.1.0", - "globby": "^10.0.1", - "loader-utils": "^1.2.3", - "lodash": "^4.5.2", - "reading-time": "^1.2.0", - "remark-admonitions": "^1.2.1", - "webpack": "^4.44.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "@docusaurus/plugin-content-docs": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-alpha.66.tgz", - "integrity": "sha512-jvFKJR7BgjIq6xdmPg+7d2DS1fBeuIfmRTtB/apgfIW8NWO5N0DRYXOj0lgpw/ICwW//o8cLbrN+jkLlzTV/eg==", - "requires": { - "@docusaurus/core": "2.0.0-alpha.66", - "@docusaurus/mdx-loader": "2.0.0-alpha.66", - "@docusaurus/types": "2.0.0-alpha.66", - "@docusaurus/utils": "2.0.0-alpha.66", - "@docusaurus/utils-validation": "2.0.0-alpha.66", - "@hapi/joi": "17.1.1", - "chalk": "^3.0.0", - "execa": "^3.4.0", - "fs-extra": "^8.1.0", - "globby": "^10.0.1", - "import-fresh": "^3.2.1", - "loader-utils": "^1.2.3", - "lodash": "^4.17.19", - "lodash.flatmap": "^4.5.0", - "lodash.groupby": "^4.6.0", - "lodash.pick": "^4.4.0", - "lodash.pickby": "^4.6.0", - "lodash.sortby": "^4.6.0", - "remark-admonitions": "^1.2.1", - "shelljs": "^0.8.4", - "utility-types": "^3.10.0", - "webpack": "^4.44.1" - }, - "dependencies": { - "execa": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", - "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "p-finally": "^2.0.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "requires": { - "path-key": "^3.0.0" - } - }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==" - } - } - }, - "@docusaurus/plugin-content-pages": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-alpha.66.tgz", - "integrity": "sha512-mY26Aeb/Wf+NFLy70YvXgdLTB+2iPN0SKOVKYwgg6ZN7Nm2kPwEpSVRq2iwiqlWk2G/vOM+ADm99Gxvm3kS61A==", - "requires": { - "@docusaurus/core": "2.0.0-alpha.66", - "@docusaurus/mdx-loader": "2.0.0-alpha.66", - "@docusaurus/types": "2.0.0-alpha.66", - "@docusaurus/utils": "2.0.0-alpha.66", - "@docusaurus/utils-validation": "2.0.0-alpha.66", - "@hapi/joi": "17.1.1", - "globby": "^10.0.1", - "loader-utils": "^1.2.3", - "minimatch": "^3.0.4", - "remark-admonitions": "^1.2.1", - "slash": "^3.0.0", - "webpack": "^4.44.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "@docusaurus/plugin-debug": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-alpha.66.tgz", - "integrity": "sha512-9AZaEUxaY0CDOCWXQMfY3TzG79HkquZlVeJOZaA6IvCoK/Oq3B58TMNLiQyA6TA2DYf5ZYQorLJaMd02x5qBQw==", - "requires": { - "@docusaurus/core": "2.0.0-alpha.66", - "@docusaurus/types": "2.0.0-alpha.66", - "@docusaurus/utils": "2.0.0-alpha.66", - "react-json-view": "^1.19.1" - } - }, - "@docusaurus/plugin-google-analytics": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-alpha.66.tgz", - "integrity": "sha512-HVWRLHtlQYpVqH3MHloUmktJMXt7oMDQzBlKzrwAMiWUK1oXFX35DrKjTt2SE2SADpObnwWFjo0E71YT0ApQLw==", - "requires": { - "@docusaurus/core": "2.0.0-alpha.66" - } - }, - "@docusaurus/plugin-google-gtag": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-alpha.66.tgz", - "integrity": "sha512-MVnzApLSQaC38nVS+A/WkXEV4kHeX6Q/KM2DqkLeovNWLBtkQ0aHL3bvn1clAEmB33Pia0v93mzG+I1+9mrquA==", - "requires": { - "@docusaurus/core": "2.0.0-alpha.66" - } - }, - "@docusaurus/plugin-sitemap": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-alpha.66.tgz", - "integrity": "sha512-ztDevVIREyq8g+QhSGpDqscVqtubcPnEE3a4JwWSALQ2D6JscIxg897axwZSZNUMxrHBuXRjOEYOtVb/O/stVg==", - "requires": { - "@docusaurus/core": "2.0.0-alpha.66", - "@docusaurus/types": "2.0.0-alpha.66", - "@hapi/joi": "17.1.1", - "fs-extra": "^8.1.0", - "sitemap": "^3.2.2" - } - }, - "@docusaurus/preset-classic": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.0.0-alpha.66.tgz", - "integrity": "sha512-FjxjchzUS6vOUSr9Pc5kqOSQAnc+cAYsR4pTlqwD2uOJcZMr2vQ6jeKbJnhEmUYwAvzdKOVnCndnxbA+Ii8L3w==", - "requires": { - "@docusaurus/core": "2.0.0-alpha.66", - "@docusaurus/plugin-content-blog": "2.0.0-alpha.66", - "@docusaurus/plugin-content-docs": "2.0.0-alpha.66", - "@docusaurus/plugin-content-pages": "2.0.0-alpha.66", - "@docusaurus/plugin-debug": "2.0.0-alpha.66", - "@docusaurus/plugin-google-analytics": "2.0.0-alpha.66", - "@docusaurus/plugin-google-gtag": "2.0.0-alpha.66", - "@docusaurus/plugin-sitemap": "2.0.0-alpha.66", - "@docusaurus/theme-classic": "2.0.0-alpha.66", - "@docusaurus/theme-search-algolia": "2.0.0-alpha.66" - } - }, - "@docusaurus/theme-classic": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.0.0-alpha.66.tgz", - "integrity": "sha512-WsWqzfzA2gIF5TUMGSbiAeDeNZtKvsgymTQzalcwyhyT/QI0ywcag+03Bmjeq4H3PTC3qU+tkhddO2Rh5w/YCw==", - "requires": { - "@docusaurus/core": "2.0.0-alpha.66", - "@docusaurus/plugin-content-blog": "2.0.0-alpha.66", - "@docusaurus/plugin-content-docs": "2.0.0-alpha.66", - "@docusaurus/plugin-content-pages": "2.0.0-alpha.66", - "@docusaurus/types": "2.0.0-alpha.66", - "@docusaurus/utils-validation": "2.0.0-alpha.66", - "@hapi/joi": "^17.1.1", - "@mdx-js/mdx": "^1.5.8", - "@mdx-js/react": "^1.5.8", - "@types/react-toggle": "^4.0.2", - "clsx": "^1.1.1", - "copy-text-to-clipboard": "^2.2.0", - "infima": "0.2.0-alpha.13", - "lodash": "^4.17.19", - "parse-numeric-range": "^0.0.2", - "prism-react-renderer": "^1.1.0", - "prismjs": "^1.20.0", - "prop-types": "^15.7.2", - "react-router-dom": "^5.1.2", - "react-toggle": "^4.1.1" - } - }, - "@docusaurus/theme-search-algolia": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-alpha.66.tgz", - "integrity": "sha512-5k/Fwt81Gyjv9vPE+gO8mraEHx5IqEmHLwqld5yXj7yix5XrxywkaanHqC0cFJG4MFUBgF6vNjJC8CtfLnT4Tw==", - "requires": { - "@docsearch/react": "^1.0.0-alpha.27", - "@docusaurus/core": "2.0.0-alpha.66", - "@docusaurus/utils": "2.0.0-alpha.66", - "@hapi/joi": "^17.1.1", - "algoliasearch": "^4.0.0", - "algoliasearch-helper": "^3.1.1", - "clsx": "^1.1.1", - "eta": "^1.1.1", - "lodash": "^4.17.19" - } - }, - "@docusaurus/types": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.0.0-alpha.66.tgz", - "integrity": "sha512-Zd2Kguw0+3faifr83ruIV4i/+KqfqM+zK3DpqCBxdtkP+ORLKbgsIQ48fJ40OOhQrvl38Ay4E+1w7USrrkj4Qg==", - "requires": { - "@types/webpack": "^4.41.0", - "commander": "^4.0.1", - "querystring": "0.2.0", - "webpack-merge": "^4.2.2" - } - }, - "@docusaurus/utils": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.0.0-alpha.66.tgz", - "integrity": "sha512-47jGB+Z3YVM6Xf1hxyNbJLMmc1qoTLmfwSf7NseKSkpjucbc5Ueivr+oVYp5yWoZw5sT5bObmdJYfJoD/RrbOg==", - "requires": { - "escape-string-regexp": "^2.0.0", - "fs-extra": "^8.1.0", - "gray-matter": "^4.0.2", - "lodash.camelcase": "^4.3.0", - "lodash.kebabcase": "^4.1.1", - "resolve-pathname": "^3.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - } - } - }, - "@docusaurus/utils-validation": { - "version": "2.0.0-alpha.66", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.0.0-alpha.66.tgz", - "integrity": "sha512-vlenwY3THondey21x1qAUZyDz9qiG7ec2CBM9HgY1Ns8XhrKah9zz7TEGXjqM9lhqMQQRkvcCcveti9EXR0fcA==", - "requires": { - "@docusaurus/utils": "2.0.0-alpha.66", - "@hapi/joi": "17.1.1", - "chalk": "^3.0.0" - } - }, - "@endiliey/static-site-generator-webpack-plugin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@endiliey/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.0.tgz", - "integrity": "sha512-3MBqYCs30qk1OBRC697NqhGouYbs71D1B8hrk/AFJC6GwF2QaJOQZtA1JYAaGSe650sZ8r5ppRTtCRXepDWlng==", - "requires": { - "bluebird": "^3.7.1", - "cheerio": "^0.22.0", - "eval": "^0.1.4", - "url": "^0.11.0", - "webpack-sources": "^1.4.3" - } - }, - "@francoischalifour/autocomplete-core": { - "version": "1.0.0-alpha.28", - "resolved": "https://registry.npmjs.org/@francoischalifour/autocomplete-core/-/autocomplete-core-1.0.0-alpha.28.tgz", - "integrity": "sha512-rL9x+72btViw+9icfBKUJjZj87FgjFrD2esuTUqtj4RAX3s4AuVZiN8XEsfjQBSc6qJk31cxlvqZHC/BIyYXgg==" - }, - "@francoischalifour/autocomplete-preset-algolia": { - "version": "1.0.0-alpha.28", - "resolved": "https://registry.npmjs.org/@francoischalifour/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.0.0-alpha.28.tgz", - "integrity": "sha512-bprfNmYt1opFUFEtD2XfY/kEsm13bzHQgU80uMjhuK0DJ914IjolT1GytpkdM6tJ4MBvyiJPP+bTtWO+BZ7c7w==" - }, - "@hapi/address": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.1.0.tgz", - "integrity": "sha512-SkszZf13HVgGmChdHo/PxchnSaCJ6cetVqLzyciudzZRT0jcOouIF/Q93mgjw8cce+D+4F4C1Z/WrfFN+O3VHQ==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@hapi/bourne": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", - "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==" - }, - "@hapi/formula": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" - }, - "@hapi/hoek": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.1.0.tgz", - "integrity": "sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw==" - }, - "@hapi/joi": { - "version": "17.1.1", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", - "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", - "requires": { - "@hapi/address": "^4.0.1", - "@hapi/formula": "^2.0.0", - "@hapi/hoek": "^9.0.0", - "@hapi/pinpoint": "^2.0.0", - "@hapi/topo": "^5.0.0" - } - }, - "@hapi/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" - }, - "@hapi/topo": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", - "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", - "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@kiwicopple/prism-react-renderer": { - "version": "git+ssh://git@github.com/kiwicopple/prism-react-renderer.git#4a09100a587bce2d94d7ac8ed3564a61c6e70781", - "from": "@kiwicopple/prism-react-renderer@github:kiwicopple/prism-react-renderer", - "requires": { - "prismjs": "^1.22.0" - } - }, - "@mdx-js/mdx": { - "version": "1.6.19", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.19.tgz", - "integrity": "sha512-L3eLhEFnV/2bcb9XwOegsRmLHd1oEDQPtTBVezhptQ5U1YM+/WQNzx1apjzVTAyukwOanUXnTUMjRUtqJNgFCg==", - "requires": { - "@babel/core": "7.11.6", - "@babel/plugin-syntax-jsx": "7.10.4", - "@babel/plugin-syntax-object-rest-spread": "7.8.3", - "@mdx-js/util": "1.6.19", - "babel-plugin-apply-mdx-type-prop": "1.6.19", - "babel-plugin-extract-import-names": "1.6.19", - "camelcase-css": "2.0.1", - "detab": "2.0.3", - "hast-util-raw": "6.0.1", - "lodash.uniq": "4.5.0", - "mdast-util-to-hast": "9.1.1", - "remark-footnotes": "2.0.0", - "remark-mdx": "1.6.19", - "remark-parse": "8.0.3", - "remark-squeeze-paragraphs": "4.0.0", - "style-to-object": "0.3.0", - "unified": "9.2.0", - "unist-builder": "2.0.3", - "unist-util-visit": "2.0.3" - }, - "dependencies": { - "@babel/core": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", - "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.6", - "@babel/helper-module-transforms": "^7.11.0", - "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.11.5", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.11.5", - "@babel/types": "^7.11.5", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz", - "integrity": "sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "@mdx-js/react": { - "version": "1.6.19", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.19.tgz", - "integrity": "sha512-RS37Tagqyp2R0XFPoUZeSbZC5uJQRPhqOHWeT1LEwxESjMWb3VORHz7E827ldeQr3UW6VEQEyq/THegu+bLj6A==" - }, - "@mdx-js/util": { - "version": "1.6.19", - "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.6.19.tgz", - "integrity": "sha512-bkkQNSHz3xSr3KRHUQ2Qk2XhewvvXAOUqjIUKwcQuL4ijOA4tUHZfUgXExi5CpMysrX7izcsyICtXjZHlfJUjg==" - }, - "@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", - "requires": { - "call-me-maybe": "^1.0.1", - "glob-to-regexp": "^0.3.0" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", - "requires": { - "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" - }, - "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", - "requires": { - "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" - } - }, - "@npmcli/move-file": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", - "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", - "requires": { - "mkdirp": "^1.0.4" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - } - } - }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" - }, - "@svgr/babel-plugin-add-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==" - }, - "@svgr/babel-plugin-remove-jsx-attribute": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", - "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==" - }, - "@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", - "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==" - }, - "@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", - "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==" - }, - "@svgr/babel-plugin-svg-dynamic-title": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", - "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==" - }, - "@svgr/babel-plugin-svg-em-dimensions": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", - "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==" - }, - "@svgr/babel-plugin-transform-react-native-svg": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", - "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==" - }, - "@svgr/babel-plugin-transform-svg-component": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.4.0.tgz", - "integrity": "sha512-zLl4Fl3NvKxxjWNkqEcpdSOpQ3LGVH2BNFQ6vjaK6sFo2IrSznrhURIPI0HAphKiiIwNYjAfE0TNoQDSZv0U9A==" - }, - "@svgr/babel-preset": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.4.0.tgz", - "integrity": "sha512-Gyx7cCxua04DBtyILTYdQxeO/pwfTBev6+eXTbVbxe4HTGhOUW6yo7PSbG2p6eJMl44j6XSequ0ZDP7bl0nu9A==", - "requires": { - "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", - "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", - "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", - "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", - "@svgr/babel-plugin-transform-svg-component": "^5.4.0" - } - }, - "@svgr/core": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.4.0.tgz", - "integrity": "sha512-hWGm1DCCvd4IEn7VgDUHYiC597lUYhFau2lwJBYpQWDirYLkX4OsXu9IslPgJ9UpP7wsw3n2Ffv9sW7SXJVfqQ==", - "requires": { - "@svgr/plugin-jsx": "^5.4.0", - "camelcase": "^6.0.0", - "cosmiconfig": "^6.0.0" - } - }, - "@svgr/hast-util-to-babel-ast": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.4.0.tgz", - "integrity": "sha512-+U0TZZpPsP2V1WvVhqAOSTk+N+CjYHdZx+x9UBa1eeeZDXwH8pt0CrQf2+SvRl/h2CAPRFkm+Ey96+jKP8Bsgg==", - "requires": { - "@babel/types": "^7.9.5" - } - }, - "@svgr/plugin-jsx": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.4.0.tgz", - "integrity": "sha512-SGzO4JZQ2HvGRKDzRga9YFSqOqaNrgLlQVaGvpZ2Iht2gwRp/tq+18Pvv9kS9ZqOMYgyix2LLxZMY1LOe9NPqw==", - "requires": { - "@babel/core": "^7.7.5", - "@svgr/babel-preset": "^5.4.0", - "@svgr/hast-util-to-babel-ast": "^5.4.0", - "svg-parser": "^2.0.2" - } - }, - "@svgr/plugin-svgo": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.4.0.tgz", - "integrity": "sha512-3Cgv3aYi1l6SHyzArV9C36yo4kgwVdF3zPQUC6/aCDUeXAofDYwE5kk3e3oT5ZO2a0N3lB+lLGvipBG6lnG8EA==", - "requires": { - "cosmiconfig": "^6.0.0", - "merge-deep": "^3.0.2", - "svgo": "^1.2.2" - } - }, - "@svgr/webpack": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.4.0.tgz", - "integrity": "sha512-LjepnS/BSAvelnOnnzr6Gg0GcpLmnZ9ThGFK5WJtm1xOqdBE/1IACZU7MMdVzjyUkfFqGz87eRE4hFaSLiUwYg==", - "requires": { - "@babel/core": "^7.9.0", - "@babel/plugin-transform-react-constant-elements": "^7.9.0", - "@babel/preset-env": "^7.9.5", - "@babel/preset-react": "^7.9.4", - "@svgr/core": "^5.4.0", - "@svgr/plugin-jsx": "^5.4.0", - "@svgr/plugin-svgo": "^5.4.0", - "loader-utils": "^2.0.0" - } - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "requires": { - "defer-to-connect": "^1.0.1" - } - }, - "@types/anymatch": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", - "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==" - }, - "@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/hast": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.1.tgz", - "integrity": "sha512-viwwrB+6xGzw+G1eWpF9geV3fnsDgXqHG+cqgiHrvQfDUW5hzhCyV7Sy3UJxhfRFBsgky2SSW33qi/YrIkjX5Q==", - "requires": { - "@types/unist": "*" - } - }, - "@types/html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==" - }, - "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" - }, - "@types/mdast": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz", - "integrity": "sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==", - "requires": { - "@types/unist": "*" - } - }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" - }, - "@types/node": { - "version": "14.14.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz", - "integrity": "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==" - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "@types/parse5": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", - "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==" - }, - "@types/prop-types": { - "version": "15.7.3", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" - }, - "@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" - }, - "@types/react": { - "version": "16.9.55", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.55.tgz", - "integrity": "sha512-6KLe6lkILeRwyyy7yG9rULKJ0sXplUsl98MGoCfpteXf9sPWFWWMknDcsvubcpaTdBuxtsLF6HDUwdApZL/xIg==", - "requires": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-toggle": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/react-toggle/-/react-toggle-4.0.2.tgz", - "integrity": "sha512-sHqfoKFnL0YU2+OC4meNEC8Ptx9FE8/+nFeFvNcdBa6ANA8KpAzj3R9JN8GtrvlLgjKDoYgI7iILgXYcTPo2IA==", - "requires": { - "@types/react": "*" - } - }, - "@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==" - }, - "@types/tapable": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz", - "integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==" - }, - "@types/uglify-js": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.11.1.tgz", - "integrity": "sha512-7npvPKV+jINLu1SpSYVWG8KvyJBhBa8tmzMMdDoVc2pWUYHN8KIXlPJhjJ4LT97c4dXJA2SHL/q6ADbDriZN+Q==", - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "@types/unist": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", - "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==" - }, - "@types/webpack": { - "version": "4.41.24", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.24.tgz", - "integrity": "sha512-1A0MXPwZiMOD3DPMuOKUKcpkdPo8Lq33UGggZ7xio6wJ/jV1dAu5cXDrOfGDnldUroPIRLsr/DT43/GqOA4RFQ==", - "requires": { - "@types/anymatch": "*", - "@types/node": "*", - "@types/tapable": "*", - "@types/uglify-js": "*", - "@types/webpack-sources": "*", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "@types/webpack-sources": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-2.0.0.tgz", - "integrity": "sha512-a5kPx98CNFRKQ+wqawroFunvFqv7GHm/3KOI52NY9xWADgc8smu4R6prt4EU/M4QfVjvgBkMqU4fBhw3QfMVkg==", - "requires": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" - } - } - }, - "@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", - "requires": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==" - }, - "@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==" - }, - "@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==" - }, - "@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "requires": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==" - }, - "@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", - "requires": { - "@webassemblyjs/ast": "1.9.0" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==" - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==" - }, - "@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" - }, - "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==" - }, - "address": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", - "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==" - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" - }, - "algoliasearch": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.5.1.tgz", - "integrity": "sha512-b6yT1vWMlBdVObQipKxvt0M6SEvGetVj+FFFlo0Fy06gkdj6WCJaS4t10Q/hC3I2VG9QmpCqlK3Esgg1y1E+uw==", - "requires": { - "@algolia/cache-browser-local-storage": "4.5.1", - "@algolia/cache-common": "4.5.1", - "@algolia/cache-in-memory": "4.5.1", - "@algolia/client-account": "4.5.1", - "@algolia/client-analytics": "4.5.1", - "@algolia/client-common": "4.5.1", - "@algolia/client-recommendation": "4.5.1", - "@algolia/client-search": "4.5.1", - "@algolia/logger-common": "4.5.1", - "@algolia/logger-console": "4.5.1", - "@algolia/requester-browser-xhr": "4.5.1", - "@algolia/requester-common": "4.5.1", - "@algolia/requester-node-http": "4.5.1", - "@algolia/transporter": "4.5.1" - } - }, - "algoliasearch-helper": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.6.2.tgz", - "integrity": "sha512-Xx0NOA6k4ySn+R2l3UMSONAaMkyfmrZ3AP1geEMo32MxDJQJesZABZYsldO9fa6FKQxH91afhi4hO1G0Zc2opg==", - "requires": { - "events": "^1.1.1" - }, - "dependencies": { - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" - } - } - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" - }, - "ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "requires": { - "string-width": "^3.0.0" - }, - "dependencies": { - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - } - } - }, - "ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==" - }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "requires": { - "type-fest": "^0.11.0" - }, - "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" - } - } - }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=" - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "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==" - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - } - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" - }, - "autoprefixer": { - "version": "9.8.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", - "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", - "requires": { - "browserslist": "^4.12.0", - "caniuse-lite": "^1.0.30001109", - "colorette": "^1.2.1", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.32", - "postcss-value-parser": "^4.1.0" - } - }, - "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dev": true, - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "babel-loader": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", - "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", - "requires": { - "find-cache-dir": "^2.1.0", - "loader-utils": "^1.4.0", - "mkdirp": "^0.5.3", - "pify": "^4.0.1", - "schema-utils": "^2.6.5" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "babel-plugin-apply-mdx-type-prop": { - "version": "1.6.19", - "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.19.tgz", - "integrity": "sha512-zAuL11EaBbeNpfTqsa9xP7mkvX3V4LaEV6M9UUaI4zQtTqN5JwvDyhNsALQs5Ud7WFQSXtoqU74saTgE+rgZOw==", - "requires": { - "@babel/helper-plugin-utils": "7.10.4", - "@mdx-js/util": "1.6.19" - } - }, - "babel-plugin-css-modules-transform": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-css-modules-transform/-/babel-plugin-css-modules-transform-1.6.2.tgz", - "integrity": "sha512-zBsI54N5n979vfYpqFzQ6oRwEiVcmLH5REyaincNW+Ecl52nvRsQPYIbDcJzHePrXI20YSRUw6G/qbPwZZDgfg==", - "dev": true, - "requires": { - "css-modules-require-hook": "^4.0.6", - "mkdirp": "^0.5.1" - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "requires": { - "object.assign": "^4.1.0" - } - }, - "babel-plugin-extract-import-names": { - "version": "1.6.19", - "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.19.tgz", - "integrity": "sha512-5kbSEhQdg1ybR9OnxybbyR1PXw51z6T6ZCtX3vYSU6t1pC/+eBlSzWXyU2guStbwQgJyxS+mHWSNnL7PUdzAlw==", - "requires": { - "@babel/helper-plugin-utils": "7.10.4" - } - }, - "bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "base16": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", - "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" - }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" - }, - "bfj": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", - "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", - "requires": { - "bluebird": "^3.5.5", - "check-types": "^8.0.3", - "hoopy": "^0.1.4", - "tryer": "^1.0.1" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" - }, - "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==" - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "bn.js": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", - "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==" - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "requires": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - }, - "dependencies": { - "array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" - } - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { - "fill-range": "^7.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "requires": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "requires": { - "pako": "~1.0.5" - } - }, - "browserslist": { - "version": "4.14.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.5.tgz", - "integrity": "sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA==", - "requires": { - "caniuse-lite": "^1.0.30001135", - "electron-to-chromium": "^1.3.571", - "escalade": "^3.1.0", - "node-releases": "^1.1.61" - } - }, - "buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==" - }, - "buffer-json": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-json/-/buffer-json-2.0.0.tgz", - "integrity": "sha512-+jjPFVqyfF1esi9fvfUs3NqM0pH1ziZ36VP4hmA/y/Ssfo/5w5xHKfTw9BwQjoJ1w/oVtpLomqwUHKdefGyuHw==" - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, - "cacache": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", - "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", - "requires": { - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.0", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - } - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "cache-loader": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cache-loader/-/cache-loader-4.1.0.tgz", - "integrity": "sha512-ftOayxve0PwKzBF/GLsZNC9fJBXl8lkZE3TOsjkboHfVHVkL39iUEs1FO07A33mizmci5Dudt38UZrrYXDtbhw==", - "requires": { - "buffer-json": "^2.0.0", - "find-cache-dir": "^3.0.0", - "loader-utils": "^1.2.3", - "mkdirp": "^0.5.1", - "neo-async": "^2.6.1", - "schema-utils": "^2.0.0" - }, - "dependencies": { - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "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==", - "requires": { - "semver": "^6.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "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==" - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "requires": { - "find-up": "^4.0.0" - } - } - } - }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - }, - "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" - } - } - }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "requires": { - "callsites": "^2.0.0" - }, - "dependencies": { - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" - } - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "camel-case": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz", - "integrity": "sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==", - "requires": { - "pascal-case": "^3.1.1", - "tslib": "^1.10.0" - } - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" - }, - "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==" - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "caniuse-lite": { - "version": "1.0.30001154", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001154.tgz", - "integrity": "sha512-y9DvdSti8NnYB9Be92ddMZQrcOe04kcQtcxtBx4NkB04+qZ+JUWotnXBJTmxlKudhxNTQ3RRknMwNU2YQl/Org==" - }, - "ccount": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", - "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==" - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "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==", - "requires": { - "color-name": "~1.1.4" - } - }, - "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==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "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==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==" - }, - "character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==" - }, - "character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" - }, - "check-types": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", - "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==" - }, - "cheerio": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", - "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" - } - }, - "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" - }, - "chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "requires": { - "tslib": "^1.9.0" - } - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "classnames": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", - "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" - }, - "clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", - "requires": { - "source-map": "~0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" - }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - } - } - }, - "clone-deep": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", - "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", - "requires": { - "for-own": "^0.1.3", - "is-plain-object": "^2.0.1", - "kind-of": "^3.0.2", - "lazy-cache": "^1.0.3", - "shallow-clone": "^0.1.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" - }, - "coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "requires": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "collapse-white-space": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", - "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==" - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", - "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==", - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.4" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "color-string": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", - "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "colorette": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", - "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" - }, - "comma-separated-tokens": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", - "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==" - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "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==", - "requires": { - "semver": "^6.0.0" - } - } - } - }, - "connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" - }, - "consola": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz", - "integrity": "sha512-vlcSGgdYS26mPf7qNi+dCisbhiyDnrN1zaRbw3CSuc2wGOMEGGPsp46PdRG5gqXwgtJfjxDkxRNAgRPr1B77vQ==" - }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" - }, - "copy-text-to-clipboard": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-2.2.0.tgz", - "integrity": "sha512-WRvoIdnTs1rgPMkgA2pUOa/M4Enh2uzCwdKsOMYNAJiz/4ZvEJgmbF4OmninPmlFdAWisfeh0tH+Cpf7ni3RqQ==" - }, - "copy-webpack-plugin": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.2.1.tgz", - "integrity": "sha512-VH2ZTMIBsx4p++Lmpg77adZ0KUyM5gFR/9cuTrbneNnJlcQXUFvsNariPqq2dq2kV3F2skHiDGPQCyKWy1+U0Q==", - "requires": { - "cacache": "^15.0.5", - "fast-glob": "^3.2.4", - "find-cache-dir": "^3.3.1", - "glob-parent": "^5.1.1", - "globby": "^11.0.1", - "loader-utils": "^2.0.0", - "normalize-path": "^3.0.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "webpack-sources": "^1.4.3" - }, - "dependencies": { - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "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==", - "requires": { - "semver": "^6.0.0" - } - }, - "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "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==" - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "requires": { - "find-up": "^4.0.0" - } - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" - }, - "core-js-compat": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", - "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", - "requires": { - "browserslist": "^4.8.5", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } - } - }, - "core-js-pure": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", - "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } - }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" - }, - "css-blank-pseudo": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", - "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", - "requires": { - "postcss": "^7.0.5" - } - }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" - }, - "css-declaration-sorter": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", - "requires": { - "postcss": "^7.0.1", - "timsort": "^0.3.0" - } - }, - "css-has-pseudo": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", - "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", - "requires": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^5.0.0-rc.4" - }, - "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" - }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "css-loader": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", - "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", - "requires": { - "camelcase": "^5.3.1", - "cssesc": "^3.0.0", - "icss-utils": "^4.1.1", - "loader-utils": "^1.2.3", - "normalize-path": "^3.0.0", - "postcss": "^7.0.32", - "postcss-modules-extract-imports": "^2.0.0", - "postcss-modules-local-by-default": "^3.0.2", - "postcss-modules-scope": "^2.2.0", - "postcss-modules-values": "^3.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^2.7.0", - "semver": "^6.3.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "css-modules-require-hook": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/css-modules-require-hook/-/css-modules-require-hook-4.2.3.tgz", - "integrity": "sha1-Z5LKQSsV4j5vm+agfc739Xf/kE0=", - "dev": true, - "requires": { - "debug": "^2.2.0", - "generic-names": "^1.0.1", - "glob-to-regexp": "^0.3.0", - "icss-replace-symbols": "^1.0.2", - "lodash": "^4.3.0", - "postcss": "^6.0.1", - "postcss-modules-extract-imports": "^1.0.0", - "postcss-modules-local-by-default": "^1.0.1", - "postcss-modules-resolve-imports": "^1.3.0", - "postcss-modules-scope": "^1.0.0", - "postcss-modules-values": "^1.1.1", - "seekout": "^1.0.1" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "postcss-modules-extract-imports": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz", - "integrity": "sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==", - "dev": true, - "requires": { - "postcss": "^6.0.1" - } - }, - "postcss-modules-local-by-default": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", - "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", - "dev": true, - "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" - } - }, - "postcss-modules-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", - "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", - "dev": true, - "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" - } - }, - "postcss-modules-values": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", - "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", - "dev": true, - "requires": { - "icss-replace-symbols": "^1.1.0", - "postcss": "^6.0.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "css-prefers-color-scheme": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", - "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", - "requires": { - "postcss": "^7.0.5" - } - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" - }, - "css-selector-tokenizer": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz", - "integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "fastparse": "^1.1.2" - } - }, - "css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "requires": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" - }, - "cssdb": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", - "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==" - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" - }, - "cssnano": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", - "requires": { - "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.7", - "is-resolvable": "^1.0.0", - "postcss": "^7.0.0" - }, - "dependencies": { - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - } - }, - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - } - } - }, - "cssnano-preset-default": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", - "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", - "requires": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.1", - "postcss-colormin": "^4.0.3", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.2", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.11", - "postcss-merge-rules": "^4.0.3", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.2", - "postcss-minify-params": "^4.0.2", - "postcss-minify-selectors": "^4.0.2", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.2", - "postcss-normalize-positions": "^4.0.2", - "postcss-normalize-repeat-style": "^4.0.2", - "postcss-normalize-string": "^4.0.2", - "postcss-normalize-timing-functions": "^4.0.2", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.2", - "postcss-ordered-values": "^4.1.2", - "postcss-reduce-initial": "^4.0.3", - "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.2", - "postcss-unique-selectors": "^4.0.1" - } - }, - "cssnano-util-get-arguments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=" - }, - "cssnano-util-get-match": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=" - }, - "cssnano-util-raw-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", - "requires": { - "postcss": "^7.0.0" - } - }, - "cssnano-util-same-parent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==" - }, - "csso": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.1.0.tgz", - "integrity": "sha512-h+6w/W1WqXaJA4tb1dk7r5tVbOm97MsKxzwnvOR04UQ6GILroryjMWu3pmCCtL2mLaEStQ0fZgeGiy99mo7iyg==", - "requires": { - "css-tree": "^1.0.0" - }, - "dependencies": { - "css-tree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0.tgz", - "integrity": "sha512-CdVYz/Yuqw0VdKhXPBIgi8DO3NicJVYZNWeX9XcIuSp9ZoFT5IcleVRW07O5rMjdcx1mb+MEJPknTTEW7DdsYw==", - "requires": { - "mdn-data": "2.0.12", - "source-map": "^0.6.1" - } - }, - "mdn-data": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.12.tgz", - "integrity": "sha512-ULbAlgzVb8IqZ0Hsxm6hHSlQl3Jckst2YEQS7fODu9ilNWy2LvcoSY7TRFIktABP2mdppBioc66va90T+NUs8Q==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "csstype": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz", - "integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA==" - }, - "cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" - }, - "d3": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.2.1.tgz", - "integrity": "sha512-E/5sP0aeK6YPXI/+4QlefvBFgmcyR2jYftId0PrYWv4Y/gW3c3thp1XG4rQzF0eUwV9tR1x05X5eWuJ6rQXvew==", - "requires": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "3", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - } - }, - "d3-array": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.1.1.tgz", - "integrity": "sha512-33qQ+ZoZlli19IFiQx4QEpf2CBEayMRzhlisJHSCsSUbDXv6ZishqS1x7uFVClKG4Wr7rZVHvaAttoLow6GqdQ==", - "requires": { - "internmap": "1 - 2" - } - }, - "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==" - }, - "d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "requires": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - } - }, - "d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "requires": { - "d3-path": "1 - 3" - } - }, - "d3-collection": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", - "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" - }, - "d3-color": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.0.1.tgz", - "integrity": "sha512-6/SlHkDOBLyQSJ1j1Ghs82OIUXpKWlR0hCsw0XrLSQhuUPuCSmLQ1QPH98vpnQxMUQM2/gfAkUEWsupVpd9JGw==" - }, - "d3-contour": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-3.0.1.tgz", - "integrity": "sha512-0Oc4D0KyhwhM7ZL0RMnfGycLN7hxHB8CMmwZ3+H26PWAG0ozNuYG5hXSDNgmP1SgJkQMrlG6cP20HoaSbvcJTQ==", - "requires": { - "d3-array": "2 - 3" - } - }, - "d3-delaunay": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", - "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", - "requires": { - "delaunator": "5" - } - }, - "d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==" - }, - "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==", - "requires": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - } - }, - "d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "requires": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" - }, - "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==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } - } - }, - "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==" - }, - "d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "requires": { - "d3-dsv": "1 - 3" - } - }, - "d3-force": { - "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==", - "requires": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - } - }, - "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==" - }, - "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==", - "requires": { - "d3-array": "2.5.0 - 3" - } - }, - "d3-hierarchy": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.1.tgz", - "integrity": "sha512-LtAIu54UctRmhGKllleflmHalttH3zkfSi4NlKrTAoFKjC+AFBJohsCAdgCBYQwH0F8hIOGY89X1pPqAchlMkA==" - }, - "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==", - "requires": { - "d3-color": "1 - 3" - } - }, - "d3-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", - "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==" - }, - "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==" - }, - "d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==" - }, - "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==" - }, - "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==", - "requires": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - } - }, - "d3-scale-chromatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", - "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", - "requires": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" - } - }, - "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==" - }, - "d3-shape": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.0.1.tgz", - "integrity": "sha512-HNZNEQoDhuCrDWEc/BMbF/hKtzMZVoe64TvisFLDp2Iyj0UShB/E6/lBsLlJTfBMbYgftHj90cXJ0SEitlE6Xw==", - "requires": { - "d3-path": "1 - 3" - } - }, - "d3-time": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", - "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", - "requires": { - "d3-array": "2 - 3" - } - }, - "d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "requires": { - "d3-time": "1 - 3" - } - }, - "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==" - }, - "d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "requires": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - } - }, - "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==" - }, - "d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "requires": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - } - }, - "dagre": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", - "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", - "requires": { - "graphlib": "^2.1.8", - "lodash": "^4.17.15" - } - }, - "dagre-d3": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/dagre-d3/-/dagre-d3-0.6.4.tgz", - "integrity": "sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==", - "requires": { - "d3": "^5.14", - "dagre": "^0.8.5", - "graphlib": "^2.1.8", - "lodash": "^4.17.15" - }, - "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==" - }, - "d3": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", - "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", - "requires": { - "d3-array": "1", - "d3-axis": "1", - "d3-brush": "1", - "d3-chord": "1", - "d3-collection": "1", - "d3-color": "1", - "d3-contour": "1", - "d3-dispatch": "1", - "d3-drag": "1", - "d3-dsv": "1", - "d3-ease": "1", - "d3-fetch": "1", - "d3-force": "1", - "d3-format": "1", - "d3-geo": "1", - "d3-hierarchy": "1", - "d3-interpolate": "1", - "d3-path": "1", - "d3-polygon": "1", - "d3-quadtree": "1", - "d3-random": "1", - "d3-scale": "2", - "d3-scale-chromatic": "1", - "d3-selection": "1", - "d3-shape": "1", - "d3-time": "1", - "d3-time-format": "2", - "d3-timer": "1", - "d3-transition": "1", - "d3-voronoi": "1", - "d3-zoom": "1" - } - }, - "d3-array": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", - "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" - }, - "d3-axis": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", - "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" - }, - "d3-brush": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.6.tgz", - "integrity": "sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==", - "requires": { - "d3-dispatch": "1", - "d3-drag": "1", - "d3-interpolate": "1", - "d3-selection": "1", - "d3-transition": "1" - } - }, - "d3-chord": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", - "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", - "requires": { - "d3-array": "1", - "d3-path": "1" - } - }, - "d3-color": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", - "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" - }, - "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==", - "requires": { - "d3-array": "^1.1.1" - } - }, - "d3-dispatch": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", - "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" - }, - "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==", - "requires": { - "d3-dispatch": "1", - "d3-selection": "1" - } - }, - "d3-dsv": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", - "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", - "requires": { - "commander": "2", - "iconv-lite": "0.4", - "rw": "1" - } - }, - "d3-ease": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", - "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" - }, - "d3-fetch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", - "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", - "requires": { - "d3-dsv": "1" - } - }, - "d3-force": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", - "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", - "requires": { - "d3-collection": "1", - "d3-dispatch": "1", - "d3-quadtree": "1", - "d3-timer": "1" - } - }, - "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==" - }, - "d3-geo": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", - "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", - "requires": { - "d3-array": "1" - } - }, - "d3-hierarchy": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", - "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" - }, - "d3-interpolate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", - "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", - "requires": { - "d3-color": "1" - } - }, - "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==" - }, - "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==" - }, - "d3-quadtree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", - "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" - }, - "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==" - }, - "d3-scale": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", - "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", - "requires": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" - } - }, - "d3-scale-chromatic": { - "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==", - "requires": { - "d3-color": "1", - "d3-interpolate": "1" - } - }, - "d3-selection": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", - "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" - }, - "d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "requires": { - "d3-path": "1" - } - }, - "d3-time": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", - "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" - }, - "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==", - "requires": { - "d3-time": "1" - } - }, - "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==" - }, - "d3-transition": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", - "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", - "requires": { - "d3-color": "1", - "d3-dispatch": "1", - "d3-ease": "1", - "d3-interpolate": "1", - "d3-selection": "^1.1.0", - "d3-timer": "1" - } - }, - "d3-zoom": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", - "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", - "requires": { - "d3-dispatch": "1", - "d3-drag": "1", - "d3-interpolate": "1", - "d3-selection": "1", - "d3-transition": "1" - } - } - } - }, - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==" - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", - "requires": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" - } - }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "del": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz", - "integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==", - "requires": { - "globby": "^10.0.1", - "graceful-fs": "^4.2.2", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.1", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0" - }, - "dependencies": { - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "requires": { - "aggregate-error": "^3.0.0" - } - } - } - }, - "delaunator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", - "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", - "requires": { - "robust-predicates": "^3.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "detab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.3.tgz", - "integrity": "sha512-Up8P0clUVwq0FnFjDclzZsy9PadzRn5FFxrr47tQQvMHqyiFYVbpH8oXDzWtF0Q7pYy3l+RPmtBl+BsFF6wH0A==", - "requires": { - "repeat-string": "^1.5.4" - } - }, - "detect-node": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", - "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" - }, - "detect-port": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.3.0.tgz", - "integrity": "sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ==", - "requires": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "requires": { - "path-type": "^4.0.0" - } - }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" - }, - "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", - "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "requires": { - "buffer-indexof": "^1.0.0" - } - }, - "dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "requires": { - "utila": "~0.4" - } - }, - "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "requires": { - "domelementtype": "1" - } - }, - "dompurify": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.4.tgz", - "integrity": "sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ==" - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "dot-case": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz", - "integrity": "sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==", - "requires": { - "no-case": "^3.0.3", - "tslib": "^1.10.0" - } - }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "requires": { - "is-obj": "^2.0.0" - } - }, - "dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", - "dev": true - }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "ejs": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", - "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" - }, - "electron-to-chromium": { - "version": "1.3.584", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.584.tgz", - "integrity": "sha512-NB3DzrTzJFhWkUp+nl2KtUtoFzrfGXTir2S+BU4tXGyXH9vlluPuFpE3pTKeH7+PY460tHLjKzh6K2+TWwW+Ww==" - }, - "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", - "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" - }, - "emoticon": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-3.2.0.tgz", - "integrity": "sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg==" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "requires": { - "iconv-lite": "^0.6.2" - }, - "dependencies": { - "iconv-lite": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", - "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "enhanced-resolve": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", - "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "dependencies": { - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "requires": { - "prr": "~1.0.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "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==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "eta": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/eta/-/eta-1.11.0.tgz", - "integrity": "sha512-lfqIE6qD55WFYT6E0phTBUe0sapHJhfvRDB7jSpXxFGwzDaP69kQqRyF7krBe8I1QzF5nE1yAByiIOLB630x4Q==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "eval": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.4.tgz", - "integrity": "sha512-npGsebJejyjMRnLdFu+T/97dnigqIU0Ov3IGrZ8ygd1v7RL1vGkEKtvyWZobqUH1AQgKlg0Yqqe2BtMA9/QZLw==", - "requires": { - "require-like": ">= 0.1.1" - } - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "events": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", - "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==" - }, - "eventsource": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", - "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", - "requires": { - "original": "^1.0.0" - } - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "exenv": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", - "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "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==" - }, - "fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" - } - }, - "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==" - }, - "fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", - "requires": { - "punycode": "^1.3.2" - } - }, - "fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", - "dev": true - }, - "fastq": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz", - "integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==", - "requires": { - "reusify": "^1.0.4" - } - }, - "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "fbemitter": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-2.1.1.tgz", - "integrity": "sha1-Uj4U/a9SSIBbsC9i78M75wP1GGU=", - "requires": { - "fbjs": "^0.8.4" - } - }, - "fbjs": { - "version": "0.8.17", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", - "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", - "requires": { - "core-js": "^1.0.0", - "isomorphic-fetch": "^2.1.1", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.18" - }, - "dependencies": { - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - } - } - }, - "feed": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.1.tgz", - "integrity": "sha512-l28KKcK1J/u3iq5dRDmmoB2p7dtBfACC2NqJh4dI2kFptxH0asfjmOfcxqh5Sv8suAlVa73gZJ4REY5RrafVvg==", - "requires": { - "xml-js": "^1.6.11" - } - }, - "figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==" - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "dependencies": { - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, - "filesize": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.0.1.tgz", - "integrity": "sha512-u4AYWPgbI5GBhs6id1KdImZWn5yfyFrrQ8OWZdN7ZMfA8Bf4HcO0BGo9bmUIEV8yrp8I1xVfJ/dn90GtFNNJcg==" - }, - "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==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "flatten": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", - "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==" - }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "flux": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/flux/-/flux-3.1.3.tgz", - "integrity": "sha1-0jvtUVp5oi2TOrU6tK2hnQWy8Io=", - "requires": { - "fbemitter": "^2.0.0", - "fbjs": "^0.8.0" - } - }, - "follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "requires": { - "for-in": "^1.0.1" - } - }, - "fork-ts-checker-webpack-plugin": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-3.1.1.tgz", - "integrity": "sha512-DuVkPNrM12jR41KM2e+N+styka0EgLkTnXmNcXdgOM37vtGeY+oCBK/Jx0hzSeEU6memFCtWb4htrHPMDfwwUQ==", - "requires": { - "babel-code-frame": "^6.22.0", - "chalk": "^2.4.1", - "chokidar": "^3.3.0", - "micromatch": "^3.1.10", - "minimatch": "^3.0.4", - "semver": "^5.6.0", - "tapable": "^1.0.0", - "worker-rpc": "^0.1.0" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "requires": { - "minipass": "^3.0.0" - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "generic-names": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-1.0.3.tgz", - "integrity": "sha1-LXhqEhruUIh2eWk56OO/+DbCCRc=", - "dev": true, - "requires": { - "loader-utils": "^0.2.16" - }, - "dependencies": { - "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", - "dev": true - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" - } - } - } - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" - }, - "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==" - }, - "get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" - }, - "github-slugger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz", - "integrity": "sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==", - "requires": { - "emoji-regex": ">=6.0.0 <=6.1.1" - }, - "dependencies": { - "emoji-regex": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz", - "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=" - } - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "requires": { - "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" - } - }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" - }, - "global-dirs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", - "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", - "requires": { - "ini": "^1.3.5" - } - }, - "global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "requires": { - "global-prefix": "^3.0.0" - } - }, - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "dependencies": { - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", - "requires": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - } - }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "graphlib": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", - "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", - "requires": { - "lodash": "^4.17.15" - } - }, - "gray-matter": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.2.tgz", - "integrity": "sha512-7hB/+LxrOjq/dd8APlK0r24uL/67w7SkYnfwhNFwg/VDIGWGmduTDYf3WNstLW2fbbmRwrDGCVSJ2isuf2+4Hw==", - "requires": { - "js-yaml": "^3.11.0", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - } - }, - "gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", - "requires": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" - } - }, - "handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - } - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "hast-to-hyperscript": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.0.tgz", - "integrity": "sha512-NJvMYU3GlMLs7hN3CRbsNlMzusVNkYBogVWDGybsuuVQ336gFLiD+q9qtFZT2meSHzln3pNISZWTASWothMSMg==", - "requires": { - "@types/unist": "^2.0.3", - "comma-separated-tokens": "^1.0.0", - "property-information": "^5.3.0", - "space-separated-tokens": "^1.0.0", - "style-to-object": "^0.3.0", - "unist-util-is": "^4.0.0", - "web-namespaces": "^1.0.0" - } - }, - "hast-util-from-parse5": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-6.0.0.tgz", - "integrity": "sha512-3ZYnfKenbbkhhNdmOQqgH10vnvPivTdsOJCri+APn0Kty+nRkDHArnaX9Hiaf8H+Ig+vkNptL+SRY/6RwWJk1Q==", - "requires": { - "@types/parse5": "^5.0.0", - "ccount": "^1.0.0", - "hastscript": "^5.0.0", - "property-information": "^5.0.0", - "vfile": "^4.0.0", - "web-namespaces": "^1.0.0" - } - }, - "hast-util-parse-selector": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.4.tgz", - "integrity": "sha512-gW3sxfynIvZApL4L07wryYF4+C9VvH3AUi7LAnVXV4MneGEgwOByXvFo18BgmTWnm7oHAe874jKbIB1YhHSIzA==" - }, - "hast-util-raw": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-6.0.1.tgz", - "integrity": "sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig==", - "requires": { - "@types/hast": "^2.0.0", - "hast-util-from-parse5": "^6.0.0", - "hast-util-to-parse5": "^6.0.0", - "html-void-elements": "^1.0.0", - "parse5": "^6.0.0", - "unist-util-position": "^3.0.0", - "vfile": "^4.0.0", - "web-namespaces": "^1.0.0", - "xtend": "^4.0.0", - "zwitch": "^1.0.0" - } - }, - "hast-util-to-parse5": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz", - "integrity": "sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ==", - "requires": { - "hast-to-hyperscript": "^9.0.0", - "property-information": "^5.0.0", - "web-namespaces": "^1.0.0", - "xtend": "^4.0.0", - "zwitch": "^1.0.0" - } - }, - "hastscript": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz", - "integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==", - "requires": { - "comma-separated-tokens": "^1.0.0", - "hast-util-parse-selector": "^2.0.0", - "property-information": "^5.0.0", - "space-separated-tokens": "^1.0.0" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" - }, - "hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" - }, - "history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "requires": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - } - }, - "hoopy": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", - "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==" - }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" - }, - "hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" - }, - "html-comment-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" - }, - "html-entities": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", - "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==" - }, - "html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", - "requires": { - "camel-case": "^4.1.1", - "clean-css": "^4.2.3", - "commander": "^4.1.1", - "he": "^1.2.0", - "param-case": "^3.0.3", - "relateurl": "^0.2.7", - "terser": "^4.6.3" - } - }, - "html-tags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", - "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==" - }, - "html-void-elements": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", - "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==" - }, - "html-webpack-plugin": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz", - "integrity": "sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw==", - "requires": { - "@types/html-minifier-terser": "^5.0.0", - "@types/tapable": "^1.0.5", - "@types/webpack": "^4.41.8", - "html-minifier-terser": "^5.0.1", - "loader-utils": "^1.2.3", - "lodash": "^4.17.15", - "pretty-error": "^2.1.1", - "tapable": "^1.1.3", - "util.promisify": "1.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" - } - } - } - }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - } - }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" - }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=" - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "requires": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==" - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", - "dev": true - }, - "icss-utils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", - "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", - "requires": { - "postcss": "^7.0.14" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" - }, - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" - }, - "immer": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", - "integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==" - }, - "import-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", - "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", - "requires": { - "import-from": "^2.1.0" - } - }, - "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-from": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", - "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", - "requires": { - "resolve-from": "^3.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - } - } - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" - }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", - "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" - }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" - }, - "infima": { - "version": "0.2.0-alpha.13", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.13.tgz", - "integrity": "sha512-BxCZ1pMcUF0PcL4WV07l/lvaeBBdUUw7uVqNyyeGAutzDpkDyFOl5gOv9wFAJKLo5yerPNFXxFPgDitNjctqIA==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "inline-style-parser": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", - "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" - }, - "inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "requires": { - "color-name": "~1.1.4" - } - }, - "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==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "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==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "requires": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - } - }, - "internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" - }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==" - }, - "is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "requires": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "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==", - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-callable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "requires": { - "ci-info": "^2.0.0" - }, - "dependencies": { - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - } - } - }, - "is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "requires": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "is-core-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", - "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" - }, - "is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==" - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" - }, - "is-docker": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", - "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==" - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" - }, - "is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "requires": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - } - }, - "is-negative-zero": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", - "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" - }, - "is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==" - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" - }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==" - }, - "is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "requires": { - "is-path-inside": "^2.1.0" - }, - "dependencies": { - "is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "requires": { - "path-is-inside": "^1.0.2" - } - } - } - }, - "is-path-inside": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", - "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==" - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=" - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" - }, - "is-root": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", - "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-svg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", - "requires": { - "html-comment-regex": "^1.1.0" - } - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-whitespace-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", - "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==" - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" - }, - "is-word-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", - "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==" - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "requires": { - "is-docker": "^2.0.0" - } - }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "requires": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" - } - }, - "jest-worker": { - "version": "26.6.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.1.tgz", - "integrity": "sha512-R5IE3qSGz+QynJx8y+ICEkdI2OJ3RJjRQVEyCcFAd3yVhQSEtquziPO29Mlzgn07LOVE8u8jhJ1FqcwegiXWOw==", - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "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==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "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==" - }, - "json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==" - }, - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "requires": { - "minimist": "^1.2.5" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "requires": { - "json-buffer": "3.0.0" - } - }, - "khroma": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/khroma/-/khroma-1.4.1.tgz", - "integrity": "sha512-+GmxKvmiRuCcUYDgR7g5Ngo0JEDeOsGdNONdU2zsiBQaK4z19Y2NvXqfEDE0ZiIrg45GTZyAnPLVsLZZACYm3Q==" - }, - "killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==" - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - }, - "last-call-webpack-plugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", - "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==", - "requires": { - "lodash": "^4.17.5", - "webpack-sources": "^1.1.0" - } - }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "requires": { - "package-json": "^6.3.0" - } - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" - }, - "loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==" - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" - }, - "lodash.assignin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" - }, - "lodash.bind": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" - }, - "lodash.chunk": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz", - "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=" - }, - "lodash.curry": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", - "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA=" - }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" - }, - "lodash.flatmap": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz", - "integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=" - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, - "lodash.flow": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", - "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" - }, - "lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" - }, - "lodash.groupby": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", - "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=" - }, - "lodash.has": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.kebabcase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha1-hImxyw0p/4gZXM7KRI/21swpXDY=" - }, - "lodash.map": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" - }, - "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==" - }, - "lodash.padstart": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.6.1.tgz", - "integrity": "sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=" - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" - }, - "lodash.pickby": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", - "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=" - }, - "lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" - }, - "lodash.reject": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" - }, - "lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" - }, - "lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "requires": { - "lodash._reinterpolate": "^3.0.0" - } - }, - "lodash.toarray": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", - "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, - "loglevel": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.0.tgz", - "integrity": "sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==" - }, - "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==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lower-case": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", - "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==", - "requires": { - "tslib": "^1.10.0" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, - "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==", - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "requires": { - "object-visit": "^1.0.0" - } - }, - "markdown-escapes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", - "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==" - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "mdast-squeeze-paragraphs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz", - "integrity": "sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==", - "requires": { - "unist-util-remove": "^2.0.0" - } - }, - "mdast-util-definitions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-3.0.1.tgz", - "integrity": "sha512-BAv2iUm/e6IK/b2/t+Fx69EL/AGcq/IG2S+HxHjDJGfLJtd6i9SZUS76aC9cig+IEucsqxKTR0ot3m933R3iuA==", - "requires": { - "unist-util-visit": "^2.0.0" - } - }, - "mdast-util-to-hast": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-9.1.1.tgz", - "integrity": "sha512-vpMWKFKM2mnle+YbNgDXxx95vv0CoLU0v/l3F5oFAG5DV7qwkZVWA206LsAdOnEVyf5vQcLnb3cWJywu7mUxsQ==", - "requires": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.3", - "mdast-util-definitions": "^3.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" - } - }, - "mdast-util-to-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", - "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==" - }, - "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "merge-deep": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", - "integrity": "sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA==", - "requires": { - "arr-union": "^3.1.0", - "clone-deep": "^0.2.4", - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" - }, - "mermaid": { - "version": "8.13.8", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.13.8.tgz", - "integrity": "sha512-Z5v31rvo8P7BPTiGicdJl9BbzyUe9s5sXILK8sM1g7ijkagpfFjPtXZVsq5P1WlN8m/fUp2PPNXVF9SqeTM91w==", - "requires": { - "@braintree/sanitize-url": "^3.1.0", - "d3": "^7.0.0", - "dagre": "^0.8.5", - "dagre-d3": "^0.6.4", - "dompurify": "2.3.4", - "graphlib": "^2.1.8", - "khroma": "^1.4.1", - "moment-mini": "^2.24.0", - "stylis": "^4.0.10" - } - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "microevent.ts": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", - "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==" - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "requires": { - "mime-db": "1.44.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, - "mini-create-react-context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", - "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", - "requires": { - "@babel/runtime": "^7.12.1", - "tiny-warning": "^1.0.3" - } - }, - "mini-css-extract-plugin": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.2.tgz", - "integrity": "sha512-a3Y4of27Wz+mqK3qrcd3VhYz6cU0iW5x3Sgvqzbj+XmlrSizmvu8QQMl5oMYJjgHOC4iyt+w7l4umP+dQeW3bw==", - "requires": { - "loader-utils": "^1.1.0", - "normalize-url": "1.9.1", - "schema-utils": "^1.0.0", - "webpack-sources": "^1.1.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - } - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", - "requires": { - "yallist": "^4.0.0" - } - }, - "minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "requires": { - "minipass": "^3.0.0" - } - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } - }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", - "requires": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" - }, - "dependencies": { - "for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=" - } - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "moment-mini": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.24.0.tgz", - "integrity": "sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==" - }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "requires": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - } - }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" - }, - "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "no-case": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz", - "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==", - "requires": { - "lower-case": "^2.0.1", - "tslib": "^1.10.0" - } - }, - "node-emoji": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", - "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", - "requires": { - "lodash.toarray": "^4.4.0" - } - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - }, - "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" - }, - "node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - } - } - }, - "node-releases": { - "version": "1.1.65", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.65.tgz", - "integrity": "sha512-YpzJOe2WFIW0V4ZkJQd/DGR/zdVwc/pI4Nl1CZrBO19FdRcSTmsuhdttw9rsTzzJLrNcSloLiBbEYx1C4f6gpA==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" - }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^2.0.0" - }, - "dependencies": { - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - } - } - }, - "nprogress": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", - "integrity": "sha1-y480xTIT2JVyP8urkH6UIq28r7E=" - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "requires": { - "boolbase": "~1.0.0" - } - }, - "null-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-3.0.0.tgz", - "integrity": "sha512-hf5sNLl8xdRho4UPBOOeoIwT3WhjYcMUQm0zj44EhD6UscMAz72o2udpoDFBgykucdEDGIcd6SXbc/G6zssbzw==", - "requires": { - "loader-utils": "^1.2.3", - "schema-utils": "^1.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - } - } - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" - }, - "object-is": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.3.tgz", - "integrity": "sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", - "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.0", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "requires": { - "isobject": "^3.0.1" - } - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } - } - }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "open": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/open/-/open-7.3.0.tgz", - "integrity": "sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==", - "requires": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - } - }, - "opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" - }, - "opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "requires": { - "is-wsl": "^1.1.0" - }, - "dependencies": { - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" - } - } - }, - "optimize-css-assets-webpack-plugin": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz", - "integrity": "sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A==", - "requires": { - "cssnano": "^4.1.10", - "last-call-webpack-plugin": "^3.0.0" - } - }, - "original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "requires": { - "url-parse": "^1.4.3" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", - "requires": { - "retry": "^0.12.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - } - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "requires": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "param-case": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz", - "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==", - "requires": { - "dot-case": "^3.0.3", - "tslib": "^1.10.0" - } - }, - "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==", - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "requires": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - }, - "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse-numeric-range": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-0.0.2.tgz", - "integrity": "sha1-tPCdQTx6282Yf26SM8e0shDJOOQ=" - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "pascal-case": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.1.tgz", - "integrity": "sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA==", - "requires": { - "no-case": "^3.0.3", - "tslib": "^1.10.0" - } - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" - }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - }, - "pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "requires": { - "find-up": "^3.0.0" - } - }, - "pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "requires": { - "find-up": "^3.0.0" - } - }, - "pnp-webpack-plugin": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", - "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", - "requires": { - "ts-pnp": "^1.1.6" - } - }, - "portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" - }, - "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-attribute-case-insensitive": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz", - "integrity": "sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==", - "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^6.0.2" - } - }, - "postcss-calc": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", - "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", - "requires": { - "postcss": "^7.0.27", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" - } - }, - "postcss-color-functional-notation": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", - "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-color-gray": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", - "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", - "requires": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.5", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-color-hex-alpha": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", - "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", - "requires": { - "postcss": "^7.0.14", - "postcss-values-parser": "^2.0.1" - } - }, - "postcss-color-mod-function": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", - "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", - "requires": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-color-rebeccapurple": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", - "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-colormin": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", - "requires": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-convert-values": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-custom-media": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", - "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", - "requires": { - "postcss": "^7.0.14" - } - }, - "postcss-custom-properties": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", - "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", - "requires": { - "postcss": "^7.0.17", - "postcss-values-parser": "^2.0.1" - } - }, - "postcss-custom-selectors": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", - "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", - "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" - }, - "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" - }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-dir-pseudo-class": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", - "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", - "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" - }, - "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" - }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-discard-comments": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-duplicates": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-empty": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-overridden": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-double-position-gradients": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", - "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", - "requires": { - "postcss": "^7.0.5", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-env-function": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", - "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-focus-visible": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", - "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-focus-within": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", - "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-font-variant": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz", - "integrity": "sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-gap-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", - "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-image-set-function": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", - "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-initial": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", - "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", - "requires": { - "lodash.template": "^4.5.0", - "postcss": "^7.0.2" - } - }, - "postcss-lab-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", - "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", - "requires": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-load-config": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", - "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", - "requires": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - }, - "dependencies": { - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - } - }, - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - } - } - }, - "postcss-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", - "requires": { - "loader-utils": "^1.1.0", - "postcss": "^7.0.0", - "postcss-load-config": "^2.0.0", - "schema-utils": "^1.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - } - } - }, - "postcss-logical": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", - "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-media-minmax": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", - "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-merge-longhand": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", - "requires": { - "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-merge-rules": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-minify-font-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-minify-gradients": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-minify-params": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", - "requires": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-minify-selectors": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", - "requires": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-modules-extract-imports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", - "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", - "requires": { - "postcss": "^7.0.5" - } - }, - "postcss-modules-local-by-default": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", - "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", - "requires": { - "icss-utils": "^4.1.1", - "postcss": "^7.0.32", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-modules-resolve-imports": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/postcss-modules-resolve-imports/-/postcss-modules-resolve-imports-1.3.0.tgz", - "integrity": "sha1-OY0wALla6WlCDN9M2D+oBn8cXq4=", - "dev": true, - "requires": { - "css-selector-tokenizer": "^0.7.0", - "icss-utils": "^3.0.1", - "minimist": "^1.2.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "icss-utils": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-3.0.1.tgz", - "integrity": "sha1-7nDTroysOMa+XtkehRsn7tNDrQ8=", - "dev": true, - "requires": { - "postcss": "^6.0.2" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "postcss-modules-scope": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", - "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", - "requires": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^6.0.0" - } - }, - "postcss-modules-values": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", - "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", - "requires": { - "icss-utils": "^4.0.0", - "postcss": "^7.0.6" - } - }, - "postcss-nesting": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", - "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-normalize-charset": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-normalize-display-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-positions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-repeat-style": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-string": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", - "requires": { - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-timing-functions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-unicode": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", - "requires": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-normalize-whitespace": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-ordered-values": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-overflow-shorthand": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", - "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-page-break": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", - "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-place": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", - "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-preset-env": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", - "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", - "requires": { - "autoprefixer": "^9.6.1", - "browserslist": "^4.6.4", - "caniuse-lite": "^1.0.30000981", - "css-blank-pseudo": "^0.1.4", - "css-has-pseudo": "^0.10.0", - "css-prefers-color-scheme": "^3.1.1", - "cssdb": "^4.4.0", - "postcss": "^7.0.17", - "postcss-attribute-case-insensitive": "^4.0.1", - "postcss-color-functional-notation": "^2.0.1", - "postcss-color-gray": "^5.0.0", - "postcss-color-hex-alpha": "^5.0.3", - "postcss-color-mod-function": "^3.0.3", - "postcss-color-rebeccapurple": "^4.0.1", - "postcss-custom-media": "^7.0.8", - "postcss-custom-properties": "^8.0.11", - "postcss-custom-selectors": "^5.1.2", - "postcss-dir-pseudo-class": "^5.0.0", - "postcss-double-position-gradients": "^1.0.0", - "postcss-env-function": "^2.0.2", - "postcss-focus-visible": "^4.0.0", - "postcss-focus-within": "^3.0.0", - "postcss-font-variant": "^4.0.0", - "postcss-gap-properties": "^2.0.0", - "postcss-image-set-function": "^3.0.1", - "postcss-initial": "^3.0.0", - "postcss-lab-function": "^2.0.1", - "postcss-logical": "^3.0.0", - "postcss-media-minmax": "^4.0.0", - "postcss-nesting": "^7.0.0", - "postcss-overflow-shorthand": "^2.0.0", - "postcss-page-break": "^2.0.0", - "postcss-place": "^4.0.1", - "postcss-pseudo-class-any-link": "^6.0.0", - "postcss-replace-overflow-wrap": "^3.0.0", - "postcss-selector-matches": "^4.0.0", - "postcss-selector-not": "^4.0.0" - } - }, - "postcss-pseudo-class-any-link": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", - "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", - "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" - }, - "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" - }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-reduce-initial": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-replace-overflow-wrap": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", - "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-selector-matches": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", - "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", - "requires": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" - } - }, - "postcss-selector-not": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", - "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", - "requires": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" - } - }, - "postcss-selector-parser": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", - "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", - "requires": { - "cssesc": "^3.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1", - "util-deprecate": "^1.0.2" - } - }, - "postcss-svgo": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", - "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", - "requires": { - "is-svg": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "svgo": "^1.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "postcss-unique-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", - "requires": { - "alphanum-sort": "^1.0.0", - "postcss": "^7.0.0", - "uniqs": "^2.0.0" - } - }, - "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" - }, - "postcss-values-parser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", - "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", - "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" - }, - "pretty-error": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", - "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", - "requires": { - "lodash": "^4.17.20", - "renderkid": "^2.0.4" - } - }, - "pretty-time": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", - "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==" - }, - "prism-react-renderer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.1.1.tgz", - "integrity": "sha512-MgMhSdHuHymNRqD6KM3eGS0PNqgK9q4QF5P0yoQQvpB6jNjeSAi3jcSAz0Sua/t9fa4xDOMar9HJbLa08gl9ug==" - }, - "prismjs": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", - "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==" - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "requires": { - "asap": "~2.0.3" - } - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "property-information": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", - "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", - "requires": { - "xtend": "^4.0.0" - } - }, - "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "requires": { - "escape-goat": "^2.0.0" - } - }, - "pure-color": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", - "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - } - }, - "react-base16-styling": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz", - "integrity": "sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw=", - "requires": { - "base16": "^1.0.0", - "lodash.curry": "^4.0.1", - "lodash.flow": "^3.3.0", - "pure-color": "^1.2.0" - } - }, - "react-dev-utils": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz", - "integrity": "sha512-XxTbgJnYZmxuPtY3y/UV0D8/65NKkmaia4rXzViknVnZeVlklSh8u6TnaEYPfAi/Gh1TP4mEOXHI6jQOPbeakQ==", - "requires": { - "@babel/code-frame": "7.8.3", - "address": "1.1.2", - "browserslist": "4.10.0", - "chalk": "2.4.2", - "cross-spawn": "7.0.1", - "detect-port-alt": "1.1.6", - "escape-string-regexp": "2.0.0", - "filesize": "6.0.1", - "find-up": "4.1.0", - "fork-ts-checker-webpack-plugin": "3.1.1", - "global-modules": "2.0.0", - "globby": "8.0.2", - "gzip-size": "5.1.1", - "immer": "1.10.0", - "inquirer": "7.0.4", - "is-root": "2.1.0", - "loader-utils": "1.2.3", - "open": "^7.0.2", - "pkg-up": "3.1.0", - "react-error-overlay": "^6.0.7", - "recursive-readdir": "2.2.2", - "shell-quote": "1.7.2", - "strip-ansi": "6.0.0", - "text-table": "0.2.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@nodelib/fs.stat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "browserslist": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.10.0.tgz", - "integrity": "sha512-TpfK0TDgv71dzuTsEAlQiHeWQ/tiPqgNZVdv046fvNtBZrjbv2O3TsWCDU0AWGJJKCF/KsjNdLzR9hXOsh/CfA==", - "requires": { - "caniuse-lite": "^1.0.30001035", - "electron-to-chromium": "^1.3.378", - "node-releases": "^1.1.52", - "pkg-up": "^3.1.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - } - } - }, - "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", - "requires": { - "address": "^1.0.1", - "debug": "^2.6.0" - } - }, - "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", - "requires": { - "arrify": "^1.0.1", - "path-type": "^3.0.0" - } - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" - }, - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "fast-glob": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", - "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", - "requires": { - "@mrmlnc/readdir-enhanced": "^2.2.1", - "@nodelib/fs.stat": "^1.1.2", - "glob-parent": "^3.1.0", - "is-glob": "^4.0.0", - "merge2": "^1.2.3", - "micromatch": "^3.1.10" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "globby": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", - "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", - "requires": { - "array-union": "^1.0.1", - "dir-glob": "2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - } - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" - }, - "inquirer": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", - "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^2.4.2", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.2.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^5.1.0", - "through": "^2.3.6" - }, - "dependencies": { - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "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==" - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - } - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - } - }, - "react-error-overlay": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz", - "integrity": "sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw==" - }, - "react-fast-compare": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", - "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" - }, - "react-from-dom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/react-from-dom/-/react-from-dom-0.5.0.tgz", - "integrity": "sha512-Vk0H+XW1oKrqS8bhq1ynG0zgLufUcDQ+CUwJsoYCr1WNH5LVmmfdYBoCBYLHWd1d2PBB3TolqT9gQjORt7f/YA==" - }, - "react-helmet": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", - "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", - "requires": { - "object-assign": "^4.1.1", - "prop-types": "^15.7.2", - "react-fast-compare": "^3.1.1", - "react-side-effect": "^2.1.0" - } - }, - "react-inlinesvg": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-inlinesvg/-/react-inlinesvg-2.1.1.tgz", - "integrity": "sha512-B4CMMuPXzWF02iX9sDeR6OuCAVgrovomg4J26E+2ltc+Zs0dPXBBrW1siBXq9NENsX1rsqPbti0L8AEMpmaVPw==", - "requires": { - "exenv": "^1.2.2", - "react-from-dom": "^0.5.0" - } - }, - "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==" - }, - "react-json-view": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.19.1.tgz", - "integrity": "sha512-u5e0XDLIs9Rj43vWkKvwL8G3JzvXSl6etuS5G42a8klMohZuYFQzSN6ri+/GiBptDqlrXPTdExJVU7x9rrlXhg==", - "requires": { - "flux": "^3.1.3", - "react-base16-styling": "^0.6.0", - "react-lifecycles-compat": "^3.0.4", - "react-textarea-autosize": "^6.1.0" - } - }, - "react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, - "react-loadable": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/react-loadable/-/react-loadable-5.5.0.tgz", - "integrity": "sha512-C8Aui0ZpMd4KokxRdVAm2bQtI03k2RMRNzOB+IipV3yxFTSVICv7WoUr5L9ALB5BmKO1iHgZtWM8EvYG83otdg==", - "requires": { - "prop-types": "^15.5.0" - } - }, - "react-loadable-ssr-addon": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon/-/react-loadable-ssr-addon-0.3.0.tgz", - "integrity": "sha512-E+lnmDakV0k6ut6R2J77vurwCOwTKEwKlHs9S62G8ez+ujecLPcqjt3YAU8M58kIGjp2QjFlZ7F9QWkq/mr6Iw==", - "requires": { - "@babel/runtime": "^7.10.3" - } - }, - "react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", - "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "requires": { - "isarray": "0.0.1" - } - } - } - }, - "react-router-config": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", - "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", - "requires": { - "@babel/runtime": "^7.1.2" - } - }, - "react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", - "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - } - }, - "react-side-effect": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.0.tgz", - "integrity": "sha512-IgmcegOSi5SNX+2Snh1vqmF0Vg/CbkycU9XZbOHJlZ6kMzTmi3yc254oB1WCkgA7OQtIAoLmcSFuHTc/tlcqXg==" - }, - "react-textarea-autosize": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz", - "integrity": "sha512-F6bI1dgib6fSvG8so1HuArPUv+iVEfPliuLWusLF+gAKz0FbB4jLrWUrTAeq1afnPT2c9toEZYUdz/y1uKMy4A==", - "requires": { - "prop-types": "^15.6.0" - } - }, - "react-toggle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.1.1.tgz", - "integrity": "sha512-+wXlMcSpg8SmnIXauMaZiKpR+r2wp2gMUteroejp2UTSqGTVvZLN+m9EhMzFARBKEw7KpQOwzCyfzeHeAndQGw==", - "requires": { - "classnames": "^2.2.5" - } - }, - "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==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "requires": { - "picomatch": "^2.2.1" - } - }, - "reading-time": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.2.0.tgz", - "integrity": "sha512-5b4XmKK4MEss63y0Lw0vn0Zn6G5kiHP88mUnD8UeEsyORj3sh1ghTH0/u6m1Ax9G2F4wUZrknlp6WlIsCvoXVA==" - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "requires": { - "resolve": "^1.1.6" - } - }, - "recursive-readdir": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", - "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", - "requires": { - "minimatch": "3.0.4" - } - }, - "regenerate": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", - "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==" - }, - "regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "requires": { - "regenerate": "^1.4.0" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - }, - "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } - } - }, - "regexpu-core": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", - "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, - "registry-auth-token": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz", - "integrity": "sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==", - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "requires": { - "rc": "^1.2.8" - } - }, - "regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" - }, - "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" - } - } - }, - "rehype-parse": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-6.0.2.tgz", - "integrity": "sha512-0S3CpvpTAgGmnz8kiCyFLGuW5yA4OQhyNTm/nwPopZ7+PI11WnGl1TTWTGv/2hPEe/g2jRLlhVVSsoDH8waRug==", - "requires": { - "hast-util-from-parse5": "^5.0.0", - "parse5": "^5.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "hast-util-from-parse5": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz", - "integrity": "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==", - "requires": { - "ccount": "^1.0.3", - "hastscript": "^5.0.0", - "property-information": "^5.0.0", - "web-namespaces": "^1.1.2", - "xtend": "^4.0.1" - } - }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" - } - } - }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" - }, - "remark-admonitions": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/remark-admonitions/-/remark-admonitions-1.2.1.tgz", - "integrity": "sha512-Ji6p68VDvD+H1oS95Fdx9Ar5WA2wcDA4kwrrhVU7fGctC6+d3uiMICu7w7/2Xld+lnU7/gi+432+rRbup5S8ow==", - "requires": { - "rehype-parse": "^6.0.2", - "unified": "^8.4.2", - "unist-util-visit": "^2.0.1" - }, - "dependencies": { - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" - }, - "unified": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-8.4.2.tgz", - "integrity": "sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA==", - "requires": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - } - } - } - }, - "remark-emoji": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-2.1.0.tgz", - "integrity": "sha512-lDddGsxXURV01WS9WAiS9rO/cedO1pvr9tahtLhr6qCGFhHG4yZSJW3Ha4Nw9Uk1hLNmUBtPC0+m45Ms+xEitg==", - "requires": { - "emoticon": "^3.2.0", - "node-emoji": "^1.10.0", - "unist-util-visit": "^2.0.2" - } - }, - "remark-footnotes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/remark-footnotes/-/remark-footnotes-2.0.0.tgz", - "integrity": "sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ==" - }, - "remark-mdx": { - "version": "1.6.19", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.6.19.tgz", - "integrity": "sha512-UKK1CFatVPNhgjsIlNQ3GjVl3+6O7x7Hag6oyntFTg8s7sgq+rhWaSfM/6lW5UWU6hzkj520KYBuBlsaSriGtA==", - "requires": { - "@babel/core": "7.11.6", - "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-proposal-object-rest-spread": "7.11.0", - "@babel/plugin-syntax-jsx": "7.10.4", - "@mdx-js/util": "1.6.19", - "is-alphabetical": "1.0.4", - "remark-parse": "8.0.3", - "unified": "9.2.0" - }, - "dependencies": { - "@babel/core": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", - "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.6", - "@babel/helper-module-transforms": "^7.11.0", - "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.11.5", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.11.5", - "@babel/types": "^7.11.5", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", - "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.4" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz", - "integrity": "sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "remark-parse": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz", - "integrity": "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==", - "requires": { - "ccount": "^1.0.0", - "collapse-white-space": "^1.0.2", - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "is-word-character": "^1.0.0", - "markdown-escapes": "^1.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "trim": "0.0.1", - "trim-trailing-lines": "^1.0.0", - "unherit": "^1.0.4", - "unist-util-remove-position": "^2.0.0", - "vfile-location": "^3.0.0", - "xtend": "^4.0.1" - } - }, - "remark-squeeze-paragraphs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz", - "integrity": "sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw==", - "requires": { - "mdast-squeeze-paragraphs": "^4.0.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "renderkid": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.4.tgz", - "integrity": "sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g==", - "requires": { - "css-select": "^1.1.0", - "dom-converter": "^0.2", - "htmlparser2": "^3.3.0", - "lodash": "^4.17.20", - "strip-ansi": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "require-like": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=" - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" - }, - "resolve": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", - "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", - "requires": { - "is-core-module": "^2.0.0", - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "requires": { - "resolve-from": "^3.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - } - } - }, - "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==" - }, - "resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "requires": { - "lowercase-keys": "^1.0.0" - } - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "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": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" - }, - "rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" - }, - "rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "robust-predicates": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", - "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" - }, - "run-parallel": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", - "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==" - }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "requires": { - "aproba": "^1.1.1" - } - }, - "rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" - }, - "rx": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", - "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" - }, - "rxjs": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", - "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - } - }, - "section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "requires": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - } - }, - "seekout": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/seekout/-/seekout-1.0.2.tgz", - "integrity": "sha1-CbqfG9W0b7sTRxjrGaaDgsuxuck=", - "dev": true - }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" - }, - "selfsigned": { - "version": "1.10.8", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", - "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", - "requires": { - "node-forge": "^0.10.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "requires": { - "semver": "^6.3.0" - } - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-handler": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", - "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", - "requires": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", - "mime-types": "2.1.18", - "minimatch": "3.0.4", - "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", - "range-parser": "1.2.0" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "~1.33.0" - } - }, - "path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" - } - } - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - } - } - }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shallow-clone": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", - "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", - "requires": { - "is-extendable": "^0.1.1", - "kind-of": "^2.0.1", - "lazy-cache": "^0.2.3", - "mixin-object": "^2.0.1" - }, - "dependencies": { - "kind-of": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", - "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", - "requires": { - "is-buffer": "^1.0.2" - } - }, - "lazy-cache": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", - "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=" - } - } - }, - "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==", - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" - }, - "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==" - }, - "shelljs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", - "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" - }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - } - } - }, - "sitemap": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-3.2.2.tgz", - "integrity": "sha512-TModL/WU4m2q/mQcrDgNANn0P4LwprM9MMvG4hu5zP4c6IIKs2YLTu6nXXnNr8ODW/WFtxKggiJ1EGn2W0GNmg==", - "requires": { - "lodash.chunk": "^4.2.0", - "lodash.padstart": "^4.6.1", - "whatwg-url": "^7.0.0", - "xmlbuilder": "^13.0.0" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "sockjs": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", - "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==", - "requires": { - "faye-websocket": "^0.10.0", - "uuid": "^3.4.0", - "websocket-driver": "0.6.5" - } - }, - "sockjs-client": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", - "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", - "requires": { - "debug": "^3.2.5", - "eventsource": "^1.0.7", - "faye-websocket": "~0.11.1", - "inherits": "^2.0.3", - "json3": "^3.3.2", - "url-parse": "^1.4.3" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "requires": { - "websocket-driver": ">=0.5.1" - } - } - } - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "requires": { - "is-plain-obj": "^1.0.0" - } - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" - }, - "space-separated-tokens": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", - "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==" - }, - "spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - } - }, - "spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "requires": { - "extend-shallow": "^3.0.0" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", - "requires": { - "minipass": "^3.1.1" - } - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "state-toggle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", - "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==" - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "std-env": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-2.2.1.tgz", - "integrity": "sha512-IjYQUinA3lg5re/YMlwlfhqNRTzMZMqE+pezevdcTaHceqx8ngEi1alX9nNCk9Sc81fy1fLDeQoaCzeiW1yBOQ==", - "requires": { - "ci-info": "^1.6.0" - } - }, - "stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "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==" - }, - "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==" - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "string.prototype.trimend": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", - "integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - } - }, - "string.prototype.trimstart": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz", - "integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "requires": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "dependencies": { - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" - } - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=" - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "style-to-object": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", - "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==", - "requires": { - "inline-style-parser": "0.1.1" - } - }, - "stylehacks": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "stylis": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", - "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" - }, - "svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==" - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - } - } - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" - }, - "tar": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", - "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - } - } - }, - "term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==" - }, - "terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "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==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "terser-webpack-plugin": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", - "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", - "requires": { - "cacache": "^15.0.5", - "find-cache-dir": "^3.3.1", - "jest-worker": "^26.5.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1", - "terser": "^5.3.4", - "webpack-sources": "^1.4.3" - }, - "dependencies": { - "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==" - }, - "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==" - }, - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "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==", - "requires": { - "semver": "^6.0.0" - } - }, - "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "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==" - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "requires": { - "find-up": "^4.0.0" - } - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" - }, - "timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "requires": { - "setimmediate": "^1.0.4" - } - }, - "timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" - }, - "tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "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==", - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } - } - }, - "trim": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", - "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" - }, - "trim-trailing-lines": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", - "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==" - }, - "trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==" - }, - "tryer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", - "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" - }, - "ts-node": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", - "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - } - }, - "ts-pnp": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", - "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==" - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "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==", - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", - "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", - "dev": true - }, - "ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==" - }, - "unherit": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", - "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", - "requires": { - "inherits": "^2.0.0", - "xtend": "^4.0.0" - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" - }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" - }, - "unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" - }, - "unified": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", - "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==", - "requires": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" - } - } - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" - }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "requires": { - "imurmurhash": "^0.1.4" - } - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "requires": { - "crypto-random-string": "^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.5", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.5.tgz", - "integrity": "sha512-1TC+NxQa4N9pNdayCYA1EGUOCAO0Le3fVp7Jzns6lnua/mYgwHo0tz5WUAfrdpNch1RZLHc61VZ1SDgrtNXLSw==" - }, - "unist-util-is": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", - "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" - }, - "unist-util-position": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz", - "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==" - }, - "unist-util-remove": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-2.0.0.tgz", - "integrity": "sha512-HwwWyNHKkeg/eXRnE11IpzY8JT55JNM1YCwwU9YNCnfzk6s8GhPXrVBBZWiwLeATJbI7euvoGSzcy9M29UeW3g==", - "requires": { - "unist-util-is": "^4.0.0" - } - }, - "unist-util-remove-position": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz", - "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==", - "requires": { - "unist-util-visit": "^2.0.0" - } - }, - "unist-util-stringify-position": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", - "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", - "requires": { - "@types/unist": "^2.0.2" - } - }, - "unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", - "requires": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - } - }, - "unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", - "requires": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" - } - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" - }, - "update-notifier": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", - "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", - "requires": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - } - }, - "uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", - "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "url-loader": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", - "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", - "requires": { - "loader-utils": "^2.0.0", - "mime-types": "^2.1.27", - "schema-utils": "^3.0.0" - }, - "dependencies": { - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "requires": { - "prepend-http": "^2.0.0" - }, - "dependencies": { - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" - } - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" - }, - "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } - } - }, - "utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=" - }, - "utility-types": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", - "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==" - }, - "vfile": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.0.tgz", - "integrity": "sha512-a/alcwCvtuc8OX92rqqo7PflxiCgXRFjdyoGVuYV+qbgCb0GgZJRvIgCD4+U/Kl1yhaRsaTwksF88xbPyGsgpw==", - "requires": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "replace-ext": "1.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-message": "^2.0.0" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" - } - } - }, - "vfile-location": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.1.0.tgz", - "integrity": "sha512-FCZ4AN9xMcjFIG1oGmZKo61PjwJHRVA+0/tPUP2ul4uIwjGGndIxavEMRpWn5p4xwm/ZsdXp9YNygf1ZyE4x8g==" - }, - "vfile-message": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", - "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", - "requires": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" - } - }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" - }, - "wait-file": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/wait-file/-/wait-file-1.0.5.tgz", - "integrity": "sha512-udLpJY/eOxlrMm3+XD1RLuF2oT9B7J7wiyR5/9xrvQymS6YR6trWvVhzOldHrVbLwyiRmLj9fcvsjzpSXeZHkw==", - "requires": { - "@hapi/joi": "^15.1.0", - "fs-extra": "^8.1.0", - "rx": "^4.1.0" - }, - "dependencies": { - "@hapi/address": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", - "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" - }, - "@hapi/hoek": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", - "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" - }, - "@hapi/joi": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz", - "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", - "requires": { - "@hapi/address": "2.x.x", - "@hapi/bourne": "1.x.x", - "@hapi/hoek": "8.x.x", - "@hapi/topo": "3.x.x" - } - }, - "@hapi/topo": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", - "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", - "requires": { - "@hapi/hoek": "^8.3.0" - } - } - } - }, - "watchpack": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", - "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", - "requires": { - "chokidar": "^3.4.1", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0", - "watchpack-chokidar2": "^2.0.0" - } - }, - "watchpack-chokidar2": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", - "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", - "optional": true, - "requires": { - "chokidar": "^2.1.8" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "optional": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "optional": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "optional": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "optional": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "optional": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "optional": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "optional": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "optional": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "optional": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "optional": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "requires": { - "minimalistic-assert": "^1.0.0" - } - }, - "web-namespaces": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz", - "integrity": "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==" - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" - }, - "webpack": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", - "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.3.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "requires": { - "randombytes": "^2.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", - "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - } - }, - "webpack-bundle-analyzer": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz", - "integrity": "sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==", - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1", - "bfj": "^6.1.1", - "chalk": "^2.4.1", - "commander": "^2.18.0", - "ejs": "^2.6.1", - "express": "^4.16.3", - "filesize": "^3.6.1", - "gzip-size": "^5.0.0", - "lodash": "^4.17.19", - "mkdirp": "^0.5.1", - "opener": "^1.5.1", - "ws": "^6.0.0" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "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==" - }, - "filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==" - } - } - }, - "webpack-dev-middleware": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", - "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", - "requires": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - }, - "dependencies": { - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" - } - } - }, - "webpack-dev-server": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz", - "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==", - "requires": { - "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.1.8", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.3.1", - "http-proxy-middleware": "0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "is-absolute-url": "^3.0.3", - "killable": "^1.0.1", - "loglevel": "^1.6.8", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.26", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.7", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "0.3.20", - "sockjs-client": "1.4.0", - "spdy": "^4.0.2", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.2", - "webpack-log": "^2.0.0", - "ws": "^6.2.1", - "yargs": "^13.3.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "del": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", - "requires": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "pify": "^4.0.1", - "rimraf": "^2.6.3" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, - "is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==" - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "requires": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - } - }, - "webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "requires": { - "lodash": "^4.17.15" - } - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "webpackbar": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-4.0.0.tgz", - "integrity": "sha512-k1qRoSL/3BVuINzngj09nIwreD8wxV4grcuhHTD8VJgUbGcy8lQSPqv+bM00B7F+PffwIsQ8ISd4mIwRbr23eQ==", - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^2.4.2", - "consola": "^2.10.0", - "figures": "^3.0.0", - "pretty-time": "^1.1.0", - "std-env": "^2.2.1", - "text-table": "^0.2.0", - "wrap-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "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==", - "requires": { - "color-name": "~1.1.4" - } - }, - "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==" - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - } - } - } - } - }, - "websocket-driver": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", - "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", - "requires": { - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" - }, - "whatwg-fetch": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz", - "integrity": "sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ==" - }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "requires": { - "string-width": "^4.0.0" - } - }, - "worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "requires": { - "errno": "~0.1.7" - } - }, - "worker-rpc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", - "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", - "requires": { - "microevent.ts": "~0.1.1" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" - }, - "xml-js": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", - "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "requires": { - "sax": "^1.2.4" - } - }, - "xmlbuilder": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", - "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yaml": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", - "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==" - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - } - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "zwitch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", - "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==" - } - } -} diff --git a/about/package.json b/about/package.json deleted file mode 100644 index c5e4bc58791..00000000000 --- a/about/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "supabase", - "version": "0.0.3", - "private": true, - "scripts": { - "start": "docusaurus start --port 3005", - "build": "docusaurus build" - }, - "dependencies": { - "@docusaurus/core": "^2.0.0-alpha.63", - "@docusaurus/preset-classic": "^2.0.0-alpha.63", - "@kiwicopple/prism-react-renderer": "github:kiwicopple/prism-react-renderer", - "mermaid": "^8.13.8", - "react": "^16.8.4", - "react-dom": "^16.8.4", - "react-inlinesvg": "^2.0.1" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "devDependencies": { - "@types/node": "^14.14.14", - "axios": "^0.21.1", - "babel-plugin-css-modules-transform": "^1.6.2", - "clsx": "^1.1.1", - "dotenv": "^10.0.0", - "js-yaml": "^3.14.0", - "minimist": "^1.2.5", - "ts-node": "^9.0.0", - "typescript": "^4.1.3" - } -} diff --git a/about/sidebars.js b/about/sidebars.js deleted file mode 100755 index 26d93d0e8be..00000000000 --- a/about/sidebars.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - docs: [ - { - type: 'category', - label: 'Introduction', - items: ['handbook'], - collapsed: false, - }, - { - type: 'category', - label: 'See Also', - collapsed: false, - items: ['handbook/contributing', 'handbook/supasquad'], - }, - ], -} diff --git a/about/src/css/custom.css b/about/src/css/custom.css deleted file mode 100755 index 0fc6287b057..00000000000 --- a/about/src/css/custom.css +++ /dev/null @@ -1,1083 +0,0 @@ -/* - Font import -*/ - -@import url('https://rsms.me/inter/inter.css'); - -/* mono font */ - -@font-face { - font-family: 'office code pro'; - src: url('/fonts/office-code-pro/OfficeCodePro-Regular.eot'); - src: url('/fonts/office-code-pro/OfficeCodePro-Regular.woff2') format('woff2'), - url('/fonts/office-code-pro/OfficeCodePro-Regular.woff') format('woff'), - url('/fonts/office-code-pro/OfficeCodePro-Regular.ttf') format('truetype'), - url('/fonts/office-code-pro/OfficeCodePro-Regular.otf') format('otf'); - font-weight: normal; - font-style: normal; - font-display: swap; -} - -/** - * Root variables - * - * Override the default Infima variables here. These values cascade down to - * the rest of the elements. - */ - -:root, -:root[data-theme=''], -:root[data-theme='dark'] { - /* See theme-specific "--custom" vars below */ - - /* - Customization here for both Light and Dark themes - */ - --custom-font-base: 'Inter', BlinkMacSystemFont, -apple-system, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Helvetica', 'Arial', - sans-serif; - --custom-font-code: 'office code pro', Menlo, monospace; - --custom-font-size-base: 1rem; - --custom-border-radius: 3px; - --custom-border-radius-md: 6px; - --custom-primary-darkest: #10633e; - --custom-primary-darker: #1c8656; - --custom-primary-dark: #38bc81; - --custom-primary: #24b47e; - --custom-primary-light: #65d9a5; - --custom-primary-lighter: #9fe7c7; - --custom-primary-lightest: #c5f1dd; - --custom-background-color: #1f1f1f; - --custom-background-color-highlight: #fff; - --custom-background-color-diff: #f5f6f7; - --custom-background-color-diff-1: #eee; - --custom-background-color-diff-2: #ccc; - --custom-content-color: #1f1f1f; - --custom-content-color-light: #484848; - --custom-content-color-lightest: #333; - --custom-content-color-emphasis: #000; - --custom-border-color: #e0e0e0; - --custom-color-subtle: #ccc; - --custom-color-orange-100: #fdf0ea; - --custom-color-orange-200: #fbe2d5; - --custom-color-orange-300: #f9d3c0; - --custom-color-orange-400: #f5b796; - --custom-color-orange-500: #f08b57; - --custom-color-orange-600: #ec6e2d; - --custom-color-orange-700: #d75614; - --custom-color-orange-800: #a93c04; - --custom-shadow-lw: 0 3px 5px 0px rgba(0, 0, 0, 0.1); - --custom-shadow-md: 0 3px 5px 0px rgba(0, 0, 0, 0.1); - --custom-shadow-tl: 0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1); - --custom-shadow-xl: 0 30px 60px 0 rgba(0, 0, 0, 0.1); - - /* - Infirma overrides with customization - */ - /* Colors */ - --ifm-color-content: var(--custom-content-color); - --ifm-heading-color: var(--custom-content-color); - --ifm-background-color: var(--custom-background-color); - --ifm-sidebar-background: var(--custom-background-color); - --ifm-color-content-secondary: var(--custom-content-color-light); - --ifm-code-background: var(--custom-background-color-diff); - --ifm-code-background-dark: #2a2a2a; - --ifm-code-color: var(--custom-content-color); - --ifm-color-warning: var(--custom-color-orange-600); - - /* Primary */ - --ifm-color-primary-darkest: var(--custom-primary-darkest); - --ifm-color-primary-darker: var(--custom-primary-darker); - --ifm-color-primary-dark: var(--custom-primary-dark); - --ifm-color-primary: var(--custom-primary); - --ifm-color-primary-light: var(--custom-primary-light); - --ifm-color-primary-lighter: var(--custom-primary-lighter); - --ifm-color-primary-lightest: var(--custom-primary-lightest); - - /* Info */ - --ifm-color-info-darker: var(--custom-background-color-diff-2); - --ifm-color-info-dark: var(--custom-background-color-diff-1); - --ifm-color-info: var(--custom-background-color-diff); - - /* Fonts */ - --ifm-font-color-base: var(--custom-content-color); - --ifm-font-family-base: var(--custom-font-base); - --ifm-font-family-monospace: var(--custom-font-code); - --ifm-heading-font-family: var(--custom-font-base); - --ifm-font-size-base: var(--custom-font-size-base); - --ifm-heading-font-weight: 500; - --ifm-font-weight-bold: 500; - - /* Globals */ - --ifm-global-shadow-lw: var(--custom-shadow-lw); - --ifm-global-shadow-md: var(--custom-shadow-md); - --ifm-global-shadow-tl: var(--custom-shadow-tl); - --ifm-contents-border-color: var(--custom-border-color); - --ifm-hr-border-color: var(--custom-border-color); - --ifm-code-border-radius: var(--custom-border-radius); - --ifm-button-border-radius: var(--custom-border-radius); - --ifm-col-spacing-vertical: 0.5rem; - --ifm-link-hover-decoration: none; - - /* Buttons */ - --ifm-button-background-color: var(--custom-background-color); - /* --ifm-button-color: var(--custom-content-color); Not great - changes the green buttons to have black text */ - - /* Navbar */ - --ifm-navbar-background-color: var(--custom-background-color); - - /* Footer */ - --ifm-footer-background-color: var(--custom-background-color); - - /* Hero */ - --ifm-hero-background-color: var(--custom-background-color); - --ifm-hero-text-color: var(--custom-content-color); - - /* Sidebar */ - --ifm-sidebar-border-color: var(--custom-border-color); - --ifm-menu-color-active: var(--custom-primary); - - /* Panel */ - --ifm-panel-border-color: var(--custom-border-color); - --ifm-panel-border-color-active: var(--custom-primary); - --ifm-panel-background-solid: var(--custom-background-color); - - /* Card */ - --ifm-card-background-color: var(--custom-background-color); - --ifm-card-border-radius: var(--custom-border-radius-md); - - /* Tables */ - --ifm-table-border-color: var(--custom-border-color); - --ifm-table-stripe-background: var(--custom-background-color-diff); - - /* Alerts */ - --ifm-alert-color: var(--custom-color-orange-600); -} - -:root[data-theme='light'] { - --custom-background-color: #fdfdfd; -} - -:root[data-theme='dark'] { - --custom-primary-darkest: #10633e; - --custom-primary-darker: #1c8656; - --custom-primary-dark: #38bc81; - --custom-primary: #3ecf8e; - --custom-primary-light: #65d9a5; - --custom-primary-lighter: #9fe7c7; - --custom-primary-lightest: #c5f1dd; - --custom-color-subtle: #484848; - - --custom-background-color: #1f1f1f; - --custom-background-color-highlight: #2a2a2a; - --custom-background-color-diff: #2a2a2a; - --custom-background-color-diff-1: #484848; - --custom-background-color-diff-2: #666; - --custom-content-color: #ddd; - --custom-content-color-light: #aaa; - --custom-content-color-lightest: #fefefe; - --custom-content-color-emphasis: #fff; - --custom-border-color: #333333; - --custom-shadow-lw: 0 0 3px 3px rgba(0, 0, 0, 0.2); - --custom-shadow-md: 0 0 5px 5px rgba(0, 0, 0, 0.3); - --custom-shadow-tl: 0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1); - --custom-shadow-xl: 0 30px 60px 0 rgba(0, 0, 0, 0.5); - - --ifm-color-gray-100: rgba(224, 235, 247, 0.9); - --ifm-color-gray-150: rgba(224, 235, 247, 0.85); - --ifm-color-gray-200: rgba(224, 235, 247, 0.8); - --ifm-color-gray-300: rgba(224, 235, 247, 0.75); - --ifm-color-gray-400: rgba(224, 235, 247, 0.7); - --ifm-color-gray-500: rgba(224, 235, 247, 0.65); - --ifm-color-gray-600: rgba(224, 235, 247, 0.6); - --ifm-color-gray-700: rgba(224, 235, 247, 0.55); - --ifm-color-gray-800: rgba(224, 235, 247, 0.5); - --ifm-color-gray-900: rgba(224, 235, 247, 0.45); - --ifm-color-secondary-darker: #000; - --ifm-color-secondary-dark: #1f1f1f; - --ifm-color-secondary: #2a2a2a; - --ifm-color-secondary-light: #2f2f2f; - --ifm-color-secondary-lighter: #313131; - --ifm-color-secondary-lightest: #333333; - - /* Button */ - --ifm-button-background-color: var(--custom-background-color-highlight); - - /* Card */ - --ifm-card-background-color: var(--custom-background-color-highlight); -} - -html, -body { - font-smoothing: antialiased; - scroll-behavior: smooth; - text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - -webkit-text-size-adjust: 100%; - - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -:root[data-theme='light'] .navbar__logo.logo--dark, -:root[data-theme=''] .navbar__logo.logo--dark, -:root .navbar__logo.logo--dark { - display: none; -} -:root[data-theme='light'] .navbar__logo.logo--light, -:root[data-theme=''] .navbar__logo.logo--light, -:root .navbar__logo.logo--light { - display: inline-block; -} -:root[data-theme='dark'] .navbar__logo.logo--light { - display: none; -} -:root[data-theme='dark'] .navbar__logo.logo--dark { - display: inline-block; -} - -h4, -h5, -h6 { - font-weight: bold !important; -} - -.button { - font-family: var(--custom-font-base); -} -.button.button--primary { - color: white; - text-shadow: 0px 0px 6px rgba(13, 128, 86, 0.8); -} -.button.button--secondary { - color: var(--custom-content-color); - /* background-color: var(--custom-background-color); */ -} -.button.button--secondary:hover { - background-color: var(--custom-background-color-diff-1); -} -:root[data-theme='dark'] .button.button--secondary:hover { - background-color: var(--custom-background-color-highlight); -} -.button.button--secondary.button--outline { - border-color: #333; -} -:root[data-theme='dark'] .button.button--secondary { - /* background-color: var(--custom-background-color-highlight); */ - border-color: #fff; -} -.hero--button { - text-transform: uppercase; - padding: 10px 25px; - font-weight: bold; -} -blockquote, -blockquote p { - color: var(--custom-content-color); -} - -div[class^='announcementBar'], -div[class*=' announcementBar'] { - padding-bottom: 5px; -} -div[class^='announcementBar'] button span, -div[class*=' announcementBar'] button span { - color: #fff !important; - padding-bottom: 5px; - display: inline-block; -} -.docusaurus-highlight-code-line { - background-color: rgb(72, 77, 91); - display: block; - margin: 0 calc(-1 * var(--ifm-pre-padding)); - padding: 0 var(--ifm-pre-padding); -} - -@media screen and (min-width: 768px) { - html, - body { - text-rendering: optimizeLegibility; - } -} - -li + li { - margin-top: 0; -} - -.button--lg { - text-transform: uppercase; -} - -.title { - font-weight: bold; - color: var(--custom-content-color-lightest) !important; -} - -.alert.alert--warning { - color: var(--custom-color-orange-800); - border-color: var(--custom-color-orange-800); - background-color: var(--custom-color-orange-200); - border-color: var(--custom-color-orange-400); - margin: 20px 0; - font-size: 0.9rem; -} - -.navbar__brand strong { - font-family: var(--ifm-heading-font-family); - font-size: 1.5rem; - /* font-weight: 700; */ - letter-spacing: 0.03rem; -} - -.navbar, -.main-wrapper { - box-shadow: none; - border-bottom: 1px solid var(--custom-border-color); -} - -.with-underline::after { - content: '\2501\2501'; - display: block; - color: var(--ifm-color-primary); -} -article header { - margin-bottom: 3rem; -} - -.hero.is--dark { - background-color: var(--custom-background-color-diff); -} - -/* TOC */ - -.table-of-contents { - border-left-color: var(--custom-border-color); -} - -/* pagination */ - -.pagination-nav__link { - border-color: var(--custom-border-color); -} - -/* Sidebar */ - -.navbar__logo { - max-width: 196px; -} - -@media (max-width: 750px) { - .DocSearch-Button-Key, - .DocSearch-Button-KeySeparator, - .DocSearch-Button-Placeholder { - display: block !important; - font-size: 0.8rem !important; - margin-top: 0 !important; - } - - .navbar__logo { - max-width: 128px; - } -} - -@media (max-width: 360px) { - .DocSearch { - display: none !important; - } -} - -@media (max-width: 750px) { - .navbar-sidebar__items { - padding-left: 1rem; - /* margin-right: 6rem !important; */ - } -} - -@media (max-width: 997px) { - .menu--responsive { - padding-right: 2rem !important; - } - .menu__link--active.active { - border: none !important; - } -} - -.navbar__toggle svg { - width: 21px; -} - -.navbar-sidebar__brand .navbar__brand { - flex: 1 0; -} -.navbar-sidebar__brand .react-toggle { - flex: 0; - text-align: right; -} -.navbar-sidebar__items .menu .menu__link { - color: var(--ifm-color-content); - display: block; -} -.navbar-sidebar__items .menu .menu__link .badge { - border-color: var(--ifm-background-color); - border-radius: 1em; - font-size: 0.6em; - margin-top: -1em; - margin-left: 0.25em; -} -.navbar-sidebar__items .menu .menu__link.menu__link--sublist::after { - display: none; -} -.navbar-sidebar__items .menu .menu__list-item .menu__list-item a.menu__link { - margin-left: 30px; -} - -@media screen and (max-width: 996px) { - .row .col { - padding-top: var(--ifm-col-spacing-vertical); - padding-bottom: var(--ifm-col-spacing-vertical); - } -} -a.card { - text-decoration: none !important; - color: var(--ifm-color-content); - box-shadow: none; - border: 1px solid #333; - display: flex; - /* background-color: var(--ifm-background-color); */ -} -a.card .card__body { - flex: 1; - flex-grow: 1; -} -a.card:hover { - transition: all 0.2s ease; - border: 1px solid var(--custom-primary); - box-shadow: var(--custom-shadow-xl), 0 0 0 1px var(--custom-primary) !important; -} -.avatar__intro { - padding-left: 15px; -} - -/* - * Docs - */ - -.menu.menu--responsive { - padding: 0; -} -@media screen and (max-width: 996px) { - .menu.menu--responsive { - padding-left: 24px; - } -} -.menu > ul.menu__list li { - margin-top: 0; - margin-bottom: 0; -} -.menu > ul.menu__list li > a { - padding-left: 0; - padding-right: 0; - /* color: var(--custom-content-color); */ -} -.menu .menu__list-item-hidden { - display: none; -} - -/* Category headers */ - -/* First sidebar nav section */ -.menu > .menu__list > .menu__list-item:first-child { - margin-top: 16px; -} - -/* First sidebar nav section */ -.menu > .menu__list > .menu__list-item > .menu__link::after { - display: none; -} - -/* All sidebar nav sections */ -.menu > .menu__list > .menu__list-item { - margin-bottom: 16px; -} - -/* Sidebar nav header */ -.menu > .menu__list > .menu__list-item > .menu__link { - margin-bottom: 4px; - color: gray; - font-size: 11px; - font-weight: 600; - letter-spacing: 1px; - text-transform: uppercase; -} - -/* Disable click events for large screens */ -@media (min-width: 997px) { - .menu > .menu__list > .menu__list-item > .menu__link { - pointer-events: none; - } -} - -.menu > ul.menu__list > li.menu__list-item:first-child div.title { - margin-top: 0; -} -.menu > ul.menu__list ul.menu__list { - border-left: 1px solid var(--custom-border-color); - margin-left: 0; - padding-left: var(--ifm-menu-link-padding-horizontal); -} -.menu > ul.menu__list ul.menu__list .menu__link { - border-radius: 0; - font-size: 0.85rem; - margin-left: calc(var(--ifm-menu-link-padding-horizontal) * -1); - padding-left: var(--ifm-menu-link-padding-horizontal); -} -.menu > ul.menu__list > li > ul.menu__list { - border-left: 0px none; - padding-left: 0; -} -.menu .menu__link, -.menu .menu__link:hover, -.menu .menu__link.menu__link--active:not(.menu__link--sublist) { - background: none; - line-height: calc(var(--ifm-spacing-vertical) * 1); -} - -/* - Link chevrons -*/ -.menu .menu__link.menu__link--sublist::after { - background-image: url('data:image/svg+xml;utf8,'); - mask-image: url('data:image/svg+xml;utf8,'); - -webkit-mask-size: cover; - width: 21px; - mask-size: cover; - background: var(--ifm-color-gray-900); - -webkit-transform: rotate(180deg); - transform: rotate(180deg); - transition: var(--ifm-transition-fast) ease; -} -.menu .menu__link.menu__link--sublist:hover::after, -.menu .menu__link.menu__link--sublist.menu__link--active::after { - background-image: url('data:image/svg+xml;utf8,'); - mask-image: url('data:image/svg+xml;utf8,'); - -webkit-mask-size: cover; - width: 21px; - mask-size: cover; - background: var(--ifm-color-gray-900); -} - -.menu .menu__list-item.menu__list-item--collapsed .menu__link--sublist::after { - -webkit-transform: rotate(90deg); - transform: rotate(90deg); -} -.menu .menu__link:hover { - color: var(--ifm-menu-color-active); -} -.menu .menu__link .badges { - vertical-align: top; - margin-top: -1px; -} -.menu .menu__link .badge { - background-clip: padding-box; - border: 1px solid var(--custom-content-color-light); - border-radius: 0.25em; - display: inline-block; - font-family: monospace; - font-size: 0.5rem; - font-weight: bold; - height: 1rem; - line-height: 1rem; - margin-right: 2px; - padding: 0 0.25rem; - vertical-align: middle; -} - -/* Make the Docs page take up full height to fix the left sidebar */ -div.container.padding-vert--lg .row { - min-height: 100vh; -} -/* Disable full height for child rows */ -div.container.padding-vert--lg .row .row { - min-height: auto; -} -div[class^='docItemContainer_'] { - max-width: none; -} -@media (min-width: 997px) { - div[class^='docItemContainer_'] { - max-width: none; - padding: 0 calc(var(--ifm-spacing-horizontal) * 2); - } -} - -main[class^='docMainContainer_'] div.container div.row div.col.col--3 { - padding-left: 0; -} - -div[class^='docSidebarContainer_'] { - border-right-color: var(--ifm-sidebar-border-color); - max-width: 250px; - min-width: 250px; -} - -div[class^='sidebar_'] { - font-size: 0.85rem; -} -div[class^='sidebar_'] .menu { - background: var(--ifm-sidebar-background); - margin-right: calc(var(--ifm-spacing-horizontal) * -1); - padding-bottom: var(--ifm-spacing-vertical); -} - -div[class^='sidebar_'] .menu { - margin-right: calc(var(--ifm-spacing-horizontal) * -1); -} -div[class^='sidebar_'] .menu .menu__link { - padding-right: var(--ifm-menu-link-padding-horizontal); - color: var(--custom-color-content); -} -div[class^='sidebar_'] .menu__link.menu__link--active:not(.menu__link--sublist) { - border-radius: 0; - border-right: 2px solid var(--ifm-color-primary); - padding-right: calc(var(--ifm-menu-link-padding-horizontal) - 2px); - color: var(--ifm-color-primary); -} -div[class^='sidebar_'] .menu__link.menu__link--active:not(.menu__link--sublist) .badge { - background: var(--ifm-color-primary); - border: 1px solid var(--ifm-color-primary); - color: white; -} -@media (min-width: 997px) { - div[class^='sidebar_'] { - padding-right: var(--ifm-spacing-horizontal); - padding-left: var(--ifm-spacing-horizontal); - } -} - -div[class^='codeBlockContent'], -div[class*=' codeBlockContent'], -.prism-code { - background-color: var(--ifm-code-background-dark) !important; -} -.code-with-header { - border-radius: 4px; - overflow: hidden; -} -.prism-code { - padding: 0 !important; - margin-bottom: 0; - background-color: var(--ifm-code-background-dark); -} -.code-with-header pre { - border-radius: 0 0 4px 4px; -} -.code-with-header.code-with-response { - border-radius: 0; -} -.code-header { - background: var(--ifm-code-background-dark); - color: #ccc; - padding: 5px 15px; - border-radius: 4px 4px 0 0; - font-size: 0.8rem; - font-weight: bold; - /* text-transform: uppercase; */ - border-bottom: 1px solid #444; -} -div[class^='codeBlockTitle'], -div[class*=' codeBlockTitle'] { - color: #ccc !important; - font-size: 0.8rem !important; - border-color: #444; -} -.response-header { - border-radius: 0 0 4px 4px; -} - -/* HomePage */ -.ForDevelopers .row { - display: flex; - flex-direction: row-reverse; -} -.ForDevelopers .button { - text-align: left; -} -.ForDevelopers .button.button--link:hover { - text-decoration: none; - transition: all 0.2s ease; - opacity: 0.8; -} - -.react-toggle-thumb { - box-shadow: none !important; -} -a.hash-link { - text-decoration: none; - font-style: none; - color: var(--custom-color-subtle); -} - -.footer { - border-top: 1px solid var(--custom-border-color); - padding-bottom: 80px; -} - -.has-emphasis { - color: var(--custom-content-color-emphasis); -} - -.ButtonTabs { - padding-left: 0 !important; -} -.ButtonTabs .button { - width: 100%; - display: block; - color: var(--custom-content-color) !important; - margin-right: 10px; - margin-bottom: 8px; - border: 2px solid var(--ifm-button-background-color) !important; -} -.ButtonTabs .button.is-active, -.ButtonTabs .button:hover, -.ButtonTabs:hover .button.is-active:hover { - border: 2px solid var(--custom-primary) !important; - box-shadow: var(--custom-shadow-lw); -} -/* Disable the border on the active button when hovering across the buttons */ -.ButtonTabs:hover .button.is-active { - border: 2px solid var(--ifm-button-background-color) !important; -} -/* But */ -@media screen and (max-width: 997px) { - .ButtonTabs { - padding-left: var(--ifm-spacing-horizontal) !important; - } - .ButtonTabs > div { - display: flex !important; - flex-wrap: nowrap !important; - overflow-x: auto !important; - -webkit-overflow-scrolling: touch !important; - -ms-overflow-style: -ms-autohiding-scrollbar !important; - } - .ButtonTabs .button { - display: inline-block; - width: auto; - } -} - -@media screen and (max-width: 600px) { - .responsive-button { - width: 100%; - margin: 5px 0 !important; - } -} - -@media screen and (min-width: 996px) { - .row.is-multiline .col { - margin-top: var(--ifm-spacing-horizontal) !important; - margin-bottom: var(--ifm-spacing-horizontal) !important; - } -} - -.has-hover-pointer:hover { - cursor: pointer; -} -.section-md { - margin: 2rem 0; -} -.section-lg { - margin: 5rem 0; -} -.hidden { - display: none; -} - -.Blog .code-with-header { - margin-bottom: 30px; -} -.badge--secondary { - color: var(--ifm-color-content); -} - -.DocSearch-Button { - border-radius: 8px !important; -} -.DocSearch-Button > svg { - display: none; -} -.DocSearch-Button-Placeholder { - font-family: var(--ifm-font-family-base); - line-height: 10px; -} - -.prism-code { - background-color: none; - white-space: normal; - word-break: break-word; - font-size: 0.8rem; - outline: none !important; -} -.prism-code div, -.prism-code div:focus, -.prism-code div:active { - outline: none !important; -} -.token.keyword.module, -.token.property-access, -.token.function { - font-style: normal !important; - color: #ccc !important; -} -.token.plain, -.token.parameter, -.token.punctuation, -.token.operator, -.token.console.class-name, -.token.doc-comment.parameter, -.token.doc-comment.optional-parameter, -.token.doc-comment.parameter.punctuation, -.token.doc-comment.optional-parameter.punctuation { - font-style: normal !important; - color: #ccc !important; -} -.token.keyword, -.token.doc-comment.keyword { - font-style: normal !important; - color: #569cd6 !important; -} -.token.string { - font-style: normal !important; - color: #24b47e !important; -} -.token.console { - font-style: normal !important; - color: #49c1ad !important; -} -.token.constant { - font-style: normal !important; - color: #569cd6 !important; -} -.token.comment { - font-style: normal !important; - color: #666 !important; -} -.token.number { - color: #b5cea8 !important; -} -.token.boolean, -.token.doc-comment.class-name { - @apply text-green-500; -} - -html[data-theme='light'] .DocSearch, -html[data-theme='dark'] .DocSearch { - --docsearch-text-color: var(--custom-content-color); - --docsearch-muted-color: var(--custom-content-color-light); - --docsearch-modal-background: var(--custom-background-color-diff); -} - -.DocSearch-Button { - border-radius: 6px !important; - background: var(--custom-background-color-diff) !important; - font-family: var(--ifm-font-family-base); -} -.DocSearch-Button-Placeholder { - font-family: var(--ifm-font-family-base); - line-height: 10px; - color: var(--custom-content-color-light) !important; -} -.DocSearch-Button-Key { - background: var(--custom-background-color-diff-1) !important; - color: var(--custom-content-color-light) !important; - box-shadow: none !important; - box-sizing: initial; - margin-top: 3px; -} -.DocSearch-SearchBar form { - background: var(--custom-background-color-diff) !important; - color: var(--custom-content-color) !important; -} -.DocSearch-Footer { - display: none !important; -} - -.navbar__link { - /* same size as sidebar */ - font-size: 0.85rem; - /* font-weight: 400; */ -} - -.navbar-item-github { - display: flex !important; - align-items: center; - justify-items: center; - justify-content: end; -} - -@media screen and (max-width: 580px) { - .navbar__items .navbar-item-github { - display: none !important; - } -} - -.navbar-item-github:hover { - opacity: 0.6; -} -.navbar-item-github:before { - margin-right: 0.5rem; - content: ''; - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-items: center; - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: center; -} -html[data-theme='dark'] .navbar-item-github:before { - background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") - no-repeat; - background-repeat: no-repeat; - background-position: center; -} - -.navbar-item-twitter { - display: flex !important; - align-items: center; - justify-items: center; - justify-content: end; -} - -@media screen and (max-width: 580px) { - .navbar__items .navbar-item-twitter { - display: none !important; - } -} - -.navbar-item-twitter:hover { - opacity: 0.6; -} -.navbar-item-twitter:before { - margin-right: 0.5rem; - content: ''; - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-items: center; - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 335 276' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m302 70a195 195 0 0 1 -299 175 142 142 0 0 0 97 -30 70 70 0 0 1 -58 -47 70 70 0 0 0 31 -2 70 70 0 0 1 -57 -66 70 70 0 0 0 28 5 70 70 0 0 1 -18 -90 195 195 0 0 0 141 72 67 67 0 0 1 116 -62 117 117 0 0 0 43 -17 65 65 0 0 1 -31 38 117 117 0 0 0 39 -11 65 65 0 0 1 -32 35'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: center; -} -html[data-theme='dark'] .navbar-item-twitter:before { - background: url("data:image/svg+xml,%3Csvg viewBox='0 0 335 276' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='m302 70a195 195 0 0 1 -299 175 142 142 0 0 0 97 -30 70 70 0 0 1 -58 -47 70 70 0 0 0 31 -2 70 70 0 0 1 -57 -66 70 70 0 0 0 28 5 70 70 0 0 1 -18 -90 195 195 0 0 0 141 72 67 67 0 0 1 116 -62 117 117 0 0 0 43 -17 65 65 0 0 1 -31 38 117 117 0 0 0 39 -11 65 65 0 0 1 -32 35'/%3E%3C/svg%3E") - no-repeat; - background-repeat: no-repeat; - background-position: center; -} -.navbar__item .dropdown__menu li:not(:first-child) { - margin-top: 5px; -} - -/* New CSS */ - -code { - padding-top: 2px; - padding-bottom: 2px; - border: 1px solid var(--ifm-hr-border-color); -} - -.tabs { - margin: 0; - font-size: 0.7rem; - background-color: var(--ifm-code-background-dark); - border-top-left-radius: 5px; - border-top-right-radius: 5px; - border-bottom: 1px solid var(--custom-border-color); -} -.tabs li { - padding: 8px; - border-radius: 0; - border-width: 0px; - color: var(--ifm-color-gray-500); -} -.tabs li.tabs__item--active { - color: var(--custom-primary); -} -div[role='tabpanel'], -div[role='tabpanel'] div { - margin-top: 0 !important; - outline: none !important; - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; -} - -.method-list-group { - list-style: none; - padding: 0; - margin: 0; -} -.method-list-group li { - border-top: 1px solid var(--custom-border-color); - padding-top: 12px; -} -.method-list-group li:last-child { - border-bottom: 1px solid var(--custom-border-color); -} - -h4.method-list-item-label { - display: flex; -} -h4.method-list-item-label .method-list-item-label-name { - font-family: var(--ifm-font-family-monospace); - font-size: 0.8rem; - margin-right: 4px; - padding: var(--ifm-code-padding-vertical) 0; -} -h4.method-list-item-label .method-list-item-label-badge { - font-family: var(--ifm-font-family-monospace); - font-size: 0.8rem; - padding: 0 5px; - font-weight: 300; - padding: var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal); -} -h4.method-list-item-label .method-list-item-validation { - font-family: var(--ifm-font-family-monospace); - font-size: 0.8rem; - padding: 0 5px; - font-weight: 300; - display: flex; - flex-wrap: wrap; - gap: 6px; -} - -.method-list-group .method-list-group { - border: 1px solid var(--custom-border-color); - border-radius: 8px; - margin-bottom: 20px; - max-width: 600px; -} -.method-list-group .method-list-group li { - padding: 12px; - border-bottom: none; -} -.method-list-title { - margin: 0; - padding: 6px 12px; -} -.method-list-group .method-list-group li div, -.method-list-group .method-list-group li h4, -.method-list-group .method-list-group li p { - padding: 0; - margin: 0; -} -.method-list-group .method-list-group li h4 { - margin-bottom: 4px; -} - -.video-with-border { - border-radius: 5px; - overflow: hidden; - border: 2px solid var(--custom-border-color); -} diff --git a/about/src/pages/index.js b/about/src/pages/index.js deleted file mode 100755 index 474473ed03f..00000000000 --- a/about/src/pages/index.js +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useState } from 'react' -import clsx from 'clsx' -import Layout from '@theme/Layout' -import Link from '@docusaurus/Link' -import useDocusaurusContext from '@docusaurus/useDocusaurusContext' -import useBaseUrl from '@docusaurus/useBaseUrl' -import styles from './styles.module.css' - -export default function Home() { - const context = useDocusaurusContext() - const { siteConfig = {} } = context - - return ( - -
- {/* HEADER */} -
-
-
-
-

{siteConfig.tagline}

-

- Supabase is the open source Firebase alternative. -

-
- - Learn More - - - Join the team → - -
-
-
- {/* Example */} -
-
-
-
- - {/*
-
- Y Combinator -

- Backed by Y Combinator -

-
-
*/} - -
-
- ) -} \ No newline at end of file diff --git a/about/src/pages/styles.module.css b/about/src/pages/styles.module.css deleted file mode 100755 index ab23c39eabb..00000000000 --- a/about/src/pages/styles.module.css +++ /dev/null @@ -1,88 +0,0 @@ -/** - * CSS files with the .module.css suffix will be treated as CSS modules - * and scoped locally. - */ - -/* .heroBanner { - padding: 4rem 0; - text-align: center; - position: relative; - overflow: hidden; -} */ - -.heroBanner { - /* height: calc(100vh - 60px); */ - min-height: 80vh; -} -.hero .code-with-header { - box-shadow: 0 0 5px 5px rgba(0, 0, 0, 0.3); - /* box-shadow: var(--ifm-global-shadow-md); */ -} - -.heroBanner h2 { - font-size: 2rem !important; -} -.heroBanner p { - font-size: 1rem !important; -} -.heroImage { - max-height: 50vh; -} -.features { - display: flex; - margin-top: 5rem; - margin-bottom: 5rem; - align-items: center; - padding: 2rem 0; - width: 100%; -} -.forDevelopers { - display: flex; - margin-top: 5rem; - align-items: center; - padding: 2rem 0; - width: 100%; -} - -.forDevelopers .button { - margin-top: 10px; -} -.featureCard { - height: 100%; - margin-bottom: 10px; -} -.featureImage { - height: 50px; - width: 50px; -} - -.button { - margin: 25px 10px; -} - -.howCard h3 { - font-size: 2.5rem; - font-weight: 800; -} - -.pricingCard h3 { - font-size: 1.8rem; - margin-bottom: 20px; -} -.pricingCard p { - margin: 0; -} - -.responsiveCentered { - text-align: center; -} - -.centered { - text-align: center; -} - -@media screen and (max-width: 999px) { - .responsiveCentered { - text-align: left; - } -} diff --git a/about/static/_headers b/about/static/_headers deleted file mode 100644 index f1ccf365082..00000000000 --- a/about/static/_headers +++ /dev/null @@ -1,20 +0,0 @@ -/*.css - Cache-Control: public, max-age=10800 - -/*.js - Cache-Control: public, max-age=10800 - -/*.png - Cache-Control: public, max-age=10800 - -/*.jpg - Cache-Control: public, max-age=10800 - -/*.svg - Cache-Control: public, max-age=10800 - -/*.gif - Cache-Control: public, max-age=10800 - -/* - X-Frame-Options: DENY \ No newline at end of file diff --git a/about/static/css/metro.css b/about/static/css/metro.css deleted file mode 100644 index ec164b2f9b2..00000000000 --- a/about/static/css/metro.css +++ /dev/null @@ -1,32 +0,0 @@ -/* Body Font (Metro) */ -@font-face { - font-family: 'Metro'; - src: url('/fonts/metro/metro-sans-book.woff2') format('woff2'), - url('/fonts/metro/metro-sans-book.woff') format('woff'); - font-weight: 300; - font-style: normal; -} - -@font-face { - font-family: 'Metro'; - src: url('/fonts/metro/metro-sans-regular.woff2') format('woff2'), - url('/fonts/metro/metro-sans-regular.woff') format('woff'); - font-weight: 400; - font-style: normal; -} - -@font-face { - font-family: 'Metro'; - src: url('/fonts/metro/metro-sans-semi-bold.woff2') format('woff2'), - url('/fonts/metro/metro-sans-semi-bold.woff') format('woff'); - font-weight: 600; - font-style: normal; -} - -@font-face { - font-family: 'Metro'; - src: url('/fonts/metro/metro-sans-bold.woff2') format('woff2'), - url('/fonts/metro/metro-sans-bold.woff') format('woff'); - font-weight: 700; - font-style: normal; -} diff --git a/about/static/css/tt-norms.css b/about/static/css/tt-norms.css deleted file mode 100644 index 63f3caf8b4d..00000000000 --- a/about/static/css/tt-norms.css +++ /dev/null @@ -1,23 +0,0 @@ -@font-face { - font-family: 'TT Norms Pro'; - font-style: normal; - font-weight: 400; - src: url('/fonts/tt-norms-pro/tt-pro-regular-webfont.woff2') format('woff2'), - url('/fonts/tt-norms-pro/tt-pro-regular-webfont.woff') format('woff'); -} - -@font-face { - font-family: 'TT Norms Pro'; - font-style: normal; - font-weight: 500; - src: url('/fonts/tt-norms-pro/tt-pro-medium-webfont.woff2') format('woff2'), - url('/fonts/tt-norms-pro/tt-pro-medium-webfont.woff') format('woff'); -} - -@font-face { - font-family: 'TT Norms Pro'; - font-style: normal; - font-weight: 700; - src: url('/fonts/tt-norms-pro/tt-pro-bold-webfont.woff2') format('woff2'), - url('/fonts/tt-norms-pro/tt-pro-bold-webfont.woff') format('woff'); -} diff --git a/about/static/favicon.ico b/about/static/favicon.ico deleted file mode 100644 index 5c4d1dcc5cf..00000000000 Binary files a/about/static/favicon.ico and /dev/null differ diff --git a/about/static/img/showcase-logo/supabase-logo.svg b/about/static/img/showcase-logo/supabase-logo.svg deleted file mode 100644 index 1315b0a5ccf..00000000000 --- a/about/static/img/showcase-logo/supabase-logo.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/about/static/img/supabase-squad.png b/about/static/img/supabase-squad.png deleted file mode 100644 index 6f52e57826d..00000000000 Binary files a/about/static/img/supabase-squad.png and /dev/null differ diff --git a/about/static/img/yc-gray.png b/about/static/img/yc-gray.png deleted file mode 100644 index f3fade0625b..00000000000 Binary files a/about/static/img/yc-gray.png and /dev/null differ diff --git a/about/static/robots.txt b/about/static/robots.txt deleted file mode 100644 index c2a49f4fb82..00000000000 --- a/about/static/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Allow: / diff --git a/about/static/supabase-dark-padded.png b/about/static/supabase-dark-padded.png deleted file mode 100644 index ccffe82a37a..00000000000 Binary files a/about/static/supabase-dark-padded.png and /dev/null differ diff --git a/about/static/supabase-dark-padded.svg b/about/static/supabase-dark-padded.svg deleted file mode 100644 index 5ed15f63561..00000000000 --- a/about/static/supabase-dark-padded.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/about/static/supabase-dark.png b/about/static/supabase-dark.png deleted file mode 100644 index 146aa567478..00000000000 Binary files a/about/static/supabase-dark.png and /dev/null differ diff --git a/about/static/supabase-dark.svg b/about/static/supabase-dark.svg deleted file mode 100644 index c95ee421c3d..00000000000 --- a/about/static/supabase-dark.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/about/static/supabase-light-rounded-corner-background.svg b/about/static/supabase-light-rounded-corner-background.svg deleted file mode 100644 index dd0d6416ad2..00000000000 --- a/about/static/supabase-light-rounded-corner-background.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/about/static/supabase-light-with-background.svg b/about/static/supabase-light-with-background.svg deleted file mode 100644 index 2123e6bb9f5..00000000000 --- a/about/static/supabase-light-with-background.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/about/static/supabase-light.svg b/about/static/supabase-light.svg deleted file mode 100644 index e7f7dc7eab3..00000000000 --- a/about/static/supabase-light.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/about/static/supabase-logo.svg b/about/static/supabase-logo.svg deleted file mode 100644 index cb6632ff5d0..00000000000 --- a/about/static/supabase-logo.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/about/vercel.json b/about/vercel.json deleted file mode 100644 index 152b4b0612f..00000000000 --- a/about/vercel.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "redirects": [ - { - "source": "/careers", - "destination": "https://boards.greenhouse.io/supabase" - } - ] -} \ No newline at end of file diff --git a/apps/docs/.env.example b/apps/docs/.env.example index 074fa5ced44..7fa24758cb1 100644 --- a/apps/docs/.env.example +++ b/apps/docs/.env.example @@ -1,8 +1,11 @@ # PUBLIC -NEXT_PUBLIC_NEW_DOCS=true NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321 NEXT_PUBLIC_SUPABASE_ANON_KEY= # PRIVATE OPENAI_KEY= SUPABASE_SERVICE_ROLE_KEY= + +# Set IS_PLATFORM to true if you have the api keys and urls +NEXT_PUBLIC_IS_PLATFORM=false +NEXT_PUBLIC_LOCAL_SUPABASE=false diff --git a/apps/docs/DEVELOPERS.md b/apps/docs/DEVELOPERS.md index 50ea5d9deec..3935d5e0ce0 100644 --- a/apps/docs/DEVELOPERS.md +++ b/apps/docs/DEVELOPERS.md @@ -14,9 +14,10 @@ For a complete run-down on how all of our tools work together, see the main DEVE [supabase.com/docs](https://supabase.com/docs) is a Next.JS site. You can get setup by following the same steps for all of our other Next.JS projects: 1. Follow the steps outlined in the Local Development section of the main [DEVELOPERS.md](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md) -2. Start the local docs site by navigating to `/apps/docs` and running `npm run dev` -3. Visit http://localhost:3001/docs in your browser - don't forget to append the `/docs` to the end -4. Your local site should look exactly like [https://supabase.com/docs](https://supabase.com/docs) +2. If you work at Supabase, run `dev:secrets:pull` to pull down the internal environment variables. If you're a community member, create a `.env` file and add this line to it: `NEXT_PUBLIC_IS_PLATFORM=false` +3. Start the local docs site by navigating to `/apps/docs` and running `npm run dev` +4. Visit http://localhost:3001/docs in your browser - don't forget to append the `/docs` to the end +5. Your local site should look exactly like [https://supabase.com/docs](https://supabase.com/docs) ## Types of documentation diff --git a/apps/docs/components/Clippy/Clippy.tsx b/apps/docs/components/Clippy/Clippy.tsx deleted file mode 100644 index 8a49e2a9306..00000000000 --- a/apps/docs/components/Clippy/Clippy.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { useTheme } from 'common/Providers' -import Image from 'next/image' -import { FC } from 'react' -import { createPortal } from 'react-dom' -import clippyImageDark from '../../public/img/clippy-dark.png' -import clippyImage from '../../public/img/clippy.png' -import ClippyBubble from './ClippyBubble' -import ClippyModal from './ClippyModal' -import { useClippy } from './ClippyProvider' - -const Clippy: FC = () => { - const { isDarkMode } = useTheme() - const { isOpen, open, close } = useClippy() - - return ( - <> - {!isOpen && ( -
- {/* <> - -
- Clippy -
- */} -
- )} - {isOpen && createPortal(, document.body)} - - ) -} - -export default Clippy diff --git a/apps/docs/components/Clippy/ClippyBubble.tsx b/apps/docs/components/Clippy/ClippyBubble.tsx deleted file mode 100644 index 92286cb85c6..00000000000 --- a/apps/docs/components/Clippy/ClippyBubble.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { FC } from 'react' -import { IconCommand } from 'ui' -import Image from 'next/image' -interface Props { - className?: string - onClick?: () => void -} - -const ClippyBubble: FC = ({ className, onClick }) => { - return ( -
-
- Clippy blur -
- What can I help you with? -
-
- - / -
-
-
- - - -
-
- ) -} - -export default ClippyBubble diff --git a/apps/docs/components/Clippy/ClippyModal.tsx b/apps/docs/components/Clippy/ClippyModal.tsx deleted file mode 100644 index 8347e85d1ef..00000000000 --- a/apps/docs/components/Clippy/ClippyModal.tsx +++ /dev/null @@ -1,267 +0,0 @@ -import type { CreateCompletionResponse } from 'openai' -import { FC, useCallback, useState } from 'react' -import ReactMarkdown from 'react-markdown' -import remarkGfm from 'remark-gfm' -import { SSE } from 'sse.js' -import clippyImageDark from '../../public/img/clippy-dark.png' -import clippyImage from '../../public/img/clippy.png' - -import { useTheme } from 'common/Providers' -import Image from 'next/image' -import { - Button, - IconAlertCircle, - IconAlertTriangle, - IconLoader, - IconSearch, - Input, - Loading, - Modal, -} from 'ui' -import components from '~/components' - -type Props = { - onClose?: () => void -} - -const questions = [ - 'How do I get started with Supabase?', - 'How do I run Supabase locally?', - 'How do I connect to my database?', - 'How do I run migrations? ', - 'How do I listen to changes in a table?', - 'How do I set up authentication?', -] - -function getEdgeFunctionUrl() { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL?.replace(/\/$/, '') - const isPlatform = supabaseUrl?.match(/(supabase\.co)|(supabase\.in)/) - - if (isPlatform) { - const [schemeAndProjectId, domain, tld] = supabaseUrl.split('.') - return `${schemeAndProjectId}.functions.${domain}.${tld}` - } else { - return `${supabaseUrl}/functions/v1` - } -} - -const edgeFunctionUrl = getEdgeFunctionUrl() - -const ClippyModal: FC = ({ onClose }) => { - const { isDarkMode } = useTheme() - const [query, setQuery] = useState('') - const [answer, setAnswer] = useState('') - const [isLoading, setIsLoading] = useState(false) - const [isResponding, setIsResponding] = useState(false) - const [hasError, setHasError] = useState(false) - - const cantHelp = answer?.trim() === "Sorry, I don't know how to help with that." - const status = isLoading - ? 'Clippy is searching...' - : isResponding - ? 'Clippy is responding...' - : cantHelp || hasError - ? 'Clippy has failed you' - : undefined - - const handleConfirm = useCallback(async (query: string) => { - setHasError(false) - setAnswer(undefined) - setIsLoading(true) - - const eventSource = new SSE(`${edgeFunctionUrl}/clippy-search`, { - headers: { - apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, - Authorization: `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`, - 'Content-Type': 'application/json', - }, - payload: JSON.stringify({ query }), - }) - - function handleError(err: T) { - setIsLoading(false) - setIsResponding(false) - setHasError(true) - console.error(err) - } - - eventSource.addEventListener('error', handleError) - eventSource.addEventListener('message', (e) => { - try { - setIsLoading(false) - - if (e.data === '[DONE]') { - setIsResponding(false) - return - } - - setIsResponding(true) - - const completionResponse: CreateCompletionResponse = JSON.parse(e.data) - const [{ text }] = completionResponse.choices - - setAnswer((answer) => { - return (answer ?? '') + text - }) - } catch (err) { - handleError(err) - } - }) - - eventSource.stream() - - setIsLoading(true) - }, []) - - function handleResetPrompt() { - setQuery('') - setAnswer(undefined) - setIsResponding(false) - setHasError(false) - } - - return ( - -
e.stopPropagation()} - > -
- setQuery(e.target.value)} - icon={} - onKeyDown={(e) => { - switch (e.key) { - case 'Enter': - handleConfirm(query) - return - default: - return - } - }} - /> -
- -
- {!isLoading && answer && ( -
- -
- )} -
- - {!isLoading && !answer && !hasError && ( -
-
-

Not sure where to start?

- -
    - {questions.map((question) => ( -
  • - -
  • - ))} -
-
-
- )} - {answer && ( -
- {cantHelp ? ( -

-

- -

Sorry, I don't know how to help with that.

-
- -

- ) : ( -
- { - const supabaseUrl = new URL('https://supabase.com') - const linkUrl = new URL(href, 'https://supabase.com') - - if (linkUrl.origin === supabaseUrl.origin) { - return linkUrl.toString() - } - - return href - }} - components={components} - > - {answer} - -
- )} -
- )} - {isLoading && ( -
- {} -

Searching for results

-
- )} - {hasError && ( -
- -

Sorry, looks like Clippy is having a hard time!

-

Please try again in a bit.

- -
- )} -
-
-
- Powered by OpenAI. - - Read the blog post - -
-
- {status ? ( - - {(isLoading || isResponding) && } - {status} - - ) : ( - <> - )} - Clippy -
-
-
-
-
- ) -} - -export default ClippyModal diff --git a/apps/docs/components/Clippy/ClippyProvider.tsx b/apps/docs/components/Clippy/ClippyProvider.tsx deleted file mode 100644 index 9bdfabea56e..00000000000 --- a/apps/docs/components/Clippy/ClippyProvider.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { - createContext, - FC, - PropsWithChildren, - useCallback, - useContext, - useEffect, - useState, -} from 'react' - -export type ClippyContextValue = { - isOpen: boolean - open: () => void - close: () => void -} - -export const ClippyContext = createContext(null) - -export const useClippy = () => { - const { isOpen, open, close } = useContext(ClippyContext) - - return { isOpen, open, close } -} - -const ClippyProvider: FC> = ({ children }) => { - const [isModalOpen, setIsModalOpen] = useState(false) - - const onOpen = useCallback(() => { - setIsModalOpen(true) - document.body.classList.add('DocSearch--active') - }, []) - - const onClose = useCallback(() => { - setIsModalOpen(false) - document.body.classList.remove('DocSearch--active') - }, []) - - useSearchKeyboardEvents({ - onOpen, - onClose, - }) - return ( - - {children} - - ) -} - -function useSearchKeyboardEvents({ onOpen, onClose }) { - useEffect(() => { - function onKeyDown(event: KeyboardEvent) { - switch (event.key) { - case 'Escape': - onClose() - return - case '/': - if (event.metaKey || event.ctrlKey) { - onOpen() - } - return - } - } - - window.addEventListener('keydown', onKeyDown) - - return () => { - window.removeEventListener('keydown', onKeyDown) - } - }, [onOpen, onClose]) -} - -export default ClippyProvider diff --git a/apps/docs/components/DocSearch.tsx b/apps/docs/components/DocSearch.tsx deleted file mode 100644 index 7ff1a5ece2a..00000000000 --- a/apps/docs/components/DocSearch.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import { useState, useCallback, useRef, createContext, useContext, useEffect, memo } from 'react' -import { createPortal } from 'react-dom' -import Link from 'next/link' -import Head from 'next/head' -import { useRouter } from 'next/router' -import { DocSearchModal } from '@docsearch/react' -import clsx from 'clsx' -import { useActionKey } from '~/hooks/useActionKey' - -// [Joshen] We're currently using this over Algolia search for the time being -// Refer to the comment in Navigation/AlgoliaSearch for more information about this - -const INDEX_NAME = process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME -const API_KEY = process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY -const APP_ID = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID - -const SearchContext = createContext(null) - -export function SearchProvider({ children }: any) { - const router = useRouter() - const [isOpen, setIsOpen] = useState(false) - const [initialQuery, setInitialQuery] = useState(null) - const onOpen = useCallback(() => { - setIsOpen(true) - }, [setIsOpen]) - - const onClose = useCallback(() => { - setIsOpen(false) - }, [setIsOpen]) - - const onInput = useCallback( - (e) => { - setIsOpen(true) - setInitialQuery(e.key) - }, - [setIsOpen, setInitialQuery] - ) - - useDocSearchKeyboardEvents({ - isOpen, - onOpen, - onClose, - // @ts-ignore - onInput, - }) - - return ( - <> - - - - - {children} - - {isOpen && - createPortal( - { - return `https://github.com/supabase/supabase/issues/new?title=Unable+to+search+docs+with+query:+${query}` - }} - hitComponent={Hit} - transformItems={(items) => { - return items.map((item, index) => { - // console.log('item', item) - // We transform the absolute URL into a relative URL to - // leverage Next's preloading. - const a = document.createElement('a') - a.href = item.url - - const hash = a.hash === '#content-wrapper' ? '' : a.hash - - return { - ...item, - url: `${a.pathname}${hash}`, - __is_result: () => true, - __is_parent: () => item.type === 'lvl1' && index === 0, - __is_guide: () => item.hierarchy.lvl0 === 'Guides', - __is_child: () => - item.type !== 'lvl1' && - items.length > 1 && - items[0].type === 'lvl1' && - index !== 0, - __is_first: () => index === 1, - __is_last: () => index === items.length - 1 && index !== 0, - } - }) - }} - />, - document.body - )} - - ) -} - -// @ts-ignore -function Hit({ hit, children }) { - return ( - - - {children} - - - ) -} - -// @ts-ignore -export function SearchButton({ children, ...props }) { - let searchButtonRef = useRef() - let actionKey = useActionKey() - // @ts-ignore - let { onOpen, onInput } = useContext(SearchContext) - - useEffect(() => { - // @ts-ignore - function onKeyDown(event) { - if (searchButtonRef && searchButtonRef.current === document.activeElement && onInput) { - if (/[a-zA-Z0-9]/.test(String.fromCharCode(event.keyCode))) { - onInput(event) - } - } - } - window.addEventListener('keydown', onKeyDown) - return () => { - window.removeEventListener('keydown', onKeyDown) - } - }, [onInput, searchButtonRef]) - - return ( - // @ts-ignore - - ) -} - -// @ts-ignore -function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }) { - useEffect(() => { - // @ts-ignore - function onKeyDown(event) { - function open() { - // We check that no other DocSearch modal is showing before opening - // another one. - if (!document.body.classList.contains('DocSearch--active')) { - onOpen() - } - } - - if ( - (event.keyCode === 27 && isOpen) || - (event.key === 'k' && (event.metaKey || event.ctrlKey)) || - (!isEditingContent(event) && event.key === '/' && !isOpen) - ) { - event.preventDefault() - - if (isOpen) { - onClose() - } else if (!document.body.classList.contains('DocSearch--active')) { - open() - } - } - } - - window.addEventListener('keydown', onKeyDown) - return () => { - window.removeEventListener('keydown', onKeyDown) - } - }, [isOpen, onOpen, onClose]) -} - -// @ts-ignore -function isEditingContent(event) { - let element = event.target - let tagName = element.tagName - return ( - element.isContentEditable || - tagName === 'INPUT' || - tagName === 'SELECT' || - tagName === 'TEXTAREA' - ) -} diff --git a/apps/docs/components/Extensions.js b/apps/docs/components/Extensions.js deleted file mode 100644 index 47753d6bfde..00000000000 --- a/apps/docs/components/Extensions.js +++ /dev/null @@ -1,35 +0,0 @@ -import React, { useState } from 'react' -import extensions from '../data/extensions.json' - -export default function Extensions() { - const [filter, setFilter] = useState('') - return ( - <> -
- - setFilter(e.target.value)} - /> -
-
- {extensions - .filter((x) => x.name.indexOf(filter) >= 0) - .map((extension) => ( -
-
-

- {extension.name} -

-

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

-
-
- ))} -
- - ) -} diff --git a/apps/docs/components/Extensions.tsx b/apps/docs/components/Extensions.tsx new file mode 100644 index 00000000000..b7b7260cd64 --- /dev/null +++ b/apps/docs/components/Extensions.tsx @@ -0,0 +1,141 @@ +import Link from 'next/link' +import React, { useState } from 'react' +import { GlassPanel, IconLink, IconX, Input } from 'ui' +import extensions from '../data/extensions.json' + +type extension = { + name: string + comment: string + tags: string[] + link?: string +} + +function getUniqueTags(json: extension[]) { + const tags = [] + for (const item of json) { + if (item.tags) { + tags.push(...item.tags) + } + } + return [...new Set(tags)] +} + +export default function Extensions() { + const [searchTerm, setSearchTerm] = useState('') + const [filters, setFilters] = useState([]) + + const tags = getUniqueTags(extensions) + + function handleChecked(tag) { + if (filters.includes(tag)) { + setFilters(filters.filter((x) => x !== tag)) + } else { + setFilters([...filters, tag]) + } + } + + return ( + <> +
+ + setSearchTerm(e.target.value)} + /> +
+
+
+
+

Filter

+
    + {tags.sort().map((tag) => ( +
  • + +
  • + ))} +
+

+ +

+
+
+ +
+
+ {extensions + .filter((x) => x.name.indexOf(searchTerm) >= 0) + .filter((x) => + filters.length === 0 ? x : x.tags.some((item) => filters.includes(item)) + ) + .map((extension) => ( + + + +

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

+
+
+ + //
+ //
+ //

+ // {extension.name} + //

+ //

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

+ // {extension.link && ( + // + // + // + // + // + // + // + // )} + //
+ //
+ ))} +
+
+
+ + ) +} diff --git a/apps/docs/components/JwtGenerator.js b/apps/docs/components/JwtGenerator.js index 4ac1b05e255..d0dc7a7bf08 100644 --- a/apps/docs/components/JwtGenerator.js +++ b/apps/docs/components/JwtGenerator.js @@ -1,7 +1,6 @@ import React, { useState } from 'react' import KJUR from 'jsrsasign' -import CodeBlock from './CodeBlock/CodeBlock' -import { Button, Select, Input } from 'ui' +import { Button, Select, Input, CodeBlock } from 'ui' const JWT_HEADER = { alg: 'HS256', typ: 'JWT' } const now = new Date() diff --git a/apps/docs/components/MDX/storage_management.mdx b/apps/docs/components/MDX/storage_management.mdx index d9df719486d..1eebf7cb575 100644 --- a/apps/docs/components/MDX/storage_management.mdx +++ b/apps/docs/components/MDX/storage_management.mdx @@ -5,26 +5,26 @@ from `public.profiles` and the older versions getting orphaned. To automatically remove obsolete storage objects, extend the database triggers. Note that it is not sufficient to delete the objects from the `storage.objects` table because that would orphan and leak the actual storage objects in -the S3 backend. Instead, invoke the storage API within Postgres via the `http` extension. +the S3 backend. Instead, invoke the storage API within Postgres via the `http` extension. Enable the [http extension for the `extensions` schema](https://app.supabase.com/project/_/database/extensions) in the Dashboard. Then, define the following SQL functions in the SQL Editor to delete storage objects via the API: ```SQL -create or replace function delete_storage_object(bucket text, object text, out status int, out content varchar) +create or replace function delete_storage_object(bucket text, object text, out status int, out content text) returns record language 'plpgsql' security definer as $$ declare - project_url varchar := ''; - service_role_key varchar := ''; -- full access needed - url varchar := project_url||'/storage/v1/object/'||bucket||'/'||object; + project_url text := ''; + service_role_key text := ''; -- full access needed + url text := project_url||'/storage/v1/object/'||bucket||'/'||object; begin select into status, content - result.status::int, result.content::varchar + result.status::int, result.content::text FROM extensions.http(( 'DELETE', url, @@ -34,7 +34,7 @@ begin end; $$; -create or replace function delete_avatar(avatar_url text, out status int, out content varchar) +create or replace function delete_avatar(avatar_url text, out status int, out content text) returns record language 'plpgsql' security definer @@ -60,7 +60,7 @@ security definer as $$ declare status int; - content varchar; + content text; begin if coalesce(old.avatar_url, '') <> '' and (tg_op = 'DELETE' or (old.avatar_url <> new.avatar_url)) then diff --git a/apps/docs/components/Navigation/AlgoliaSearch.tsx b/apps/docs/components/Navigation/AlgoliaSearch.tsx deleted file mode 100644 index eeb954f037a..00000000000 --- a/apps/docs/components/Navigation/AlgoliaSearch.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { render } from 'react-dom' -import { IconCommand } from 'ui' -import { createElement, FC, useEffect, useRef, Fragment } from 'react' -import algoliasearch from 'algoliasearch/lite' -import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js' -import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches' -import { useRouter } from 'next/router' - -// [Joshen] We're currently using DocSearch from Algolia as it provides a nice -// UI out of the box + some good preconfigured search settings (e.g hierarchy). -// However, we're using our own Algolia account to store the records in the indexes -// (rather than going through the DocSearch program from Algolia where they'll crawl -// our site for us). Refer to scripts/build-search on how we're saving the records. - -// Using Algolia's autocomplete library gives us full flexbility in terms of customizing -// our search experience, but that will take time to figure out. Hence why for now we're just -// using DocSearch with our own records. - -// Potentially for search, we could -// - Go ahead with the DocSearch program and let them crawl our site to generate the records -// - But we need to ensure that our site is semantically correct first -// - Use Algolia itself to flesh out our own search logic -// - The basics are already set up to be honest, but will take time to make it great -// - Go back to Typesense if we deem that Algolia is not helpful in the long run - -const searchClient = algoliasearch( - process.env.NEXT_PUBLIC_ALGOLIA_APP_ID, - process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY -) - -// [Joshen] Not working properly, but lets not get stuck on this -// Priority just to get a search working -const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ - key: 'docs-search', - limit: 3, - //@ts-ignore - transformSource({ source }) { - return { - ...source, - templates: { - ...source.templates, - header({ state }) { - if (state.query) return null - return ( - - Your searches -
- - ) - }, - }, - } - }, -}) - -interface Props {} - -const AlgoliaSearch: FC = ({}) => { - const searchRef = useRef(null) - const router = useRouter() - - useEffect(() => { - if (!searchRef.current) { - return undefined - } - const search = autocomplete({ - openOnFocus: true, - container: searchRef.current, - defaultActiveItemId: 0, - detachedMediaQuery: '', - // @ts-ignore - renderer: { createElement, Fragment, render }, - placeholder: 'Search docs', - plugins: [recentSearchesPlugin], - renderNoResults({ state, render }, root) { - render( -
- No results found for {state.query}. -
, - root - ) - }, - navigator: { - navigate({ itemUrl }) { - router.push(itemUrl) - }, - }, - onSelect({ item, setQuery, setIsOpen, refresh }) { - //console.log('onSelect', item.url) - }, - // @ts-ignore - getSources({ query }) { - return [ - { - sourceId: 'pages', - templates: { - item({ item, components }) { - return ( - -
-
- {item.category && ( -

- <> - {item.category} - {item.version ? ` (${item.version})` : ''}: - -

- )} -

- -

-
-

{item.description as string}

-
-
- ) - }, - }, - getItemUrl({ item }) { - return item.url - }, - getItems() { - // if (!query) return [] - return getAlgoliaResults({ - searchClient, - queries: [ - { - indexName: 'dev_docs', - query, - }, - ], - }) - }, - }, - ] - }, - }) - - return () => { - search.destroy() - } - }, []) - - return ( -
-
-
-
- -
-
- K -
-
-
- ) -} - -export default AlgoliaSearch diff --git a/apps/docs/components/Navigation/NavBar.tsx b/apps/docs/components/Navigation/NavBar.tsx index d71e2d55c43..eb7ee10434f 100644 --- a/apps/docs/components/Navigation/NavBar.tsx +++ b/apps/docs/components/Navigation/NavBar.tsx @@ -1,11 +1,10 @@ +import { useTheme } from 'common/Providers' import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/router' -import { useState, useEffect, FC } from 'react' -import { IconMenu, IconMoon, IconSearch, IconSun, IconCommand, Listbox } from 'ui' -import { useTheme } from 'common/Providers' +import { FC, useEffect, useState } from 'react' +import { IconCommand, IconMenu, IconMoon, IconSearch, IconSun, Listbox, SearchButton } from 'ui' import { REFERENCES } from './Navigation.constants' -import { SearchButton } from '../DocSearch' import { getPageType } from '~/lib/helpers' @@ -36,14 +35,6 @@ const NavBar: FC = () => { { text: 'Reference', key: 'reference', link: '/reference' }, ] - const toggleDarkMode = () => { - localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString()) - toggleTheme() - - const key = localStorage.getItem('supabaseDarkMode') - document.documentElement.className = key === 'true' ? 'dark' : '' - } - const onSelectVersion = (version: string) => { // [Joshen] Ideally we use but this works for now if (!library) return @@ -183,7 +174,7 @@ const NavBar: FC = () => {
  • -
    +
    toggleTheme()}> {isDarkMode ? ( ) : ( diff --git a/apps/docs/components/Navigation/Navigation.constants.ts b/apps/docs/components/Navigation/Navigation.constants.ts index 94f40fce1ee..f3b9ee85430 100644 --- a/apps/docs/components/Navigation/Navigation.constants.ts +++ b/apps/docs/components/Navigation/Navigation.constants.ts @@ -80,6 +80,7 @@ export const menuItems: NavMenu = { label: 'Auth', items: [ { name: 'Overview', url: '/guides/auth', items: [] }, + { name: 'Quickstarts', url: '/guides/auth/quickstarts/react', items: [] }, { name: 'Authentication', url: undefined, @@ -129,9 +130,9 @@ export const menuItems: NavMenu = { items: [ { name: 'Overview', url: '/guides/auth/auth-helpers', items: [] }, { name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui', items: [] }, - { name: 'Next.js', url: '/guides/auth/auth-helpers/nextjs', items: [] }, + { name: 'Next.js (pages)', url: '/guides/auth/auth-helpers/nextjs', items: [] }, { - name: 'Next.js Server Components', + name: 'Next.js (app)', url: '/guides/auth/auth-helpers/nextjs-server-components', items: [], }, @@ -174,14 +175,6 @@ export const menuItems: NavMenu = { { name: 'Database Webhooks', url: '/guides/database/webhooks', items: [] }, { name: 'Full Text Search', url: '/guides/database/full-text-search', items: [] }, { name: 'Database Testing', url: '/guides/database/testing', items: [] }, - { - name: 'Serverless APIs', - url: undefined, - items: [ - { name: 'Overview', url: '/guides/database/api', items: [] }, - { name: 'Generating Types', url: '/guides/database/api/generating-types', items: [] }, - ], - }, { name: 'Extensions', url: undefined, @@ -228,6 +221,13 @@ export const menuItems: NavMenu = { }, ], }, + { + label: 'Serverless APIs', + items: [ + { name: 'Overview', url: '/guides/api', items: [] }, + { name: 'Generating Types', url: '/guides/api/generating-types', items: [] }, + ], + }, { label: 'Edge Functions', items: [ @@ -241,12 +241,62 @@ export const menuItems: NavMenu = { { label: 'Realtime', items: [ - { name: 'Overview', url: '/guides/realtime', items: [] }, - { name: 'Quickstart', url: '/guides/realtime/quickstart', items: [] }, - { name: 'Broadcast', url: '/guides/realtime/broadcast', items: [] }, - { name: 'Presence', url: '/guides/realtime/presence', items: [] }, - { name: 'Postgres Changes', url: '/guides/realtime/postgres-changes', items: [] }, - { name: 'Rate Limits', url: '/guides/realtime/rate-limits', items: [] }, + { + name: 'Overview', + url: '/guides/realtime', + items: [], + }, + { + name: 'Quickstart', + url: '/guides/realtime/quickstart', + items: [], + }, + { + name: 'Features', + url: undefined, + items: [ + { name: 'Channels', url: '/guides/realtime/channels', items: [] }, + { + name: 'Extensions', + url: '/guides/realtime/extensions', + items: [ + { name: 'Broadcast', url: '/guides/realtime/broadcast', items: [] }, + { name: 'Presence', url: '/guides/realtime/presence', items: [] }, + { name: 'Postgres Changes', url: '/guides/realtime/postgres-changes', items: [] }, + ], + }, + ], + }, + { + name: 'Guides', + url: undefined, + items: [ + { + name: 'Subscribing to Database Changes', + url: '/guides/realtime/subscribing-to-database-changes', + items: [], + }, + { + name: 'Bring Your Own Database', + url: '/guides/realtime/bring-your-own-database', + items: [], + }, + { + name: 'Using Realtime with Next.js', + url: '/guides/realtime/realtime-with-nextjs', + items: [], + }, + ], + }, + { + name: 'Deep dive', + url: undefined, + items: [ + { name: 'Rate Limits', url: '/guides/realtime/rate-limits', items: [] }, + { name: 'Architecture', url: '/guides/realtime/architecture', items: [] }, + { name: 'Protocol', url: '/guides/realtime/protocol', items: [] }, + ], + }, ], }, { @@ -338,7 +388,6 @@ export const menuItems: NavMenu = { url: undefined, items: [ { name: 'Appsmith', url: '/guides/integrations/appsmith', items: [] }, - { name: 'Dashibase', url: '/guides/integrations/dashibase', items: [] }, { name: 'DhiWise', url: '/guides/integrations/dhiwise', items: [] }, { name: 'Directus', url: '/guides/integrations/directus', items: [] }, { name: 'Draftbit', url: '/guides/integrations/draftbit', items: [] }, diff --git a/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx index 36f01bf6830..db93e1616a2 100644 --- a/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx @@ -27,10 +27,16 @@ const home = [ href: '/guides/database', level: 'database', }, + { + label: 'Serverless APIs', + icon: '/img/icons/menu/reference', + href: '/guides/api', + level: 'api', + }, { label: 'Auth', icon: '/img/icons/menu/auth', - href: '/guides/auth/overview', + href: '/guides/auth', level: 'auth', }, { @@ -109,6 +115,13 @@ const home = [ level: 'reference_csharp', community: true, }, + { + label: 'Swift', + icon: '/img/icons/menu/reference-swift', + href: '/reference/swift/introduction', + level: 'reference_swift', + community: true, + }, { label: 'Tools', }, diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 7385afd2d3d..a2f78fc985d 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -28,6 +28,12 @@ export const REFERENCES: References = { versions: ['v0'], icon: '/docs/img/libraries/c-sharp-icon.svg', }, + swift: { + name: 'Swift', + library: 'supabase-swift', + versions: ['v1'], + icon: '/docs/img/libraries/swift-icon.svg', + }, cli: { name: 'CLI', library: undefined, @@ -46,17 +52,18 @@ export const gettingstarted = { icon: 'getting-started', title: 'Getting Started', items: [ - { name: 'Features', url: '/guides/getting-started/features', items: [] }, - { name: 'Architecture', url: '/guides/getting-started/architecture', items: [] }, + { name: 'Features', url: '/guides/getting-started/features' }, + { name: 'Architecture', url: '/guides/getting-started/architecture' }, + { name: 'Local Development', url: '/guides/cli/local-development' }, { name: 'Framework Quickstarts', items: [ - { name: 'React', url: '/guides/getting-started/quickstarts/reactjs', items: [] }, - { name: 'NextJS', url: '/guides/getting-started/quickstarts/nextjs', items: [] }, - { name: 'Flutter', url: '/guides/getting-started/quickstarts/flutter', items: [] }, - { name: 'SvelteKit', url: '/guides/getting-started/quickstarts/sveltekit', items: [] }, - { name: 'SolidJS', url: '/guides/getting-started/quickstarts/solidjs', items: [] }, - { name: 'Vue', url: '/guides/getting-started/quickstarts/vue', items: [] }, + { name: 'React', url: '/guides/getting-started/quickstarts/reactjs' }, + { name: 'NextJS', url: '/guides/getting-started/quickstarts/nextjs' }, + { name: 'Flutter', url: '/guides/getting-started/quickstarts/flutter' }, + { name: 'SvelteKit', url: '/guides/getting-started/quickstarts/sveltekit' }, + { name: 'SolidJS', url: '/guides/getting-started/quickstarts/solidjs' }, + { name: 'Vue', url: '/guides/getting-started/quickstarts/vue' }, ], }, { @@ -65,47 +72,38 @@ export const gettingstarted = { { name: 'NextJS', url: '/guides/getting-started/tutorials/with-nextjs', - items: [], }, { name: 'React', url: '/guides/getting-started/tutorials/with-react', - items: [], }, { name: 'Vue 3', url: '/guides/getting-started/tutorials/with-vue-3', - items: [], }, { name: 'Nuxt 3', url: '/guides/getting-started/tutorials/with-nuxt-3', - items: [], }, { name: 'Angular', url: '/guides/getting-started/tutorials/with-angular', - items: [], }, { name: 'RedwoodJS', url: '/guides/getting-started/tutorials/with-redwoodjs', - items: [], }, { name: 'SolidJS', url: '/guides/getting-started/tutorials/with-solidjs', - items: [], }, { name: 'Svelte', url: '/guides/getting-started/tutorials/with-svelte', - items: [], }, { name: 'SvelteKit', url: '/guides/getting-started/tutorials/with-sveltekit', - items: [], }, ], }, @@ -115,28 +113,23 @@ export const gettingstarted = { { name: 'Flutter', url: '/guides/getting-started/tutorials/with-flutter', - items: [], }, { name: 'Expo', url: '/guides/getting-started/tutorials/with-expo', - items: [], }, { name: 'Ionic React', url: '/guides/getting-started/tutorials/with-ionic-react', - items: [], }, { name: 'Ionic Vue', url: '/guides/getting-started/tutorials/with-ionic-vue', - items: [], }, { name: 'Ionic Angular', url: '/guides/getting-started/tutorials/with-ionic-angular', - items: [], }, ], }, @@ -144,11 +137,11 @@ export const gettingstarted = { } export const cli = { - label: 'CLI', + title: 'CLI', items: [ - { name: 'Overview', url: '/guides/cli', items: [] }, - { name: 'Local Development', url: '/guides/cli/local-development', items: [] }, - { name: 'Managing Environments', url: '/guides/cli/managing-environments', items: [] }, + { name: 'Overview', url: '/guides/cli' }, + { name: 'Local Development', url: '/guides/cli/local-development' }, + { name: 'Managing Environments', url: '/guides/cli/managing-environments' }, ], } @@ -157,104 +150,87 @@ export const SocialLoginItems = [ name: 'Google', icon: '/docs/img/icons/google-icon', url: '/guides/auth/social-login/auth-google', - items: [], }, { name: 'Facebook', icon: '/docs/img/icons/facebook-icon', url: '/guides/auth/social-login/auth-facebook', - items: [], }, { name: 'Apple', icon: '/docs/img/icons/apple-icon', url: '/guides/auth/social-login/auth-apple', - items: [], }, { name: 'Azure', icon: '/docs/img/icons/microsoft-icon', url: '/guides/auth/social-login/auth-azure', - items: [], }, { name: 'Twitter', icon: '/docs/img/icons/twitter-icon', url: '/guides/auth/social-login/auth-twitter', - items: [], }, { name: 'GitHub', icon: '/docs/img/icons/github-icon', url: '/guides/auth/social-login/auth-github', - items: [], isDarkMode: true, }, { name: 'Gitlab', icon: '/docs/img/icons/gitlab-icon', url: '/guides/auth/social-login/auth-gitlab', - items: [], }, { name: 'Bitbucket', icon: '/docs/img/icons/bitbucket-icon', url: '/guides/auth/social-login/auth-bitbucket', - items: [], }, { name: 'Discord', icon: '/docs/img/icons/discord-icon', url: '/guides/auth/social-login/auth-discord', - items: [], }, { name: 'Keycloak', icon: '/docs/img/icons/keycloak-icon', url: '/guides/auth/social-login/auth-keycloak', - items: [], }, { name: 'LinkedIn', icon: '/docs/img/icons/linkedin-icon', url: '/guides/auth/social-login/auth-linkedin', - items: [], }, { name: 'Notion', icon: '/docs/img/icons/notion-icon', url: '/guides/auth/social-login/auth-notion', - items: [], }, { name: 'Slack', icon: '/docs/img/icons/slack-icon', url: '/guides/auth/social-login/auth-slack', - items: [], }, { name: 'Spotify', icon: '/docs/img/icons/spotify-icon', url: '/guides/auth/social-login/auth-spotify', - items: [], }, { name: 'Twitch', icon: '/docs/img/icons/twitch-icon', url: '/guides/auth/social-login/auth-twitch', - items: [], }, { name: 'WorkOS', icon: '/docs/img/icons/workos-icon', url: '/guides/auth/social-login/auth-workos', - items: [], }, { name: 'Zoom', icon: '/docs/img/icons/zoom-icon', url: '/guides/auth/social-login/auth-zoom', - items: [], }, ] @@ -264,14 +240,12 @@ export const PhoneLoginsItems = [ icon: '/docs/img/icons/messagebird-icon', linkDescription: 'Communication between businesses and their customers — across any channel.', url: '/guides/auth/phone-login/messagebird', - items: [], }, { name: 'Twilio SMS Login', icon: '/docs/img/icons/twilio-icon', url: '/guides/auth/phone-login/twilio', linkDescription: 'Customer engagement platform used by hundreds of thousands of businesses.', - items: [], }, { name: 'Vonage SMS Login', @@ -279,25 +253,28 @@ export const PhoneLoginsItems = [ url: '/guides/auth/phone-login/vonage', linkDescription: 'Vonage is a communication platform as a service (CPaaS) provider for consumers and businesses.', - items: [], isDarkMode: true, }, ] export const auth = { icon: 'auth', - label: 'Auth', + title: 'Auth', items: [ { name: 'Overview', - url: '/guides/auth/overview', + url: '/guides/auth', + }, + { + name: 'Quickstarts', + items: [{ name: 'Auth with React', url: '/guides/auth/quickstarts/react', items: [] }], }, { name: 'Authentication', url: undefined, items: [ - { name: 'Email Login', url: '/guides/auth/auth-email', items: [] }, - { name: 'Magic Link Login', url: '/guides/auth/auth-magic-link', items: [] }, + { name: 'Email Login', url: '/guides/auth/auth-email' }, + { name: 'Magic Link Login', url: '/guides/auth/auth-magic-link' }, { name: 'Phone Login', url: '/guides/auth/phone-login', @@ -308,34 +285,43 @@ export const auth = { url: '/guides/auth/social-login', items: [...SocialLoginItems], }, - { name: 'Email Templates', url: '/guides/auth/auth-email-templates', items: [] }, + { + name: 'Enterprise SSO', + url: '/guides/auth/enterprise-sso', + items: [ + { + name: 'SAML 2.0', + url: '/guides/auth/sso/auth-sso-saml', + }, + ], + }, + { name: 'Email Templates', url: '/guides/auth/auth-email-templates' }, ], }, { name: 'Authorization', url: undefined, items: [ - { name: 'Enable Captcha Protection', url: '/guides/auth/auth-captcha', items: [] }, - { name: 'Managing User Data', url: '/guides/auth/managing-user-data', items: [] }, - { name: 'Multi-Factor Authentication', url: '/guides/auth/auth-mfa', items: [] }, - { name: 'Row Level Security', url: '/guides/auth/row-level-security', items: [] }, - { name: 'Server-side Rendering', url: '/guides/auth/server-side-rendering', items: [] }, + { name: 'Enable Captcha Protection', url: '/guides/auth/auth-captcha' }, + { name: 'Managing User Data', url: '/guides/auth/managing-user-data' }, + { name: 'Multi-Factor Authentication', url: '/guides/auth/auth-mfa' }, + { name: 'Row Level Security', url: '/guides/auth/row-level-security' }, + { name: 'Server-side Rendering', url: '/guides/auth/server-side-rendering' }, ], }, { name: 'Auth Helpers', url: undefined, items: [ - { name: 'Overview', url: '/guides/auth/auth-helpers', items: [] }, - { name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui', items: [] }, - { name: 'Next.js', url: '/guides/auth/auth-helpers/nextjs', items: [] }, + { name: 'Overview', url: '/guides/auth/auth-helpers' }, + { name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui' }, + { name: 'Next.js (pages)', url: '/guides/auth/auth-helpers/nextjs' }, { - name: 'Next.js Server Components', + name: 'Next.js (app)', url: '/guides/auth/auth-helpers/nextjs-server-components', - items: [], }, - { name: 'Remix', url: '/guides/auth/auth-helpers/remix', items: [] }, - { name: 'SvelteKit', url: '/guides/auth/auth-helpers/sveltekit', items: [] }, + { name: 'Remix', url: '/guides/auth/auth-helpers/remix' }, + { name: 'SvelteKit', url: '/guides/auth/auth-helpers/sveltekit' }, ], }, { @@ -345,19 +331,16 @@ export const auth = { { name: 'Part One: JWTs', url: '/learn/auth-deep-dive/auth-deep-dive-jwts', - items: [], }, { name: 'Part Two: Row Level Security', url: '/learn/auth-deep-dive/auth-row-level-security', - items: [], }, - { name: 'Part Three: Policies', url: '/learn/auth-deep-dive/auth-policies', items: [] }, - { name: 'Part Four: GoTrue', url: '/learn/auth-deep-dive/auth-gotrue', items: [] }, + { name: 'Part Three: Policies', url: '/learn/auth-deep-dive/auth-policies' }, + { name: 'Part Four: GoTrue', url: '/learn/auth-deep-dive/auth-gotrue' }, { name: 'Part Five: Google OAuth', url: '/learn/auth-deep-dive/auth-google-oauth', - items: [], }, ], }, @@ -366,362 +349,117 @@ export const auth = { export const database = { icon: 'database', - label: 'Database', + title: 'Database', url: '/guides/database', items: [ - { name: 'Database Connections', url: '/guides/database/connecting-to-postgres', items: [] }, - { name: 'Tables and Data', url: '/guides/database/tables', items: [] }, - { name: 'Database Functions', url: '/guides/database/functions', items: [] }, - { name: 'Database Webhooks', url: '/guides/database/webhooks', items: [] }, - { name: 'Full Text Search', url: '/guides/database/full-text-search', items: [] }, - { name: 'Database Testing', url: '/guides/database/testing', items: [] }, - { - name: 'Serverless APIs', - url: undefined, - items: [ - { name: 'Overview', url: '/guides/database/api', items: [] }, - { name: 'Generating Types', url: '/guides/database/api/generating-types', items: [] }, - ], - }, + { name: 'Database Connections', url: '/guides/database/connecting-to-postgres' }, + { name: 'Tables and Data', url: '/guides/database/tables' }, + { name: 'Database Functions', url: '/guides/database/functions' }, + { name: 'Database Webhooks', url: '/guides/database/webhooks' }, + { name: 'Full Text Search', url: '/guides/database/full-text-search' }, + { name: 'Database Testing', url: '/guides/database/testing' }, { name: 'Extensions', url: undefined, items: [ - { name: 'Overview', url: '/guides/database/extensions', items: [] }, + { name: 'Overview', url: '/guides/database/extensions' }, { name: 'HypoPG: Hypothetical indexes', url: '/guides/database/extensions/hypopg', - items: [], }, { name: 'plv8: Javascript Language', url: '/guides/database/extensions/plv8', - items: [], }, - { name: 'http: RESTful Client', url: '/guides/database/extensions/http', items: [] }, + { name: 'http: RESTful Client', url: '/guides/database/extensions/http' }, { name: 'PGAudit: Postgres Auditing', url: '/guides/database/extensions/pgaudit', - items: [], }, { name: 'pgjwt: JSON Web Tokens', url: '/guides/database/extensions/pgjwt', - items: [], }, { name: 'PGroonga: Multilingual Full Text Search', url: '/guides/database/extensions/pgroonga', - items: [], }, { name: 'pgRouting: Geospatial Routing', url: '/guides/database/extensions/pgrouting', - items: [], }, { name: 'pg_cron: Job Scheduling', url: '/guides/database/extensions/pgcron', - items: [], }, { name: 'pg_graphql: GraphQL Support', url: '/guides/database/extensions/pg_graphql', - items: [], }, { name: 'pg_hashids: Short UIDs', url: '/guides/database/extensions/pg_hashids', - items: [], }, { name: 'pg_jsonschema: JSON Schema Validation', url: '/guides/database/extensions/pg_jsonschema', - items: [], }, { name: 'pg_net: Async Networking', url: '/guides/database/extensions/pgnet', - items: [], }, { name: 'pg_plan_filter: Restrict Total Cost', url: '/guides/database/extensions/pg_plan_filter', - items: [], }, { name: 'pg_stat_monitor: Extended Query Performance Monitoring', url: '/guides/database/extensions/pg_stat_monitor', - items: [], }, { name: 'pgvector: Embeddings and vector similarity', url: '/guides/database/extensions/pgvector', - items: [], }, { name: 'pg_stat_statements: SQL Planning and Execution Statistics', url: '/guides/database/extensions/pg_stat_statements', - items: [], }, { name: 'pg_repack: Storage Optimization', url: '/guides/database/extensions/pgrepack', - items: [], }, { name: 'PostGIS: Geo queries', url: '/guides/database/extensions/postgis', - items: [], }, { name: 'pg-safeupdate: Required Where Clauses', url: '/guides/database/extensions/pg-safeupdate', - items: [], }, { name: 'pgsodium: Encryption Features', url: '/guides/database/extensions/pgsodium', - items: [], }, - { name: 'pgTAP: Unit Testing', url: '/guides/database/extensions/pgtap', items: [] }, + { name: 'pgTAP: Unit Testing', url: '/guides/database/extensions/pgtap' }, { name: 'plpgsql_check: PL/pgSQL Linter', url: '/guides/database/extensions/plpgsql_check', - items: [], }, { name: 'timescaledb: Time-series data', url: '/guides/database/extensions/timescaledb', - items: [], }, { name: 'uuid-ossp: Unique Identifiers', url: '/guides/database/extensions/uuid-ossp', - items: [], }, { name: 'RUM: inverted index for full-text search', url: '/guides/database/extensions/rum', - items: [], }, { name: 'wrappers: 3rd Party Integrations', url: '/guides/database/extensions/wrappers', - items: [], - }, - ], - }, - { - name: 'Configuration', - url: undefined, - items: [ - { name: 'Timeouts', url: '/guides/database/timeouts', items: [] }, - { name: 'Replication', url: '/guides/database/replication', items: [] }, - { name: 'Passwords', url: '/guides/database/managing-passwords', items: [] }, - { name: 'Timezones', url: '/guides/database/managing-timezones', items: [] }, - ], - }, - ], -} - -export const functions = { - icon: 'functions', - label: 'Edge Functions', - url: '/guides/functions', - items: [ - { name: 'Quickstart', url: '/guides/functions/quickstart', items: [] }, - { name: 'Auth', url: '/guides/functions/auth', items: [] }, - { name: 'CI/CD Workflow', url: '/guides/functions/cicd-workflow', items: [] }, - { name: 'CORS', url: '/guides/functions/cors', items: [] }, - { name: 'Debugging', url: '/guides/functions/debugging', items: [] }, - { name: 'Import Maps', url: '/guides/functions/import-maps', items: [] }, - { name: 'Local Development', url: '/guides/functions/local-development', items: [] }, - { name: 'Managing Secrets', url: '/guides/functions/secrets', items: [] }, - { name: 'Schedule Functions', url: '/guides/functions/schedule-functions', items: [] }, - { - name: 'Examples', - url: '/guides/functions/examples', - items: [ - { - name: 'Cloudflare Turnstile', - url: '/guides/functions/examples/cloudflare-turnstile', - items: [], - }, - { - name: 'Connect to Postgres', - url: '/guides/functions/examples/connect-to-postgres', - items: [], - }, - { name: 'GitHub Actions', url: '/guides/functions/examples/github-actions', items: [] }, - { name: 'OG Image', url: '/guides/functions/examples/og-image', items: [] }, - { name: 'Storage Caching', url: '/guides/functions/examples/storage-caching', items: [] }, - { name: 'Stripe Webhooks', url: '/guides/functions/examples/stripe-webhooks', items: [] }, - { name: 'Telegram Bot', url: '/guides/functions/examples/telegram-bot', items: [] }, - { name: 'Upstash Redis', url: '/guides/functions/examples/upstash-redis', items: [] }, - ], - }, - ], -} - -export const realtime = { - icon: 'realtime', - label: 'Realtime', - url: '/guides/realtime', - items: [ - { name: 'Overview', url: '/guides/realtime', items: [] }, - { name: 'Quickstart', url: '/guides/realtime/quickstart', items: [] }, - { - name: 'Channels', - url: undefined, - items: [ - { name: 'Broadcast', url: '/guides/realtime/broadcast', items: [] }, - { name: 'Presence', url: '/guides/realtime/presence', items: [] }, - { name: 'Postgres Changes', url: '/guides/realtime/postgres-changes', items: [] }, - { name: 'Rate Limits', url: '/guides/realtime/rate-limits', items: [] }, - ], - }, - ], -} - -export const storage = { - icon: 'storage', - label: 'Storage', - url: '/guides/storage', - items: [ - { name: 'Overview', url: '/guides/storage', items: [] }, - { name: 'Quickstart', url: '/guides/storage/quickstart', items: [] }, - { name: 'Access Control', url: '/guides/storage/access-control', items: [] }, - { name: 'CDN', url: '/guides/storage/cdn', items: [] }, - { name: 'Image Transformations', url: '/guides/storage/image-transformations', items: [] }, - ], -} - -export const supabase_cli = { - icon: 'reference-cli', - title: 'Supabase CLI', - url: '/guides/cli', - items: [ - { name: 'Overview', url: '/guides/cli', items: [] }, - { name: 'Local Development', url: '/guides/cli/local-development', items: [] }, - { name: 'Managing Environments', url: '/guides/cli/managing-environments', items: [] }, - { - name: 'Reference', - url: undefined, - items: [ - { name: 'Commands', url: '/reference/cli/introduction', items: [] }, - { name: 'Configuration', url: '/reference/cli/config', items: [] }, - ], - }, - ], -} - -export const platform = { - icon: 'platform', - label: 'Platform', - url: '/guides/platform', - items: [ - { - name: 'Add-ons', - url: undefined, - items: [ - { name: 'Compute Add-ons', url: '/guides/platform/compute-add-ons', items: [] }, - { name: 'Custom Domains', url: '/guides/platform/custom-domains', items: [] }, - { name: 'Database Backups', url: '/guides/platform/backups', items: [] }, - ], - }, - { - name: 'Platform Management', - url: undefined, - items: [ - { name: 'Access Control', url: '/guides/platform/access-control', items: [] }, - { name: 'Database Usage', url: '/guides/platform/database-usage', 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: [] }, - { - name: 'Migrating and Upgrading', - url: '/guides/platform/migrating-and-upgrading-projects', - items: [], - }, - { name: 'Network Restrictions', url: '/guides/platform/network-restrictions', items: [] }, - { name: 'Performance Tuning', url: '/guides/platform/performance', items: [] }, - { name: 'Permissions', url: '/guides/platform/permissions', items: [] }, - { name: 'SSL Enforcement', url: '/guides/platform/ssl-enforcement', items: [] }, - ], - }, - { - name: 'Single sign-on', - url: undefined, - items: [ - { - name: 'Enable SSO for your organization', - url: '/guides/platform/sso', - items: [], - }, - { name: 'SSO with Azure AD', url: '/guides/platform/sso/azure', items: [] }, - { - name: 'SSO with Google Workspace', - url: '/guides/platform/sso/gsuite', - items: [], - }, - { name: 'SSO with Okta', url: '/guides/platform/sso/okta', items: [] }, - ], - }, - { - name: 'Go-live Checklist', - url: undefined, - items: [{ name: 'Production Readiness', url: '/guides/platform/going-into-prod', items: [] }], - }, - { - name: 'Troubleshooting', - url: undefined, - items: [ - { - name: 'HTTP and Project Issues', - url: '/guides/platform/troubleshooting', - items: [], - }, - ], - }, - ], -} - -export const resources = { - icon: 'resources', - label: 'Resources', - url: '/guides/resources', - items: [ - { name: 'Examples', url: '/guides/resources/examples', items: [] }, - { name: 'Glossary', url: '/guides/resources/glossary', items: [] }, - { - name: 'Migrate to Supabase', - url: '/guides/resources/migrating-to-supabase', - items: [ - { - name: 'Firebase Auth', - url: '/guides/resources/migrating-to-supabase/firebase-auth', - items: [], - }, - { - name: 'Firestore Data', - url: '/guides/resources/migrating-to-supabase/firestore-data', - items: [], - }, - { - name: 'Firebase Storage', - url: '/guides/resources/migrating-to-supabase/firebase-storage', - items: [], - }, - { - name: 'Heroku', - url: '/guides/resources/migrating-to-supabase/heroku', - items: [], - }, - { - name: 'Render', - url: '/guides/resources/migrating-to-supabase/render', - items: [], }, ], }, @@ -730,16 +468,338 @@ export const resources = { url: undefined, items: [ { - name: 'Drop all tables in schema', - url: '/guides/resources/postgres/dropping-all-tables-in-schema', + name: 'Managing Indexes', + url: '/guides/database/postgres/indexes', }, { - name: 'Select first row per group', - url: '/guides/resources/postgres/first-row-in-group', + name: 'Drop All Tables in Schema', + url: '/guides/database/postgres/dropping-all-tables-in-schema', }, { - name: 'Print PostgreSQL version', - url: '/guides/resources/postgres/which-version-of-postgres', + 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, + items: [ + { name: 'Timeouts', url: '/guides/database/timeouts' }, + { name: 'Replication', url: '/guides/database/replication' }, + { name: 'Passwords', url: '/guides/database/managing-passwords' }, + { name: 'Timezones', url: '/guides/database/managing-timezones' }, + ], + }, + ], +} + +export const api = { + icon: 'reference', + title: 'Serverless APIs', + url: '/guides/api', + items: [ + { name: 'Overview', url: '/guides/api', items: [] }, + { name: 'Quickstart', url: '/guides/api/quickstart', items: [] }, + { + name: 'Guides', + url: '/guides/api', + items: [ + { name: 'Creating API routes', url: '/guides/api/creating-routes', items: [] }, + { name: 'How API Keys work', url: '/guides/api/api-keys', items: [] }, + { name: 'Securing your API', url: '/guides/api/securing-your-api', items: [] }, + { + name: 'Querying joins and nested tables', + url: '/guides/api/joins-and-nesting', + items: [], + }, + ], + }, + { + name: 'REST & REALTIME', + url: undefined, + items: [ + { name: 'Auto-generated Docs', url: '/guides/api/rest/auto-generated-docs', items: [] }, + { name: 'Client Libraries', url: '/guides/api/rest/client-libs', items: [] }, + { name: 'Generating Types', url: '/guides/api/rest/generating-types', items: [] }, + ], + }, + { + name: 'GRAPHQL', + url: undefined, + items: [{ name: 'GraphiQL Documentation', url: '/guides/api/graphql/graphiql', items: [] }], + }, + ], +} + +export const functions = { + icon: 'functions', + title: 'Edge Functions', + url: '/guides/functions', + items: [ + { + name: 'Overview', + url: '/guides/functions', + }, + { + name: 'Quickstart', + url: '/guides/functions/quickstart', + }, + { + name: 'Features', + url: undefined, + items: [ + { name: 'TypeScript Support', url: '/guides/functions/typescript-support' }, + { name: 'Debugging Edge Functions', url: '/guides/functions/debugging' }, + { name: 'Managing packages using Import Maps', url: '/guides/functions/import-maps' }, + { name: 'Globally Distributed Deployments', url: '/guides/functions/global-deployments' }, + ], + }, + { + name: 'Guides', + url: undefined, + items: [ + { name: 'Developing Functions locally', url: '/guides/functions/local-development' }, + { name: 'Deploying with Git', url: '/guides/functions/cicd-workflow' }, + { name: 'Managing Secrets and Environment Variables', url: '/guides/functions/secrets' }, + { name: 'Integrating With Supabase Auth', url: '/guides/functions/auth' }, + { + name: 'Integrating with Supabase Storage', + url: '/guides/functions/storage-caching', + }, + { name: 'CORS support for Invoking from the browser', url: '/guides/functions/cors' }, + { name: 'Scheduling Functions', url: '/guides/functions/schedule-functions' }, + { + name: 'Connecting directly to Postgres', + url: '/guides/functions/connect-to-postgres', + }, + ], + }, + { + name: 'Examples', + url: '/guides/functions/examples', + items: [ + { name: 'Generating OpenAI GPT3 completions', url: '/guides/functions/examples/openai' }, + { name: 'Generating OG images ', url: '/guides/functions/examples/og-image' }, + { + name: 'CAPTCHA support with Cloudflare Turnstile', + url: '/guides/functions/examples/cloudflare-turnstile', + }, + { name: 'Building a Discord Bot', url: '/guides/functions/examples/discord-bot' }, + { name: 'Building a Telegram Bot', url: '/guides/functions/examples/telegram-bot' }, + { name: 'Handling Stripe Webhooks ', url: '/guides/functions/examples/stripe-webhooks' }, + { name: 'Integrating with Upstash Redis', url: '/guides/functions/examples/upstash-redis' }, + { name: 'Rate Limiting Edge Functions', url: '/guides/functions/examples/rate-limiting' }, + { + name: 'Taking Screenshots with Puppeteer', + url: '/guides/functions/examples/screenshots', + }, + ], + }, + ], +} + +export const realtime = { + icon: 'realtime', + title: 'Realtime', + url: '/guides/realtime', + items: [ + { + name: 'Overview', + url: '/guides/realtime', + }, + { + name: 'Quickstart', + url: '/guides/realtime/quickstart', + }, + { + name: 'Features', + url: undefined, + items: [ + { name: 'Channels', url: '/guides/realtime/channels' }, + { + name: 'Extensions', + url: '/guides/realtime/extensions', + items: [ + { name: 'Broadcast', url: '/guides/realtime/extensions/broadcast' }, + { name: 'Presence', url: '/guides/realtime/extensions/presence' }, + { + name: 'Postgres Changes', + url: '/guides/realtime/extensions/postgres-changes', + }, + ], + }, + ], + }, + { + name: 'Guides', + url: undefined, + items: [ + { + name: 'Subscribing to Database Changes', + url: '/guides/realtime/subscribing-to-database-changes', + }, + { + name: 'Bring Your Own Database', + url: '/guides/realtime/bring-your-own-database', + items: [], + }, + { + name: 'Using Realtime with Next.js', + url: '/guides/realtime/realtime-with-nextjs', + }, + ], + }, + { + name: 'Deep dive', + url: undefined, + items: [ + { name: 'Rate Limits', url: '/guides/realtime/rate-limits' }, + { name: 'Architecture', url: '/guides/realtime/architecture' }, + { name: 'Protocol', url: '/guides/realtime/protocol' }, + ], + }, + ], +} + +export const storage = { + icon: 'storage', + title: 'Storage', + url: '/guides/storage', + items: [ + { name: 'Overview', url: '/guides/storage' }, + { name: 'Quickstart', url: '/guides/storage/quickstart' }, + { name: 'Access Control', url: '/guides/storage/access-control' }, + { name: 'CDN', url: '/guides/storage/cdn' }, + { name: 'Image Transformations', url: '/guides/storage/image-transformations' }, + ], +} + +export const supabase_cli = { + icon: 'reference-cli', + title: 'Supabase CLI', + url: '/guides/cli', + items: [ + { name: 'Overview', url: '/guides/cli' }, + { name: 'Local Development', url: '/guides/cli/local-development' }, + { name: 'Managing Environments', url: '/guides/cli/managing-environments' }, + { + name: 'Reference', + url: undefined, + items: [ + { name: 'Commands', url: '/reference/cli/introduction' }, + { name: 'Configuration', url: '/reference/cli/config' }, + ], + }, + ], +} + +export const platform = { + icon: 'platform', + title: 'Platform', + url: '/guides/platform', + items: [ + { + name: 'Add-ons', + url: undefined, + items: [ + { name: 'Compute Add-ons', url: '/guides/platform/compute-add-ons' }, + { name: 'Custom Domains', url: '/guides/platform/custom-domains' }, + { name: 'Database Backups', url: '/guides/platform/backups' }, + ], + }, + { + name: 'Platform Management', + url: undefined, + items: [ + { name: 'Access Control', url: '/guides/platform/access-control' }, + { name: 'Database Size', url: '/guides/platform/database-size' }, + { name: 'HTTP Status Codes', url: '/guides/platform/http-status-codes' }, + { name: 'Logging', url: '/guides/platform/logs' }, + { name: 'Metrics', url: '/guides/platform/metrics' }, + { + name: 'Migrating and Upgrading', + url: '/guides/platform/migrating-and-upgrading-projects', + }, + { name: 'Network Restrictions', url: '/guides/platform/network-restrictions' }, + { name: 'Performance Tuning', url: '/guides/platform/performance' }, + { name: 'Permissions', url: '/guides/platform/permissions' }, + { name: 'SSL Enforcement', url: '/guides/platform/ssl-enforcement' }, + ], + }, + { + name: 'Billing', + url: undefined, + items: [{ name: 'Spend cap', url: '/guides/platform/spend-cap' }], + }, + { + name: 'Single sign-on', + url: undefined, + items: [ + { + name: 'Enable SSO for your organization', + url: '/guides/platform/sso', + }, + { name: 'SSO with Azure AD', url: '/guides/platform/sso/azure' }, + { + name: 'SSO with Google Workspace', + url: '/guides/platform/sso/gsuite', + }, + { name: 'SSO with Okta', url: '/guides/platform/sso/okta' }, + ], + }, + { + name: 'Go-live Checklist', + url: undefined, + items: [{ name: 'Production Readiness', url: '/guides/platform/going-into-prod' }], + }, + { + name: 'Troubleshooting', + url: undefined, + items: [ + { + name: 'HTTP and Project Issues', + url: '/guides/platform/troubleshooting', + }, + ], + }, + ], +} + +export const resources = { + icon: 'resources', + title: 'Resources', + url: '/guides/resources', + items: [ + { name: 'Examples', url: '/guides/resources/examples' }, + { name: 'Glossary', url: '/guides/resources/glossary' }, + { + name: 'Migrate to Supabase', + url: '/guides/resources/migrating-to-supabase', + items: [ + { + name: 'Firebase Auth', + url: '/guides/resources/migrating-to-supabase/firebase-auth', + }, + { + name: 'Firestore Data', + url: '/guides/resources/migrating-to-supabase/firestore-data', + }, + { + name: 'Firebase Storage', + url: '/guides/resources/migrating-to-supabase/firebase-storage', + }, + { + name: 'Heroku', + url: '/guides/resources/migrating-to-supabase/heroku', + }, + { + name: 'Render', + url: '/guides/resources/migrating-to-supabase/render', }, ], }, @@ -751,50 +811,57 @@ export const self_hosting = { icon: 'resources', url: '/guides/self-hosting', items: [ - { name: 'Overview', url: '/guides/self-hosting', items: [] }, - { name: 'Self-Hosting with Docker', url: '/guides/self-hosting/docker', items: [] }, + { name: 'Overview', url: '/guides/self-hosting' }, + { name: 'Self-Hosting with Docker', url: '/guides/self-hosting/docker' }, { name: 'Auth Server', items: [ - { name: 'Reference', url: '/reference/self-hosting-auth/introduction', items: [] }, - { name: 'Configuration', url: '/guides/self-hosting/auth/config', items: [] }, + { name: 'Reference', url: '/reference/self-hosting-auth/introduction' }, + { name: 'Configuration', url: '/guides/self-hosting/auth/config' }, ], }, { name: 'Storage Server', items: [ - { name: 'Reference', url: '/reference/self-hosting-storage/introduction', items: [] }, - { name: 'Configuration', url: '/guides/self-hosting/storage/config', items: [] }, + { name: 'Reference', url: '/reference/self-hosting-storage/introduction' }, + { name: 'Configuration', url: '/guides/self-hosting/storage/config' }, ], }, { name: 'Realtime Server', items: [ - { name: 'Reference', url: '/reference/self-hosting-realtime/introduction', items: [] }, - { name: 'Configuration', url: '/guides/self-hosting/realtime/config', items: [] }, + { name: 'Reference', url: '/reference/self-hosting-realtime/introduction' }, + { name: 'Configuration', url: '/guides/self-hosting/realtime/config' }, + ], + }, + { + name: 'Analytics Server', + items: [ + { name: 'Reference', url: '/reference/self-hosting-analytics/introduction', items: [] }, + { name: 'Configuration', url: '/guides/self-hosting/analytics/config', items: [] }, ], }, ], } export const migrate = { - label: 'Migrate to Supabase', + title: 'Migrate to Supabase', url: '/guides/migrate', items: [ - { name: 'Firebase Auth', url: '/guides/migrations/firebase-auth', items: [] }, - { name: 'Firestore Data', url: '/guides/migrations/firestore-data', items: [] }, - { name: 'Firebase Storage', url: '/guides/migrations/firebase-storage', items: [] }, - { name: 'Heroku', url: '/guides/migrations/heroku', items: [] }, - { name: 'Render', url: '/guides/migrations/render', items: [] }, + { name: 'Firebase Auth', url: '/guides/migrations/firebase-auth' }, + { name: 'Firestore Data', url: '/guides/migrations/firestore-data' }, + { name: 'Firebase Storage', url: '/guides/migrations/firebase-storage' }, + { name: 'Heroku', url: '/guides/migrations/heroku' }, + { name: 'Render', url: '/guides/migrations/render' }, ], } export const integrations = { icon: 'integrations', - label: 'Integrations', + title: 'Integrations', url: '/guides/integrations', items: [ - { name: 'Overview', url: '/guides/integrations/integrations', items: [] }, + { name: 'Overview', url: '/guides/integrations/integrations' }, { name: 'Auth', url: undefined, @@ -802,52 +869,52 @@ export const integrations = { { name: 'Auth0', url: '/guides/integrations/auth0', - items: [], }, - { name: 'Authsignal', url: '/guides/integrations/authsignal', items: [] }, - { name: 'Clerk', url: '/guides/integrations/clerk', items: [] }, - { name: 'keyri', url: '/guides/integrations/keyri', items: [] }, - { name: 'Stytch', url: '/guides/integrations/stytch', items: [] }, - { name: 'SuperTokens', url: '/guides/integrations/supertokens', items: [] }, + { name: 'Authsignal', url: '/guides/integrations/authsignal' }, + { name: 'Clerk', url: '/guides/integrations/clerk' }, + { name: 'keyri', url: '/guides/integrations/keyri' }, + { name: 'Stytch', url: '/guides/integrations/stytch' }, + { name: 'SuperTokens', url: '/guides/integrations/supertokens' }, ], }, { name: 'Caching / Offline-first', url: undefined, - items: [{ name: 'Polyscale', url: '/guides/integrations/polyscale', items: [] }], + items: [{ name: 'Polyscale', url: '/guides/integrations/polyscale' }], }, { name: 'Developer Tools', url: undefined, items: [ - { name: 'Estuary', url: '/guides/integrations/estuary', items: [] }, - { name: 'pgMustard', url: '/guides/integrations/pgmustard', items: [] }, - { name: 'Prisma', url: '/guides/integrations/prisma', items: [] }, - { name: 'Sequin', url: '/guides/integrations/sequin', items: [] }, - { name: 'Snaplet', url: '/guides/integrations/snaplet', items: [] }, - { name: 'Vercel', url: '/guides/integrations/vercel', items: [] }, - { name: 'WeWeb', url: '/guides/integrations/weweb', items: [] }, - { name: 'Zuplo', url: '/guides/integrations/zuplo', items: [] }, + { name: 'Estuary', url: '/guides/integrations/estuary' }, + { name: 'OpenAI', url: '/guides/functions/examples/openai' }, + { name: 'pgMustard', url: '/guides/integrations/pgmustard' }, + { name: 'Prisma', url: '/guides/integrations/prisma' }, + { name: 'Sequin', url: '/guides/integrations/sequin' }, + { name: 'Snaplet', url: '/guides/integrations/snaplet' }, + { name: 'Vercel', url: '/guides/integrations/vercel' }, + { name: 'Upstash Redis', url: '/guides/functions/examples/upstash-redis' }, + { name: 'WeWeb', url: '/guides/integrations/weweb' }, + { name: 'Zuplo', url: '/guides/integrations/zuplo' }, ], }, { name: 'Low-code', url: undefined, items: [ - { name: 'Appsmith', url: '/guides/integrations/appsmith', items: [] }, - { name: 'Dashibase', url: '/guides/integrations/dashibase', items: [] }, - { name: 'DhiWise', url: '/guides/integrations/dhiwise', items: [] }, - { name: 'Directus', url: '/guides/integrations/directus', items: [] }, - { name: 'Draftbit', url: '/guides/integrations/draftbit', items: [] }, - { name: 'FlutterFlow', url: '/guides/integrations/flutterflow', items: [] }, - { name: 'Plasmic', url: '/guides/integrations/plasmic', items: [] }, - { name: 'ILLA', url: '/guides/integrations/illa', items: [] }, + { name: 'Appsmith', url: '/guides/integrations/appsmith' }, + { name: 'DhiWise', url: '/guides/integrations/dhiwise' }, + { name: 'Directus', url: '/guides/integrations/directus' }, + { name: 'Draftbit', url: '/guides/integrations/draftbit' }, + { name: 'FlutterFlow', url: '/guides/integrations/flutterflow' }, + { name: 'Plasmic', url: '/guides/integrations/plasmic' }, + { name: 'ILLA', url: '/guides/integrations/illa' }, ], }, { name: 'Messaging', url: undefined, - items: [{ name: 'OneSignal', url: '/guides/integrations/onesignal', items: [] }], + items: [{ name: 'OneSignal', url: '/guides/integrations/onesignal' }], }, ], } @@ -863,35 +930,38 @@ export const reference = { name: 'supabase-js', url: '/reference/javascript/start', level: 'reference_javascript', - items: [], icon: '/img/icons/menu/reference-javascript', }, { name: 'supabase-dart', url: '/reference/dart/start', level: 'reference_dart', - items: [], icon: '/img/icons/menu/reference-dart', }, { name: 'supabase-csharp', url: '/reference/csharp/start', level: 'reference_csharp', - items: [], icon: '/img/icons/menu/reference-csharp', }, { name: 'supbase-python', url: '/reference/python/start', level: 'reference_python', - items: [], icon: '/img/icons/menu/reference-python', }, + { + name: 'supbase-swift', + url: '/reference/swift/start', + level: 'reference_swift', + items: [], + icon: '/img/icons/menu/reference-swift', + }, // { // name: 'supabase-python', // url: '/reference/python/start', // level: 'reference_python', - // items: [], + // // icon: '/img/icons/menu/reference-javascript', // }, ], @@ -902,13 +972,11 @@ export const reference = { { name: 'Supabase CLI', url: '/reference/cli/start', - items: [], icon: '/img/icons/menu/reference-cli', }, { name: 'Management API', url: '/reference/javascript', - items: [], icon: '/img/icons/menu/reference-api', }, ], @@ -958,6 +1026,13 @@ export const reference_python_v2 = { parent: '/reference', } +export const reference_swift_v1 = { + icon: 'reference-swift', + title: 'swift', + url: 'guides/reference/swift', + parent: '/reference', +} + export const reference_cli = { icon: 'reference-cli', title: 'Supabase CLI', @@ -986,35 +1061,42 @@ export const reference_self_hosting_storage = { } export const reference_self_hosting_realtime = { - icon: 'reference-auth', + icon: 'reference-realtime', title: 'Self-Hosting Realtime', url: '/guides/reference/self-hosting/realtime', parent: '/reference', } +export const reference_self_hosting_analytics = { + icon: 'reference-analytics', + title: 'Self-Hosting Analytics', + url: '/guides/reference/self-hosting/analytics', + parent: '/reference', +} + // export const reference: [ // { // label: 'Official' // items: [ -// { name: 'Reference Documentation'; url: '/reference'; items: [] }, -// { name: 'Supabase JavaScript Library'; url: '/reference/javascript'; items: [] }, -// { name: 'Supabase Flutter Library'; url: '/reference/dart'; items: [] }, -// { name: 'Supabase CLI'; url: '/reference/cli'; items: [] }, -// { name: 'Management API'; url: '/reference/api'; items: [] } +// { name: 'Reference Documentation'; url: '/reference'; }, +// { name: 'Supabase JavaScript Library'; url: '/reference/javascript'; }, +// { name: 'Supabase Flutter Library'; url: '/reference/dart'; }, +// { name: 'Supabase CLI'; url: '/reference/cli'; }, +// { name: 'Management API'; url: '/reference/api'; } // ] // }, // { // label: 'Self-hosting' // items: [ -// { name: 'Auth Server'; url: '/reference/auth'; items: [] }, -// { name: 'Storage Server'; url: '/reference/storage'; items: [] } +// { name: 'Auth Server'; url: '/reference/auth'; }, +// { name: 'Storage Server'; url: '/reference/storage'; } // ] // } // { // label: 'Clients', // items: [ -// { name: 'Auth Server', url: '/reference/auth', items: [] }, -// { name: 'Storage Server', url: '/reference/storage', items: [] }, +// { name: 'Auth Server', url: '/reference/auth'}, +// { name: 'Storage Server', url: '/reference/storage'}, // ], // }, // 'reference/javascript': SupabaseJsV2Nav, @@ -1058,6 +1140,13 @@ export const references = [ icon: '/docs/img/icons/c-sharp-icon.svg', url: '/reference/csharp/start', }, + { + label: 'supabase-swift', + versions: ['v1'], + description: 'something about the reference', + icon: '/docs/img/icons/swift-icon.svg', + url: '/reference/swift/start', + }, ], }, { diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index c00988dd8d3..945d817abcf 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -15,6 +15,8 @@ import spec_dart_v0 from '~/../../spec/supabase_dart_v0.yml' assert { type: 'yml import spec_csharp_v0 from '~/../../spec/supabase_csharp_v0.yml' assert { type: 'yml' } // @ts-expect-error import spec_python_v2 from '~/../../spec/supabase_py_v2.yml' assert { type: 'yml' } +// @ts-expect-error +import spec_swift_v1 from '~/../../spec/supabase_swift_v1.yml' assert { type: 'yml' } // import { gen_v3 } from '~/lib/refGenerator/helpers' import apiCommonSections from '~/../../spec/common-api-sections.json' @@ -22,6 +24,7 @@ import cliCommonSections from '~/../../spec/common-cli-sections.json' import libCommonSections from '~/../../spec/common-client-libs-sections.json' import authServerCommonSections from '~/../../spec/common-self-hosting-auth-sections.json' import realtimeServerCommonSections from '~/../../spec/common-self-hosting-realtime-sections.json' +import analyticsServerCommonSections from '~/../../spec/common-self-hosting-analytics-sections.json' import storageServerCommonSections from '~/../../spec/common-self-hosting-storage-sections.json' import { flattenSections } from '~/lib/helpers' import NavigationMenuHome from './HomeMenu' @@ -54,22 +57,26 @@ export type RefIdOptions = | 'reference_dart_v1' | 'reference_csharp_v0' | 'reference_python_v2' + | 'reference_swift_v1' | 'reference_cli' | 'reference_api' | 'reference_self_hosting_auth' | 'reference_self_hosting_storage' | 'reference_self_hosting_realtime' + | 'reference_self_hosting_analytics' export type RefKeyOptions = | 'javascript' | 'dart' | 'csharp' | 'python' + | 'swift' | 'cli' | 'api' | 'self-hosting-auth' | 'self-hosting-storage' | 'self-hosting-realtime' + | 'self-hosting-analytics' const NavigationMenu = () => { const router = useRouter() @@ -85,6 +92,9 @@ const NavigationMenu = () => { case url.includes(`/docs/guides/database`) && url: menuState.setMenuLevelId('database') break + case url.includes(`/docs/guides/api`) && url: + menuState.setMenuLevelId('api') + break case url.includes(`/docs/guides/auth`) && url: menuState.setMenuLevelId('auth') break @@ -132,10 +142,14 @@ const NavigationMenu = () => { case url.includes(`/docs/reference/csharp`) && url: menuState.setMenuLevelId('reference_csharp_v0') break - // puthon v2 (latest) + // python v2 (latest) case url.includes(`/docs/reference/python`) && url: menuState.setMenuLevelId('reference_python_v2') break + // swift v1 (latest) + case url.includes(`/docs/reference/swift`) && url: + menuState.setMenuLevelId('reference_swift_v1') + break case url.includes(`/docs/reference/cli/config`) && url: menuState.setMenuLevelId('supabase_cli') break @@ -154,6 +168,9 @@ const NavigationMenu = () => { case url.includes(`/docs/reference/self-hosting-realtime`) && url: menuState.setMenuLevelId('reference_self_hosting_realtime') break + case url.includes(`/docs/reference/self-hosting-analytics`) && url: + menuState.setMenuLevelId('reference_self_hosting_analytics') + break default: break @@ -174,6 +191,7 @@ const NavigationMenu = () => { const isHomeActive = 'home' === level const isGettingStartedActive = 'gettingstarted' === level const isDatabaseActive = 'database' === level + const isApiActive = 'api' === level const isAuthActive = 'auth' === level const isFunctionsActive = 'functions' === level const isRealtimeActive = 'realtime' === level @@ -191,11 +209,13 @@ const NavigationMenu = () => { const isReference_Dart_V1 = 'reference_dart_v1' === level const isReference_Csharp_V0 = 'reference_csharp_v0' === level const isReference_Python_V2 = 'reference_python_v2' === level + const isReference_Swift_V1 = 'reference_swift_v1' === level const isReference_Cli = 'reference_cli' === level const isReference_Api = 'reference_api' === level const isReference_Self_Hosting_Auth = 'reference_self_hosting_auth' === level const isReference_Self_Hosting_Storage = 'reference_self_hosting_storage' === level const isReference_Self_Hosting_Realtime = 'reference_self_hosting_realtime' === level + const isReference_Self_Hosting_Analytics = 'reference_self_hosting_analytics' === level return (
    @@ -203,9 +223,14 @@ const NavigationMenu = () => { + - + @@ -254,7 +279,14 @@ const NavigationMenu = () => { lib="csharp" spec={spec_csharp_v0} /> - + { id={'reference_self_hosting_realtime'} active={isReference_Self_Hosting_Realtime} commonSections={realtimeServerCommonSections} - lib="self-hosting-auth" + lib="self-hosting-realtime" + /> +
    ) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx index 7e52d424832..fe3ea4e290e 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx @@ -2,14 +2,19 @@ import * as Accordion from '@radix-ui/react-accordion' import { useRouter } from 'next/router' import React from 'react' import NavigationMenuGuideListItems from './NavigationMenuGuideListItems' +import * as NavItems from './NavigationMenu.constants' interface Props { id: string active: boolean + collapsible?: boolean + value?: string[] } -const NavigationMenuGuideList: React.FC = ({ id, active }) => { +const NavigationMenuGuideList: React.FC = ({ id, active, value }) => { const router = useRouter() + const menu = NavItems[id] + // get url const url = router.asPath @@ -26,22 +31,22 @@ const NavigationMenuGuideList: React.FC = ({ id, active }) => { return ( - + ) } diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx index ee9bbf30f38..285c63bcf09 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx @@ -21,7 +21,11 @@ const HeaderImage = React.memo(function HeaderImage(props: any) { ) }) -const HeaderLink = React.memo(function HeaderLink(props: any) { +const HeaderLink = React.memo(function HeaderLink(props: { + title: string + id: string + url: string +}) { const router = useRouter() return ( @@ -143,9 +147,7 @@ const ContentLink = React.memo(function ContentLink(props: any) { }) const Content = (props) => { - const { id } = props - - const menu = NavItems[id] + const { menu, id } = props return (
      diff --git a/apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx b/apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx index 3748821840b..270269d5085 100644 --- a/apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx @@ -1,11 +1,19 @@ +import { useTheme } from 'common/Providers' import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/router' import { FC, useEffect, useState } from 'react' -import { Button, IconCommand, IconMenu, IconMoon, IconSearch, IconSun, Input, Listbox } from 'ui' -import { SearchButton } from '~/components/DocSearch' +import { + Button, + IconCommand, + IconMenu, + IconMoon, + IconSearch, + IconSun, + Listbox, + SearchButton, +} from 'ui' import { REFERENCES } from '~/components/Navigation/Navigation.constants' -import { useTheme } from 'common/Providers' import { getPageType } from '~/lib/helpers' @@ -36,14 +44,6 @@ const TopNavBar: FC = () => { { text: 'Reference', key: 'reference', link: '/reference' }, ] - const toggleDarkMode = () => { - localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString()) - toggleTheme() - - const key = localStorage.getItem('supabaseDarkMode') - document.documentElement.className = key === 'true' ? 'dark' : '' - } - const onSelectVersion = (version: string) => { // [Joshen] Ideally we use but this works for now if (!library) return @@ -147,7 +147,7 @@ const TopNavBar: FC = () => {
      • -
        +
        toggleTheme()}> {isDarkMode ? ( { const { isDarkMode, toggleTheme } = useTheme() const [mounted, setMounted] = useState(false) const [mobileMenuOpen, setMobileMenuOpen] = useState(false) - const { open: openClippy } = useClippy() const { asPath, push } = useRouter() const pathSegments = asPath.split('/') @@ -40,15 +35,6 @@ const TopNavBarRef: FC = () => { { text: 'Reference', key: 'reference', link: '/reference' }, ] - const toggleDarkMode = () => { - localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString()) - toggleTheme() - - const key = localStorage.getItem('supabaseDarkMode') - document.documentElement.className = key === 'true' ? 'dark' : '' - document.documentElement.style.colorScheme = key === 'true' ? 'dark' : '' - } - const onSelectVersion = (version: string) => { // [Joshen] Ideally we use but this works for now if (!library) return @@ -117,16 +103,6 @@ const TopNavBarRef: FC = () => {
        - -
        - Clippy -
    • -
      +
      toggleTheme()}> {isDarkMode ? ( ), - // pre: (props: any) => { - // const linesToHighlight = parseNumericRange(props.lines ?? '') - // return - // }, - mono: (props: any) => {props.children}, - // inlineCode: (props: { children: string }) => , RefSubLayout, - // CH, - code: (props: any) => , RefHeaderSection: (props: any) => , CliGlobalFlagsHandler: () => , Options, Param, - img: (props: any) => { - return ( - - - - ) - }, } export default components diff --git a/apps/docs/components/reference/ApiOperationSection.tsx b/apps/docs/components/reference/ApiOperationSection.tsx index f97549b4775..6713809bccf 100644 --- a/apps/docs/components/reference/ApiOperationSection.tsx +++ b/apps/docs/components/reference/ApiOperationSection.tsx @@ -1,5 +1,4 @@ -import { Tabs } from '~/../../packages/ui' -import CodeBlock from '~/components/CodeBlock/CodeBlock' +import { CodeBlock, Tabs } from 'ui' import Param from '~/components/Params' import RefSubLayout from '~/layouts/ref/RefSubLayout' diff --git a/apps/docs/components/reference/CLICommandSection.tsx b/apps/docs/components/reference/CLICommandSection.tsx index 9217dd4980f..d20c1db8086 100644 --- a/apps/docs/components/reference/CLICommandSection.tsx +++ b/apps/docs/components/reference/CLICommandSection.tsx @@ -1,8 +1,7 @@ import ReactMarkdown from 'react-markdown' -import { IconChevronRight, Tabs } from '~/../../packages/ui' +import { CodeBlock, IconChevronRight, Tabs } from 'ui' // @ts-expect-error import spec from '~/../../spec/cli_v1_commands.yaml' assert { type: 'yml' } -import CodeBlock from '~/components/CodeBlock/CodeBlock' import Options from '~/components/Options' import Param from '~/components/Params' import RefSubLayout from '~/layouts/ref/RefSubLayout' diff --git a/apps/docs/components/reference/RefFunctionSection.tsx b/apps/docs/components/reference/RefFunctionSection.tsx index 8b228a6bfa6..ddb35ba930b 100644 --- a/apps/docs/components/reference/RefFunctionSection.tsx +++ b/apps/docs/components/reference/RefFunctionSection.tsx @@ -1,7 +1,6 @@ import ReactMarkdown from 'react-markdown' -import { IconDatabase, Tabs } from 'ui' -import CodeBlock from '~/components/CodeBlock/CodeBlock' +import { CodeBlock, IconDatabase, Tabs } from 'ui' import Options from '~/components/Options' import Param from '~/components/Params' diff --git a/apps/docs/components/reference/RefSectionHandler.tsx b/apps/docs/components/reference/RefSectionHandler.tsx index 76f1e0cc3e1..b1b5bebf134 100644 --- a/apps/docs/components/reference/RefSectionHandler.tsx +++ b/apps/docs/components/reference/RefSectionHandler.tsx @@ -1,3 +1,4 @@ +import Head from 'next/head' import { useRouter } from 'next/router' import { useEffect } from 'react' @@ -30,51 +31,78 @@ const RefSectionHandler = (props: RefSectionHandlerProps) => { } }) - return ( - - {props.sections.map((x, i) => { - switch (x.type) { - case 'markdown': - const markdownData = props.pageProps.docs.find((doc) => doc.id === x.id) + function getPageTitle() { + switch (props.type) { + case 'client-lib': + return props.spec.info.title + case 'cli': + return 'Supabase CLI reference' + case 'api': + return 'Supabase API reference' + default: + return 'Supabase Docs' + } + } - return - break - case 'function': - return ( - - ) - case 'cli-command': - return - break - case 'operation': - return ( - - ) - default: - return ( - - ) - break - } - })} - + const pageTitle = getPageTitle() + + return ( + <> + + {pageTitle} + + + + + + + {props.sections.map((x, i) => { + switch (x.type) { + case 'markdown': + const markdownData = props.pageProps.docs.find((doc) => doc.id === x.id) + + return + break + case 'function': + return ( + + ) + case 'cli-command': + return + break + case 'operation': + return ( + + ) + default: + return ( + + ) + break + } + })} + + ) } diff --git a/apps/docs/components/reference/Reference.types.ts b/apps/docs/components/reference/Reference.types.ts index 04a9c04b7da..4ff54b1b991 100644 --- a/apps/docs/components/reference/Reference.types.ts +++ b/apps/docs/components/reference/Reference.types.ts @@ -34,13 +34,13 @@ export interface IFunctionDefinition { } export interface ICommonFunc { - id: string + id?: string title: string - slug: string - product: string - type: string + slug?: string + product?: string + type?: string parent?: string - items: ICommonFunc[] + items?: ICommonFunc[] } export interface IRefFunctionSection { diff --git a/apps/docs/data/extensions.json b/apps/docs/data/extensions.json index a084c22663f..c46542b2cfd 100644 --- a/apps/docs/data/extensions.json +++ b/apps/docs/data/extensions.json @@ -1,499 +1,462 @@ [ { "name": "address_standardizer", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "Used to parse an address into constituent elements. Generally used to support geocoding address normalization step." + "comment": "Used to parse an address into constituent elements. Generally used to support geocoding address normalization step.", + "tags": ["Utility"], + "link": "http://supabase.com/docs/guides/extensions/etc/etc/etc" }, { "name": "address_standardizer-3", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "Used to parse an address into constituent elements. Generally used to support geocoding address normalization step." + "comment": "Used to parse an address into constituent elements. Generally used to support geocoding address normalization step.", + "tags": ["Utility"] }, { "name": "address_standardizer_data_us", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "Address Standardizer US dataset example" + "comment": "Address Standardizer US dataset example", + "tags": ["Dataset"] }, { "name": "address_standardizer_data_us-3", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "Address Standardizer US dataset example" + "comment": "Address Standardizer US dataset example", + "tags": ["Dataset"] }, { "name": "adminpack", - "schema": null, - "default_version": "2.0", - "installed_version": null, - "comment": "administrative functions for PostgreSQL" + "comment": "administrative functions for PostgreSQL", + "tags": ["Admin", "Utility"] }, { "name": "amcheck", - "schema": null, - "default_version": "1.2", - "installed_version": null, - "comment": "functions for verifying relation integrity" + "comment": "functions for verifying relation integrity", + "tags": ["Admin", "Utility"] }, { "name": "autoinc", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "functions for autoincrementing fields" + "comment": "functions for autoincrementing fields", + "tags": ["Utility"] }, { "name": "bloom", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "bloom access method - signature file based index" + "comment": "bloom access method - signature file based index", + "tags": ["Index"] }, { "name": "btree_gin", - "schema": null, - "default_version": "1.3", - "installed_version": null, - "comment": "support for indexing common datatypes in GIN" + "comment": "support for indexing common datatypes in GIN", + "tags": ["Index"] }, { "name": "btree_gist", - "schema": null, - "default_version": "1.5", - "installed_version": null, - "comment": "support for indexing common datatypes in GiST" + "comment": "support for indexing common datatypes in GiST", + "tags": ["Index"] }, { "name": "citext", - "schema": null, - "default_version": "1.6", - "installed_version": null, - "comment": "data type for case-insensitive character strings" + "comment": "data type for case-insensitive character strings", + "tags": ["Data Type"] }, { "name": "cube", - "schema": null, - "default_version": "1.4", - "installed_version": null, - "comment": "data type for multidimensional cubes" + "comment": "data type for multidimensional cubes", + "tags": ["Data Type"] }, { "name": "dblink", - "schema": null, - "default_version": "1.2", - "installed_version": null, - "comment": "connect to other PostgreSQL databases from within a database" + "comment": "connect to other PostgreSQL databases from within a database", + "tags": ["Admin", "Utility"] }, { "name": "dict_int", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "text search dictionary template for integers" + "comment": "text search dictionary template for integers", + "tags": ["Search"] }, { "name": "dict_xsyn", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "text search dictionary template for extended synonym processing" + "comment": "text search dictionary template for extended synonym processing", + "tags": ["Search"] }, { "name": "earthdistance", - "schema": null, - "default_version": "1.1", - "installed_version": null, - "comment": "calculate great-circle distances on the surface of the Earth" + "comment": "calculate great-circle distances on the surface of the Earth", + "tags": ["Geo", "Utility"] }, { "name": "file_fdw", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "foreign-data wrapper for flat file access" + "comment": "foreign-data wrapper for flat file access", + "tags": ["Admin", "Utility"] }, { "name": "fuzzystrmatch", - "schema": null, - "default_version": "1.1", - "installed_version": null, - "comment": "determine similarities and distance between strings" + "comment": "determine similarities and distance between strings", + "tags": ["Search"] }, { "name": "hstore", - "schema": null, - "default_version": "1.6", - "installed_version": null, - "comment": "data type for storing sets of (key, value) pairs" + "comment": "data type for storing sets of (key, value) pairs", + "tags": ["Data Type"] + }, + { + "name": "hypopg", + "comment": "Hypothetical indexes for PostgreSQL", + "link": "https://supabase.com/docs/guides/database/extensions/hypopg", + "tags": ["Admin", "Index"] }, { "name": "http", - "schema": "extensions", - "default_version": "1.3", - "installed_version": "1.3", - "comment": "HTTP client for PostgreSQL, allows web page retrieval inside the database." + "comment": "HTTP client for PostgreSQL, allows web page retrieval inside the database.", + "link": "https://supabase.com/docs/guides/database/extensions/http", + "tags": ["Utility"] }, { "name": "insert_username", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "functions for tracking who changed a table" + "comment": "functions for tracking who changed a table", + "tags": ["Audit", "Utility"] + }, + { + "name": "old_snapshot", + "comment": "utilities in support of old_snapshot_threshold", + "tags": ["Admin", "Utility"] }, { "name": "intagg", - "schema": null, - "default_version": "1.1", - "installed_version": null, - "comment": "integer aggregator and enumerator (obsolete)" + "comment": "integer aggregator and enumerator (obsolete)", + "tags": ["Utility"] }, { "name": "intarray", - "schema": null, - "default_version": "1.2", - "installed_version": null, - "comment": "functions, operators, and index support for 1-D arrays of integers" + "comment": "functions, operators, and index support for 1-D arrays of integers", + "tags": ["Utility"] }, { "name": "isn", - "schema": null, - "default_version": "1.2", - "installed_version": null, - "comment": "data types for international product numbering standards" + "comment": "data types for international product numbering standards", + "tags": ["Data Type"] }, { "name": "lo", - "schema": null, - "default_version": "1.1", - "installed_version": null, - "comment": "Large Object maintenance" + "comment": "Large Object maintenance", + "tags": ["Data Type"] }, { "name": "ltree", - "schema": null, - "default_version": "1.1", - "installed_version": null, - "comment": "data type for hierarchical tree-like structures" + "comment": "data type for hierarchical tree-like structures", + "tags": ["Data Type"] }, { "name": "moddatetime", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "functions for tracking last modification time" + "comment": "functions for tracking last modification time", + "tags": ["Audit", "Utility"] }, { "name": "pageinspect", - "schema": null, - "default_version": "1.7", - "installed_version": null, - "comment": "inspect the contents of database pages at a low level" + "comment": "inspect the contents of database pages at a low level", + "tags": ["Admin", "Utility"] }, { "name": "pg_buffercache", - "schema": null, - "default_version": "1.3", - "installed_version": null, - "comment": "examine the shared buffer cache" + "comment": "examine the shared buffer cache", + "tags": ["Admin", "Utility"] }, { "name": "pg_cron", - "schema": "extensions", - "default_version": "1.3", - "installed_version": "1.3", - "comment": "Job scheduler for PostgreSQL" + "comment": "Job scheduler for PostgreSQL", + "link": "https://supabase.com/docs/guides/database/extensions/pgcron", + "tags": ["Utility"] }, { "name": "pg_freespacemap", - "schema": null, - "default_version": "1.2", - "installed_version": null, - "comment": "examine the free space map (FSM)" + "comment": "examine the free space map (FSM)", + "tags": ["Admin", "Utility"] + }, + { + "name": "pg_graphql", + "comment": "pg_graphql: GraphQL support", + "link": "https://supabase.com/docs/guides/database/extensions/pg_graphql", + "tags": ["Utility"] + }, + { + "name": "pg_hashids", + "comment": "pg_hashids", + "link": "https://supabase.com/docs/guides/database/extensions/pg_hashids", + "tags": ["Utility"] + }, + { + "name": "pg_jsonschema", + "comment": "pg_jsonschema", + "link": "https://supabase.com/docs/guides/database/extensions/pg_jsonschema", + "tags": ["Utility"] + }, + { + "name": "pg_net", + "comment": "Async HTTP", + "link": "https://supabase.com/docs/guides/database/extensions/pgnet", + "tags": ["Utility", "Notifications"] }, { "name": "pg_prewarm", - "schema": null, - "default_version": "1.2", - "installed_version": null, - "comment": "prewarm relation data" + "comment": "prewarm relation data", + "tags": ["Admin", "Utility"] + }, + { + "name": "pg_stat_monitor", + "comment": "The pg_stat_monitor is a PostgreSQL Query Performance Monitoring tool, based on PostgreSQL contrib module pg_stat_statements. pg_stat_monitor provides aggregated statistics, client information, plan details including plan, and histogram information.", + "link": "https://supabase.com/docs/guides/database/extensions/pg_stat_monitor", + "tags": ["Utility"] }, { "name": "pg_stat_statements", - "schema": null, - "default_version": "1.7", - "installed_version": null, - "comment": "track execution statistics of all SQL statements executed" + "comment": "track execution statistics of all SQL statements executed", + "link": "https://supabase.com/docs/guides/database/extensions/pg_stat_statements", + "tags": ["Admin", "Utility"] + }, + { + "name": "pg_surgery", + "comment": "extension to perform surgery on a damaged relation", + "tags": ["Admin", "Utility"] }, { "name": "pg_trgm", - "schema": null, - "default_version": "1.4", - "installed_version": null, - "comment": "text similarity measurement and index searching based on trigrams" + "comment": "text similarity measurement and index searching based on trigrams", + "tags": ["Search"] }, { "name": "pg_visibility", - "schema": null, - "default_version": "1.2", - "installed_version": null, - "comment": "examine the visibility map (VM) and page-level visibility info" + "comment": "examine the visibility map (VM) and page-level visibility info", + "tags": ["Admin", "Utility"] }, { "name": "pgaudit", - "schema": null, - "default_version": "1.4", - "installed_version": null, - "comment": "provides auditing functionality" + "comment": "provides auditing functionality", + "link": "https://supabase.com/docs/guides/database/extensions/pgaudit", + "tags": ["Audit", "Utility"] + }, + { + "name": "pg_walinspect", + "comment": "functions to inspect contents of PostgreSQL Write-Ahead Log", + "tags": ["Admin", "Utility"] }, { "name": "pgcrypto", - "schema": "extensions", - "default_version": "1.3", - "installed_version": "1.3", - "comment": "cryptographic functions" + "comment": "cryptographic functions", + "tags": ["Utility", "Cryptography"] }, { "name": "pgjwt", - "schema": "extensions", - "default_version": "0.1.0", - "installed_version": "0.1.0", - "comment": "JSON Web Token API for Postgresql" + "comment": "JSON Web Token API for Postgresql", + "link": "https://supabase.com/docs/guides/database/extensions/pgjwt", + "tags": ["Utility", "Cryptography"] + }, + { + "name": "pgroonga", + "comment": "Super fast and all languages supported full text search index based on Groonga", + "link": "https://supabase.com/docs/guides/database/extensions/pgroonga", + "tags": ["Search"] + }, + { + "name": "pgroonga_database", + "comment": "PGroonga database management module", + "tags": ["Admin"] + }, + { + "name": "pgrouting", + "comment": "pgRouting Extension", + "link": "https://supabase.com/docs/guides/database/extensions/pgrouting", + "tags": ["Geo", "Utility"] }, { "name": "pgrowlocks", - "schema": null, - "default_version": "1.2", - "installed_version": null, - "comment": "show row-level locking information" + "comment": "show row-level locking information", + "tags": ["Admin", "Utility"] + }, + { + "name": "pgsodium", + "comment": "Postgres extension for libsodium functions", + "link": "https://supabase.com/docs/guides/database/extensions/pgsodium", + "tags": ["Utility", "Cryptography"] }, { "name": "pgstattuple", - "schema": null, - "default_version": "1.5", - "installed_version": null, - "comment": "show tuple-level statistics" + "comment": "show tuple-level statistics", + "tags": ["Admin", "Utility"] }, { "name": "pgtap", - "schema": null, - "default_version": "1.1.0", - "installed_version": null, - "comment": "Unit testing for PostgreSQL" + "comment": "Unit testing for PostgreSQL", + "link": "https://supabase.com/docs/guides/database/extensions/pgtap", + "tags": ["Utility", "Testing"] }, { "name": "plcoffee", - "schema": null, - "default_version": "3.0alpha", - "installed_version": null, - "comment": "PL/CoffeeScript (v8) trusted procedural language" + "comment": "PL/CoffeeScript (v8) trusted procedural language", + "tags": ["Language"] }, { "name": "pljava", - "schema": null, - "default_version": "1.6.0", - "installed_version": null, - "comment": "PL/Java procedural language (https://tada.github.io/pljava/)" + "comment": "PL/Java procedural language (https://tada.github.io/pljava/)", + "tags": ["Language"] }, { "name": "plls", - "schema": null, - "default_version": "3.0alpha", - "installed_version": null, - "comment": "PL/LiveScript (v8) trusted procedural language" + "comment": "PL/LiveScript (v8) trusted procedural language", + "tags": ["Language"] }, { "name": "plpgsql", - "schema": "pg_catalog", - "default_version": "1.0", - "installed_version": "1.0", - "comment": "PL/pgSQL procedural language" + "comment": "PL/pgSQL procedural language", + "tags": ["Language"] }, { "name": "plpgsql_check", - "schema": null, - "default_version": "1.11", - "installed_version": null, - "comment": "extended check for plpgsql functions" + "comment": "extended check for plpgsql functions", + "href": "https://supabase.com/docs/guides/database/extensions/plpgsql_check", + "tags": ["Utility", "Testing"] }, { "name": "plv8", - "schema": null, - "default_version": "3.0alpha", - "installed_version": null, - "comment": "PL/JavaScript (v8) trusted procedural language" + "comment": "PL/JavaScript (v8) trusted procedural language", + "link": "http://localhost:3001/docs/guides/database/extensions/plv8", + "tags": ["Language"] }, { "name": "postgis", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "PostGIS geometry and geography spatial types and functions" + "comment": "PostGIS geometry and geography spatial types and functions", + "link": "https://supabase.com/docs/guides/database/extensions/postgis", + "tags": ["Geo"] }, { "name": "postgis-3", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "PostGIS geometry and geography spatial types and functions" + "comment": "PostGIS geometry and geography spatial types and functions", + "tags": ["Geo"] }, { "name": "postgis_raster", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "PostGIS raster types and functions" + "comment": "PostGIS raster types and functions", + "tags": ["Geo"] }, { "name": "postgis_raster-3", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "PostGIS raster types and functions" + "comment": "PostGIS raster types and functions", + "tags": ["Geo"] }, { "name": "postgis_sfcgal", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "PostGIS SFCGAL functions" + "comment": "PostGIS SFCGAL functions", + "tags": ["Geo"] }, { "name": "postgis_sfcgal-3", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "PostGIS SFCGAL functions" + "comment": "PostGIS SFCGAL functions", + "tags": ["Geo"] }, { "name": "postgis_tiger_geocoder", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "PostGIS tiger geocoder and reverse geocoder" + "comment": "PostGIS tiger geocoder and reverse geocoder", + "tags": ["Geo"] }, { "name": "postgis_tiger_geocoder-3", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "PostGIS tiger geocoder and reverse geocoder" + "comment": "PostGIS tiger geocoder and reverse geocoder", + "tags": ["Geo"] }, { "name": "postgis_topology", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "PostGIS topology spatial types and functions" + "comment": "PostGIS topology spatial types and functions", + "tags": ["Geo"] }, { "name": "postgis_topology-3", - "schema": null, - "default_version": "3.1.1", - "installed_version": null, - "comment": "PostGIS topology spatial types and functions" + "comment": "PostGIS topology spatial types and functions", + "tags": ["Geo"] }, { "name": "postgres_fdw", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "foreign-data wrapper for remote PostgreSQL servers" + "comment": "foreign-data wrapper for remote PostgreSQL servers", + "tags": ["Admin"] }, { "name": "refint", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "functions for implementing referential integrity (obsolete)" - }, - { - "name": "seg", - "schema": null, - "default_version": "1.3", - "installed_version": null, - "comment": "data type for representing line segments or floating-point intervals" - }, - { - "name": "sslinfo", - "schema": null, - "default_version": "1.2", - "installed_version": null, - "comment": "information about SSL certificates" - }, - { - "name": "tablefunc", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "functions that manipulate whole tables, including crosstab" - }, - { - "name": "tcn", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "Triggered change notifications" - }, - { - "name": "tsm_system_rows", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "TABLESAMPLE method which accepts number of rows as a limit" - }, - { - "name": "tsm_system_time", - "schema": null, - "default_version": "1.0", - "installed_version": null, - "comment": "TABLESAMPLE method which accepts time in milliseconds as a limit" - }, - { - "name": "unaccent", - "schema": null, - "default_version": "1.1", - "installed_version": null, - "comment": "text search dictionary that removes accents" - }, - { - "name": "uuid-ossp", - "schema": "extensions", - "default_version": "1.1", - "installed_version": "1.1", - "comment": "generate universally unique identifiers (UUIDs)" - }, - { - "name": "vector", - "schema": "extensions", - "default_version": "0.4.0", - "installed_version": null, - "comment": "vector data type with similarity search" - }, - { - "name": "xml2", - "schema": null, - "default_version": "1.1", - "installed_version": null, - "comment": "XPath querying and XSLT" + "comment": "functions for implementing referential integrity (obsolete)", + "tags": ["Utility"] }, { "name": "rum", - "schema": "extensions", - "default_version": "1.3.13", - "installed_version": "1.3.13", - "comment": "GIN-like index for text search" + "comment": "GIN-like index for text search", + "link": "https://supabase.com/docs/guides/database/extensions/rum", + "tags": ["Index", "Search"] + }, + { + "name": "seg", + "comment": "data type for representing line segments or floating-point intervals", + "tags": ["Utility"] + }, + { + "name": "sslinfo", + "comment": "information about SSL certificates", + "tags": ["Admin", "Utility"] + }, + { + "name": "supautils", + "comment": "Supabase standard library", + "tags": ["Utility"] + }, + { + "name": "tablefunc", + "comment": "functions that manipulate whole tables, including crosstab", + "tags": ["Utility"] + }, + { + "name": "tcn", + "comment": "Triggered change notifications", + "tags": ["Utility", "Notifications"] + }, + { + "name": "timescaledb", + "comment": "Enables scalable inserts and complex queries for time-series data", + "link": "https://supabase.com/docs/guides/database/extensions/timescaledb", + "tags": ["Time Series", "Data Type", "Utility"] + }, + { + "name": "tsm_system_rows", + "comment": "TABLESAMPLE method which accepts number of rows as a limit", + "tags": ["Utility"] + }, + { + "name": "tsm_system_time", + "comment": "TABLESAMPLE method which accepts time in milliseconds as a limit", + "tags": ["Utility"] + }, + { + "name": "unaccent", + "comment": "text search dictionary that removes accents", + "tags": ["Search"] + }, + { + "name": "uuid-ossp", + "comment": "generate universally unique identifiers (UUIDs)", + "link": "https://supabase.com/docs/guides/database/extensions/uuid-ossp", + "tags": ["Utility", "Data Type"] + }, + { + "name": "pgvector", + "comment": "vector data type with similarity search", + "link": "https://supabase.com/docs/guides/database/extensions/pgvector", + "tags": ["AI", "Data Type", "Search"] }, { "name": "pg_repack", - "schema": "extensions", - "default_version": "1.4.8", - "installed_version": "1.4.8", - "comment": "optimize physical storage and remove bloat from tables and indexes" + "comment": "Optimize physical storage and remove bloat from tables and indexes", + "link": "https://supabase.com/docs/guides/database/extensions/pgrepack", + "tags": ["Admin", "Utility"] + }, + { + "name": "wrappers", + "comment": "Foreign data wrappers developed by Supabase", + "link": "https://supabase.com/docs/guides/database/extensions/wrappers", + "tags": ["Admin", "Utility"] + }, + { + "name": "xml2", + "comment": "XPath querying and XSLT", + "tags": ["Utility"] } ] diff --git a/apps/docs/docs/ref/self-hosting-analytics/introduction.mdx b/apps/docs/docs/ref/self-hosting-analytics/introduction.mdx new file mode 100644 index 00000000000..5d6efe41eda --- /dev/null +++ b/apps/docs/docs/ref/self-hosting-analytics/introduction.mdx @@ -0,0 +1,108 @@ +--- +id: introduction +title: Introduction +hideTitle: true +--- + +
      + +
      +

      Self-Hosting Analytics

      +
      +
      + + + + +The Supabase Analytics server is a Logflare self-hostable instance that manages the ingestion and query pipelines for searching and aggregating structured analytics events. + +When self-hosting the Analytics server, the full logging experience matching that of the Supabase Platform is available in the Studio instance, allowing for an integrated and enhanced development experience. +However, it's important to note that certain [differences](#differences) may arise due to the platform's infrastructure. + + + All logflare technical docs are available at + [https://docs.logflare.app](https://docs.logflare.app) + + +## Getting Started + +Using the example [self-hosting stack based on docker-compose](https://github.com/supabase/supabase/tree/master/docker), you include the logging related services using the following command + +```bash +# assuming you clone the supabase/supabase repo. +cd docker +docker compose -f docker-compose.yml -f docker-compose-logging.yml up +``` + +This would include two additional docker services to your compose stack: Logflare and Vector. + + + Read more about [self-hosting Logflare](https://docs.logflare.app/self-hosting) as your analytics + server. + + +## Differences + +API logs rely on Kong instead of the Supabase Cloud API Gateway. Logs from Kong are not enriched with platform-only data. + +Within the self-hosted setup, all logs are routed to Logflare via Vector. As Kong routes API requests to PostgREST, self-hosted or local deployments will result in Kong request logs instead. +This would result in differences in the log event metadata between self-hosted API requests and Supabase Platform requests. + +## BigQuery + +All log event data is stored in and queried from BigQuery. To use the Analytics server with Supabase you'll need a Google Cloud Platform account for access to BigQuery. + +Make sure to set the `GOOGLE_DATASET_ID_APPEND`, `GOOGLE_PROJECT_ID` and `GOOGLE_PROJECT_NUMBER` environment variables. + +Download your Google Cloud API JWT and store it under gcloud.json in your working directory. + + + You must also enable billing on your Google Cloud project, as the streaming inserts feature is + required. + + +## Production Recommendations + +To self-host in a production setting, we recommend performing the following for a better experience. + +### Ensure that Logflare is behind a firewall and restrict all network access to it besides safe requests. + +Self-hosted Logflare has UI authentication disabled and is intended for exposure to the internet. We recommend restricting access to the dashboard, accessible at the `/dashboard` path. +If dashboard access is required for managing sources, we recommend having an authentication layer, such as a VPN. + +### Use a different Postgres Database to store Logflare data. + +Logflare requires a Postgres database to function. However, if there is an issue with you self-hosted Postgres service, you would not be able to debug it as it would also bring Logflare down together. + +The self-hosted example is only used as a minimal example on running the entire stack, however it is not recommended to use the same database server for both production and observability. + + + + + + ### Client libraries + + - [JavaScript - Pino Transport](https://github.com/Logflare/pino-logflare) + - [Elixir](https://github.com/Logflare/logflare_api_client) + - [Elixir - Logger Backend](https://github.com/Logflare/logflare_logger_backend) + - [Erlang](https://github.com/Logflare/logflare_erl) + - [Erlang - Lager Backend](https://github.com/Logflare/logflare_lager_backend) + - [Cloudflare Worker](https://gist.github.com/chasers/c7a220e91820a1084b27fcfdb18ad6bd) + + ### Integrations + + - [Fly - Logs](https://github.com/Logflare/fly-log-shipper) + - [Vercel Integration - Logs](https://vercel.com/integrations/logflare) + - [Cloudflare App - Logs](https://www.cloudflare.com/apps/logflare/install) + + ### Additional links + + - [Source code](https://github.com/logflare/logflare) + - [Analytics guides](/docs/guides/analytics) - TODO + - [OpenAPI docs](https://logflare.app/api/openapi) + - [Supabase Acquires Logflare](https://supabase.com/blog/supabase-acquires-logflare) + - [Logflare self-hosting docs](https://docs.logflare.app/self-hosting) + + + + diff --git a/apps/docs/docs/ref/swift/introduction.mdx b/apps/docs/docs/ref/swift/introduction.mdx new file mode 100644 index 00000000000..80dc3584bd1 --- /dev/null +++ b/apps/docs/docs/ref/swift/introduction.mdx @@ -0,0 +1,29 @@ +--- +id: introduction +title: Introduction +hideTitle: true +--- + +
      + +
      +

      Swift Client Library

      +

      @supabase-community/supabase-swift

      +
      +
      + +
      +This reference documents every object and method available in Supabase's Swift library, [supabase-swift](https://github.com/supabase-community/supabase-swift). You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files. + +We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Swift projects. +
      + +
      +

      The Swift client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.

      + +

      + Huge thanks to official maintainer, [Maail](https://github.com/maail). +

      + +
      + diff --git a/apps/docs/hooks/useActionKey.ts b/apps/docs/hooks/useActionKey.ts index 883c8ee5b7b..98db6a52d01 100644 --- a/apps/docs/hooks/useActionKey.ts +++ b/apps/docs/hooks/useActionKey.ts @@ -1,18 +1,16 @@ import { useState, useEffect } from 'react' -const ACTION_KEY_DEFAULT = ['Ctrl ', 'Control'] -const ACTION_KEY_APPLE = ['⌘', 'Command'] +const ACTION_KEY_DEFAULT = ['Ctrl ', 'Control'] as const +const ACTION_KEY_APPLE = ['⌘', 'Command'] as const export function useActionKey() { - let [actionKey, setActionKey] = useState() + let [actionKey, setActionKey] = useState() useEffect(() => { if (typeof navigator !== 'undefined') { if (/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)) { - // @ts-ignore setActionKey(ACTION_KEY_APPLE) } else { - // @ts-ignore setActionKey(ACTION_KEY_DEFAULT) } } diff --git a/apps/docs/internals/getSecrets.js b/apps/docs/internals/getSecrets.js new file mode 100644 index 00000000000..4782a3f1ed5 --- /dev/null +++ b/apps/docs/internals/getSecrets.js @@ -0,0 +1,35 @@ +// for internal supabase use only +const fs = require('fs/promises') +const AWS = require('aws-sdk') + +const secretName = 'local/docs' +const region = 'ap-southeast-2' + +const getSecrets = async (name, region) => { + try { + AWS.config.update({ region }) + const secretsmanager = new AWS.SecretsManager() + + const data = await secretsmanager + .getSecretValue({ + SecretId: name, + }) + .promise() + + if (!data.SecretString) { + throw new Error('Secrets not found') + } + return JSON.parse(data.SecretString) + } catch (err) { + console.log('Error getting secrets', err) + } +} + +// gets secrets from secrets manager and writes it to .env.local file +getSecrets(secretName, region).then(async (secrets) => { + let secretContent = '' + for (const [secretKey, secretValue] of Object.entries(secrets)) { + secretContent += `${secretKey}="${secretValue}"\n` + } + await fs.writeFile('.env.local', secretContent.trim()) +}) diff --git a/apps/docs/layouts/DefaultLayout.tsx b/apps/docs/layouts/DefaultLayout.tsx index 3e610896b8b..b8338b9d451 100644 --- a/apps/docs/layouts/DefaultLayout.tsx +++ b/apps/docs/layouts/DefaultLayout.tsx @@ -1,17 +1,27 @@ 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 @@ -19,16 +29,36 @@ const Layout: FC = (props: Props) => { return ( <> - {props.meta?.title} | Supabase Docs + {asPath === '/' ? 'Supabase Docs' : `${props.meta?.title} | Supabase Docs`} - - - - - - + + + - +
      {/*

      Tutorials

      */}
      diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index d1f37b40d1b..68c0b88d87b 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -4,12 +4,10 @@ import Link from 'next/link' import NavigationMenu from '~/components/Navigation/NavigationMenu/NavigationMenu' import TopNavBarRef from '~/components/Navigation/NavigationMenu/TopNavBarRef' -import FooterHelpCallout from '~/components/FooterHelpCallout' - import { memo, useEffect } from 'react' -import Clippy from '~/components/Clippy/Clippy' import Footer from '~/components/Navigation/Footer' import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState' +import Head from 'next/head' const levelsData = { home: { @@ -24,6 +22,10 @@ const levelsData = { icon: '/docs/img/icons/menu/database', name: 'Database', }, + api: { + icon: '/docs/img/icons/menu/database', + name: 'Serverless APIs', + }, auth: { icon: '/docs/img/icons/menu/auth', name: 'Auth', @@ -36,6 +38,10 @@ const levelsData = { icon: '/docs/img/icons/menu/realtime', name: 'Realtime', }, + analytics: { + icon: '/docs/img/icons/menu/analytics', + name: 'Analytics', + }, storage: { icon: '/docs/img/icons/menu/storage', name: 'Storage', @@ -84,6 +90,10 @@ const levelsData = { icon: '/docs/img/icons/menu/reference-python', name: 'Python Reference v2.0', }, + reference_swift_v1: { + icon: '/docs/img/icons/menu/reference-swift', + name: 'Swift Reference v1.0', + }, reference_cli: { icon: '/docs/img/icons/menu/reference-cli', name: 'CLI Reference', @@ -104,6 +114,10 @@ const levelsData = { icon: '/docs/img/icons/menu/reference-realtime', name: 'Realtime Server Reference', }, + reference_self_hosting_analytics: { + icon: '/docs/img/icons/menu/reference-analytics', + name: 'Analytics Server Reference', + }, } const MobileHeader = memo(function MobileHeader() { @@ -315,35 +329,37 @@ const SiteLayout = ({ children }) => { }, []) return ( -
      -
      - - -
      - -
      -
      -
      - + <> + + Supabase Docs + +
      +
      + + +
      +
      -
      -
      - {children} -
      -
      - - -
      - +
      +
      + +
      +
      +
      + {children} +
      +
      + +
      -
      -
      + + ) } diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index 5899d282fc7..e24008fe864 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 = encodeURI( + `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ + ogPageType ? `&type=${ogPageType}` : '' + }&title=${props.meta?.title}&description=${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/layouts/ref/RefSubLayoutNonFunc.tsx b/apps/docs/layouts/ref/RefSubLayoutNonFunc.tsx index d21aa1d79a4..7aee77fbc50 100644 --- a/apps/docs/layouts/ref/RefSubLayoutNonFunc.tsx +++ b/apps/docs/layouts/ref/RefSubLayoutNonFunc.tsx @@ -37,7 +37,6 @@ const RefSubLayoutNonFunc: FC & RefSubLayoutNonFuncSubC } const Section: FC = (props) => { - // console.log({ props }) return (
      = (props: Props) => { {props.meta?.title} | Supabase - + diff --git a/apps/docs/lib/constants.ts b/apps/docs/lib/constants.ts new file mode 100644 index 00000000000..d1bbedbf47c --- /dev/null +++ b/apps/docs/lib/constants.ts @@ -0,0 +1,2 @@ +export const IS_PLATFORM = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' +export const LOCAL_SUPABASE = process.env.NEXT_PUBLIC_LOCAL_SUPABASE === 'true' diff --git a/apps/docs/lib/helpers.ts b/apps/docs/lib/helpers.ts index 899aea15618..7a6c5de3d45 100644 --- a/apps/docs/lib/helpers.ts +++ b/apps/docs/lib/helpers.ts @@ -1,4 +1,7 @@ // [Joshen] I think this can be done better, as its mostly used to choose what + +import { ICommonFunc } from '../components/reference/Reference.types' + // menus to render in the SideBar.js (Ref Nav.constants.ts) export function getPageType(asPath: string) { let page @@ -33,9 +36,9 @@ export function getPageType(asPath: string) { return page } -export function flattenSections(sections) { - var a = [] - for (var i = 0; i < sections.length; i++) { +export function flattenSections(sections: ICommonFunc[]) { + let a: ICommonFunc[] = [] + for (let i = 0; i < sections.length; i++) { if (sections[i].id) { // only push a section that has an id // these are reserved for sidebar subtitles diff --git a/apps/docs/lib/mdx/getConfig.tsx b/apps/docs/lib/mdx/getConfig.tsx index 8a014486212..d208d4243cc 100644 --- a/apps/docs/lib/mdx/getConfig.tsx +++ b/apps/docs/lib/mdx/getConfig.tsx @@ -4,6 +4,8 @@ import specStorageV0 from '~/../../spec/storage_v0_config.yaml' assert { type: ' import specRealtimeV0 from '~/../../spec/realtime_v0_config.yaml' assert { type: 'yml' } // @ts-expect-error import specAuthV1 from '~/../../spec/gotrue_v1_config.yaml' assert { type: 'yml' } +// @ts-expect-error +import specAnalyticsV0 from '~/../../spec/analytics_v0_config.yaml' assert { type: 'yml' } function getStorageConfigV0() { return { ...specStorageV0 } @@ -17,4 +19,8 @@ function getAuthConfigV1() { return { ...specAuthV1 } } -export { getStorageConfigV0, getRealtimeConfigV0, getAuthConfigV1 } +function getAnalyticsConfigV0() { + return { ...specAnalyticsV0 } +} + +export { getStorageConfigV0, getRealtimeConfigV0, getAuthConfigV1, getAnalyticsConfigV0 } diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index 777b2e2224a..b3c71cae82e 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -80,6 +80,16 @@ const nextConfig = { }, ] }, + async redirects() { + return [ + { + source: '/', + destination: '/docs', + basePath: false, + permanent: false, + }, + ] + }, } // next.config.js diff --git a/apps/docs/package.json b/apps/docs/package.json index a8c42541d9e..cfda86a3831 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -4,11 +4,13 @@ "private": true, "scripts": { "dev": "next dev --port 3001", + "dev:secrets:pull": "AWS_PROFILE=supabase-dev node internals/getSecrets.js", "build": "npm run generate:all && next build", "start": "next start", "lint": "next lint", "build:sitemap": "node ./internals/generate-sitemap.mjs", - "build:embeddings": "tsx scripts/generate-embeddings.ts", + "embeddings": "tsx scripts/generate-embeddings.ts", + "embeddings:refresh": "npm run embeddings -- --refresh", "postbuild": "ts-node ./scripts/build-search.ts && node ./internals/generate-sitemap.mjs", "generate:all": "npm-run-all --parallel gen:api gen:cli gen:gotrue gen:storage gen:supabase-dart:v0 gen:supabase-dart:v1 gen:supabase-csharp:v0 gen:supabase-js:v1 gen:supabase-js:v2 gen:realtime", "gen:api": "npm-run-all gen:api:usage", @@ -33,7 +35,10 @@ "gen:supabase-js:v2": "npm-run-all gen:supabase-js:v2:ref", "gen:supabase-js:v2:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_js_v2.yml --output ./docs/reference/javascript/generated", "gen:realtime": "npm-run-all gen:realtime:config", - "gen:realtime:config": "ts-node ./generator/index.ts gen --type config --input ../../spec/realtime_v0_config.yaml --output ./docs/reference/realtime/generated/config.mdx" + "gen:realtime:config": "ts-node ./generator/index.ts gen --type config --input ../../spec/realtime_v0_config.yaml --output ./docs/reference/realtime/generated/config.mdx", + "gen:analytics": "npm-run-all gen:analytics:config gen:analytics:usage", + "gen:analytics:config": "ts-node ./generator/index.ts gen --type config --input ../../spec/analytics_v0_config.yaml --output ./docs/reference/analytics/generated/config.mdx", + "gen:analytics:usage": "ts-node ./generator/index.ts gen --type api --input ../../spec/transforms/analytics_v0_openapi_deparsed.json --output ./docs/reference/analytics/generated/usage.mdx" }, "dependencies": { "@algolia/autocomplete-js": "^1.7.2", @@ -43,14 +48,15 @@ "@mdx-js/react": "^1.6.22", "@next/mdx": "^12.0.4", "@radix-ui/react-accordion": "^1.0.1", - "@supabase/auth-helpers-nextjs": "^0.5.4", + "@supabase/auth-helpers-nextjs": "^0.5.6", "@supabase/auth-helpers-react": "^0.3.1", - "@supabase/supabase-js": "^2.4.1", + "@supabase/supabase-js": "^2.13.0", "algoliasearch": "^4.14.2", "babel": "^6.23.0", "clsx": "^1.2.1", "common": "*", "config": "*", + "github-slugger": "^2.0.0", "gray-matter": "^4.0.3", "isbot": "^3.6.5", "jsrsasign": "^10.5.26", @@ -60,7 +66,9 @@ "mdast-util-from-markdown": "^1.2.0", "mdast-util-mdx": "^2.0.0", "mdast-util-to-markdown": "^1.5.0", + "mdast-util-to-string": "^3.1.1", "mdx-mermaid": "2.0.0-rc3", + "mermaid": "^10.0.2", "micromark-extension-mdxjs": "^1.0.0", "next": "12.3.2", "next-compose-plugins": "^2.2.1", @@ -100,7 +108,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 e73aa39d38f..58f7fea9f7b 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -1,25 +1,28 @@ 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 { AuthProvider, ThemeProvider } from 'common' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' import { AppPropsWithLayout } from 'types' -import ClippyProvider from '~/components/Clippy/ClippyProvider' -import { SearchProvider } from '~/components/DocSearch' +import { CommandMenuProvider } from 'ui' +import components from '~/components' import Favicons from '~/components/Favicons' import SiteLayout from '~/layouts/SiteLayout' +import { IS_PLATFORM, LOCAL_SUPABASE } from '~/lib/constants' import { post } from '~/lib/fetchWrappers' -import '../styles/algolia-search.scss' import '../styles/ch.scss' -import '../styles/docsearch.scss' import '../styles/main.scss?v=1.0.0' import '../styles/new-docs.scss' import '../styles/prism-okaidia.scss' function MyApp({ Component, pageProps }: AppPropsWithLayout) { const router = useRouter() - const [supabase] = useState(() => createBrowserSupabaseClient()) + + const [supabase] = useState(() => + IS_PLATFORM || LOCAL_SUPABASE ? createBrowserSupabaseClient() : undefined + ) function telemetry(route: string) { return post(`https://api.supabase.io/platform/telemetry/page`, { @@ -57,45 +60,34 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { }, [router.events]) const SITE_TITLE = 'Supabase Documentation' - const SITE_DESCRIPTION = 'The open source Firebase alternative.' - const { basePath } = useRouter() + + const AuthContainer = (props) => { + return IS_PLATFORM || LOCAL_SUPABASE ? ( + + {props.children} + + ) : ( + {props.children} + ) + } return ( <> - - + - - - - - - - + ( + + )} + > + + + + - + ) } diff --git a/apps/docs/pages/_document.tsx b/apps/docs/pages/_document.tsx index 98acbb79912..ed9a8a70c10 100644 --- a/apps/docs/pages/_document.tsx +++ b/apps/docs/pages/_document.tsx @@ -3,7 +3,9 @@ import { Html, Head, Main, NextScript } from 'next/document' export default function Document() { return ( - + + +
      diff --git a/apps/docs/pages/guides/api.mdx b/apps/docs/pages/guides/api.mdx new file mode 100644 index 00000000000..8e45a1243c7 --- /dev/null +++ b/apps/docs/pages/guides/api.mdx @@ -0,0 +1,52 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'api', + title: 'Serverless APIs', + description: 'Auto-generating and Realtime APIs.', + sidebar_label: 'Overview', + video: 'https://www.youtube.com/v/rPAJJFdtPw0', +} + +Supabase auto-generates three types of API directly from your database schema. + +- REST - connect to your database through a restful interface, directly from the browser. +- Realtime - for listening to database changes. +- GraphQL - a fast and auto-generate GraphQL API. + +All the APIs are auto-generated from your database and are designed to get you building as fast as possible, without writing a single line of code. + +You can use them directly from the browser (two-tier architecture), or as a complement to your own API server (three-tier architecture). + +## Features + +- **Instant and auto-generated.**
      As you update your database the changes are immediately accessible through your API. +- **Self documenting.**
      Supabase generates documentation in the Dashboard which updates as you make database changes. +- **Secure.**
      The API is configured to work with PostgreSQL's Row Level Security, provisioned behind an API gateway with key-auth enabled. +- **Fast.**
      Our benchmarks for basic reads are more than 300% faster than Firebase. The API is a very thin layer on top of Postgres, which does most of the heavy lifting. +- **Scalable.**
      The API can serve thousands of simultaneous requests, and works well for Serverless workloads. + +## REST API [#rest-api-overview] + +Supabase provides a RESTful API using [PostgREST](https://postgrest.org/). This is a very thin API layer on top of Postgres. +It provides everything you need from a CRUD API: + +- Basic CRUD operations +- Deeply nested joins, allowing you to fetch data from multiple tables in a single fetch +- Works with Postgres Views +- Works with Postgres Functions +- Works with the Postgres security model - including Row Level Security, Roles, and Grants. + +## GraphQL API [#graphql-api-overview] + +GraphQL in Supabase works through [pg_graphql](https://supabase.com/blog/pg-graphql), an open source PostgreSQL extension for GraphQL. + +## Realtime API [#realtime-api-overview] + +Supabase provides a Realtime API using [Realtime](https://github.com/supabase/realtime). You can use this to listen to database changes over websockets. +Realtime leverages PostgreSQL's built-in logical replication. You can manage your Realtime API simply by managing Postgres publications. +Go to your project's [Replication section](https://app.supabase.com/project/_/database/replication) to get started. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/api/api-keys.mdx b/apps/docs/pages/guides/api/api-keys.mdx new file mode 100644 index 00000000000..3f9869586f5 --- /dev/null +++ b/apps/docs/pages/guides/api/api-keys.mdx @@ -0,0 +1,70 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'api-keys', + title: 'Understanding API Keys', + description: 'Securing your Serverless API with Postgres Row Level Security.', +} + +Supabase provides two default keys when you create a project: an `anon` key, and a `service_role` key. You can find both keys in the [API Settings](https://app.supabase.com/project/_/settings/api). + +The Serverless APIs are designed to work with Postgres Row Level Security (RLS). These keys both map to Postgres roles. You can find an `anon` user and a `service_role` user in the [Roles](http://app.supabase.com/project/_/database/roles) section of the dashboard. + +The keys are both long-lived JWTs. If you decode these keys, you will see that they contain the "role", an "issued date", and an "expiry date" ~10 years in the future. + +```json +{ + "role": "anon", + "iat": 1625137684, + "exp": 1940713684 +} +``` + +## The `anon` key + +The `anon` key has very few privileges. You can use it in your [RLS policies](/docs/guides/auth/row-level-security) for "anonymous" access. For example, this policy will allow access to the `profiles` table: + +```sql +create policy "Allow anonymous access" on profiles to anon for +select + using (true); +``` + +And similarity for disallowing access: + +```sql +create policy "Disallow anonymous access" on profiles to anon for +select + using (false); +``` + +If you are using [Supabase Auth](/docs/guides/auth/overview), then the `anon` role will automatically update to `authenticated` once a user is logged in: + +```sql +create policy "Allow access to authenticated users" on profiles to authenticated for +select + using (true); +``` + +## The `service_role` key + +The "service_role" is a predefined Postgres role with elevated privileges, designed to perform various administrative and service-related tasks. It can bypass Row Level Security, so it should only be used on a private server. + + + Never expose the `service_role` key in a browser or anywhere where a user can see it. + + +A common use case for the `service_role` key is running data analytics jobs on the backend. To support joins on user id, it is often useful to grant the service role read access to `auth.users` table. + +```sql +grant +select + on table auth.users to service_role; +``` + +We have [partnered with GitHub](https://github.blog/changelog/2022-03-28-supabase-is-now-a-github-secret-scanning-partner/) to scan for Supabase `service_role` keys pushed to public repositories. +If they detect any keys with service_role privileges being pushed to GitHub, they will forward the API key to us, so that we can automatically revoke the detected secrets and notify you, protecting your data against malicious actors. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/api/creating-routes.mdx b/apps/docs/pages/guides/api/creating-routes.mdx new file mode 100644 index 00000000000..c48442f12d1 --- /dev/null +++ b/apps/docs/pages/guides/api/creating-routes.mdx @@ -0,0 +1,252 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'creating-routes', + title: 'Creating API Routes', + description: + 'API routes are automatically created when you create Postgres Tables, Views, or Functions.', +} + +API routes are automatically created when you create Postgres Tables, Views, or Functions. + +## Create a table + +Let's create our first API route by creating a table called `todos` to store tasks. +This creates a corresponding route `todos` which can accept `GET`, `POST`, `PATCH`, & `DELETE` requests. + + + + +1. Go to the [Table editor](https://app.supabase.com/project/_/editor) page in the Dashboard. +1. Click **New Table** and create a table with the name `todos`. +1. Click **Save**. +1. Click **New Column** and create a column with the name `task` and type `text`. +1. Click **Save**. + + + + + + +```sql + -- Create a table called "todos" with a column to store tasks. +create table + todos ( + id bigint generated by default as identity primary key, + task text check (char_length(task) > 3) + ); +``` + + + + +## API URL and Keys + +Every Supabase project has a unique API URL. Your API is secured behind an API gateway which requires an API Key for every request. + +1. Go to the [Settings](https://app.supabase.com/project/_/settings/general) page in the Dashboard. +2. Click **API** in the sidebar. +3. Find your API `URL`, `anon`, and `service_role` keys on this page. + + + +The REST API and the GraphQL API are both accessible through this URL: + +- REST: `https://.supabase.co/rest/v1` +- GraphQL: `https://.supabase.co/graphql/v1` + +Both of these routes require the `anon` key to be passed through an `apikey` header. + +## Using the API + +### REST API + +You can interact with your API directly via HTTP requests, or you can use the client libraries which we provide. + +Let's see how to make a request to the `todos` table which we created in the first step, +using the API URL (`SUPABASE_URL`) and Key (`SUPABASE_ANON_KEY`) we provided: + + + + +```javascript +// Initialize the JS client +import { createClient } from '@supabase/supabase-js' +const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY) + +// Make a request +const { data: todos, error } = await supabase.from('todos').select('*') +``` + + + + +```bash +# Append /rest/v1/ to your URL, and then use the table name as the route +curl '/rest/v1/todos' \ +-H "apikey: " \ +-H "Authorization: Bearer " +``` + + + + +JS Reference: [select()](/docs/reference/javascript/select), +[insert()](/docs/reference/javascript/insert), +[update()](/docs/reference/javascript/update), +[upsert()](/docs/reference/javascript/upsert), +[delete()](/docs/reference/javascript/delete), +[rpc()](/docs/reference/javascript/rpc) (call Postgres functions). + +### GraphQL API + +You can use any GraphQL client with the Supabase GraphQL API. For our GraphQL example we will use [urql](https://formidable.com/open-source/urql/docs/). + + + + +```javascript +import { createClient, useQuery } from 'urql' + +// Prepare API key and Authorization header +const headers = { + apikey: , + authorization: `Bearer ${}`, +} + +// Create GraphQL client +// See: https://formidable.com/open-source/urql/docs/basics/react-preact/#setting-up-the-client +const client = createClient({ + url: '/graphql/v1', + fetchOptions: function createFetchOptions() { + return { headers } + }, +}) + +// Prepare our GraphQL query +const TodosQuery = ` + query { + todosCollection { + edges { + node { + id + title + } + } + } + } +` + +// Query for the data (React) +const [result, reexecuteQuery] = useQuery({ + query: TodosQuery, +}) + +// Read the result +const { data, fetching, error } = result +``` + + + + +```bash +# Append /graphql/v1/ to your URL, and then use the table name as the route +curl --request POST '/graphql/v1' \ +-H 'apikey: ' \ +-H 'Authorization: Bearer ' \ +-H 'Content-Type: application/json' \ +-d '{ "query":"{ todos(first: 3) { edges { node { id } } } }" }' +``` + + + + +### Realtime API + +By default Realtime is disabled on your database. Let's turn on Realtime for the `todos` table. + + + + +1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard. +2. Click on **Replication** in the sidebar. +3. Control which database events are sent by toggling **Insert**, **Update**, and **Delete**. +4. Control which tables broadcast changes by selecting **Source** and toggling each table. + + + + + + +```sql +alter + publication supabase_realtime add table todos; +``` + + + + +From the client, we can listen to any new data that is inserted into the `todos` table: + +```javascript +// Initialize the JS client +import { createClient } from '@supabase/supabase-js' +const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY) + +// Create a function to handle inserts +const handleInserts = (payload) => { + console.log('Change received!', payload) +} + +// Listen to inserts +const { data: todos, error } = await supabase.from('todos').on('INSERT', handleInserts).subscribe() +``` + +Use [subscribe()](/docs/reference/javascript/subscribe) to listen to database changes. +The Realtime API works through PostgreSQL's replication functionality. Postgres sends database changes to a [publication](/docs/guides/database/replication#publications) +called `supabase_realtime`, and by managing this publication you can control which data is broadcast. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/api/graphql/graphiql.mdx b/apps/docs/pages/guides/api/graphql/graphiql.mdx new file mode 100644 index 00000000000..a3ce41073fd --- /dev/null +++ b/apps/docs/pages/guides/api/graphql/graphiql.mdx @@ -0,0 +1,22 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'graphiql', + title: 'GraphiQL documentation', + description: 'Some docs on graphiql.', + video: 'https://www.youtube.com/v/7CqlTU9aOR4', +} + +Every Supabase project has a GraphQL Endpoint: `https://.supabase.co/graphql/v1`. + +This endpoint is compatible with any GraphiQL implementation that can pass an `apikey` header. +Some suggested applications: + +- [paw.cloud](https://paw.cloud) +- [insomnia.rest](https://insomnia.rest) +- [postman.com/graphql](https://www.postman.com/graphql/) +- Self-hosted GraphiQL: GraphiQL can be served through a simple HTML file. See [this discussion](https://github.com/supabase/supabase/discussions/6144) for more details. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/api/joins-and-nesting.mdx b/apps/docs/pages/guides/api/joins-and-nesting.mdx new file mode 100644 index 00000000000..470eba7a5aa --- /dev/null +++ b/apps/docs/pages/guides/api/joins-and-nesting.mdx @@ -0,0 +1,206 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'joins-and-nested-tables', + title: 'Querying Joins and Nested tables', + description: 'The Serverless APIs automatically detect relationships between Postgres tables.', +} + +The Serverless APIs automatically detect relationships between Postgres tables. Since Postgres is a relational database, this is a very common scenario. + +## One-to-many joins + +Let's use an example database that stores `countries` and `cities`: + + + + +**Countries** + +| `id` | `name` | +| ---- | -------------- | +| 1 | United Kingdom | +| 2 | United States | + +**Cities** + +| `id` | `name` | `country_id` | +| ---- | ----------- | ------------ | +| 1 | London | 1 | +| 2 | Manchester | 1 | +| 3 | Los Angeles | 2 | +| 4 | New York | 2 | + + + + +```sql +create table + countries ("id" serial primary key, "name" text); + +insert into + countries (id, name) +values + (1, 'United Kingdom'), + (2, 'United States'); + +create table + cities ( + "id" serial primary key, + "name" text, + "country_id" int references "countries" + ); + +insert into + cities (name, country_id) +values + ('London', 1), + ('Manchester', 1), + ('Los Angeles', 2), + ('New York', 2); +``` + + + + +The APIs will automatically detect relationships based on the foreign keys: + + + + +```js +const { data, error } = await supabase.from('countries').select(` + id, + name, + cities ( id, name ) +`) +``` + + + + +```dart +final data = await supabase.from('todos').select('id, name, cities(id, name)'); +``` + + + + +```javascript +const Query = ` + query { + countriesCollection { + edges { + node { + id + name + cities { + id, + name + } + } + } + } + } +` +``` + + + + +```bash +GET https://[REF].supabase.co/rest/v1/countries?select=id,name,cities(id,name) +``` + + + + +## Many-to-many joins + +The Serverless APIs will detect many-to-many joins. For example, if you have a database which stored teams of users (where each user could below to many teams): + +```sql +create table + users ("id" serial primary key, "name" text); + +create table + teams ("id" serial primary key, "team_name" text); + +create table + members ( + "id" serial primary key, + "user_id" int references users, + "team_id" int references teams + ); +``` + +In these cases you don't need to explicitly define the joining table (members). If we wanted to fetch all the teams and the members in each team: + + + + +```js +const { data, error } = await supabase.from('teams').select(` + id, + team_name, + users ( id, name ) +`) +``` + + + + +```dart +final data = await supabase.from('teams').select('id, team_name, users(id, name)'); +``` + + + + +```javascript +const Query = ` + query { + teamsCollection { + edges { + node { + id + team_name + users { + id, + name + } + } + } + } + } +` +``` + + + + +```bash +GET https://[REF].supabase.co/rest/v1/teams?select=id,team_name,users(id,name) +``` + + + + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/api/quickstart.mdx b/apps/docs/pages/guides/api/quickstart.mdx new file mode 100644 index 00000000000..7d645a8b5f7 --- /dev/null +++ b/apps/docs/pages/guides/api/quickstart.mdx @@ -0,0 +1,236 @@ +import Layout from '~/layouts/DefaultGuideLayout' +import StepHikeCompact from '~/components/StepHikeCompact' + +export const meta = { + title: 'Build an API route in less than 2 minutes.', + subtitle: 'Create your first API route by creating a table called `todos` to store tasks.', + breadcrumb: 'API Quickstart', +} + +Let's create our first REST route which we can query using `cURL` or the browser. + +We'll create a database table called `todos` for storing tasks. This creates a corresponding API route `/rest/v1/todos` which can accept `GET`, `POST`, `PATCH`, & `DELETE` requests. + + + + + + + [Create a new project](https://app.supabase.com) in the Supabase Dashboard. + + After your project is ready, create a table in your Supabase database. You can do this with either the Table interface or the [SQL Editor](https://app.supabase.com/project/_/sql). + + + + + + + + + ```sql + -- Create a table called "todos" + -- with a column to store tasks. + create table todos ( + id serial primary key, + task text + ); + ``` + + + + + + + + + + + + + + + + + + Let's turn on Row Level Security for this table and allow anonymous access. + + + + + + ```sql + -- Turn on security + alter table "todos" + enable row level security; + + -- Allow anonymous access + create policy "Allow anonymous access" + on todos + to anon + for select + using (true); + ``` + + + + + + + + + Now we can add some data to our table which we can access through our API. + + + + + + ```sql + insert into todos (task) + values + ('Create tables'), + ('Enable security'), + ('Add data'), + ('Fetch data from the API'); + ``` + + + + + + + + + Find your API URL and Keys in your Dashboard [API Settings](https://app.supabase.com/project/_/settings/api). You can now query your "todos" table by appending `/rest/v1/todos` to the API URL. + + Copy this block of code, substitute `` and ``, then run it from a terminal. + + + + + ```bash Terminal + curl 'https://.supabase.co/rest/v1/todos' \ + -H "apikey: " \ + -H "Authorization: Bearer " + ``` + + + + + + + +## Bonus + +There are several options for accessing your data: + +### Browser + +You can query the route in your browser, by appending the `anon` key as a query parameter: + +`https://.supabase.co/rest/v1/users?apikey=` + +### Client libraries + +We provide a numerous [Client Libraries](https://github.com/supabase/supabase#client-libraries). + + + + +```js +const { data, error } = await supabase.from('todos').select() +``` + + + + +```dart +final data = await supabase.from('todos').select('*'); +``` + + + + +```python +response = supabase.table('todos').select("*").execute() +``` + + + + +### GraphQL + +Every table can be accessed through the GraphQL API by switching `/rest/v1` with `/graphql/v1`. + + + + +```javascript +import { createClient, useQuery } from 'urql' + +const URL = '/graphql/v1' +const ANON_KEY = '' + +// Prepare API key and Authorization header +const headers = { + apikey: `${ANON_KEY}`, + authorization: `Bearer ${ANON_KEY}`, +} + +const client = createClient({ + url: URL, + fetchOptions: function createFetchOptions() { + return { headers } + }, +}) + +// Prepare our GraphQL query +const TodosQuery = ` + query { + todosCollection { + edges { + node { + id + task + } + } + } + } +` + +// Query for the data (React) +const [result, reexecuteQuery] = useQuery({ + query: TodosQuery, +}) + +// Read the result +const { data, fetching, error } = result +``` + + + + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/api/rest/auto-generated-docs.mdx b/apps/docs/pages/guides/api/rest/auto-generated-docs.mdx new file mode 100644 index 00000000000..82709aa7a1a --- /dev/null +++ b/apps/docs/pages/guides/api/rest/auto-generated-docs.mdx @@ -0,0 +1,26 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'auto-docs', + title: 'Auto-generated documentation', + description: 'Supabase provides documentation that updates automatically.', +} + +Supabase generates documentation in the [Dashboard](https://app.supabase.com) which updates as you make database changes. + +1. Go to the [API](https://app.supabase.com/project/_/api) page in the Dashboard. +2. Select any table under **Tables and Views** in the sidebar. +3. Switch between the JavaScript and the cURL docs using the tabs. + + + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/api/rest/client-libs.mdx b/apps/docs/pages/guides/api/rest/client-libs.mdx new file mode 100644 index 00000000000..4b6272e1f6d --- /dev/null +++ b/apps/docs/pages/guides/api/rest/client-libs.mdx @@ -0,0 +1,33 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'client-libs', + title: 'Client Libraries', + description: 'Supabase provides several client libraries for the REST and Realtime APIs.', + video: 'https://www.youtube.com/v/7CqlTU9aOR4', +} + +Supabase provides client libraries for the REST and Realtime APIs. Some libraries are officially supported, and some are contributed by the community. + +## Official Libraries + +| `Language` | `Source Code` | `Documentation` | +| --------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------- | +| Javascript/Typescript | [supabase-js](https://github.com/supabase/supabase-js) | [Docs](https://supabase.com/docs/reference/javascript/introduction) | +| Dart/Flutter | [supabase-dart](https://github.com/supabase/supabase-dart) | [Docs](https://supabase.com/docs/reference/dart/introduction) | + +## Community Libraries + +| `Language` | `Source Code` | `Documentation` | +| ----------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| C# | [supabase-js](https://github.com/supabase-community/supabase-csharp) | [Docs](https://supabase.com/docs/reference/csharp/introduction) | +| Go | [supabase-go](https://github.com/supabase-community/supabase-go) | | +| Kotlin | [supabase-kt](https://github.com/supabase-community/supabase-kt) | | +| Python | [supabase-py](https://github.com/supabase-community/supabase-py) | [Docs](https://supabase.com/docs/reference/python/initializing) | +| Ruby | [supabase-rb](https://github.com/supabase-community/supabase-rb) | | +| Swift | [supabase-swift](https://github.com/supabase-community/supabase-swift) | | +| Godot Engine (GDScript) | [supabase-gdscript](https://github.com/supabase-community/godot-engine.supabase) | | + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/database/api/generating-types.mdx b/apps/docs/pages/guides/api/rest/generating-types.mdx similarity index 100% rename from apps/docs/pages/guides/database/api/generating-types.mdx rename to apps/docs/pages/guides/api/rest/generating-types.mdx diff --git a/apps/docs/pages/guides/api/securing-your-api.mdx b/apps/docs/pages/guides/api/securing-your-api.mdx new file mode 100644 index 00000000000..70fa2ef38ae --- /dev/null +++ b/apps/docs/pages/guides/api/securing-your-api.mdx @@ -0,0 +1,66 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'securing-your-api', + title: 'Securing your API', + description: 'Securing your Serverless API with Postgres Row Level Security.', +} + +The Serverless APIs are designed to work with Postgres Row Level Security (RLS). If you use [Supabase Auth](/docs/guides/auth), you can restrict data based on the logged-in user. +To control access to your data, you can use [Policies](/docs/guides/auth#policies). + +## Enabling Row Level Security + +If you create a table through the Dashboard, RLS will be enabled by default. This is not the case, however, if you create a table or view using SQL. +To enable RLS on any table: + + + + +1. Go to the [Authentication](https://app.supabase.com/project/_/auth/users) page in the Dashboard. +2. Click on **Policies** in the sidebar. +3. Select **Enable RLS** to enable Row Level Security. + + + + +```sql +alter table + todos enable row level security; +``` + + + + +With RLS enabled, you can create Policies that allow or disallow users to access and update data. We provide a detailed guide for creating Row Level Security Policies in our [Authorization documentation](/docs/guides/auth/row-level-security). + +## Safeguards towards accidental deletes and updates + +By default, all projects have the [safeupdate](https://github.com/eradman/pg-safeupdate) Postgres extension enabled for API queries. +This ensures that `delete()` and `update()` requests will fail if there are no filters provided. +To confirm that safeupdate is enabled for API queries, run the following query: + +```sql +select + usename, + useconfig +from + pg_shadow +where + usename = 'authenticator'; +``` + +The expected value for `useconfig` should be: + +```sql +['session_preload_libraries=supautils, safeupdate'] +``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/auth/overview.mdx b/apps/docs/pages/guides/auth.mdx similarity index 98% rename from apps/docs/pages/guides/auth/overview.mdx rename to apps/docs/pages/guides/auth.mdx index a0b80e6f018..2e6760545b4 100644 --- a/apps/docs/pages/guides/auth/overview.mdx +++ b/apps/docs/pages/guides/auth.mdx @@ -58,7 +58,7 @@ We provide a suite of Providers and login methods, as well as [Auth helpers](/do ### Configure third-party providers -You can enable third-party providers with the click of a button by navigating to Authentication > Settings > Auth Providers and inputting your `Client ID` and `Secret` for each. +You can enable third-party providers with the click of a button by navigating to Authentication > Providers > Auth Providers and inputting your `Client ID` and `Secret` for each. ![OAuth Logins.](/docs/img/supabase-oauth-logins.png) diff --git a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx index 273b108db5b..52f2e5acf68 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx @@ -23,7 +23,7 @@ It supports custom themes and extensible styles to match your brand and aestheti Install the latest version of [supabase-js](/docs/reference/javascript) and the Auth UI package: ```bash -npm install @supabase/supabase-js @supabase/auth-ui-react +npm install @supabase/supabase-js @supabase/auth-ui-react @supabase/auth-ui-shared ``` ### Import the Auth component @@ -44,11 +44,11 @@ We recommend using one of the predefined themes to style the UI. Import the theme you want to use and pass it to the `appearence.theme` prop. ```js lines=4,16 title=/src/index.js +import { Auth } from '@supabase/auth-ui-react' import { - Auth, // Import predefined theme ThemeSupa, -} from '@supabase/auth-ui-react' +} from '@supabase/auth-ui-shared' const supabase = createClient( '', @@ -70,7 +70,8 @@ The Auth component also supports login with [offical social providers](../../aut ```js lines=13 title=/src/index.js import { createClient } from '@supabase/supabase-js' -import { Auth, ThemeSupa } from '@supabase/auth-ui-react' +import { Auth } from '@supabase/auth-ui-react' +import { ThemeSupa } from '@supabase/auth-ui-shared' const supabase = createClient('', '') @@ -112,7 +113,8 @@ Auth UI comes with several themes to customize the appearance. Each predefined t ```js lines=2,13 title=/src/index.js import { createClient } from '@supabase/supabase-js' -import { Auth, ThemeSupa } from '@supabase/auth-ui-react' +import { Auth } from '@supabase/auth-ui-react' +import { ThemeSupa } from '@supabase/auth-ui-shared' const supabase = createClient( '', @@ -140,7 +142,8 @@ Auth UI comes with two theme variations: `default` and `dark`. You can switch be ```js lines=14 title=/src/index.js import { createClient } from '@supabase/supabase-js' -import { Auth, ThemeSupa } from '@supabase/auth-ui-react' +import { Auth } from '@supabase/auth-ui-react' +import { ThemeSupa } from '@supabase/auth-ui-shared' const supabase = createClient( '', @@ -161,11 +164,12 @@ If you don't pass a value to `theme` it uses the `"default"` theme. You can pass ### Override themes -Auth UI themes can be overridden using variable tokens. See the [list of variable tokens](https://github.com/supabase-community/auth-ui/blob/main/packages/react/common/theming/Themes.tsx). +Auth UI themes can be overridden using variable tokens. See the [list of variable tokens](https://github.com/supabase/auth-ui/blob/main/packages/shared/src/theming/Themes.ts). ```js lines=14-21 title=/src/index.js import { createClient } from '@supabase/supabase-js' -import { Auth, ThemeSupa } from '@supabase/auth-ui-react' +import { Auth } from '@supabase/auth-ui-react' +import { ThemeSupa } from '@supabase/auth-ui-shared' const supabase = createClient('', '') @@ -192,7 +196,7 @@ If you created your own theme, you may not need to override any of the them. ### Create your own theme [#create-theme] You can create your own theme by following the same structure within a `appearance.theme` property. -See the list of [tokens within a theme](https://github.com/supabase-community/auth-ui/blob/main/packages/react/common/theming/Themes.tsx). +See the list of [tokens within a theme](https://github.com/supabase/auth-ui/blob/main/packages/shared/src/theming/Themes.ts). ```js title=/src/index.js import { createClient } from '@supabase/supabase-js' @@ -293,7 +297,7 @@ const App = () => ( ### Custom labels [#custom-labels] -You can use custom labels with `localization.variables`. See the [list of labels](https://github.com/supabase-community/auth-ui/blob/main/packages/react/common/lib/Localization/en.json) that can be overwritten. +You can use custom labels with `localization.variables`. See the [list of labels](https://github.com/supabase/auth-ui/blob/main/packages/shared/src/localization/en.json) that can be overwritten. ```js title=/src/index.js import { createClient } from '@supabase/supabase-js' diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx index a31afc3d055..5cad755ec61 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx @@ -2,17 +2,26 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'nextjs-server-components', - title: 'Supabase Auth with Next.js Server Components', + title: 'Supabase Auth with Next.js app directory', description: - 'Authentication helpers for creating an authenticated Supabase client in Next.js 13 app directory Server Components.', - sidebar_label: 'Next.js Server Components', + 'Authentication helpers for creating an authenticated Supabase client in Next.js 13 app directory Server Components and Route Handlers.', + sidebar_label: 'Next.js (app)', } -This submodule provides experimental convenience helpers for implementing user authentication in Next.js Server Components - the `app` directory. For examples using the `pages` directory check out [Auth Helpers in Next.js](/docs/guides/auth/auth-helpers/nextjs). +The Next.js Auth Helpers package configures Supabase Auth to store the user's session in a cookie, rather than `localStorage`. This makes the users's session available server-side - in Server Components and Route Handlers - and is automatically sent along with any requests to Supabase. -> For a complete implementation example, check out [this repo](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs-server-components). +> The `app` directory in Next.js is still in beta and expected to change. For examples using the `pages` directory check out [Auth Helpers in Next.js](/docs/guides/auth/auth-helpers/nextjs). -> To learn more about fetching and caching Supabase data with Next.js 13 Server Components, check out our [blog](/blog/fetching-and-caching-supabase-data-in-next-js-server-components) or [live stream](https://www.youtube.com/watch?v=QH0P5xZt5wY). +
      + +
      + +> To learn more about Supabase and the Next.js 13 app directory, check out [this playlist](https://youtube.com/playlist?list=PL5S4mPUpp4OtwG-qCxm8gA_hjaBq0OPdz). ## Install the Next.js helper library @@ -52,9 +61,7 @@ NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY ``` -## Creating a Supabase Client - -### Server-side +## Configure Middleware -Create a new file at `/utils/supabase-server.js` and populate with the following: +Middleware runs immediately before each route in rendered. Next.js only provides read access to headers and cookies in Server Components and Route Handlers, however, Supabase needs to be able to set cookies and headers to refresh expired access tokens. Therefore, you must call the `getSession` function in `middleware.js` in order to use a Supabase client in Server Components or Route Handlers. -```js title="/utils/supabase-server.js" -import { headers, cookies } from 'next/headers' -import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' - -export const createClient = () => - createServerComponentSupabaseClient({ - headers, - cookies, - }) -``` - -> This needs to export a function, as the headers and cookies are not populated with values until the Server Component is requesting data. - - - - - -Create a new file at `/utils/supabase-server.ts` and populate with the following: - -```ts title="/utils/supabase-server.ts" -import { headers, cookies } from 'next/headers' -import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' - -import type { Database } from '../lib/database.types' - -export const createClient = () => - createServerComponentSupabaseClient({ - headers, - cookies, - }) -``` - -> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerSupabaseClient` to add type support to the Supabase client. - -> This needs to export a function, as the headers and cookies are not populated with values until the Server Component is requesting data. - - - - -This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example. - -Next, we need a middleware file to refresh the user's session on navigation. - -> If you were using Middleware prior to 12.2, see the [upgrade guide](https://nextjs.org/docs/messages/middleware-upgrade-guide). - - - - -Create a new `middleware.js` file at the same level as your `app` (in the root or `src` directory) and populate with the following: +Create a new `middleware.js` file in the root of your project and populate with the following: ```jsx title="middleware.js" import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' @@ -127,13 +81,8 @@ import { NextResponse } from 'next/server' export async function middleware(req) { const res = NextResponse.next() - const supabase = createMiddlewareSupabaseClient({ req, res }) - - const { - data: { session }, - } = await supabase.auth.getSession() - + await supabase.auth.getSession() return res } ``` @@ -142,24 +91,21 @@ export async function middleware(req) { -Create a new `middleware.ts` file at the same level as your `app` (in the root or `src` directory) and populate with the following: +Middleware runs immediately before each route in rendered. Next.js only provides read access to headers and cookies in Server Components and Route Handlers, however, Supabase needs to be able to set cookies and headers to refresh expired access tokens. Therefore, you must call the `getSession` function in `middleware.ts` in order to use a Supabase client in Server Components or Route Handlers. + +Create a new `middleware.ts` file in the root of your project and populate with the following: ```tsx title="middleware.ts" import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' -import type { Database } from '../lib/database.types' +import type { Database } from '@/lib/database.types' export async function middleware(req: NextRequest) { const res = NextResponse.next() - const supabase = createMiddlewareSupabaseClient({ req, res }) - - const { - data: { session }, - } = await supabase.auth.getSession() - + await supabase.auth.getSession() return res } ``` @@ -169,27 +115,9 @@ export async function middleware(req: NextRequest) { -We can now use our server-side Supabase client to fetch data in Server Components. +## Supabase Provider -```jsx -import 'server-only' - -import { createClient } from '../../utils/supabase-server' - -// do not cache this page -export const revalidate = 0 - -export default async function ServerComponent() { - const supabase = createClient() - const { data } = await supabase.from('posts').select('*') - - return
      {JSON.stringify({ data }, null, 2)}
      -} -``` - -### Client-side - -We still need a Supabase instance client-side for authentication and realtime subscriptions. It is important, when using Supabase client-side, to have a single instance of a client. We can share this singleton instance across our components using providers and React context. +All Client Components need to share a single instance of the Supabase client. We can wrap our application in a `` and use React Context to create a global Supabase instance. -Create a new file at `/utils/supabase-browser.js` and populate with the following: +Create a new file at `/app/supabase-provider.jsx` and populate with the following: -```js title="/utils/supabase-browser.js" -import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' -export const createClient = () => createBrowserSupabaseClient() -``` - - - - - -Create a new file at `/utils/supabase-browser.ts` and populate with the following: - -```ts -import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { Database } from '../lib/database.types' - -export const createClient = () => createBrowserSupabaseClient() -``` - -> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client. - - - - - - - -Next, we need to create a single instance of Supabase to use client-side. Let's create a new Provider for Supabase at `/components/supabase-provider.jsx` and populate with the following: - -```jsx title=components/supabase-provider.jsx +```jsx title="app/supabase-provider.jsx" 'use client' -import { createContext, useContext, useState } from 'react' -import { createClient } from '../utils/supabase-browser' +import { createContext, useContext, useEffect, useState } from 'react' +import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { useRouter } from 'next/navigation' -const Context = createContext() +const Context = createContext(undefined) export default function SupabaseProvider({ children }) { - const [supabase] = useState(() => createClient()) + const [supabase] = useState(() => createBrowserSupabaseClient()) + const router = useRouter() useEffect(() => { - const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => { - if (session?.access_token !== accessToken) { - router.refresh() - } + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange(() => { + router.refresh() }) - return () => subscription.unsubscribe() - }, [accessToken]) - - return ( - - <>{children} - - ) -} - -export const useSupabase = () => useContext(Context) -``` - - - - - -Next, we need to create a single instance of Supabase to use client-side. Let's create a new Provider for Supabase at `/components/supabase-provider.tsx` and populate with the following: - -```tsx title=components/supabase-provider.tsx -'use client' - -import { createContext, useContext, useState } from 'react' -import { createClient } from '../utils/supabase-browser' - -import type { SupabaseClient } from '@supabase/auth-helpers-nextjs' -import type { Database } from '../lib/database.types' - -type SupabaseContext = { - supabase: SupabaseClient -} - -const Context = createContext(undefined) - -export default function SupabaseProvider({ children }: { children: React.ReactNode }) { - const [supabase] = useState(() => createClient()) + return () => { + subscription.unsubscribe() + } + }, [router, supabase]) return ( @@ -297,12 +162,69 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo } export const useSupabase = () => { - let context = useContext(Context); + const context = useContext(Context) + if (context === undefined) { - throw new Error("useSupabase must be used inside SupabaseProvider"); - } else { - return context; + throw new Error('useSupabase must be used inside SupabaseProvider') } + + return context +} +``` + + + + + +Create a new file at `/app/supabase-provider.tsx` and populate with the following: + +```tsx title="app/supabase-provider.tsx" +'use client' + +import { createContext, useContext, useEffect, useState } from 'react' +import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { useRouter } from 'next/navigation' + +import type { SupabaseClient } from '@supabase/auth-helpers-nextjs' +import type { Database } from '@/lib/database.types' + +type SupabaseContext = { + supabase: SupabaseClient +} + +const Context = createContext(undefined) + +export default function SupabaseProvider({ children }: { children: React.ReactNode }) { + const [supabase] = useState(() => createBrowserSupabaseClient()) + const router = useRouter() + + useEffect(() => { + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange(() => { + router.refresh() + }) + + return () => { + subscription.unsubscribe() + } + }, [router, supabase]) + + return ( + + <>{children} + + ) +} + +export const useSupabase = () => { + const context = useContext(Context) + + if (context === undefined) { + throw new Error('useSupabase must be used inside SupabaseProvider') + } + + return context } ``` @@ -311,16 +233,6 @@ export const useSupabase = () => { -We need to set up a listener to fetch fresh data whenever our user logs in or out. For this we need to check whether our client and server sessions match. Let's start by installing the `server-only` package. - -```bash -npm install server-only -``` - -This will ensure that any component that imports this package will be a Server Component, and excluded from the browser bundle. - -Next, let's modify our root layout to fetch the user's session, wrap our application in our Supabase Provider, and pass the server access token as a prop to the `` component (we will create this next). - -```jsx title=app/layout.jsx -import 'server-only' +Modify `layout.jsx` to wrap the application with the `` component: -import SupabaseListener from '../components/supabase-listener' -import SupabaseProvider from '../components/supabase-provider' +```jsx title="app/layout.jsx" import './globals.css' -import { createClient } from '../utils/supabase-server' +import SupabaseProvider from './supabase-provider' -// do not cache this layout -export const revalidate = 0 - -export default async function RootLayout({ children }) { - const supabase = createClient() - - const { - data: { session }, - } = await supabase.auth.getSession() +export const metadata = { + title: 'Create Next App', + description: 'Generated by create next app', +} +export default function RootLayout({ children }) { return ( - {/* - will contain the components returned by the nearest parent - head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head - */} - - - - {children} - + {children} ) @@ -369,36 +267,22 @@ export default async function RootLayout({ children }) { -```tsx title=app/layout.tsx -import 'server-only' +Modify `layout.tsx` to wrap the application with the `` component: -import SupabaseListener from '../components/supabase-listener' -import SupabaseProvider from '../components/supabase-provider' +```tsx title="app/layout.tsx" import './globals.css' -import { createClient } from '../utils/supabase-server' +import SupabaseProvider from './supabase-provider' -// do not cache this layout -export const revalidate = 0 - -export default async function RootLayout({ children }: { children: React.ReactNode }) { - const supabase = createClient() - - const { - data: { session }, - } = await supabase.auth.getSession() +export const metadata = { + title: 'Create Next App', + description: 'Generated by create next app', +} +export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - {/* - will contain the components returned by the nearest parent - head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head - */} - - - - {children} - + {children} ) @@ -408,7 +292,15 @@ export default async function RootLayout({ children }: { children: React.ReactNo -And now create our Supabase listener component that uses the singleton Supabase instance to listen for auth changes. +Now any of our Client Components can use the `useSupabase` hook to ensure they are using the same instance of a Supabase client. + +## Creating a Supabase Client + +### Client Components + +While Server Components are great for data fetching, we still need to use Supabase client-side for [authentication](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/login.tsx) and [realtime subscriptions](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/realtime-posts.tsx). + +As mentioned above, it is important that all Client Components share a single instance of the Supabase client. We can use the `useSupabase` hook we created above to ensure this is the case. -```jsx title="/components/supabase-listener.jsx" +```jsx title="app/new-post.jsx" 'use client' -import { useRouter } from 'next/navigation' -import { useEffect } from 'react' +import { useState } from 'react' import { useSupabase } from './supabase-provider' -export default function SupabaseListener({ serverAccessToken }) { +export default function NewPost() { + const [content, setContent] = useState('') const { supabase } = useSupabase() - const router = useRouter() - useEffect(() => { - const { - data: { subscription }, - } = supabase.auth.onAuthStateChange((event, session) => { - if (session?.access_token !== serverAccessToken) { - router.refresh() - } - }) - - return () => { - subscription.unsubscribe() - } - }, [serverAccessToken, router, supabase]) - - return null -} -``` - - - - - -```tsx title="/components/supabase-listener.tsx" -'use client' - -import { useRouter } from 'next/navigation' -import { useEffect } from 'react' -import { useSupabase } from './supabase-provider' - -export default function SupabaseListener({ serverAccessToken }: { serverAccessToken?: string }) { - const { supabase } = useSupabase() - const router = useRouter() - - useEffect(() => { - const { - data: { subscription }, - } = supabase.auth.onAuthStateChange((event, session) => { - if (session?.access_token !== serverAccessToken) { - router.refresh() - } - }) - - return () => { - subscription.unsubscribe() - } - }, [serverAccessToken, router, supabase]) - - return null -} -``` - - - - -> `use client` tells Next.js that this is a [Client Component](https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components). Only Client Components can use hooks like `useEffect` and `useRouter`. - -The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `serverAccessToken` prop, which is the server's state for our user. If the `serverAccessToken` and the new session's `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route. - -Now we can use our `useSupabase` hook throughout our client-side components. - -### Authentication - - - - -```jsx title="/components/login.jsx" -'use client' - -import { useSupabase } from './supabase-provider' - -// Supabase auth needs to be triggered client-side -export default function Login() { - const { supabase, session } = useSupabase() - - const handleEmailLogin = async () => { - await supabase.auth.signInWithPassword({ - email: 'jon@supabase.com', - password: 'password', - }) - } - - const handleGitHubLogin = async () => { - await supabase.auth.signInWithOAuth({ - provider: 'github', - }) - } - - const handleLogout = async () => { - await supabase.auth.signOut() + const handleSave = async () => { + const { data } = await supabase.from('posts').insert({ content }).select() } return ( <> - - - + setContent(e.target.value)} value={content} /> + ) } @@ -539,37 +337,24 @@ export default function Login() { -```tsx title="/components/login.tsx" +```jsx title="app/new-post.tsx" 'use client' +import { useState } from 'react' import { useSupabase } from './supabase-provider' -// Supabase auth needs to be triggered client-side -export default function Login() { - const { supabase, session } = useSupabase() +export default function NewPost() { + const [content, setContent] = useState('') + const { supabase } = useSupabase() - const handleEmailLogin = async () => { - await supabase.auth.signInWithPassword({ - email: 'jon@supabase.com', - password: 'password', - }) - } - - const handleGitHubLogin = async () => { - await supabase.auth.signInWithOAuth({ - provider: 'github', - }) - } - - const handleLogout = async () => { - await supabase.auth.signOut() + const handleSave = async () => { + const { data } = await supabase.from('posts').insert({ content }).select() } return ( <> - - - + setContent(e.target.value)} value={content} /> + ) } @@ -578,11 +363,11 @@ export default function Login() { -### Realtime +> check out [this example](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/supabase-provider.tsx) for making the user's session available to all Client Components. -A nice pattern for fetching data server-side and subscribing to changes client-side can be done by combining Server and Client components. +### Server Components -> To receive realtime events, you must [enable replication on your "posts" table in Supabase](https://app.supabase.com/project/_/database/replication). +In order to use Supabase in Server Components, you need to have implemented the `middleware.ts` steps above 👆 -Create a new file at `/app/realtime/posts.jsx` and populate with the following: - -```jsx title="/app/realtime/posts.jsx" -'use client' - -import { useEffect, useState } from 'react' -import { useSupabase } from '../../components/supabase-provider' - -export default function Posts({ serverPosts }) { - const [posts, setPosts] = useState(serverPosts) - const { supabase } = useSupabase() - - useEffect(() => { - setPosts(serverPosts) - }, [serverPosts]) - - useEffect(() => { - const channel = supabase - .channel('*') - .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts' }, (payload) => - setPosts((posts) => [...posts, payload.new]) - ) - .subscribe() - - return () => { - supabase.removeChannel(channel) - } - }, [supabase, setPosts, posts]) - - return
      {JSON.stringify(posts, null, 2)}
      -} -``` - -This can now be used in a Server Component to subscribe to realtime updates. - -Create a new file at `/app/realtime/page.jsx` and populate with the following: - -```jsx title="/app/realtime/page.jsx" -import 'server-only' - -import { createClient } from '../../utils/supabase-server' -import Posts from './posts' +```jsx title="app/page.jsx" +import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { headers, cookies } from 'next/headers' // do not cache this page export const revalidate = 0 -export default async function Realtime() { - const supabase = createClient() +export default async function ServerComponent() { + const supabase = createServerComponentSupabaseClient({ + headers, + cookies, + }) const { data } = await supabase.from('posts').select('*') - return + return
      {JSON.stringify(data, null, 2)}
      } ``` @@ -650,69 +399,91 @@ export default async function Realtime() { -Create a new file at `/app/realtime/posts.tsx` and populate with the following: +```tsx title="app/page.tsx" +import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { headers, cookies } from 'next/headers' -```tsx title="/app/realtime/posts.tsx" -'use client' - -import { useEffect, useState } from 'react' -import { useSupabase } from '../../components/supabase-provider' - -import type { Database } from '../../lib/database.types' - -type Post = Database['public']['Tables']['posts']['Row'] - -export default function Posts({ serverPosts }: { serverPosts: Post[] }) { - const [posts, setPosts] = useState(serverPosts) - const { supabase } = useSupabase() - - useEffect(() => { - setPosts(serverPosts) - }, [serverPosts]) - - useEffect(() => { - const channel = supabase - .channel('*') - .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts' }, (payload) => - setPosts((posts) => [...posts, payload.new as Post]) - ) - .subscribe() - - return () => { - supabase.removeChannel(channel) - } - }, [supabase, setPosts, posts]) - - return
      {JSON.stringify(posts, null, 2)}
      -} -``` - -> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerSupabaseClient` to add type support to the Supabase client. - -This can now be used in a Server Component to subscribe to realtime updates. - -Create a new file at `/app/realtime/page.tsx` and populate with the following: - -```tsx title="/app/realtime/page.tsx" -import 'server-only' - -import { createClient } from '../../utils/supabase-server' -import Posts from './posts' +import type { Database } from '@/lib/database.types' // do not cache this page export const revalidate = 0 -export default async function Realtime() { - const supabase = createClient() +export default async function ServerComponent() { + const supabase = createServerComponentSupabaseClient({ + headers, + cookies, + }) const { data } = await supabase.from('posts').select('*') - return + return
      {JSON.stringify(data, null, 2)}
      } ```
      +> check out [this example](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/page.tsx) for redirecting unauthenticated users - protected pages. + +### Route Handlers + +In order to use Supabase in Route Handlers, you need to have implemented the `middleware.ts` steps above 👆 + + + + +```jsx title="app/api/posts/route.jsx" +import { createRouteHandlerSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { NextResponse } from 'next/server' +import { headers, cookies } from 'next/headers' + +// do not cache this page +export const revalidate = 0 + +export async function GET() { + const supabase = createRouteHandlerSupabaseClient({ + headers, + cookies, + }) + const { data } = await supabase.from('posts').select('*') + return NextResponse.json(data) +} +``` + + + + + +```tsx title="app/api/posts/route.tsx" +import { createRouteHandlerSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { NextResponse } from 'next/server' +import { headers, cookies } from 'next/headers' + +import type { Database } from '@/lib/database.types' + +// do not cache this page +export const revalidate = 0 + +export async function GET() { + const supabase = createRouteHandlerSupabaseClient({ + headers, + cookies, + }) + const { data } = await supabase.from('posts').select('*') + + return NextResponse.json(data) +} +``` + + + + +> Check out [this repo](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs) for a full example including [authentication](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/login.tsx), [realtime](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/realtime-posts.tsx) and [protected pages](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/page.tsx). + export const Page = ({ children }) => export default Page diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx index 3b093bd91b4..231edb486ca 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx @@ -4,7 +4,7 @@ export const meta = { id: 'nextjs', title: 'Supabase Auth with Next.js', description: 'Authentication helpers for Next.js API routes, middleware, and SSR.', - sidebar_label: 'Next.js', + sidebar_label: 'Next.js (pages)', } This submodule provides convenience helpers for implementing user authentication in Next.js applications. @@ -185,7 +185,8 @@ export default async (req: NextApiRequest, res: NextApiResponse) => { For [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to use the `supabaseClient` from the `useSupabaseClient` hook and only run your query once the user is defined client-side in the `useUser()` hook: ```jsx lines=10-17 -import { Auth, ThemeSupa } from '@supabase/auth-ui-react' +import { Auth } from '@supabase/auth-ui-react' +import { ThemeSupa } from '@supabase/auth-ui-shared' import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react' import { useEffect, useState } from 'react' diff --git a/apps/docs/pages/guides/auth/auth-helpers/remix.mdx b/apps/docs/pages/guides/auth/auth-helpers/remix.mdx index f15596e84a1..6b30622ed86 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/remix.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/remix.mdx @@ -9,7 +9,16 @@ export const meta = { This submodule provides convenience helpers for implementing user authentication in Remix applications. -> Check out [this repo](https://github.com/supabase/auth-helpers/tree/main/examples/remix) for a full example. +
      + +
      + +> For a complete implementation example, check out [this free egghead course](https://egghead.io/courses/build-a-realtime-chat-app-with-remix-and-supabase-d36e2618) or [this GitHub repo](https://github.com/supabase/auth-helpers/tree/main/examples/remix). ## Install the Remix helper library diff --git a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx index 7598037b2d8..5ffe6f1b488 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx @@ -13,28 +13,10 @@ This submodule provides convenience helpers for implementing user authentication This library supports Node.js `^16.15.0`. - - - ```sh npm install @supabase/auth-helpers-sveltekit ``` - - - -```sh -yarn add @supabase/auth-helpers-sveltekit -``` - - - - ## Getting Started ### Configuration @@ -42,36 +24,465 @@ yarn add @supabase/auth-helpers-sveltekit Set up the following env vars. For local development you can set them in a `.env` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/sveltekit/.env.example). ```bash -# Find these in your Supabase project settings > API +# Find these in your Supabase project settings https://app.supabase.com/project/_/settings/api PUBLIC_SUPABASE_URL=https://your-project.supabase.co PUBLIC_SUPABASE_ANON_KEY=your-anon-key ``` ### Set up the Supabase client -Start off by creating a `db.ts` file inside of the `src/lib` directory and instantiate the `supabaseClient`. +Create a server supabase client in a handle hook: -```ts title=src/lib/db.ts +```ts title=src/hooks.server.ts +// src/hooks.server.ts +import { + PUBLIC_SUPABASE_URL, + PUBLIC_SUPABASE_ANON_KEY +} from '$env/static/public'; +import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'; +import type { Handle } from '@sveltejs/kit'; + +export const handle: Handle = async ({ event, resolve }) => { + event.locals.supabase = createSupabaseServerClient({ + supabaseUrl: PUBLIC_SUPABASE_URL, + supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + event + }); + + /** + * a little helper that is written for convenience so that instead + * of calling `const { data: { session } } = await supabase.auth.getSession()` + * you just call this `await getSession()` + */ + event.locals.getSession = async () => { + const { + data: { session } + } = await event.locals.supabase.auth.getSession(); + return session; + }; + + return resolve(event, { + /** + * There´s an issue with `filterSerializedResponseHeaders` not working when using `sequence` + * + * https://github.com/sveltejs/kit/issues/8061 + */ + filterSerializedResponseHeaders(name) { + return name === 'content-range'; + } + }); +}; +``` + +> Note that we are specifying filterSerializedResponseHeaders here. We need to tell SvelteKit that supabase needs the content-range header. + +### Send session to client + +In order to make the session available to the UI (pages, layouts) we need to pass the session in the root layout server load function: + +```ts title=src/routes/+layout.server.ts +// src/routes/+layout.server.ts +import type { LayoutServerLoad } from './$types'; + +export const load: LayoutServerLoad = async ({ locals: { getSession } }) => { + return { + session: await getSession() + }; +}; +``` + +### Shared Load functions and pages + +To be able to use Supabase in shared load functions and inside pages you need to create a Supabase client in the root layout load: + +```ts +// src/routes/+layout.ts +import { + PUBLIC_SUPABASE_ANON_KEY, + PUBLIC_SUPABASE_URL +} from '$env/static/public'; +import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'; +import type { LayoutLoad } from './$types'; +import type { Database } from '../DatabaseDefinitions'; + +export const load: LayoutLoad = async ({ fetch, data, depends }) => { + depends('supabase:auth'); + + const supabase = createSupabaseLoadClient({ + supabaseUrl: PUBLIC_SUPABASE_URL, + supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + event: { fetch }, + serverSession: data.session + }); + + const { + data: { session } + } = await supabase.auth.getSession(); + + return { supabase, session }; +}; +``` + +Access the client inside pages by `$page.data.supabase` or `data.supabase` when using `export let data: PageData`. + +The usage of `depends` tells sveltekit that this load function should be executed whenever `invalidate` is called to keep the page store in sync. + +`createSupabaseLoadClient` caches the client when running in a browser environment and therefore does not create a new client for every time the load function runs. + +### Setting up the event listener on the client side + +We need to create an event listener in the root `+layout.svelte` file in order catch supabase events being triggered. + +```svelte + + + + +``` + +The usage of `invalidate` tells sveltekit that the root `+layout.ts` load function should be executed whenever the session updates to keep the page store in sync. + +### Generate types from your database + +In order to get the most out of TypeScript and it's intellisense, you should import the generated Database types into the `app.d.ts` type definition file that comes with your SvelteKit project, where `import('./DatabaseDefinitions')` points to the generated types file outlined in [v2 docs here](https://supabase.com/docs/reference/javascript/release-notes#typescript-support) after you have logged in, linked, and generated types through the Supabase CLI. + +```ts +// src/app.d.ts + +import { SupabaseClient, Session } from '@supabase/supabase-js'; +import { Database } from './DatabaseDefinitions'; + +declare global { + namespace App { + interface Locals { + supabase: SupabaseClient; + getSession(): Promise; + } + interface PageData { + session: Session | null; + } + // interface Error {} + // interface Platform {} + } +} +``` + +## Client-side data fetching with RLS + +For [row level security](https://supabase.com/docs/guides/auth/row-level-security) to work properly when fetching data client-side, you need to use `supabaseClient` from `PageData` and only run your query once the session is defined client-side: + +```html + + +{#if data.session} +

      client-side data fetching with RLS

      +
      {JSON.stringify(loadedData, null, 2)}
      +{/if} +``` + +## Server-side data fetching with RLS + +```html + + + +
      Protected content for {user.email}
      +
      {JSON.stringify(tableData, null, 2)}
      +
      {JSON.stringify(user, null, 2)}
      +``` + +```ts +// src/routes/profile/+page.ts +import type { PageLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +export const load: PageLoad = async ({ parent }) => { + const { supabase, session } = await parent(); + if (!session) { + throw redirect(303, '/'); + } + const { data: tableData } = await supabase.from('test').select('*'); + + return { + user: session.user, + tableData + }; +}; +``` + +## Protecting API routes + +Wrap an API Route to check that the user has a valid session. If they're not logged in the session is `null`. + +```ts +// src/routes/api/protected-route/+server.ts +import type { RequestHandler } from './$types'; +import { json, error } from '@sveltejs/kit'; + +export const GET: RequestHandler = async ({ + locals: { supabase, getSession } +}) => { + const session = await getSession(); + if (!session) { + // the user is not signed in + throw error(401, { message: 'Unauthorized' }); + } + const { data } = await supabase.from('test').select('*'); + + return json({ data }); +}; +``` + +If you visit `/api/protected-route` without a valid session cookie, you will get a 401 response. + +## Protecting Actions + +Wrap an Action to check that the user has a valid session. If they're not logged in the session is `null`. + +```ts +// src/routes/posts/+page.server.ts +import type { Actions } from './$types'; +import { error, fail } from '@sveltejs/kit'; + +export const actions: Actions = { + createPost: async ({ request, locals: { supabase, getSession } }) => { + const session = await getSession(); + + if (!session) { + // the user is not signed in + throw error(401, { message: 'Unauthorized' }); + } + // we are save, let the user create the post + const formData = await request.formData(); + const content = formData.get('content'); + + const { error: createPostError, data: newPost } = await supabase + .from('posts') + .insert({ content }); + + if (createPostError) { + return fail(500, { + supabaseErrorMessage: createPostError.message + }); + } + return { + newPost + }; + } +}; +``` + +If you try to submit a form with the action `?/createPost` without a valid session cookie, you will get a 401 error response. + +## Saving and deleting the session + +```ts +import type { Actions } from './$types'; +import { fail, redirect } from '@sveltejs/kit'; +import { AuthApiError } from '@supabase/supabase-js'; + +export const actions: Actions = { + signin: async ({ request, locals: { supabase } }) => { + const formData = await request.formData(); + + const email = formData.get('email') as string; + const password = formData.get('password') as string; + + const { error } = await supabase.auth.signInWithPassword({ + email, + password + }); + + if (error) { + if (error instanceof AuthApiError && error.status === 400) { + return fail(400, { + error: 'Invalid credentials.', + values: { + email + } + }); + } + return fail(500, { + error: 'Server error. Try again later.', + values: { + email + } + }); + } + + throw redirect(303, '/dashboard'); + }, + + signout: async ({ locals: { supabase } }) => { + await supabase.auth.signOut(); + throw redirect(303, '/'); + } +}; +``` + +## Protecting multiple routes + +To avoid writing the same auth logic in every single route you can use the handle hook to +protect multiple routes at once. + +```ts +// src/hooks.server.ts +import type { RequestHandler } from './$types'; +import { getSupabase } from '@supabase/auth-helpers-sveltekit'; +import { redirect, error } from '@sveltejs/kit'; + +export const handle: Handle = async ({ event, resolve }) => { + // protect requests to all routes that start with /protected-routes + if (event.url.pathname.startsWith('/protected-routes')) { + const session = await event.locals.getSession(); + if (!session) { + // the user is not signed in + throw redirect(303, '/'); + } + } + + // protect POST requests to all routes that start with /protected-posts + if ( + event.url.pathname.startsWith('/protected-posts') && + event.request.method === 'POST' + ) { + const session = await event.locals.getSession(); + if (!session) { + // the user is not signed in + throw error(303, '/'); + } + } + + return resolve(event); +}; +``` + +## Migrate from 0.8.x to 0.9 [#migration] + +### Set up the Supabase client [#migration-set-up-supabase-client] + +In version 0.9 we now setup our Supabase client for the server inside of a `hooks.server.ts` file. + + + + +```js title=src/lib/db.ts +// src/lib/db.ts import { createClient } from '@supabase/auth-helpers-sveltekit' import { env } from '$env/dynamic/public' // or use the static env + // import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY) ``` -To make sure the client is initialized on the server and the client, include this file in `src/hooks.server.js` and `src/hooks.client.js`: + + -```ts -import '$lib/db' +```js title=src/hooks.server.ts +// src/hooks.server.ts +import { + PUBLIC_SUPABASE_URL, + PUBLIC_SUPABASE_ANON_KEY +} from '$env/static/public'; +import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'; +import type { Handle } from '@sveltejs/kit'; + +export const handle: Handle = async ({ event, resolve }) => { + event.locals.supabase = createSupabaseServerClient({ + supabaseUrl: PUBLIC_SUPABASE_URL, + supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + event + }); + + /** + * a little helper that is written for convenience so that instead + * of calling `const { data: { session } } = await supabase.auth.getSession()` + * you just call this `await getSession()` + */ + event.locals.getSession = async () => { + const { + data: { session } + } = await event.locals.supabase.auth.getSession(); + return session; + }; + + return resolve(event, { + /** + * There´s an issue with `filterSerializedResponseHeaders` not working when using `sequence` + * + * https://github.com/sveltejs/kit/issues/8061 + */ + filterSerializedResponseHeaders(name) { + return name === 'content-range'; + } + }); +}; ``` -### Synchronizing the page store + + -Edit your `+layout.svelte` file and set up the client side. +### Initialize the client [#migration-initialize-client] + +In order to use the Supabase library in your client code you will need to setup a shared load function inside the root `+layout.ts` and create a `+layout.svelte` to handle our event listening for Auth events. + + + ```html title=src/routes/+layout.svelte - + + +``` + + + + +### Set up hooks [#migration-set-up-hooks] + +Since version 0.9 relies on `hooks.server.ts` to setup our client, we no longer need the `hooks.client.ts` in our project for Supabase related code. + +### Typings [#migration-typings] + + + ```ts title=src/app.d.ts +// src/app.d.ts /// // See https://kit.svelte.dev/docs/types#app @@ -143,60 +594,52 @@ declare namespace App { // interface Locals {} interface PageData { - session: import('@supabase/supabase-js').Session | null + session: import('@supabase/auth-helpers-sveltekit').SupabaseSession } // interface Error {} // interface Platform {} } ``` -### Basic Setup + + -You can now determine if a user is authenticated on the client-side by checking that the `session` object in `$page.data` is defined. +```ts title=src/app.d.ts +// src/app.d.ts +import { SupabaseClient, Session } from '@supabase/supabase-js'; +import { Database } from './DatabaseDefinitions'; -```html title=src/routes/+page.svelte - - -{#if !$page.data.session} -

      I am not logged in

      -{:else} -

      Welcome {$page.data.session.user.email}

      -

      I am logged in!

      -{/if} +declare global { + namespace App { + interface Locals { + supabase: SupabaseClient; + getSession(): Promise; + } + interface PageData { + session: Session | null; + } + // interface Error {} + // interface Platform {} + } +} ``` -## Client-side data fetching with RLS +
      +
      -For [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to import the `{ supabaseClient }` from `$lib/db` and only run your query once the session is defined client-side in `$page.data`: +### Protecting a page [#migration-protecting-a-page] -```html - - -{#if $page.data.session} -

      client-side data fetching with RLS

      -
      {JSON.stringify(loadedData, null, 2)}
      -{/if} -``` - -## Server-side data fetching with RLS + + ```html title=src/routes/profile/+page.svelte - + +
      Protected content for {user.email}
      +
      {JSON.stringify(tableData, null, 2)}
      +
      {JSON.stringify(user, null, 2)}
      +``` + +```ts title=src/routes/profile/+page.ts +// src/routes/profile/+page.ts +import type { PageLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +export const load: PageLoad = async ({ parent }) => { + const { supabase, session } = await parent(); + if (!session) { + throw redirect(303, '/'); + } + const { data: tableData } = await supabase.from('test').select('*'); + + return { + user: session.user, + tableData + }; +}; +``` + +
      +
      + +### Protecting a API route [#migration-protecting-a-api-route] + + + ```ts title=src/routes/api/protected-route/+server.ts +// src/routes/api/protected-route/+server.ts import type { RequestHandler } from './$types' import { getSupabase } from '@supabase/auth-helpers-sveltekit' import { json, redirect } from '@sveltejs/kit' @@ -248,133 +736,35 @@ export const GET: RequestHandler = async (event) => { } ``` -If you visit `/api/protected-route` without a valid session cookie, you will get a 303 response. + + -## Protecting Actions +```ts title=src/routes/api/protected-route/+server.ts +// src/routes/api/protected-route/+server.ts +import type { RequestHandler } from './$types'; +import { json, error } from '@sveltejs/kit'; -Wrap an Action to check that the user has a valid session. If they're not logged in the session is `null`. - -```ts title=src/routes/posts/+page.server.ts -import type { Actions } from './$types' -import { getSupabase } from '@supabase/auth-helpers-sveltekit' -import { error, fail } from '@sveltejs/kit' - -export const actions: Actions = { - createPost: async (event) => { - const { request } = event - const { session, supabaseClient } = await getSupabase(event) - if (!session) { - // the user is not signed in - throw error(403, { message: 'Unauthorized' }) - } - // we are save, let the user create the post - const formData = await request.formData() - const content = formData.get('content') - - const { error: createPostError, data: newPost } = await supabaseClient - .from('posts') - .insert({ content }) - - if (createPostError) { - return fail(500, { - supabaseErrorMessage: createPostError.message, - }) - } - return { - newPost, - } - }, -} -``` - -If you try to submit a form with the action `?/createPost` without a valid session cookie, you will get a 403 error response. - -## Saving and deleting the session - -```ts -import type { Actions } from './$types' -import { fail, redirect } from '@sveltejs/kit' -import { getSupabase } from '@supabase/auth-helpers-sveltekit' -import { AuthApiError } from '@supabase/supabase-js' - -export const actions: Actions = { - signin: async (event) => { - const { request, cookies, url } = event - const { session, supabaseClient } = await getSupabase(event) - const formData = await request.formData() - - const email = formData.get('email') as string - const password = formData.get('password') as string - - const { error } = await supabaseClient.auth.signInWithPassword({ - email, - password, - }) - - if (error) { - if (error instanceof AuthApiError && error.status === 400) { - return fail(400, { - error: 'Invalid credentials.', - values: { - email, - }, - }) - } - return fail(500, { - error: 'Server error. Try again later.', - values: { - email, - }, - }) - } - - throw redirect(303, '/dashboard') - }, - - signout: async (event) => { - const { supabaseClient } = await getSupabase(event) - await supabaseClient.auth.signOut() - throw redirect(303, '/') - }, -} -``` - -## Protecting multiple routes - -To avoid writing the same auth logic in every single route you can use the handle hook to -protect multiple routes at once. - -```ts title=src/hooks.server.ts -import type { RequestHandler } from './$types' -import { getSupabase } from '@supabase/auth-helpers-sveltekit' -import { redirect, error } from '@sveltejs/kit' - -export const handle: Handle = async ({ event, resolve }) => { - // protect requests to all routes that start with /protected-routes - if (event.url.pathname.startsWith('/protected-routes')) { - const { session, supabaseClient } = await getSupabase(event) - - if (!session) { - throw redirect(303, '/') - } +export const GET: RequestHandler = async ({ + locals: { supabase, getSession } +}) => { + const session = await getSession(); + if (!session) { + // the user is not signed in + throw error(401, { message: 'Unauthorized' }); } + const { data } = await supabase.from('test').select('*'); - // protect POST requests to all routes that start with /protected-posts - if (event.url.pathname.startsWith('/protected-posts') && event.request.method === 'POST') { - const { session, supabaseClient } = await getSupabase(event) - - if (!session) { - throw error(303, '/') - } - } - - return resolve(event) -} + return json({ data }); +}; ``` -## Migrate from 0.7.x to 0.8 [#migration] + + -### Set up the Supabase client [#migration-set-up-supabase-client] + +## Migrate from 0.7.x to 0.8 [#migration-0-8] + +### Set up the Supabase client [#migration-set-up-supabase-client-0-8] -### Initialize the client [#migration-initialize-client] +### Initialize the client [#migration-initialize-client-0-8] -### Set up hooks [#migration-set-up-hooks] +### Set up hooks [#migration-set-up-hooks-0-8] -### Typings [#migration-typings] +### Typings [#migration-typings-0-8] -### withPageAuth [#migration-with-page-auth] +### withPageAuth [#migration-with-page-auth-0-8] { -### withApiAuth [#migration-with-api-auth] +### withApiAuth [#migration-with-api-auth-0-8] + +export default Page diff --git a/apps/docs/pages/guides/auth/managing-user-data.mdx b/apps/docs/pages/guides/auth/managing-user-data.mdx index bd5ae4f7db7..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. @@ -87,6 +93,32 @@ If you need to fetch a full list of user profiles, we supply a `service_key` whi Make sure you _NEVER_ expose this publicly. But it can be used on the server-side to fetch all of the profiles. +## Accessing User Metadata + +You can assign metadata to users on sign up: + +```js +const { data, error } = await supabase.auth.signUp({ + email: 'example@email.com', + password: 'example-password', + options: { + data: { + first_name: 'John', + age: 27, + }, + }, +}) +``` + +User metadata is stored on the `raw_user_meta_data` column of the `auth.users` table. To view the metadata: + +```js +const { + data: { user }, +} = await supabase.auth.getUser() +let metadata = user.user_metadata +``` + ## Advanced techniques ### Using triggers diff --git a/apps/docs/pages/guides/auth/quickstarts/react.mdx b/apps/docs/pages/guides/auth/quickstarts/react.mdx new file mode 100644 index 00000000000..c2ae0efb7fb --- /dev/null +++ b/apps/docs/pages/guides/auth/quickstarts/react.mdx @@ -0,0 +1,137 @@ +import Layout from '~/layouts/DefaultGuideLayout' +import StepHikeCompact from '~/components/StepHikeCompact' + +export const meta = { + title: 'Use Supabase Auth with React', + subtitle: 'Learn how to Supabase Auth with React.js.', + breadcrumb: 'Auth Quickstarts', +} + + + + + + + [Launch a new project](https://app.supabase.com) in the Supabase Dashboard. + + Your new database has a table for storing your users. You can see that this table is currently empty by running some SQL in the [SQL Editor](https://app.supabase.com/project/_/sql). + + + + + + ```sql SQL_EDITOR + select * from auth.users; + ```` + + + + + + + + + + Create a React app using the `create-react-app` command. + + + + + + ```bash Terminal + npx create-react-app my-app + ``` + + + + + + + + + The fastest way to get started is to use Supabase's `auth-ui-react` library which provides a convenient interface for working with Supabase Auth from a React app. + + Navigate to the React app and install the Supabase libraries. + + + + + + ```bash Terminal + cd my-app && npm install @supabase/supabase-js @supabase/auth-ui-react + ``` + + + + + + + + + In `index.js`, create a Supabase client using your [Project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api). + + You can configure the Auth component to display whenever there is no session inside `supabase.auth.getSession()` + + + + + + ```js src/index.js + import './index.css' + import { useState, useEffect } from 'react' + import { createClient } from '@supabase/supabase-js' + import { Auth, ThemeSupa } from '@supabase/auth-ui-react' + + const supabase = createClient('https://.supabase.co', '') + + export default function App() { + const [session, setSession] = useState(null) + + useEffect(() => { + supabase.auth.getSession().then(({ data: { session } }) => { + setSession(session) + }) + + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange((_event, session) => { + setSession(session) + }) + + return () => subscription.unsubscribe() + }, []) + + if (!session) { + return () + } + else { + return (
      Logged in!
      ) + } + } + ``` + +
      + +
      + + + + + Start the app, go to http://localhost:3000 in a browser, and open the browser console and you should be able to log in. + + + + + + ```bash Terminal + npm start + ``` + + + + +
      + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/auth/row-level-security.mdx b/apps/docs/pages/guides/auth/row-level-security.mdx index 1be866869e0..1987e8acbea 100644 --- a/apps/docs/pages/guides/auth/row-level-security.mdx +++ b/apps/docs/pages/guides/auth/row-level-security.mdx @@ -328,6 +328,132 @@ 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. + +Initializing a client with a Service Key will not override RLS if a user token is set. If a user is signed in with a client, Supabase will adhere to the RLS policy of the user. + + +### 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 diff --git a/apps/docs/pages/guides/auth/server-side-rendering.mdx b/apps/docs/pages/guides/auth/server-side-rendering.mdx index 635407cd986..87235781711 100644 --- a/apps/docs/pages/guides/auth/server-side-rendering.mdx +++ b/apps/docs/pages/guides/auth/server-side-rendering.mdx @@ -130,6 +130,9 @@ if (refreshToken && accessToken) { await supabase.auth.setSession({ refresh_token: refreshToken, access_token: accessToken, + { + auth: { persistSession: false }, + } }) } else { // make sure you handle this case! @@ -160,6 +163,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/auth/social-login.mdx b/apps/docs/pages/guides/auth/social-login.mdx index 1c950411577..8864a1b6dc9 100644 --- a/apps/docs/pages/guides/auth/social-login.mdx +++ b/apps/docs/pages/guides/auth/social-login.mdx @@ -43,6 +43,12 @@ Supabase supports a suite of social providers. Follow these guides to configure ))}
      + +## Provider Tokens + +Once the OAuth flow completes, Supabase Auth will sign your user in. You will receive a copy of the provider token used in the OAuth flow in case you need to use it further. For example, you can use the Google provider token to access Google APIs on behalf of your user. + +Provider tokens are intentionally not stored in your project's database, however. This is because provider tokens give access to potentially sensitive user data in third-party systems. Different applications have different needs, and one application's OAuth scopes may be significantly more permissive than another. If you do want to use the provider token outside of the browser that completed the OAuth flow, you will have to send it manually to a secure server under your control. export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/auth/social-login/auth-github.mdx b/apps/docs/pages/guides/auth/social-login/auth-github.mdx index e6f2dff566b..5a3e76eed1d 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-github.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-github.mdx @@ -36,8 +36,9 @@ Go to the [GitHub Developer Settings](https://github.com/settings/developers) pa -## Register a new OAuth application +## Register a new OAuth application on Github. +- Navigate to `Settings`/`Developer settings`/`OAuth Apps` - Click `Register a new application`. If you've created an app before, click `New OAuth App` here. - In `Application name`, type the name of your app. - In `Homepage URL`, type the full URL to your app's website. diff --git a/apps/docs/pages/guides/auth/social-login/auth-google.mdx b/apps/docs/pages/guides/auth/social-login/auth-google.mdx index 261887a8468..0e9cbe5fc3d 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-google.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-google.mdx @@ -83,6 +83,24 @@ async function signInWithGoogle() { } ``` +Google OAuth2.0 doesn't return the `provider_refresh_token` by default. If you need the `provider_refresh_token` returned, you will need to add additional query parameters: + +```js +async function signInWithGoogle() { + const { data, error } = await supabase.auth.signInWithOAuth({ + provider: 'google', + options: { + queryParams: { + access_type: 'offline', + prompt: 'consent', + }, + }, + }) +} +``` + +You can view the full list of query parameters and their descriptions [here](https://developers.google.com/identity/protocols/oauth2/web-server#creatingclient). + When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage: ```js diff --git a/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx b/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx new file mode 100644 index 00000000000..d5598cc9c7a --- /dev/null +++ b/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx @@ -0,0 +1,364 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'auth-sso-saml', + title: 'Single Sign-On with SAML 2.0 for Projects', + description: 'Use Single Sign-On (SSO) authentication on your project with SAML 2.0', + video: 'https://www.youtube.com/v/em1cpOAXknM', +} + +Supabase Auth supports enterprise-level Single Sign-On (SSO) for any identity providers compatible with the using the SAML 2.0 protocol. This is a non-exclusive list of supported identity providers: + +- Google Workspaces (formerly known as GSuite) +- Okta, Auth0 +- Microsoft Active Directory, Azure Active Directory, Microsoft Entra +- PingIdentity +- OneLogin + +If you're having issues with identity provider software not on this list, please [open a support ticket](https://app.supabase.com/support/new). + +## Prerequisites + +This guide requires the use of the [Supabase CLI](/docs/guides/cli). Please make sure you're using version v1.46.4 or higher. You can use `supabase -v` to see the currently installed version. + +SAML 2.0 support is disabled by default on Supabase projects. You can configure this on the [Auth Providers](https://app.supabase.com/project/_/auth/providers) page on your project. + +Please note that SAML 2.0 support is offered on tiers Pro and above. Check the [Pricing](https://supabase.com/pricing) page for more information. + +## Terminology + +The number of SAML and SSO acronyms can often be overwhelming. Here's a glossary which you can refer back to at any time: + +- **Identity Provider**, **IdP**, or **IDP** + An identity provider is a service that manages user accounts at a company or organization. It can verify the identity of a user and exchange that information with your Supabase project and other applications. It acts as a single source of truth for user identities and access rights. Commonly used identity providers are: Microsoft Active Directory (Azure AD, Microsoft Entra), Okta, Google Workspaces (GSuite), PingIdentity, OneLogin, and many others. There are also self-hosted and on-prem versions of identity providers, and sometimes they are accessible only by having access to a company VPN or being in a specific building. +- **Service Provider**, **SP** + This is the software that is asking for user information from an identity provider. In Supabase, this is your project's Auth server. +- **Assertion** + An assertion is a statement issued by an identity provider that contains information about a user. +- **EntityID** + A globally unique ID (usually a URL) that identifies an Identity Provider or Service Provider across the world. +- **NameID** + A unique ID (usually an email address) that identifies a user at an Identity Provider. +- **Metadata** + An XML document that describes the features and configuration of an Identity Provider or Service Provider. It can be as a standalone document or as a URL. Usually (but not always) the `EntityID` is the URL at which you can access the Metadata. +- **Certificate** + Supabase Auth (the Service Provider) trusts assertions from an Identity Provider based on the signature attached to the assertion. The signature is verified according to the certificate present in the Metadata. +- **Assertion Consumer Service (ACS) URL** + This is one of the most important SAML URLs. It is the URL where Supabase Auth will accept assertions from an identity provider. Basically, once the identity provider verifies the user's identity it will redirect to this URL and the redirect request will contain the assertion. +- **Binding (Redirect, POST, or Artifact)** + This is a description of the way an identity provider communicates with Supabase Auth. When using the Redirect binding, the communication occurs using HTTP 301 redirects. When it's `POST`, it's using `POST` requests sent with `
      ` elements on a page. When using Artifact, it's using a more secure exchange over a Redirect or `POST`. +- **RelayState** + State used by Supabase Auth to hold information about a request to verify the identity of a user. + +## Important SAML 2.0 information + +Below is information about your project's SAML 2.0 configuration which you can share with the company or organization that you're trying to on-board. + +| Name | Value | +| --------------------------- | ----------------------------------------------------------------------- | +| EntityID | `https://.supabase.co/auth/v1/sso/saml/metadata` | +| Metadata URL | `https://.supabase.co/auth/v1/sso/saml/metadata` | +| Metadata URL
      (download) | `https://.supabase.co/auth/v1/sso/saml/metadata?download=true` | +| ACS URL | `https://.supabase.co/auth/v1/sso/saml/acs` | +| SLO URL | `https://.supabase.co/auth/v1/sso/slo` | +| NameID | Required `emailAddress` or `persistent` | + +Note that SLO (Single Logout) is not supported at this time with Supabase Auth as it is a rarely supported feature by identity providers. However, the URL is registered and advertised for when this does become available. + +Append `?download=true` to the Metadata URL to download the Metadata XML file. This is useful in cases where the identity provider requires a file. + +Alternatively, you can use the `supabase sso info --project-ref ` command to get setup information for your project. + +### User accounts and identities + +User accounts and identities created via SSO differ from regular (email, phone, password, social login...) accounts in these ways: + +- **No automatic linking.** + Each user account verified using a SSO identity provider will not be automatically linked to existing user accounts in the system. That is, if a user `jane.doe@company.com` had signed up with a password, and then uses their company SSO login with your project, there will be two `jane.doe@company.com` user accounts in the system. +- **Emails are not necessarily unique.** + Given the behavior with no automatic linking, email addresses are no longer a unique identifier for a user account. Please always use the user's UUID to correctly reference user accounts. +- **Sessions may have a maximum duration.** + Depending on the configuration of the identity provider, a login session established with SSO may forcibly log out a user after a certain period of time. + +### Row Level Security + +You can use information about the SSO identity provider in Row Level Security policies. + +Here are some commonly used statements to extract SSO related information from the user's JWT: + +- `auth.jwt()#>>'{amr,0,method}'` + Returns the name of the last method used to verify the identity of this user. With SAML SSO this is `sso/saml`. +- `auth.jwt()#>>'{amr,0,provider}'` + Returns the UUID of the SSO identity provider used by the user to sign-in. +- `auth.jwt()#>>'{user_metadata,iss}'` + Returns the identity provider's SAML 2.0 `EntityID` + + + If you use [Multi-Factor Authentication](/guides/auth/auth-mfa) with SSO, the `amr` array may have + a different method at index `0`! + + +A common use case with SSO is to use the UUID of the identity provider as the identifier for the organization the user belongs to -- frequently known as a tenant. By associating the identity provider's UUID with your tenants, you can use restrictive RLS policies to scope down actions and data that a user is able to access. + +For example, let's say you have a table like: + +```sql +CREATE TABLE + organization_settings ( + -- the organization's unique ID + id UUID NOT NULL PRIMARY KEY, + -- the organization's SSO identity provider + sso_provider_id UUID UNIQUE, + -- name of the organization + name TEXT, + -- billing plan (paid, free, enterprise) + billing_plan TEXT + ); +``` + +You can use the information present in the user's JWT to scope down which rows from this table the user can see, without doing any additional user management: + +```sql +CREATE POLICY "View organization settings." + ON organization_settings + AS RESTRICTIVE + USING ( + sso_provider_id = auth.jwt()#>>'{amr,0,provider}' + ); +``` + +## Managing SAML 2.0 connections + +Once you've enabled SAML 2.0 support on your project via the [Auth Providers](https://app.supabase.com/project/_/auth/providers) page in the dashboard, you can use the [Supabase CLI](/docs/guides/cli) to add, update, remove and view information about identity providers. + +### Add a connection + +To establish a connection to a SAML 2.0 Identity Provider (IdP) you will need: + +- A SAML 2.0 Metadata XML file, or a SAML 2.0 Metadata URL pointing to an XML file +- (Optional) Email domains that the organization's IdP uses +- (Optional) Attribute mappings between the user properties of the IdP and the claims stored by Supabase Auth + +You should obtain the SAML 2.0 Metadata XML file or URL from the organization whose IdP you wish to connect. Most SAML 2.0 Identity Providers support the Metadata URL standard, and we recommend using a URL if this is available. + +Commonly used SAML 2.0 Identity Providers that support Metadata URLs: + +- Okta +- Azure AD (Microsoft Entra) +- PingIdentity + +Commonly used SAML 2.0 Identity Providers that only support Metadata XML files: + +- Google Workspaces (GSuite) +- Any self-hosted or on-prem identity provider behind a VPN + +Once you've obtained the SAML 2.0 Metadata XML file or URL you can establish a connection with your project's Supabase Auth server by using the [Supabase CLI](/docs/guides/cli): + +```bash +supabase sso add --type saml --project-ref \ + --metadata-url 'https://company.com/idp/saml/metadata' \ + --domains company.com +``` + +If you wish to use a Metadata XML file instead, you can use: + +```bash +supabase sso add --type saml --project-ref \ + --metadata-file /path/to/saml/metadata.xml \ + --domains company.com +``` + +This command will register a new identity provider with your project's Auth server. When successful, you will see the details of the provider such as it's SAML information and registered domains. + +Please note that only persons with write access to the project can register, update or remove identity providers. + +Once you've added an identity provider, users who have access to it can sign in to your application. With SAML 2.0 there are two ways that users can sign in to your project: + +- By signing-in from your application's user interface, commonly known as **SP (Service Provider) Initiated Flow** +- By clicking on an icon in the application menu on the company intranet or identity provider page, commonly known as **Identity Provider Initiated (IdP) Flow** + +To initiate a sign-in request from your application's user interface (i.e. the SP Initiated Flow), you can use: + +```typescript +supabase.auth.signInWithSSO({ + domain: 'company.com', +}) +``` + +Calling this method starts the sign-in process using the identity provider registered for the `company.com` domain name. It is not required that identity providers be assigned one or multiple domain names, in which case you can use the provider's unique ID instead. + +### Understanding attribute mappings + +When a user signs in using the SAML 2.0 Single Sign-On protocol, an XML document called the SAML Assertion is exchanged between the identity provider and Supabase Auth. + +This assertion contains information about the user's identity and other authentication information, such as: + +- Unique ID of the user (called `NameID` in SAML) +- Email address +- Name of the user +- Department or organization +- Other attributes present in the users directory managed by the identity provider + +With exception of the unique user ID, SAML does not require any other attributes in the assertion. Identity providers can be configured so that only select user information is shared with your project. + +Your project can be configured to recognize these attributes and map them into your project's database using a JSON structure. This process is called attribute mapping, and varies according to the configuration of the identity provider. + +For example, the following JSON structure configures attribute mapping for the `email` and `first_name` user identity properties. + +```json +{ + "keys": { + "email": { + "name": "mail" + }, + "first_name": { + "name": "givenName" + } + } +} +``` + +When creating or updating an identity provider with the [Supabase CLI](/docs/guides/cli) you can include this JSON as a file with the `--attribute-mapping /path/to/attribute/mapping.json` flag. + +For example, to change the attribute mappings to an existing provider you can use: + +```bash +supabase sso update --project-ref \ + --attribute-mapping /path/to/attribute/mapping.json +``` + +Given a SAML 2.0 assertion that includes these attributes: + +```xml + + + + + jane.doe@company.com + + + + + + + Jane Doe + + + +``` + +Will result in the following claims in the user's identity in the database and JWT: + +```json +{ + "email": "jane.doe@company.com", + "custom_claims": { + "first_name": "Jane Doe" + } +} +``` + +Supabase Auth does not require specifying attribute mappings if you only need access to the user's email. It will attempt to find an email attribute specified in the assertion. All other properties will not be automatically included, and it is those you need to map. + +At this time it is not possible to have users without an email address, so SAML assertions without one will be rejected. + +Most SAML 2.0 identity providers use Lightweight Directory Access Protocol (LDAP) attribute names. However, due to their variability and complexity operators of identity providers are able to customize both the `Name` and attribute value that is sent to Supabase Auth in an assertion. Please refer to the identity provider's documentation and contact the operator for details on what attributes are mapped for your project. + +### Remove a connection + +Once a connection to an identity provider is established, you can remove it by running: + +```bash +supabase sso remove --project-ref +``` + +If successful, the details of the removed identity provider will be shown. All user accounts from that identity provider will be immediately logged out. User information will remain in the system, but it will no longer be possible for any of those accounts to be accessed in the future, even if you add the connection again. + +If you need to reassign those user accounts to another identity provider, please [open a support ticket](https://app.supabase.com/support/new). + +A list of all registered identity providers can be displayed by running: + +```bash +supabase sso list --project-ref +``` + +### Update a connection + +You may wish to update settings about a connection to a SAML 2.0 identity provider. + +Commonly this is necessary when: + +- Cryptographic keys are rotated or have expired +- Metadata URL has changed, but is the same identity provider +- Other SAML 2.0 Metadata attributes have changed, but it is still the same identity provider +- You are updating the domains or attribute mapping + +You can use the following command to update the configuration of an identity provider: + +```bash +supabase sso update --project-ref +``` + +Please use `--help` to see all available flags. + +It is not possible to change the unique SAML identifier of the identity provider, known as `EntityID`. Everything else can be updated. If the SAML `EntityID` of your identity provider has changed, it is regarded as a new identity provider and you will have to register it like a new connection. + +### Retrieving information about a connection + +You can always obtain a list of all registered providers using: + +```bash +supabase sso list --project-ref +``` + +This list will only include basic information about each provider. To see all of the information about a provider you can use: + +```bash +supabase sso show --project-ref +``` + +You can use the `-o json` flag to output the information as JSON, should you need to. Other formats may be supported, please use `--help` to see all available options. + +## Frequently Asked Questions + +### How do I publish my application to an identity provider's marketplace? + +Many cloud-based identity providers offer a marketplace where you can register your application for easy on-boarding with customers. When you use Supabase Auth's SAML 2.0 support you can register your project in any one of these marketplaces. + +Please refer to the relevant documentation for each cloud-based identity provider on how you can do this. Some common marketplaces are: + +- [Okta Integration Network](https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/) +- [Azure Active Directory App Gallery](https://learn.microsoft.com/en-us/azure/active-directory-b2c/publish-app-to-azure-ad-app-gallery) +- [Google Workspaces Pre-integrated SAML apps catalog](https://support.google.com/a/table/9217027) + +### Why do some users get: SAML Assertion does not contain email address? + +Identity providers do not have to send back and email address for the user, though they often do. Supabase Auth requires that an email address is present. + +The following list of commonly used SAML attribute names is inspected, in order of appearance, to discover the email address in the assertion: + +- `urn:oid:0.9.2342.19200300.100.1.3` +- `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` +- `http://schemas.xmlsoap.org/claims/EmailAddress` +- `mail` +- `email` + +Finally if there is no such attribute, it will use the SAML `NameID` value but only if the format is advertised as `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`. + +Should you run into this problem, it is most likely a misconfiguration issue **on the identity provider side.** Please instruct your contact at the company to map the user's email address to one of the above listed attribute names, typically `email`. + +### How do I access the private key used for SAML in my project? + +At this time it is not possible to extract the RSA private key used by your project's Supabase Auth server. This is done to keep the private key as secure as possible, given that SAML does not offer an easy way to rotate keys without disrupting service. (Please use a SAML 2.0 Metadata URL whenever possible for this reason!) + +If you really need access to the key, please [open a support ticket](https://app.supabase.com/support/new) and we'll try to support you as best as possible. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/cli/local-development.mdx b/apps/docs/pages/guides/cli/local-development.mdx index 1c8f21a9b05..f6fe7c6c637 100644 --- a/apps/docs/pages/guides/cli/local-development.mdx +++ b/apps/docs/pages/guides/cli/local-development.mdx @@ -7,7 +7,23 @@ export const meta = { video: 'https://www.youtube.com/v/vyHyYpvjaks', } -Learn how to use the Supabase CLI to develop your project locally and deploy to the Supabase Platform. +Supabase is a flexible platform that lets you decide how you want to build your projects. You can use the Dashboard directly to get up and running quickly, or use a proper local setup. We suggest you work locally and deploy your changes to a linked project on the [Supabase Platform](https://app.supabase.io/). + +Doing things directly on the platform via the [Dashboard](https://app.supabase.io/) is fine when you're getting started, but it's a good idea to move to a proper local workflow before you get too far. Working locally, generating migrations as you change your tables, and applying those migrations to a linked project on the [Platform](https://app.supabase.io/) keeps everything nicely organized as you grow. + +## Why develop locally? + +The Dashboard provides a wide range of features for setting up your project: creating tables, adding columns, changing existing columns, creating views, setting up RLS policies, and more. Given all of the Dashboard's capabilities, you might question the need to work locally. Here's a few advantages to working this way: + +1. **Faster Development**: Developing locally allows you to work without any network latency or internet disruptions. + +2. **Easier Collaboration**: Developing locally can make it easier to collaborate with others on the same project. + +3. **Cost-Effective**: Supabase provides a generous free tier and gives you two free projects to get started. But what if you need more than two? When you develop locally, you can spin up unlimited local projects and link them with live projects when you're ready to launch. + +4. **Configuration in code**: If you directly change your tables via the Dashboard, none of that gets captured in code. If you follow these local development practices, you'll store all of your table schemas in code. + +5. **Work offline**: Need to work from a train? A plane? An automobile? No problem. Developing your project locally allows you to work offline.
      -### Make database changes +For this guide, we'll create a table called `employees` and see how we can make changes to it. -For this guide, create a table called `employees`. In Supabase Studio, navigate to the **SQL Editor** page and run the following SQL command: + + + + + + To get started, generate a [new migration](https://supabase.com/docs/reference/cli/supabase-migration-new) to store the SQL needed to create our `employees` table + + + + + +```bash +supabase migration new create_employees_table +``` + + + + + + + + + + + This creates a new migration: supabase/migrations/\ + _create_employees_table.sql. + + To that file, add the SQL to create this `employees` table + + + + ```sql -```sql create table - employees ( - id integer primary key generated always as identity, - name text - ); -``` +employees ( +id bigint primary key generated always as identity, +name text, +email text, +created_at timestamptz default now() +); - +```` -You can execute any SQL using the `DB URL` shown by [`supabase status`](/docs/reference/cli/usage#supabase-status). + - + + -Run the [`db diff`](/docs/reference/cli/usage#supabase-db-diff) command to detect changes in the local database: -```sh -supabase db diff create_employees -f create_employees -``` -This creates a new migration named `supabase/migrations/_create_employees.sql`, representing any changes made to the local database since [`supabase start`](/docs/reference/cli/usage#supabase-start). + + + + + + Now that you have a migration file, you can run this migration and create the `employees` table. + + Use the `reset` command here to reset the database to the current migrations + + + + ```bash +supabase db reset +```` + + + + + + + + + + + Now you can visit your new `employees` table in the Dashboard. + + Next, modify your `employees` table by adding a column for department. Create a new migration file for that. + + + + ```bash + supabase migration new add_department_to_employees_table + +```` + + + + + + + + + + + + This creates a new migration file: supabase/migrations/\ + _add_department_to_employees_table.sql. + + To that file, add the SQL to create a new department column + + + + ```sql +alter table + if exists public.employees add department text default 'Hooli'; +```` + + + + + ### Add sample data -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. +Now that you are managing your database with migrations scripts, it would be great have some seed data to use every time you reset the database. -```sql - -- in supabase/seed.sql +For this, you can use the seed script in `supabase/seed.sql`. This file was automatically created when you ran [`supabase init`](/docs/reference/cli/usage#supabase-init)) at the beginning. + + + + + + Insert data into your `employees` table with your `supabase/seed.sql` file. + + + + ```sql + +-- in supabase/seed.sql insert into - public.employees (name) +public.employees (name) values - ('Erlich Bachman'), - ('Richard Hendricks'), - ('Monica Hall'); -``` +('Erlich Bachman'), +('Richard Hendricks'), +('Monica Hall'); -Rerun the migration and seed scripts: +```` + -```bash + + + + + + + + + Reset your database (apply current migrations), and populate with seed data + + + + ```bash supabase db reset +```` + + + + + + +You should now see the `employees` table, along with your seed data in the Dashboard! All of your database changes are captured in code, and you can reset to a known state at any time, complete with seed data. + +### Diffing changes + +This workflow is great if you know SQL and are comfortable creating tables and columns. If not, you can still use the Dashboard to create tables and columns, and then use the CLI to diff your changes and create migrations. + +Create a new table called `cities`, with columns `id`, `name` and `population`. To see the corresponding SQL for this, you can use the `supabase db diff --schema public` command. This will show you the SQL that will be run to create the table and columns. The output of `supabase db diff` will look something like this: + +``` +Diffing schemas: public +Finished supabase db diff on branch main. + +create table "public"."cities" ( + "id" bigint primary key generated always as identity, + "name" text, + "population" bigint +); + ``` -You should now see the contents of `employees` in Studio. +You can then copy this SQL into a new migration file, and run `supabase db reset` to apply the changes. -### Reset database changes - -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 the following command to reset the local database: - -```sh -supabase db reset -``` +The last step is deploying these changes to a live Supabase project. ## Deploy your project -Go to the [Supabase Dashboard](https://app.supabase.com) and create a project to deploy the changes. +You've been developing your project locally, making changes to your tables via migrations. It's time to deploy your project to the Supabase Platform and start scaling up to millions of users! Head over to [Supabase](https://app.supabase.com) and create a new project to deploy to. ### Link your project +Associate your project with your remote project using [`supabase link`](/docs/reference/cli/usage#supabase-link). + +```bash +supabase link --project-ref +# You can get from your project's dashboard URL: https://app.supabase.com/project/ + +supabase db remote commit +# Capture any changes that you have made to your remote database before you went through the steps above +# If you have not made any changes to the remote database, skip this step +``` + +`supabase/migrations` is now populated with a migration in `..._remote_commit.sql`. +This migration captures any changes required for your local database to match the schema of your remote Supabase project. + There are a few commands required to link your project. We are in the process of consolidating these commands into a single command. Bear with us! -Associate your project with your remote project using [`supabase link`](/docs/reference/cli/usage#supabase-link). - -```bash -supabase link --project-ref -# You can get from your project’s dashboard URL: https://app.supabase.com/project/ - -supabase db remote commit -# Capture any changes that you have made to your database before setting up the CLI -``` - -`supabase/migrations` is now populated with a migration in `..._remote_commit.sql`. -This migration captures any changes required for your local database to match the schema of your remote Supabase project. - ### Deploy database changes Deploy any local database migrations using [`db push`](/docs/reference/cli/usage#supabase-db-push): @@ -244,21 +397,54 @@ Deploy any local database migrations using [`db push`](/docs/reference/cli/usage supabase db push ``` +Visiting your live project on [Supabase](https://app.supabase.com), you'll see a new `employees` table, complete with the `department` column you added in the second migration above. + ### Deploy Edge Functions -Deploy any Edge Functions using [`functions deploy`](/docs/reference/cli/usage#supabase-functions-deploy): +If your project uses Edge Functions, you can deploy these using [`functions deploy`](/docs/reference/cli/usage#supabase-functions-deploy): ```sh supabase functions deploy ``` -## Limitations +### Enabling Local Logging -The local development environment is not as feature-complete as the Supabase Platform. Here are some of the differences: +Local logs rely on the Supabase Analytics Server. This can be enabled via the CLI configuration, and requires a Google Cloud project and BigQuery access. + + +The Google Cloud project must have billing enabled. + + +Requirements: + +1. Google Cloud project number (must be paid) +2. Google Cloud project ID +3. Google Cloud JWT, obtained through [Google Cloud IAM dashboard](https://console.cloud.google.com/iam-admin/iam) + +Once you have these 3 items, follow these steps: + +1. Update your project's `config.toml` and fill in the following: + +``` +[analytics] +enabled = true +gcp_project_number = "123456" +gcp_project_id = "my-project-id" +gcp_jwt_path = "supabase/gcloud.json" +``` + +2. Place your JWT file at the corresponding path and ensure that it is correctly named. + +3. Start your local stack using `supabase start` + +This will switch the logging drivers and will direct logs to the Analytics server. You will be able to view and query your logs via the Studio Logs Explorer and Logs UI. + +## Limitations and considerations + +The local development environment is not as feature-complete as the Supabase Platform. We're working towards parity between the hosted platform and the local environment. Here are some of the differences: - The Functions interface is coming soon. -- Logs are not supported through the interface (however you can access them through the Docker containers). -- You cannot update your project settings in the Dashboard—this must be done using the CLI. +- You cannot update your project settings in the Dashboard. This must be done using the CLI. export const Page = ({ children }) => 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.mdx b/apps/docs/pages/guides/database/extensions.mdx index 1c10af0d6bd..8b729b6ba73 100644 --- a/apps/docs/pages/guides/database/extensions.mdx +++ b/apps/docs/pages/guides/database/extensions.mdx @@ -23,38 +23,36 @@ Supabase has pre-installed some of the most useful open source extensions. 2. Click **Extensions** in the sidebar. 3. Enable or disable an extension. - - ```sql --- Example: enable the "pgtap" extension and ensure it is installed -create extension pgtap with schema extensions; + -- Example: enable the "pgtap" extension and ensure it is installed +create extension pgtap +with + schema extensions; -- Example: disable the "pgtap" extension -drop extension pgtap; +drop + extension pgtap; ``` Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension". To disable an extension call `drop extension`. - - -Enabling some extensions with `create extension with schema extensions` may lead to permission issues (e.g., `dblink`, `http`, `pg_cron`). - - - + + +Most extensions are installed under the `extensions` schema, which is accessible to public by default. To avoid namespace pollution, we do not recommend creating other entities in the `extensions` schema. + +If you need to restrict user access to tables managed by extensions, we recommend creating a separate schema for installing that specific extension. + +Some extensions can only be created under a specific schema, for eg. `postgis_tiger_geocoder` extension creates a schema named `tiger`. Before enabling such extensions, make sure you have not created a conflicting schema with the same name. + + + ### Full list of extensions Supabase is pre-configured with over 50 extensions. You can also install your own SQL extensions directly in the database through our SQL editor. diff --git a/apps/docs/pages/guides/database/extensions/pg_graphql.mdx b/apps/docs/pages/guides/database/extensions/pg_graphql.mdx index a63be68c164..e4a27cb523c 100644 --- a/apps/docs/pages/guides/database/extensions/pg_graphql.mdx +++ b/apps/docs/pages/guides/database/extensions/pg_graphql.mdx @@ -54,8 +54,8 @@ Given a table ```sql create table "Blog"( id serial primary key, - name varchar(255) not null, - description varchar(255), + name text not null, + description text, ); insert into "Blog"(name) 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 ``` @@ -215,6 +227,8 @@ final result = await client ### Search multiple columns +Right now there is no direct way to use JavaScript or Dart to search through multiple columns but you can do it by creating [computed columns](https://postgrest.org/en/stable/api.html#computed-virtual-columns) on the database. + To find all `books` where `description` or `title` contain the word `little`: + + +```sql +create function title_description(books) returns text as $$ + select $1.title || ' ' || $1.description; +$$ language sql immutable; +``` + +```js +const { data, error } = await supabase + .from('books') + .select() + .textSearch('title_description', `little`) +``` + + + + +```sql +create function title_description(books) returns text as $$ + select $1.title || ' ' || $1.description; +$$ language sql immutable; +``` + +```dart +final result = await client + .from('books') + .select() + .textSearch('title_description', "little") +``` + 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/database/postgres/indexes.mdx b/apps/docs/pages/guides/database/postgres/indexes.mdx new file mode 100644 index 00000000000..138143348de --- /dev/null +++ b/apps/docs/pages/guides/database/postgres/indexes.mdx @@ -0,0 +1,106 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + title: 'Managing Indexes in PostgreSQL', + description: 'Improve query performance using various index types in Postgres', + footerHelpType: 'postgres', +} + +An index makes your Postgres queries faster. The index is like a "table of contents" for your data - a reference list which allows queries to quickly locate a row in a given table without needing to scan the entire table (which in large tables can take a long time). + +Indexes can be structured in a few different ways. The type of index chosen depends on the values you are indexing. By far the most common index type, and the default in Postgres, is the B-Tree. A B-Tree is the generalized form of a binary search tree, where nodes can have more than two children. + +Even though indexes improve query performance, the Postgres query planner may not always make use of a given index when choosing which optimizations to make. Additionally indexes come with some overhead - additional writes and increased storage - so it's useful to understand how and when to use indexes, if at all. + +## Create an index + +Let's take an example table: +```sql +create table persons ( + id bigint generated by default as identity primary key, + age int, + height int, + weight int, + name text, + deceased boolean +); +``` + + +All the queries in this guide can be run using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Supabase Dashboard, or via `psql` if you're [connecting directly to the database](/docs/guides/database/connecting-to-postgres#direct-connections). + + + +We might want to frequently query users based on their age: +```sql +select name from persons where age = 32; +``` + +Without an index, Postgres will scan every row in the table to find equality matches on age. + +You can verify this by doing an explain on the query: +```sql +explain select name from persons where age = 32; +``` + +Outputs: +``` +Seq Scan on persons (cost=0.00..22.75 rows=x width=y) +Filter: (age = 32) +``` + +To add a simple B-Tree index you can run: +```sql +create index idx_persons_age on persons(age); +``` + + + +It can take a long time to build indexes on large datasets and the default behaviour of `create index` is to lock the table from writes. + +Luckily Postgres provides us with `create index concurrently` which prevents blocking writes on the table, but does take a bit longer to build. + + + +Here is a simplified diagram of the index we just created (note that in practice, nodes actually have more than two children). + +![B-Tree index example in Postgres](/docs/img/b-tree-example.png) + +You can see that in any large data set, traversing the index to locate a given value can be done in much less operations (O(log n)) than compared to scanning the table one value at a time from top to bottom (O(n)). + +## Partial indexes + +If you are frequently querying a subset of rows then it may be more efficient to build a partial index. In our example, perhaps we only want to match on `age` where `deceased is false`. We could build a partial index: + +```sql +create index idx_living_persons_age ON persons(age) where deceased is false; +``` + +## Ordering indexes + +By default B-Tree indexes are sorted in ascending order, but sometimes you may want to provide a different ordering. Perhaps our application has a page featuring the top 10 oldest people. Here we would want to sort in descending order, and include `NULL` values last. For this we can use: + +```sql +create index idx_persons_age_desc on persons(age desc nulls last); +``` + +## Reindexing + +After a while indexes can become stale and may need rebuilding. Postgres provides a `reindex` command for this, but due to Postgres locks being placed on the index during this process, you may want to make use of the `concurrent` keyword. + + +```sql +reindex index concurrently idx_persons_age; +``` + +Alternatively you can reindex all indexes on a particular table: + +```sql +reindex table concurrently persons; +``` + +Take note that `reindex` can be used inside a transaction, but `reindex [index/table] concurrently` cannot. + +export const Page = ({ children }) => + +export default Page \ No newline at end of file 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.mdx b/apps/docs/pages/guides/functions.mdx index a382f7ccde1..c3b9901e49d 100644 --- a/apps/docs/pages/guides/functions.mdx +++ b/apps/docs/pages/guides/functions.mdx @@ -43,12 +43,12 @@ export const examples = [ { name: 'With supabase-js', description: 'Use the Supabase client inside your Edge Function.', - href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/select-from-table-with-auth-rls/index.ts', + href: '/guides/functions/auth', }, { name: 'With CORS headers', description: 'Send CORS headers for invoking from the browser.', - href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/browser-with-cors/index.ts', + href: '/guides/functions/cors', }, { name: 'React Native with Stripe', @@ -86,6 +86,76 @@ export const examples = [ description: `Get user location data from user's IP address.`, href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/location', }, + { + name: 'Cloudflare Turnstile', + description: `Protecting Forms with Cloudflare Turnstile.`, + href: '/guides/functions/examples/cloudflare-turnstile', + }, + { + name: 'Connect to Postgres', + description: `Connecting to Postgres from Edge Functions.`, + href: '/guides/functions/examples/connect-to-postgres', + }, + { + name: 'Github Actions', + description: `Deploying Edge Functions with GitHub Actions.`, + href: '/guides/functions/examples/github-actions', + }, + { + name: 'Oak Server Middleware', + description: `Request Routing with Oak server middleware.`, + href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/oak-server', + }, + { + name: 'OpenAI', + description: `Using OpenAI in Edge Functions.`, + href: '/guides/functions/examples/openai', + }, + { + name: 'Stripe Webhooks', + description: `Handling signed Stripe Webhooks with Edge Functions.`, + href: '/guides/functions/examples/stripe-webhooks', + }, + { + name: 'Send emails', + description: `Send emails in Edge Functions.`, + href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/send-email-smtp', + }, + { + name: 'Web Stream', + description: `Server-Sent Events in Edge Functions.`, + href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/streams', + }, + { + name: 'Puppeteer', + description: `Generate screenshots with Puppeteer.`, + href: '/guides/functions/examples/screenshots', + }, + { + name: 'Discord Bot', + description: `Building a Slash Command Discord Bot with Edge Functions.`, + href: '/guides/functions/examples/discord-bot', + }, + { + name: 'Telegram Bot', + description: `Building a Telegram Bot with Edge Functions.`, + href: '/guides/functions/examples/telegram-bot', + }, + { + name: 'Upload File', + description: `Process multipart/form-data.`, + href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/file-upload-storage', + }, + { + name: 'Upstash Redis', + description: `Build an Edge Functions Counter with Upstash Redis.`, + href: '/guides/functions/examples/upstash-redis', + }, + { + name: 'Rate Limiting', + description: `Rate Limiting Egde Functions with Upstash Redis.`, + href: '/guides/functions/examples/rate-limiting', + }, ] export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/functions/auth.mdx b/apps/docs/pages/guides/functions/auth.mdx index 871c16bd6f2..c5bff683160 100644 --- a/apps/docs/pages/guides/functions/auth.mdx +++ b/apps/docs/pages/guides/functions/auth.mdx @@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'auth', - title: 'Auth', + title: 'Integrating With Supabase Auth', description: 'Supabase Edge Functions and Auth.', } @@ -20,7 +20,7 @@ By creating a supabase client with the auth context from the function, you can d 2. Run queries in the context of the user with [Row Level Security (RLS)](/docs/guides/auth/row-level-security) policies enforced. ```js lines=14,17-19,22-23 title=supabase/functions/select-from-table-with-auth-rls/index.ts -import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' +import { serve } from 'https://deno.land/std@0.177.0/http/server.ts' import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' serve(async (req: Request) => { diff --git a/apps/docs/pages/guides/functions/cicd-workflow.mdx b/apps/docs/pages/guides/functions/cicd-workflow.mdx index e168c6c7642..01c30d69182 100644 --- a/apps/docs/pages/guides/functions/cicd-workflow.mdx +++ b/apps/docs/pages/guides/functions/cicd-workflow.mdx @@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'cicd-workflow', - title: 'CI / CD Workflow', + title: 'Deploying with Git', description: 'How to deploy Supabase Edge Functions with a CI / CD pipeline.', video: 'https://www.youtube.com/v/6OMVWiiycLs', } diff --git a/apps/docs/pages/guides/functions/examples/connect-to-postgres.mdx b/apps/docs/pages/guides/functions/connect-to-postgres.mdx similarity index 86% rename from apps/docs/pages/guides/functions/examples/connect-to-postgres.mdx rename to apps/docs/pages/guides/functions/connect-to-postgres.mdx index 3b66a4affd9..461ae904c49 100644 --- a/apps/docs/pages/guides/functions/examples/connect-to-postgres.mdx +++ b/apps/docs/pages/guides/functions/connect-to-postgres.mdx @@ -2,8 +2,9 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'examples-postgres-on-the-edge', - title: 'Connect to Postgres', + title: 'Connecting directly to Postgres', description: 'Connecting to Postgres from Edge Functions.', + video: 'https://www.youtube.com/v/cl7EuF1-RsY', }
      @@ -19,10 +20,10 @@ Supabase Edge Functions allow you to go beyond HTTP and can connect to your Post ```ts index.ts import * as postgres from 'https://deno.land/x/postgres@v0.14.2/mod.ts' -import { serve } from 'https://deno.land/std@0.168.0/http/server.ts' +import { serve } from 'https://deno.land/std@0.177.0/http/server.ts' -// Get the connection string from the environment variable "DATABASE_URL" -const databaseUrl = Deno.env.get('DATABASE_URL')! +// Get the connection string from the environment variable "SUPABASE_DB_URL" +const databaseUrl = Deno.env.get('SUPABASE_DB_URL')! // Create a database pool with three connections that are lazily established const pool = new postgres.Pool(databaseUrl, 3, true) diff --git a/apps/docs/pages/guides/functions/cors.mdx b/apps/docs/pages/guides/functions/cors.mdx index 766da74ae86..75525da0741 100644 --- a/apps/docs/pages/guides/functions/cors.mdx +++ b/apps/docs/pages/guides/functions/cors.mdx @@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'functions-cors', - title: 'CORS (Cross-Origin Resource Sharing)', + title: 'CORS (Cross-Origin Resource Sharing) support for Invoking from the browser', description: 'Add CORS headers to invoke Edge Functions from the browser.', } @@ -24,7 +24,7 @@ export const corsHeaders = { You can then import and use the CORS headers within your functions: ```ts index.ts -import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' +import { serve } from 'https://deno.land/std@0.177.0/http/server.ts' import { corsHeaders } from '../_shared/cors.ts' console.log(`Function "browser-with-cors" up and running!`) diff --git a/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx b/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx index ef38552855f..bb6bdf7d71d 100644 --- a/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx +++ b/apps/docs/pages/guides/functions/examples/cloudflare-turnstile.mdx @@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'examples-cloudflare-turnstile', - title: 'Cloudflare Turnstile', + title: 'CAPTCHA support with Cloudflare Turnstile', description: 'Protecting Forms with Cloudflare Turnstile.', video: 'https://www.youtube.com/v/OwW0znboh60', } @@ -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 @@ -34,7 +34,7 @@ supabase functions new cloudflare-turnstile And add the code to the `index.ts` file: ```ts index.ts -import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' +import { serve } from 'https://deno.land/std@0.177.0/http/server.ts' import { corsHeaders } from '../_shared/cors.ts' console.log('Hello from Cloudflare Trunstile!') diff --git a/apps/docs/pages/guides/functions/examples/discord-bot.mdx b/apps/docs/pages/guides/functions/examples/discord-bot.mdx new file mode 100644 index 00000000000..e542f346ca6 --- /dev/null +++ b/apps/docs/pages/guides/functions/examples/discord-bot.mdx @@ -0,0 +1,178 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'examples-discord-bot', + title: 'Building a Discord Bot', + description: 'Building a Slash Command Discord Bot with Edge Functions.', + video: 'https://www.youtube.com/v/J24Bvo_m7DM', +} + +
      + +
      + +## Create an application on Discord Developer Portal + +1. Go to [https://discord.com/developers/applications](https://discord.com/developers/applications) (login using your discord account if required). +2. Click on **New Application** button available at left side of your profile picture. +3. Name your application and click on **Create**. +4. Go to **Bot** section, click on **Add Bot**, and finally on **Yes, do it!** to confirm. + +That's it. A new application is created which will hold our Slash Command. Don't close the tab as we need information from this application page throughout our development. + +Before we can write some code, we need to curl a discord endpoint to register a Slash Command in our app. + +Fill `DISCORD_BOT_TOKEN` with the token available in the **Bot** section and `CLIENT_ID` with the ID available on the **General Information** section of the page and run the command on your terminal. + +```bash +BOT_TOKEN='replace_me_with_bot_token' +CLIENT_ID='replace_me_with_client_id' +curl -X POST \ +-H 'Content-Type: application/json' \ +-H "Authorization: Bot $BOT_TOKEN" \ +-d '{"name":"hello","description":"Greet a person","options":[{"name":"name","description":"The name of the person","type":3,"required":true}]}' \ +"https://discord.com/api/v8/applications/$CLIENT_ID/commands" +``` + +This will register a Slash Command named `hello` that accepts a parameter named `name` of type string. + +## Code + +```ts index.ts +// Sift is a small routing library that abstracts away details like starting a +// listener on a port, and provides a simple function (serve) that has an API +// to invoke a function for a specific path. +import { json, serve, validateRequest } from 'sift' +// TweetNaCl is a cryptography library that we use to verify requests +// from Discord. +import nacl from 'tweetnacl' + +enum DiscordCommandType { + Ping = 1, + ApplicationCommand = 2, +} + +// For all requests to "/" endpoint, we want to invoke home() handler. +serve({ + '/discord-bot': home, +}) + +// The main logic of the Discord Slash Command is defined in this function. +async function home(request: Request) { + // validateRequest() ensures that a request is of POST method and + // has the following headers. + const { error } = await validateRequest(request, { + POST: { + headers: ['X-Signature-Ed25519', 'X-Signature-Timestamp'], + }, + }) + if (error) { + return json({ error: error.message }, { status: error.status }) + } + + // verifySignature() verifies if the request is coming from Discord. + // When the request's signature is not valid, we return a 401 and this is + // important as Discord sends invalid requests to test our verification. + const { valid, body } = await verifySignature(request) + if (!valid) { + return json( + { error: 'Invalid request' }, + { + status: 401, + } + ) + } + + const { type = 0, data = { options: [] } } = JSON.parse(body) + // Discord performs Ping interactions to test our application. + // Type 1 in a request implies a Ping interaction. + if (type === DiscordCommandType.Ping) { + return json({ + type: 1, // Type 1 in a response is a Pong interaction response type. + }) + } + + // Type 2 in a request is an ApplicationCommand interaction. + // It implies that a user has issued a command. + if (type === DiscordCommandType.ApplicationCommand) { + const { value } = data.options.find( + (option: { name: string; value: string }) => option.name === 'name' + ) + return json({ + // Type 4 responds with the below message retaining the user's + // input at the top. + type: 4, + data: { + content: `Hello, ${value}!`, + }, + }) + } + + // We will return a bad request error as a valid Discord request + // shouldn't reach here. + return json({ error: 'bad request' }, { status: 400 }) +} + +/** Verify whether the request is coming from Discord. */ +async function verifySignature(request: Request): Promise<{ valid: boolean; body: string }> { + const PUBLIC_KEY = Deno.env.get('DISCORD_PUBLIC_KEY')! + // Discord sends these headers with every request. + const signature = request.headers.get('X-Signature-Ed25519')! + const timestamp = request.headers.get('X-Signature-Timestamp')! + const body = await request.text() + const valid = nacl.sign.detached.verify( + new TextEncoder().encode(timestamp + body), + hexToUint8Array(signature), + hexToUint8Array(PUBLIC_KEY) + ) + + return { valid, body } +} + +/** Converts a hexadecimal string to Uint8Array. */ +function hexToUint8Array(hex: string) { + return new Uint8Array(hex.match(/.{1,2}/g)!.map((val) => parseInt(val, 16))) +} +``` + +## Deploy the Slash Command Handler + +```bash +supabase functions deploy discord-bot --no-verify-jwt +supabase secrets set DISCORD_PUBLIC_KEY=your_public_key +``` + +Navigate to your Function details in the Supabase Dashboard to get your Endpoint URL. + +### Configure Discord application to use our URL as interactions endpoint URL + +1. Go back to your application (Greeter) page on Discord Developer Portal +2. Fill **INTERACTIONS ENDPOINT URL** field with the URL and click on **Save Changes**. + +The application is now ready. Let's proceed to the next section to install it. + +## Install the Slash Command on your Discord server + +So to use the `hello` Slash Command, we need to install our Greeter application on our Discord server. Here are the steps: + +1. Go to **OAuth2** section of the Discord application page on Discord Developer Portal +2. Select `applications.commands` scope and click on the **Copy** button below. +3. Now paste and visit the URL on your browser. Select your server and click on **Authorize**. + +Open Discord, type `/Promise` and press **Enter**. + +## Run locally + +```bash +supabase functions serve discord-bot --no-verify-jwt --env-file ./supabase/.env.local +ngrok http 54321 +``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/examples/og-image.mdx b/apps/docs/pages/guides/functions/examples/og-image.mdx index efa8eb99e59..be4f4dc46d2 100644 --- a/apps/docs/pages/guides/functions/examples/og-image.mdx +++ b/apps/docs/pages/guides/functions/examples/og-image.mdx @@ -50,7 +50,7 @@ export default function handler(req: Request) { Create an `index.ts` file to execute the handler on incoming requests: ```ts index.ts -import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' +import { serve } from 'https://deno.land/std@0.177.0/http/server.ts' import handler from './handler.tsx' console.log('Hello from og-image Function!') diff --git a/apps/docs/pages/guides/functions/examples/openai.mdx b/apps/docs/pages/guides/functions/examples/openai.mdx new file mode 100644 index 00000000000..236ad59354f --- /dev/null +++ b/apps/docs/pages/guides/functions/examples/openai.mdx @@ -0,0 +1,84 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'examples-openai', + title: 'Generating OpenAI GPT3 completions', + description: 'Using OpenAI in Edge Functions.', + video: 'https://www.youtube.com/v/29p8kIqyU_Y', +} + +
      + +
      + +Use the [OpenAI completions API](https://platform.openai.com/docs/api-reference/completions) in Supabase Edge Functions. + +```ts index.ts +import 'xhr_polyfill' +import { serve } from 'std/server' +import { CreateCompletionRequest } from 'openai' + +serve(async (req) => { + const { query } = await req.json() + + const completionConfig: CreateCompletionRequest = { + model: 'text-davinci-003', + prompt: query, + max_tokens: 256, + temperature: 0, + stream: true, + } + + return fetch('https://api.openai.com/v1/completions', { + method: 'POST', + headers: { + Authorization: `Bearer ${Deno.env.get('OPENAI_API_KEY')}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(completionConfig), + }) +}) +``` + +## Run locally + +```bash +supabase functions serve --env-file ./supabase/.env.local --no-verify-jwt +``` + +Use cURL or Postman to make a POST request to http://localhost:54321/functions/v1/openai. + +```bash +curl -i --location --request POST http://localhost:54321/functions/v1/openai \ + --header 'Content-Type: application/json' \ + --data '{"query":"What is Supabase?"}' +``` + +## Deploy + +```bash +supabase functions deploy --no-verify-jwt openai +supabase secrets set --env-file ./supabase/.env.local +``` + +## Go deeper + +If you're interesting in learning how to use this to build your own ChatGPT, read [the blog post](/blog/chatgpt-supabase-docs) and check out the video: + +
      + +
      + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/examples/rate-limiting.mdx b/apps/docs/pages/guides/functions/examples/rate-limiting.mdx new file mode 100644 index 00000000000..6c99592a8fc --- /dev/null +++ b/apps/docs/pages/guides/functions/examples/rate-limiting.mdx @@ -0,0 +1,25 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + title: 'Rate Limiting Edge Functions', + description: 'Rate Limiting Edge Functions with Upstash Redis.', +} + +
      + +
      + +[Redis](https://redis.io/docs/about/) is an open source (BSD licensed), in-memory data structure store used as a database, cache, message broker, and streaming engine. It is optimized for atomic operations like incrementing a value, for example for a view counter or rate limiting. We can even rate limit based on the user ID from Supabase Auth! + +[Upstash](https://upstash.com/) provides an HTTP/REST based Redis client which is ideal for serverless use-cases and therefore works well with Supabase Edge Functions. + +Find the code on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/upstash-redis-ratelimit). + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/examples/screenshots.mdx b/apps/docs/pages/guides/functions/examples/screenshots.mdx new file mode 100644 index 00000000000..6e02568e47e --- /dev/null +++ b/apps/docs/pages/guides/functions/examples/screenshots.mdx @@ -0,0 +1,23 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + title: 'Taking Screenshots with Puppeteer', + description: 'Take screenshots in Edge Functions with Puppeteer and Browserless.io.', +} + +
      + +
      + +[Puppeteer](https://pptr.dev/) is a handy tool to programmatically take screenshots and generate PDFs. However, trying to do so in Edge Functions can be challenging due to the size restrictions. Luckily there is a [serverless browser offering available](https://www.browserless.io/) that we can connect to via websockets. + +Find the code on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/puppeteer). + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/examples/stripe-webhooks.mdx b/apps/docs/pages/guides/functions/examples/stripe-webhooks.mdx index b71a4ca2b36..c10c46426c8 100644 --- a/apps/docs/pages/guides/functions/examples/stripe-webhooks.mdx +++ b/apps/docs/pages/guides/functions/examples/stripe-webhooks.mdx @@ -1,10 +1,8 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { - id: 'examples-stripe-webhooks', title: 'Handling Stripe Webhooks', description: 'Handling signed Stripe Webhooks with Edge Functions.', - video: 'https://www.youtube.com/v/6OMVWiiycLs', }
      diff --git a/apps/docs/pages/guides/functions/examples/telegram-bot.mdx b/apps/docs/pages/guides/functions/examples/telegram-bot.mdx index ad87c1a7103..68254bc1b53 100644 --- a/apps/docs/pages/guides/functions/examples/telegram-bot.mdx +++ b/apps/docs/pages/guides/functions/examples/telegram-bot.mdx @@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'examples-telegram-bot', - title: 'Telegram Bot', + title: 'Building a Telegram Bot', description: 'Building a Telegram Bot with Edge Functions.', video: 'https://www.youtube.com/v/AWfE3a9J_uo', } diff --git a/apps/docs/pages/guides/functions/examples/upstash-redis.mdx b/apps/docs/pages/guides/functions/examples/upstash-redis.mdx index bbe58dd4dd1..d97d1879e63 100644 --- a/apps/docs/pages/guides/functions/examples/upstash-redis.mdx +++ b/apps/docs/pages/guides/functions/examples/upstash-redis.mdx @@ -5,7 +5,16 @@ export const meta = { description: 'Build an Edge Functions Counter with Upstash Redis.', } -A Redis counter example that stores a [hash](https://redis.io/commands/hincrby/) of function invocation count per region. +
      + +
      + +A Redis counter example that stores a [hash](https://redis.io/commands/hincrby/) of function invocation count per region. Find the code on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/upstash-redis-counter). ## Redis database setup @@ -73,7 +82,7 @@ serve(async (_req) => { ```bash supabase start -supabase functions serve upstash-redis-counter --no-verify-jwt --env-file supabase/functions/upstash-redis-counter/.env +supabase functions serve --no-verify-jwt --env-file supabase/functions/upstash-redis-counter/.env ``` Navigate to http://localhost:54321/functions/v1/upstash-redis-counter. diff --git a/apps/docs/pages/guides/functions/global-deployments.mdx b/apps/docs/pages/guides/functions/global-deployments.mdx new file mode 100644 index 00000000000..dbd3810cb71 --- /dev/null +++ b/apps/docs/pages/guides/functions/global-deployments.mdx @@ -0,0 +1,49 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'functions-debugging', + title: 'Globally Distributed Deployments', + description: 'We deploy your code throughout the world, close to your users.', +} + +We deploy your code throughout the world. Each new request is served from the closest region to your user. Currently located in the following regions: + +1. Taiwan (`asia-east1`) +1. Hong Kong (`asia-east2`) +1. Tokyo (`asia-northeast1`) +1. Osaka (`asia-northeast2`) +1. Seoul (`asia-northeast3`) +1. Mumbai (`asia-south1`) +1. Delhi (`asia-south2`) +1. Singapore (`asia-southeast1`) +1. Jakarta (`asia-southeast2`) +1. Sydney (`australia-southeast1`) +1. Melbourne (`australia-southeast2`) +1. Warsaw (`europe-central2`) +1. Finland (`europe-north1`) +1. Belgium (`europe-west1`) +1. London (`europe-west2`) +1. Frankfurt (`europe-west3`) +1. Netherlands (`europe-west4`) +1. Zurich (`europe-west6`) +1. Milan (`europe-west8`) +1. Paris (`europe-west9`) +1. Tel Aviv (`me-west1`) +1. Madrid (`europe-southwest1`) +1. Montréal (`northamerica-northeast1`) +1. Toronto (`northamerica-northeast2`) +1. São Paulo (`southamerica-east1`) +1. Chile (`southamerica-west1`) +1. Iowa (`us-central1`) +1. South Carolina (`us-east1`) +1. North Virginia (`us-east4`) +1. Ohio (`us-east5`) +1. Texas (`us-south1`) +1. Oregon (`us-west1`) +1. California (`us-west2`) +1. Utah (`us-west3`) +1. Nevada (`us-west4`) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/functions/import-maps.mdx b/apps/docs/pages/guides/functions/import-maps.mdx index 6d61e774b55..f6c360dc3fd 100644 --- a/apps/docs/pages/guides/functions/import-maps.mdx +++ b/apps/docs/pages/guides/functions/import-maps.mdx @@ -2,8 +2,8 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'functions-import-maps', - title: 'Import Maps', - description: 'Using Import Maps.', + title: 'Managing packages using Import Maps', + description: 'Managing packages using Import Maps.', video: 'https://www.youtube.com/v/ILr3cneZuFk', } diff --git a/apps/docs/pages/guides/functions/local-development.mdx b/apps/docs/pages/guides/functions/local-development.mdx index e342abe9b14..e3e77c95105 100644 --- a/apps/docs/pages/guides/functions/local-development.mdx +++ b/apps/docs/pages/guides/functions/local-development.mdx @@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'functions-local-development', - title: 'Local Development', + title: 'Developing Functions locally', description: 'Run your Edge Functions locally.', } @@ -10,7 +10,7 @@ You can run your Edge Function locally using [`supabase functions serve`](/docs/ ```bash supabase start # start the supabase stack -supabase functions serve hello-world # start the Function watcher +supabase functions serve # start the Functions watcher ``` The `functions serve` command has hot-reloading capabilities. It will watch for any changes to your files and restart the Deno server. @@ -20,7 +20,7 @@ The `functions serve` command has hot-reloading capabilities. It will watch for While serving your local Edge Function, you can invoke it using curl: ```bash -curl --request POST 'http://localhost:54321/functions/v1/hello-world' \ +curl --request POST 'http://localhost:54321/functions/v1/function-name' \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \ --header 'Content-Type: application/json' \ --data '{ "name":"Functions" }' @@ -37,7 +37,7 @@ const supabase = createClient( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' ) -const { data, error } = await supabase.functions.invoke('hello-world', { +const { data, error } = await supabase.functions.invoke('function-name', { body: { name: 'Functions' }, }) ``` diff --git a/apps/docs/pages/guides/functions/quickstart.mdx b/apps/docs/pages/guides/functions/quickstart.mdx index f878b495c65..ce34b931a66 100644 --- a/apps/docs/pages/guides/functions/quickstart.mdx +++ b/apps/docs/pages/guides/functions/quickstart.mdx @@ -128,8 +128,8 @@ Edge Functions supports `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, and `OPTIONS`. - Deno Deploy limitations - Deno does not support outgoing connections to ports `25`, `465`, and `587`. - Cannot read or write to File System + - NPM modules are not supported. - Edge Functions - - Local development - only one function at a time - Serving of HTML content is not supported (`GET` requests that return `text/html` will be rewritten to `text/plain`). export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/functions/schedule-functions.mdx b/apps/docs/pages/guides/functions/schedule-functions.mdx index 3339fcab8fa..fb4b0552824 100644 --- a/apps/docs/pages/guides/functions/schedule-functions.mdx +++ b/apps/docs/pages/guides/functions/schedule-functions.mdx @@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'schedule-functions', - title: 'Schedule Edge Functions', + title: 'Scheduling Edge Functions', description: 'Schedule Edge Functions with pg_cron.', } diff --git a/apps/docs/pages/guides/functions/secrets.mdx b/apps/docs/pages/guides/functions/secrets.mdx index 3d60a239a8b..ffbe7920042 100644 --- a/apps/docs/pages/guides/functions/secrets.mdx +++ b/apps/docs/pages/guides/functions/secrets.mdx @@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'functions-secrets', - title: 'Secrets and Environment Variables', + title: 'Managing Secrets and Environment Variables', description: 'Managing secrets and environment variables.', } @@ -46,7 +46,7 @@ console.log(Deno.env.get('MY_NAME')) Now we can invoke our function locally, by serving it with our new `.env.local` file: ```bash -supabase functions serve hello-world --env-file ./supabase/.env.local +supabase functions serve --env-file ./supabase/.env.local ``` When the function starts you should see the name “Yoda” output to the terminal. diff --git a/apps/docs/pages/guides/functions/examples/storage-caching.mdx b/apps/docs/pages/guides/functions/storage-caching.mdx similarity index 90% rename from apps/docs/pages/guides/functions/examples/storage-caching.mdx rename to apps/docs/pages/guides/functions/storage-caching.mdx index c9cf4de7be9..e7f70d58b0f 100644 --- a/apps/docs/pages/guides/functions/examples/storage-caching.mdx +++ b/apps/docs/pages/guides/functions/storage-caching.mdx @@ -1,8 +1,8 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { - id: 'examples-storage-caching', - title: 'Caching Images with Supabase Storage CDN', + id: 'storage-caching', + title: 'Integrating with Supabase Storage', description: 'Integrate Edge Functions with Supabase Storage to cache images on the Edge (CDN).', video: 'https://www.youtube.com/v/wW6L52v9Ldo', } diff --git a/apps/docs/pages/guides/functions/typescript-support.mdx b/apps/docs/pages/guides/functions/typescript-support.mdx new file mode 100644 index 00000000000..66f6ce4c9b6 --- /dev/null +++ b/apps/docs/pages/guides/functions/typescript-support.mdx @@ -0,0 +1,54 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'functions-debugging', + title: 'TypeScript Support', + description: 'Using TypeScript in Deno Edge Functions.', +} + +One of the benefits of Deno is that it treats TypeScript as a first class +language, just like JavaScript or Web Assembly, when running code in Deno. What +that means is you can run or import TypeScript without installing anything more +than the Deno CLI. + +## How does it work? + +At a high level, Deno converts TypeScript (as well as TSX and JSX) into +JavaScript. It does this via a combination of the +[TypeScript compiler](https://github.com/microsoft/TypeScript), which we build +into Deno, and a Rust library called [swc](https://swc.rs/). When the code has +been type checked and transformed, it is stored in a cache, ready for the next +run without the need to convert it from its source to JavaScript again. + +## Strict by default + +Deno type checks TypeScript in _strict_ mode by default, and the TypeScript core +team recommends _strict_ mode as a sensible default. This mode generally enables +features of TypeScript that probably should have been there from the start, but +as TypeScript continued to evolve, would be breaking changes for existing code. + +## Mixing JavaScript and TypeScript + +By default, Deno does not type check JavaScript. This can be changed, and is +discussed further in [Configuring TypeScript in Deno](./configuration.md). Deno +does support JavaScript importing TypeScript and TypeScript importing +JavaScript, in complex scenarios. + +An important note though is that when type checking TypeScript, by default Deno +will "read" all the JavaScript in order to be able to evaluate how it might have +an impact on the TypeScript types. The type checker will do the best it can to +figure out what the types are of the JavaScript you import into TypeScript, +including reading any JSDoc comments. Details of this are discussed in detail in +the [Types and type declarations](./types.md) section. + +## Type resolution + +One of the core design principles of Deno is to avoid non-standard module +resolution, and this applies to type resolution as well. If you want to utilize +JavaScript that has type definitions (e.g. a `.d.ts` file), you have to +explicitly tell Deno about this. The details of how this is accomplished are +covered in the [Types and type declarations](https://deno.land/manual/advanced/typescript/types) section. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/getting-started.mdx b/apps/docs/pages/guides/getting-started.mdx index 820eae29e0c..bb335915b34 100644 --- a/apps/docs/pages/guides/getting-started.mdx +++ b/apps/docs/pages/guides/getting-started.mdx @@ -218,6 +218,12 @@ export const resources = [ href: '/guides/getting-started/architecture', description: "An overview of Supabase's architecture and product principles.", }, + { + title: 'Local Development', + hasLightIcon: true, + href: '/guides/cli/local-development', + description: 'Use the Supabase CLI to develop locally and collaborate between teams.', + }, ] export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/getting-started/architecture.mdx b/apps/docs/pages/guides/getting-started/architecture.mdx index c97053b0c54..5eb9e4e2520 100644 --- a/apps/docs/pages/guides/getting-started/architecture.mdx +++ b/apps/docs/pages/guides/getting-started/architecture.mdx @@ -55,7 +55,7 @@ We use this with our [pg_graphql](https://github.com/supabase/pg_graphql) extens - Official Docs: [postgrest.org](https://postgrest.org/) - Source code: [github.com/PostgREST/postgrest](https://github.com/PostgREST/postgrest) - License: [MIT](https://github.com/PostgREST/postgrest/blob/main/LICENSE) -- Language: Haskel +- Language: Haskell ### Realtime (API & multiplayer) diff --git a/apps/docs/pages/guides/getting-started/features.mdx b/apps/docs/pages/guides/getting-started/features.mdx index 4cf63f342c0..f69a2f41901 100755 --- a/apps/docs/pages/guides/getting-started/features.mdx +++ b/apps/docs/pages/guides/getting-started/features.mdx @@ -149,35 +149,38 @@ Manage your projects programmatically. [Docs](/docs/reference/api). Both Postgres and the Supabase Platform are production-ready. Some tools we offer on top of Postgres are still under development. -| Product | Feature | Stage | -| -------------------------- | ---------------------- | ------- | -| Database | Postgres | `GA` | -| Database | Triggers | `GA` | -| Database | Functions | `GA` | -| Database | Extensions | `GA` | -| Database | Full Text Search | `GA` | -| Database | Webhooks | `alpha` | -| Database | Point-in-Time Recovery | `alpha` | -| Database | Vault | `alpha` | -| Studio | | `GA` | -| Realtime | Postgres Changes | `GA` | -| Realtime | Broadcast | `beta` | -| Realtime | Presence | `beta` | -| Storage | | `GA` | -| Storage | S3 Backend | `GA` | -| Storage | CDN | `GA` | -| Storage | Smart CDN | `beta` | -| Storage | Image Transformations | `beta` | -| Edge Functions | | `beta` | -| Auth | OAuth Providers | `beta` | -| Auth | Passwordless | `beta` | -| Auth | Next.js Auth Helpers | `alpha` | -| Auth | SvelteKit Auth Helpers | `alpha` | -| Auth | Remix Auth Helpers | `alpha` | -| Management API | | `beta` | -| CLI | | `beta` | -| Client Library: JavaScript | | `GA` | -| Client Library: Dart | | `beta` | +| Product | Feature | Stage | Available on self-hosted | +| -------------------------- | ---------------------- | ------- | ------------------------------------------- | +| Database | Postgres | `GA` | ✅ | +| Database | Triggers | `GA` | ✅ | +| Database | Functions | `GA` | ✅ | +| Database | Extensions | `GA` | ✅ | +| Database | Full Text Search | `GA` | ✅ | +| Database | Webhooks | `GA` | ✅ | +| Database | Point-in-Time Recovery | `alpha` | 🚧 [wal-g](https://github.com/wal-g/wal-g) | +| Database | Vault | `alpha` | ✅ | +| Studio | | `GA` | ✅ | +| Realtime | Postgres Changes | `GA` | ✅ | +| Realtime | Broadcast | `beta` | ✅ | +| Realtime | Presence | `beta` | ✅ | +| Storage | | `GA` | ✅ | +| Storage | S3 Backend | `GA` | ✅ | +| Storage | CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) | +| Storage | Smart CDN | `beta` | 🚧 [Cloudflare](https://www.cloudflare.com) | +| Storage | Image Transformations | `beta` | ✅ | +| Edge Functions | | `beta` | 🚧 [Deno Deploy](https://deno.com/deploy) | +| Auth | OAuth Providers | `beta` | ✅ | +| Auth | Passwordless | `beta` | ✅ | +| Auth | Next.js Auth Helpers | `alpha` | ✅ | +| Auth | SvelteKit Auth Helpers | `alpha` | ✅ | +| Auth | Remix Auth Helpers | `alpha` | ✅ | +| Management API | | `beta` | N/A | +| CLI | | `beta` | N/A | +| Client Library: JavaScript | | `GA` | N/A | +| Client Library: Dart | | `beta` | N/A | + +- ✅ = Fully Available +- 🚧 = Available, but requires external tools or configuration export const Page = ({ children }) => 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/getting-started/quickstarts/nuxtjs.mdx b/apps/docs/pages/guides/getting-started/quickstarts/nuxtjs.mdx index 366120f35e9..f9259185926 100644 --- a/apps/docs/pages/guides/getting-started/quickstarts/nuxtjs.mdx +++ b/apps/docs/pages/guides/getting-started/quickstarts/nuxtjs.mdx @@ -126,7 +126,7 @@ The fastest way to get started with supabase and Nuxt.js is to use the supabase- - Next, in your Next.js app, create a file called supabase-client.js and add the following code to initialize the Supabase client and set your project's credentials: + Next, in your Nuxt.js app, create a file called supabase-client.js and add the following code to initialize the Supabase client and set your project's credentials: diff --git a/apps/docs/pages/guides/getting-started/quickstarts/reactjs.mdx b/apps/docs/pages/guides/getting-started/quickstarts/reactjs.mdx index ea117046949..02422ea1927 100644 --- a/apps/docs/pages/guides/getting-started/quickstarts/reactjs.mdx +++ b/apps/docs/pages/guides/getting-started/quickstarts/reactjs.mdx @@ -24,8 +24,8 @@ export const meta = { ```sql SQL_EDITOR -- Create the table CREATE TABLE countries ( - id SERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL ); -- Insert some sample data into the table INSERT INTO countries (name) VALUES ('United States'); @@ -41,14 +41,14 @@ export const meta = { - Create a React app using the `create-react-app` command. + Create a React app using a [Vite](https://vitejs.dev/guide/) template. ```bash Terminal - npx create-react-app my-app + npm create vite@latest my-app -- --template react ``` @@ -77,25 +77,42 @@ export const meta = { - In `index.js`, create a Supabase client using your [Project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api). + In `App.jsx`, create a Supabase client using your [Project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api). - Add a `getCountries` function to fetch the data and log the query result to the browser console. + Add a `getCountries` function to fetch the data and display the query result to the page. - ```js src/index.js - import { createClient } from '@supabase/supabase-js' + ```js src/App.jsx + import { useEffect, useState } from "react"; + import { createClient } from "@supabase/supabase-js"; - const supabase = createClient('https://.supabase.co', '') + const supabase = createClient("https://.supabase.co", ""); - async function getCountries() { - const countries = await supabase.from('countries').select() - console.log(countries) + function App() { + const [countries, setCountries] = useState([]); + + useEffect(() => { + getCountries(); + }, []); + + async function getCountries() { + const { data } = await supabase.from("countries").select(); + setCountries(data); + } + + return ( +
        + {countries.map((country) => ( +
      • {country.name}
      • + ))} +
      + ); } - getCountries() + export default App; ```
      @@ -105,14 +122,14 @@ export const meta = { - Start the app, go to http://localhost:3000 in a browser, and open the browser console and you should see the list of countries. + Start the app, go to http://localhost:5173 in a browser, and open the browser console and you should see the list of countries. ```bash Terminal - npm start + npm run dev ``` diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx index 5c47ae577b3..aa420134d55 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx @@ -36,6 +36,7 @@ Then let's install the additional dependencies: [supabase-js](https://github.com ```bash npm install @supabase/supabase-js npm install react-native-elements @react-native-async-storage/async-storage react-native-url-polyfill +npx expo install expo-secure-store ``` Now let's create a helper file to initialize the Supabase client. @@ -44,15 +45,28 @@ These variables will be exposed on the browser, and that's completely fine since [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database. ```ts title=lib/supabase.ts -import AsyncStorage from '@react-native-async-storage/async-storage' +import * as SecureStore from "expo-secure-store"; import { createClient } from '@supabase/supabase-js' + +const ExpoSecureStoreAdapter = { + getItem: (key: string) => { + return SecureStore.getItemAsync(key); + }, + setItem: (key: string, value: string) => { + SecureStore.setItemAsync(key, value); + }, + removeItem: (key: string) => { + SecureStore.deleteItemAsync(key); + }, +}; + const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL const supabaseAnonKey = YOUR_REACT_NATIVE_SUPABASE_ANON_KEY export const supabase = createClient(supabaseUrl, supabaseAnonKey, { auth: { - storage: AsyncStorage as any, + storage: ExpoSecureStoreAdapter as any, autoRefreshToken: true, persistSession: true, detectSessionInUrl: false, @@ -99,7 +113,7 @@ export default function Auth() { } return ( - + + diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx index bbee6933442..14c32a07bbb 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx @@ -166,7 +166,8 @@ npm install @supabase/auth-ui-react Add the `Auth` component to your home page ```jsx title=pages/index.js -import { Auth, ThemeSupa } from '@supabase/auth-ui-react' +import { Auth } from '@supabase/auth-ui-react' +import { ThemeSupa } from '@supabase/auth-ui-shared' import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react' const Home = () => { @@ -317,6 +318,8 @@ export default function Account({ session }) { Let's create a new component for that called `Account.tsx` within a `components` folder. +First, [generate type definitions from your database using the Supabase CLI](https://supabase.com/docs/guides/database/api/generating-types) to `../utils/database.types`. + ```tsx title=components/Account.tsx import { useState, useEffect } from 'react' import { useUser, useSupabaseClient, Session } from '@supabase/auth-helpers-react' @@ -448,7 +451,8 @@ export default function Account({ session }: { session: Session }) { Now that we have all the components in place, let's update `pages/index.js`: ```jsx lines=3,14 title=pages/index.js -import { Auth, ThemeSupa } from '@supabase/auth-ui-react' +import { Auth } from '@supabase/auth-ui-react' +import { ThemeSupa } from '@supabase/auth-ui-shared' import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react' import Account from '../components/Account' diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-react.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-react.mdx index 73c30cd56cb..b72fa02a445 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-react.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-react.mdx @@ -22,11 +22,11 @@ Let's start building the React app from scratch. ### Initialize a React app -We can use [Create React App](https://create-react-app.dev/docs/getting-started/) to initialize +We can use [Vite](https://vitejs.dev/guide/) to initialize an app called `supabase-react`: ```bash -npx create-react-app supabase-react +npm create vite@latest supabase-react -- --template react cd supabase-react ``` @@ -40,8 +40,8 @@ And finally we want to save the environment variables in a `.env.local` file. All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys). ```bash title=.env -REACT_APP_SUPABASE_URL=YOUR_SUPABASE_URL -REACT_APP_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY +VITE_SUPABASE_URL=YOUR_SUPABASE_URL +VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY ``` Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed @@ -52,8 +52,8 @@ Create and edit `src/supabaseClient.js`: ```js title=src/supabaseClient.js import { createClient } from '@supabase/supabase-js' -const supabaseUrl = process.env.REACT_APP_SUPABASE_URL -const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL +const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY export const supabase = createClient(supabaseUrl, supabaseAnonKey) ``` @@ -65,9 +65,9 @@ You can find the full contents of this file [here](https://raw.githubusercontent Let's set up a React component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords. -Create and edit `src/Auth.js`: +Create and edit `src/Auth.jsx`: -```jsx title=src/Auth.js +```jsx title=src/Auth.jsx import { useState } from 'react' import { supabase } from './supabaseClient' @@ -75,44 +75,42 @@ export default function Auth() { const [loading, setLoading] = useState(false) const [email, setEmail] = useState('') - const handleLogin = async (e) => { - e.preventDefault() + const handleLogin = async (event) => { + event.preventDefault() - try { - setLoading(true) - const { error } = await supabase.auth.signInWithOtp({ email }) - if (error) throw error - alert('Check your email for the login link!') - } catch (error) { + setLoading(true) + const { error } = await supabase.auth.signInWithOtp({ email }) + + if (error) { alert(error.error_description || error.message) - } finally { - setLoading(false) + } else { + alert('Check your email for the login link!') } + setLoading(false) } return ( -
      -
      +
      +

      Supabase + React

      Sign in via magic link with your email below

      - {loading ? ( - 'Sending magic link...' - ) : ( - - + +
      setEmail(e.target.value)} /> -
      +
      + - - )} +
      +
      ) @@ -123,130 +121,119 @@ export default function Auth() { After a user is signed in we can allow them to edit their profile details and manage their account. -Let's create a new component for that called `src/Account.js`. +Let's create a new component for that called `src/Account.jsx`. -```jsx title=src/Account.js +```jsx title=src/Account.jsx import { useState, useEffect } from 'react' import { supabase } from './supabaseClient' -const Account = ({ session }) => { +export default function Account({ session }) { const [loading, setLoading] = useState(true) const [username, setUsername] = useState(null) const [website, setWebsite] = useState(null) const [avatar_url, setAvatarUrl] = useState(null) useEffect(() => { - getProfile() - }, [session]) - - const getProfile = async () => { - try { + async function getProfile() { setLoading(true) const { user } = session - let { data, error, status } = await supabase + let { data, error } = await supabase .from('profiles') .select(`username, website, avatar_url`) .eq('id', user.id) .single() - if (error && status !== 406) { - throw error - } - - if (data) { + if (error) { + console.warn(error) + } else if (data) { setUsername(data.username) setWebsite(data.website) setAvatarUrl(data.avatar_url) } - } catch (error) { - alert(error.message) - } finally { + setLoading(false) } - } - const updateProfile = async (e) => { - e.preventDefault() + getProfile() + }, [session]) - try { - setLoading(true) - const { user } = session + async function updateProfile(event) { + event.preventDefault() - const updates = { - id: user.id, - username, - website, - avatar_url, - updated_at: new Date(), - } + setLoading(true) + const { user } = session - let { error } = await supabase.from('profiles').upsert(updates) - - if (error) { - throw error - } - } catch (error) { - alert(error.message) - } finally { - setLoading(false) + const updates = { + id: user.id, + username, + website, + avatar_url, + updated_at: new Date(), } + + let { error } = await supabase.from('profiles').upsert(updates) + + if (error) { + alert(error.message) + } + setLoading(false) } return ( -
      - {loading ? ( - 'Saving ...' - ) : ( -
      -
      Email: {session.user.email}
      -
      - - setUsername(e.target.value)} - /> -
      -
      - - setWebsite(e.target.value)} - /> -
      -
      - -
      -
      - )} - -
      +
      +
      + + +
      +
      + + setUsername(e.target.value)} + /> +
      +
      + + setWebsite(e.target.value)} + /> +
      + +
      + +
      + +
      + +
      +
      ) } - -export default Account ``` ### Launch! -Now that we have all the components in place, let's update `src/App.js`: +Now that we have all the components in place, let's update `src/App.jsx`: -```jsx title=src/App.js -import './index.css' +```jsx title=src/App.jsx +import './App.css' import { useState, useEffect } from 'react' import { supabase } from './supabaseClient' import Auth from './Auth' import Account from './Account' -export default function App() { +function App() { const [session, setSession] = useState(null) useEffect(() => { @@ -265,15 +252,17 @@ export default function App() {
      ) } + +export default App ``` Once that's done, run this in a terminal window: ```bash -npm start +npm run dev ``` -And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app. +And then open the browser to [localhost:5173](http://localhost:5173) and you should see the completed app. ![Supabase React](/docs/img/supabase-react-demo.png) @@ -285,9 +274,9 @@ Every Supabase project is configured with [Storage](/docs/guides/storage) for ma Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component: -Create and edit `src/Avatar.js`: +Create and edit `src/Avatar.jsx`: -```jsx title=src/Avatar.js +```jsx title=src/Avatar.jsx import { useEffect, useState } from 'react' import { supabase } from './supabaseClient' @@ -299,7 +288,7 @@ export default function Avatar({ url, size, onUpload }) { if (url) downloadImage(url) }, [url]) - const downloadImage = async (path) => { + async function downloadImage(path) { try { const { data, error } = await supabase.storage.from('avatars').download(path) if (error) { @@ -312,7 +301,7 @@ export default function Avatar({ url, size, onUpload }) { } } - const uploadAvatar = async (event) => { + async function uploadAvatar(event) { try { setUploading(true) @@ -331,7 +320,7 @@ export default function Avatar({ url, size, onUpload }) { throw uploadError } - onUpload(filePath) + onUpload(event, filePath) } catch (error) { alert(error.message) } finally { @@ -340,31 +329,33 @@ export default function Avatar({ url, size, onUpload }) { } return ( -
      - {avatarUrl - {uploading ? ( - 'Uploading...' +
      + {avatarUrl ? ( + Avatar ) : ( - <> - -
      - -
      - +
      )} +
      + + +
      ) } @@ -372,9 +363,9 @@ export default function Avatar({ url, size, onUpload }) { ### Add the new widget -And then we can add the widget to the Account page at `src/Account.js`: +And then we can add the widget to the Account page at `src/Account.jsx`: -```jsx title=src/Account.js +```jsx title=src/Account.jsx // Import the new component import Avatar from './Avatar' @@ -386,9 +377,9 @@ return ( { + onUpload={(event, url) => { setAvatarUrl(url) - updateProfile({ username, website, avatar_url: url }) + updateProfile(event) }} /> {/* ... */} diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx index a99cce9b9d9..c67ccea9008 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx @@ -26,12 +26,12 @@ We can use the [SvelteKit Skeleton Project](https://kit.svelte.dev/docs) to init an app called `supabase-sveltekit` (for this tutorial we will be using TypeScript): ```bash -npm init svelte@next supabase-sveltekit +npm create svelte@latest supabase-sveltekit cd supabase-sveltekit npm install ``` -Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js) +Then install the Supabase client library: [supabase-js](https://github.com/supabase/supabase-js) ```bash npm install @supabase/supabase-js @@ -45,17 +45,7 @@ PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL" PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_KEY" ``` -Now that we have the API credentials in place, create a `src/lib/supabaseClient.ts` helper file to initialize the Supabase client. -These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database. - -```js title=src/lib/supabaseClient.ts -import { createClient } from '@supabase/auth-helpers-sveltekit' -import { env } from '$env/dynamic/public' - -export const supabase = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY) -``` - -Optionally, update `src/routes/style.css` with the [CSS from the example](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/svelte-user-management/src/app.css). +Optionally, add `src/styles.css` with the [CSS from the example](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/svelte-user-management/src/app.css). ### Supabase Auth Helpers @@ -69,249 +59,392 @@ Install the auth helpers for SvelteKit: npm install @supabase/auth-helpers-sveltekit ``` -Update your `src/routes/+layout.svelte`: +Add the code below to your `src/hooks.server.ts` to initialize the client on the server: -```html title=src/routes/+layout.svelte - - -
      - -
      + }); +}; ``` -Create a new `src/routes/+layout.ts` file to handle the session on the client-side. +If you are using TypeScript the compiler might complain about `event.locals.supabase` and `event.locals.getSession`, this can be fixed by updating your `src/app.d.ts` with the content below: -```ts title=src/routes/+layout.ts -import type { LayoutLoad } from './$types' -import { getSupabase } from '@supabase/auth-helpers-sveltekit' +```ts title=src/app.d.ts +// src/app.d.ts -export const load: LayoutLoad = async (event) => { - const { session } = await getSupabase(event) - return { session } +import { SupabaseClient, Session } from '@supabase/supabase-js'; + +declare global { + namespace App { + interface Locals { + supabase: SupabaseClient; + getSession(): Promise; + } + interface PageData { + session: Session | null; + } + // interface Error {} + // interface Platform {} + } } ``` Create a new `src/routes/+layout.server.ts` file to handle the session on the server-side. ```ts title=src/routes/+layout.server.ts -import type { LayoutServerLoad } from './$types' -import { getServerSession } from '@supabase/auth-helpers-sveltekit' +// src/routes/+layout.server.ts +import type { LayoutServerLoad } from './$types'; -export const load: LayoutServerLoad = async (event) => { +export const load: LayoutServerLoad = async ({ locals: { getSession } }) => { return { - session: await getServerSession(event), - } -} + session: getSession() + }; +}; ``` -Be sure to create `src/hooks.client.ts` and `src/hooks.server.ts` in order to get the auth helper started on the client and server-side. +> Start your dev server (`npm run dev`) in order to generate the `./$types` files we are referencing in our project. -```ts title=src/hooks.client.ts -import '$lib/supabaseClient' +Create a new `src/routes/+layout.ts` file to handle the session and the supabase object on the client-side. + +```ts title=src/routes/+layout.ts +// src/routes/+layout.ts +import { invalidate } from '$app/navigation'; +import { + PUBLIC_SUPABASE_ANON_KEY, + PUBLIC_SUPABASE_URL +} from '$env/static/public'; +import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'; +import type { LayoutLoad } from './$types'; + +export const load: LayoutLoad = async ({ fetch, data, depends }) => { + depends('supabase:auth'); + + const supabase = createSupabaseLoadClient({ + supabaseUrl: PUBLIC_SUPABASE_URL, + supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + event: { fetch }, + serverSession: data.session + }); + + const { + data: { session } + } = await supabase.auth.getSession(); + + return { supabase, session }; +}; ``` -```ts title=src/hooks.server.ts -import '$lib/supabaseClient' -``` +Update your `src/routes/+layout.svelte`: -### Set up a Login component - -Let's set up a Svelte component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords. -Create a new `Auth.svelte` component in the `src/routes` directory to handle this functionality. - -```html title=src/routes/Auth.svelte +```svelte title=src/routes/+layout.svelte + -
      -
      -

      Supabase + SvelteKit

      -

      Sign in via magic link with your email below

      -
      - -
      -
      - -
      -
      -
      + + User Management + + +
      + +
      ``` -### Account component +### Set up a Login page + +#### Supabase Auth UI + +We can use the [Supabase Auth UI](/docs/guides/auth/auth-helpers/auth-ui), a pre-built Svelte component, for authenticating users via OAuth, email, and magic links. + +Install the Supabase Auth UI for Svelte + +```bash +npm install @supabase/auth-ui-svelte @supabase/auth-ui-shared +``` + +Add the `Auth` component to your home page + +```svelte title=src/routes/+page.svelte + + + + + User Management + + +
      +
      + +
      +
      +``` + +Create a `src/routes/+page.server.ts` file that will return our website url to be used in our `redirectTo` above. + +> This is necessary because the current Supabase auth flow uses implicit grant which returns the tokens as part of the url fragment (#). +The redirect to a non server protected page will make sure that the client captures the url fragment as we cannot read these on the server +and send it over to the server. This all happens behind the scenes and isn't something you will need to do manually. + +```ts +// src/routes/+page.server.ts +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ url, locals: { getSession } }) => { + const session = await getSession(); + + // if the user is already logged in return them to the account page + if (session) { + throw redirect(303, '/account'); + } + + return { url: url.origin }; +}; +``` + +### Loading page + +Create a `src/routes/logging-in/+page.svelte` file with the code below. + +This page is used for waiting while your client-side code inside of `src/routes/+layout.ts` sends the tokens over to your server-side code to store it in a cookie. + +```svelte + + + +
      + "Because as we know, there are known knowns; there are things we know we know. We also know there + are known unknowns; that is to say we know there are some things we do not know. But there are + also unknown unknowns—the ones we don't know we don't know" - Donald Rumsfeld +
      +``` + +### Account page After a user is signed in, they need to be able to edit their profile details and manage their account. -Create a new `Account.svelte` component in the `src/routes` directory to handle this functionality. +Create a new `src/routes/account/+page.svelte` file with the content below. -```html title=src/routes/Account.svelte +```svelte title=src/routes/account/+page.svelte + -
      -
      - - -
      -
      - - -
      -
      - - -
      +
      + +
      + + +
      -
      - -
      +
      + + +
      -
      - -
      - +
      + + +
      + +
      + + +
      + +
      + +
      + + +
      +
      + +
      +
      +
      +``` + +Now create the associated `src/routes/account/+page.server.ts` file that will handle loading our data from the server through the `load` function +and handle all our form actions through the `actions` object. + +```ts +import { fail, redirect } from '@sveltejs/kit'; +import type { Actions, PageServerLoad } from './$types'; + +export const load = (async ({ locals: { supabase, getSession } }) => { + const session = await getSession(); + + if (!session) { + throw redirect(303, '/'); + } + + const { data: profile } = await supabase + .from('profiles') + .select(`username, full_name, website, avatar_url`) + .eq('id', session.user.id) + .single(); + + return { session, profile }; +}) satisfies PageServerLoad; + +export const actions = { + update: async ({ request, locals: { supabase, getSession } }) => { + const formData = await request.formData(); + const fullName = formData.get('fullName') as string; + const username = formData.get('username') as string; + const website = formData.get('website') as string; + const avatarUrl = formData.get('avatarUrl') as string; + + const session = await getSession(); + + const { error } = await supabase.from('profiles').upsert({ + id: session?.user.id, + full_name: fullName, + username, + website, + avatar_url: avatarUrl, + updated_at: new Date() + }); + + if (error) { + return fail(500, { + fullName, + username, + website, + avatarUrl + }); + } + + return { + fullName, + username, + website, + avatarUrl + }; + }, + signout: async ({ locals: { supabase, getSession } }) => { + const session = await getSession(); + if (session) { + await supabase.auth.signOut(); + throw redirect(303, '/'); + } + } +} satisfies Actions; ``` ### Launch! -Now that we have all the components in place, let's update `src/routes/+page.svelte`: - -```html title=src/routes/+page.svelte - - - - Supabase + SvelteKit - - - -{#if !$page.data.session} - -{:else} - -{/if} -``` - -Once that's done, run this in a terminal window: +Now that we have all the pages in place, run this in a terminal window: ```bash npm run dev @@ -327,91 +460,99 @@ Every Supabase project is configured with [Storage](/docs/guides/storage) for ma ### Create an upload widget -Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component called `Avatar.svelte` in the `src/routes` directory: +Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component called `Avatar.svelte` in the `src/routes/account` directory: -```html title=src/routes/Avatar.svelte +```svelte title=src/routes/account/Avatar.svelte +
      - {#if avatarUrl} {avatarUrl {:else} -
      - {/if} + {#if avatarUrl} + {avatarUrl + {:else} +
      + {/if} + -
      - - -
      +
      + + +
      ``` @@ -419,18 +560,34 @@ Let's create an avatar for the user so that they can upload a profile photo. We And then we can add the widget to the Account page: -```html title=src/routes/Account.svelte - -
      - - +
      + + + { + profileForm.requestSubmit(); + }} + /> - - + + +
      ``` ### Storage management diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-vue-3.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-vue-3.mdx index 5480df2d4aa..0cbdd921ca2 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-vue-3.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-vue-3.mdx @@ -99,7 +99,7 @@ const handleLogin = async () => {

      Supabase + Vue 3

      Sign in via magic link with your email below

      - +

      {item.name}

      - {item.items.map((integration) => ( + {item.items?.map((integration) => ( Database > Connection info. For additional security, you can also download your SSL certificate and upload it to Dashibase. - -![Supabase credentials](/docs/img/guides/integrations/dashibase/supabase-credentials.png) - -## Step 2. Set up your data access policy - -Once you have connected your database, you can also control what your team can see and edit in your database via Dashibase. There are usually certain data that you don’t want others to edit or even see (e.g. address, email, identification number). - -Note: If you prefer to restrict access to your database _before_ you connect to Dashibase for security reasons, you can [create a restricted user](https://aws.amazon.com/blogs/database/managing-postgresql-users-and-roles/) via [Supabase's SQL Editor](/docs/guides/database#the-sql-editor). The slight advantage of doing this in Dashibase is that there is a GUI, which makes it easier to set up the access control (but we understand some developers might find it more secure to restrict the access themselves). - -![Set data access in Dashibase](/docs/img/guides/integrations/dashibase/set-data-access.png) - -You can even control which columns within a table that others can see and edit. In the example below, for the `tags` table, the `name` column can be read and updated, the `created_at` column can be read, and the `id` column cannot even be read. - -![Set column-level access in Dashibase](/docs/img/guides/integrations/dashibase/set-column-access.png) - -Once you are happy with the data access control, you can decide who in your organization can edit this data source (usually other developers) or use this data source to create dashboards. - -![Set user access to data source in Dashibase](/docs/img/guides/integrations/dashibase/data-source-user-access.png) - -## Step 3. Build your dashboard - -To create a dashboard, go to “Dashboards” via the left sidebar in Dashibase and click on “Create dashboard” in the upper-right corner. - -Here’s where the fun begins. There are many ways to build your dashboards in Dashibase using features such as markdown formatting and slash command. - -You will see some automatically-generated content in your dashboard so that you can set up your dashboard more quickly. For this example, let's clear them out and start from scratch. - -![Automatically-generated content for your dashboard](/docs/img/guides/integrations/dashibase/initial-dashboard-content.png) - -Using our Notion-like UI, you can quickly build out your dashboard through typing. For example, you can type '/table' to add a table from your database to your dashboard. No more dragging components across your screen and dropping them on a blank canvas repeatedly. - -![Automatically-generated content for your dashboard](/docs/img/guides/integrations/dashibase/slash-command.gif) - -If you have set up [foreign keys in your Supabase database](/docs/guides/database/tables#joining-tables-with-foreign-keys), you can easily show data from multiple tables on the table in your Dashibase dashboard. Simply click on the "Columns" button beside the "Filter" button and select the columns you want to show or hide. For this example, we want to show the customers' country and plan price, which are in separate `countries` and `plans` tables. - -![Setting up the Customers table](/docs/img/guides/integrations/dashibase/customers-table.png) - -For all the tables in your dashboard, you can rename the column headers, add filter and sort, adjust the column width, and more. - -You can also add text to provide context or instructions for the rest of your team as they are using your dashboard. This saves you the trouble of having your dashboard and documentation in separate places. - -![Customers table with instructions](/docs/img/guides/integrations/dashibase/customers-table-with-text.png) - -Most importantly, we automatically generate a single-item view for each of the items on your table so that you do not have to manually create it yourself. You can easily edit or rearrange the blocks just like in Notion. - -![Automatically generated single-item view](/docs/img/guides/integrations/dashibase/single-item-view.png) - -## Step 4. Share your dashboard with your team - -Finally, save and share the dashboard with your team. There are two levels of access: - -- Edit dashboard: Edit the text blocks, edit the tables, etc. -- Use dashboard: View and update the data via the dashboard - -Click on the “Share” button in the upper-right corner of your dashboard to share access: - -![Share dashboard access](/docs/img/guides/integrations/dashibase/share-dashboard.png) - -Now your team can use this customer admin panel to find and update customer information easily! - -P.S. How long did we take? :) - -## Resources - -- [Dashibase website](https://dashibase.com/) -- [Dashibase open-source repo](https://github.com/dashibase) -- [Dashibase Twitter](https://twitter.com/dashibase) -- [Dashibase Discord](https://discord.gg/crhDFYvbbq) - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/guides/integrations/flutterflow.mdx b/apps/docs/pages/guides/integrations/flutterflow.mdx index b7a25f8d1a5..ed6ddf1f046 100644 --- a/apps/docs/pages/guides/integrations/flutterflow.mdx +++ b/apps/docs/pages/guides/integrations/flutterflow.mdx @@ -8,8 +8,14 @@ export const meta = { canonical: 'https://docs.flutterflow.io/actions/actions/backend-database/supabase', } + + FlutterFlow and Supabase integration is currently in alpha, and supported features may be limited. + + [FlutterFlow](https://flutterflow.io/) is a low-code builder for developing native mobile applications using Flutter. You can use the simple drag-and-drop interface to build your app faster than traditional development. +This guide gives you a quick overview of implementing basic CRUD operations using FlutterFlow and Supabase. You can find the full docs on FlutterFlow and Supabase [here](https://docs.flutterflow.io/actions/actions/backend-database/supabase). +
      -This guide gives you a quick overview of implementing basic CRUD operations using FlutterFlow and Supabase. You can find the full docs on FlutterFlow and Supabase [here](https://docs.flutterflow.io/actions/actions/backend-database/supabase). ## Step 1: Connect FlutterFlow to Supabase diff --git a/apps/docs/pages/guides/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/onesignal.mdx b/apps/docs/pages/guides/integrations/onesignal.mdx index 3f270c6f60e..bf4922e749f 100644 --- a/apps/docs/pages/guides/integrations/onesignal.mdx +++ b/apps/docs/pages/guides/integrations/onesignal.mdx @@ -8,7 +8,7 @@ export const meta = { video: 'https://www.youtube.com/v/mw0DLwItue4', } -[OneSignal](https://onesignal.com/) is a tool that allows you to send messages across different channels such as the following to keep your users engaged. +[OneSignal](https://onesignal.com/) is a tool that allows you to send messages across different channels such as the following to keep your users engaged. - Push notifications - SMS @@ -36,7 +36,6 @@ You can find the complete example app along with the edge functions code to send ![Ordering app UI](/docs/img/guides/integrations/onesignal/app-ui.png) - ## Step 1: Getting started Before we dive into the code, this guide assumes that you have the following ready @@ -80,8 +79,7 @@ const supabase = createClient(supabaseUrl, supabaseAnonKey) const Home: NextPage = () => { const [user, setUser] = useState(null) - const [oneSignalInitialized, setOneSignalInitialized] = - useState(false) + const [oneSignalInitialized, setOneSignalInitialized] = useState(false) /** * Initializes OneSignal SDK for a given Supabase User ID @@ -123,9 +121,7 @@ const Home: NextPage = () => { const { price } = Object.fromEntries(new FormData(event.currentTarget)) if (typeof price !== 'string') return - const { error } = await supabase - .from('orders') - .insert({ price: Number(price) }) + const { error } = await supabase.from('orders').insert({ price: Number(price) }) if (error) { alert(error.message) } @@ -142,15 +138,13 @@ const Home: NextPage = () => { initialize() - const authListener = supabase.auth.onAuthStateChange( - async (event, session) => { - const user = session?.user ?? null - setUser(user) - if (user) { - initializeOneSignal(user.id) - } + const authListener = supabase.auth.onAuthStateChange(async (event, session) => { + const user = session?.user ?? null + setUser(user) + if (user) { + initializeOneSignal(user.id) } - ) + }) return () => { authListener.data.subscription.unsubscribe() @@ -175,10 +169,7 @@ const Home: NextPage = () => { - @@ -190,10 +181,7 @@ const Home: NextPage = () => { name="email" placeholder="Email" /> - @@ -211,7 +199,7 @@ There is quite a bit of stuff going on here, but basically, it’s creating a si Notice that inside the `initializeOneSignal()` function, we are setting the Supabase user ID as an [external user ID of OneSignal](https://documentation.onesignal.com/docs/external-user-ids). This allows us to later send push notifications to the user using their Supabase user ID from the backend, which is very handy. ```tsx -await OneSignal.setExternalUserId(uid); +await OneSignal.setExternalUserId(uid) ``` The front-end side of things is done here. Let’s get into the backend. @@ -237,7 +225,7 @@ supabase functions new notify Replace the contents of `supabase/functions/notify/index.ts` with the following ```tsx -import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' +import { serve } from 'https://deno.land/std@0.177.0/http/server.ts' import * as OneSignal from 'https://esm.sh/@onesignal/node-onesignal@1.0.0-beta7' const _OnesignalAppId_ = Deno.env.get('ONESIGNAL_APP_ID')! @@ -263,12 +251,9 @@ serve(async (req) => { } const onesignalApiRes = await onesignal.createNotification(notification) - return new Response( - JSON.stringify({ onesignalResponse: onesignalApiRes }), - { - headers: { 'Content-Type': 'application/json' }, - } - ) + return new Response(JSON.stringify({ onesignalResponse: onesignalApiRes }), { + headers: { 'Content-Type': 'application/json' }, + }) } catch (err) { console.error('Failed to create OneSignal notification', err) return new Response('Server error.', { @@ -295,7 +280,6 @@ ONESIGNAL_REST_API_KEY=YOUR_ONESIGNAL_REST_API_KEY ![Where to find OneSignal User Auth Key](/docs/img/guides/integrations/onesignal/onesignal-api-key.png) - Once your environment variables are filled in, you can run the following command to set the environment variable. ```bash @@ -313,35 +297,35 @@ supabase functions deploy notify --no-verify-jwt Finally, we get to set up the database! Run the following SQL to set up the `orders` table. ```sql -create table if not exists public.orders ( - id uuid not null primary key default uuid_generate_v4(), - created_at timestamptz not null default now(), - user_id uuid not null default auth.uid(), +create table + if not exists public.orders ( + id uuid not null primary key default uuid_generate_v4 (), + created_at timestamptz not null default now (), + user_id uuid not null default auth.uid (), price int8 not null -); + ); ``` As you can see, the `orders` table has 4 columns and 3 of them have default values. That means all we need to send from the front-end app is the price. That is why our insert statement looked very simple. ```tsx const { error } = await supabase.from('orders').insert({ - price: 100, - }) + price: 100, +}) ``` Let’s also set up the webhook so that whenever a new row is inserted in the `orders` table, it calls the edge function. Go to `Database > Webhooks` and create a new Database Webhook. The table should be set to `orders` and Events should be inserted. The type should be HTTP Request, the HTTP method should be POST, and the URL should be the URL of your edge function. Hit confirm to save the webhook configuration. ![Supabase Webhooks configuration](/docs/img/guides/integrations/onesignal/webhook.png) -At this point, the app should be complete! Run your app locally with `npm run dev`, or deploy your app to a hosting service and see how you receive a push notification when you place an order! +At this point, the app should be complete! Run your app locally with `npm run dev`, or deploy your app to a hosting service and see how you receive a push notification when you place an order! Remember that if you decide to deploy your app to a hosting service, you would need to create another OneSignal app configured for your local address. ![Ordering app UI](/docs/img/guides/integrations/onesignal/app-ui.png) - ## Resources -This particular example was using Next.js, but you can apply the same principles to implement send push notification, SMS, Emails, and in-app-notifications on other platforms as well. +This particular example was using Next.js, but you can apply the same principles to implement send push notification, SMS, Emails, and in-app-notifications on other platforms as well. - [OneSignal + Flutter + Supabase example](https://github.com/OneSignalDevelopers/onesignal-supabase-sample-integration-supabase) - [OneSignal Mobile Quickstart](https://documentation.onesignal.com/docs/mobile-sdk-setup) diff --git a/apps/docs/pages/guides/integrations/prisma.mdx b/apps/docs/pages/guides/integrations/prisma.mdx index c2f0d5a8f14..20633415788 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/integrations/supertokens.mdx b/apps/docs/pages/guides/integrations/supertokens.mdx index add7a43d02f..1d23791f1d9 100644 --- a/apps/docs/pages/guides/integrations/supertokens.mdx +++ b/apps/docs/pages/guides/integrations/supertokens.mdx @@ -43,8 +43,8 @@ Remove the default columns Create two new columns: -- `user_id` as `varchar` as primary key -- `email` as `varchar` +- `user_id` as `text` as primary key +- `email` as `text` Click `Save` to create the new table. diff --git a/apps/docs/pages/guides/integrations/zuplo.mdx b/apps/docs/pages/guides/integrations/zuplo.mdx index 06b621b5b47..c66860ca73b 100644 --- a/apps/docs/pages/guides/integrations/zuplo.mdx +++ b/apps/docs/pages/guides/integrations/zuplo.mdx @@ -20,11 +20,11 @@ If you haven't already, create a new project in Supabase and create a table call - id (int8) - created_at (timestamptz) -- make (varchar) -- model (varchar) +- make (text) +- model (text) - year (int8) - rating (int2) -- author (varchar) +- author (text) Manually enter a couple of rows of data, so that we have something to read from the DB. diff --git a/apps/docs/pages/guides/platform/backups.mdx b/apps/docs/pages/guides/platform/backups.mdx index 83d606cd856..bf942d9923f 100644 --- a/apps/docs/pages/guides/platform/backups.mdx +++ b/apps/docs/pages/guides/platform/backups.mdx @@ -9,7 +9,9 @@ Database backups are an integral part of any disaster recovery plan. Disasters c ## Frequency of Backups -When deciding how often a database should be backed up, the key business metric Recovery Point Objective (RPO) should be considered. RPO is the threshold for how much data, measured in time, a business could lose when disaster strikes. This amount is fully dependent on a business and its underlying requirements. A low RPO would mean that database backups would have to be taken at an increased cadence throughout the day. Each Supabase project has access to two forms of backups, Daily Backups and Point-in-Time Recovery. The agreed upon RPO would be a deciding factor in choosing which solution best fits a project. +When deciding how often a database should be backed up, the key business metric Recovery Point Objective (RPO) should be considered. RPO is the threshold for how much data, measured in time, a business could lose when disaster strikes. This amount is fully dependent on a business and its underlying requirements. A low RPO would mean that database backups would have to be taken at an increased cadence throughout the day. Each Supabase project has access to two forms of backups, Daily Backups and Point-in-Time Recovery (PITR). The agreed upon RPO would be a deciding factor in choosing which solution best fits a project. + +Daily Backups and PITR are mutually exclusive. If your project opts into using PITR, Daily Backups will no longer be taken. They're also unnecessary, as PITR supports a superset of functionality, in terms of the granular recovery that can be performed. Database backups do not include objects stored via the Storage API, as the database only includes diff --git a/apps/docs/pages/guides/platform/custom-domains.mdx b/apps/docs/pages/guides/platform/custom-domains.mdx index 92ea5fd14a3..d6f838187f8 100644 --- a/apps/docs/pages/guides/platform/custom-domains.mdx +++ b/apps/docs/pages/guides/platform/custom-domains.mdx @@ -7,12 +7,6 @@ export const meta = { video: 'https://www.youtube.com/v/6rcGnW_Mh-0', } - - -Custom Domains are currently in beta, and are being slowly made available to projects. - - - Custom domains allow you to present a branded experience to your users. Custom domains are available as a [add-on for projects on a paid tier](https://app.supabase.com/project/_/settings/billing/update). Setting up a custom domain requires [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project. Currently, you must use a subdomain (e.g., `api.example.com`, rather than `example.com`) for the purposes of this guide.
      @@ -99,12 +93,6 @@ As with the final activation stage of the process for setting up a custom domain ## Vanity Subdomains - - -Vanity Subdomains are currently in beta, and are being slowly made available to projects. [Contact Support](https://app.supabase.com/support/new) if you'd like to request early access. - - - Vanity Subdomains allow you to present a basic branded experience, compared to custom domains. They allow you to host your services at a custom subdomain on Supabase (e.g., `my-example-brand.supabase.co`) instead of the default, randomly-assigned `foobarbaz.supabase.co`. To get started: @@ -138,7 +126,7 @@ As such, it is recommended that you schedule a downtime window of 20-30 minutes, The `activate` subcommand can be used to initiate the activation: ```bash -supabase vanity-subdomains --project-ref fwmssjhjgszhnavvqxnt check-availability --desired-subdomain my-example-subdomain --experimental +supabase vanity-subdomains --project-ref fwmssjhjgszhnavvqxnt activate --desired-subdomain my-example-subdomain --experimental ``` ## Remove a Vanity Subdomain @@ -155,6 +143,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/going-into-prod.mdx b/apps/docs/pages/guides/platform/going-into-prod.mdx index 30274b5277a..2e68fbb8998 100644 --- a/apps/docs/pages/guides/platform/going-into-prod.mdx +++ b/apps/docs/pages/guides/platform/going-into-prod.mdx @@ -34,7 +34,7 @@ After developing your project and deciding it's Production Ready, you should run - Perform load testing (preferably on a staging env) - Tools like [k6](https://k6.io/) can simulate traffic from many different users. - Upgrade your database if you require more resources. If you need anything beyond what is listed, contact enterprise@supabase.io. -- If you are expecting a surge in traffic (for a big launch), let the team know by sending your Project Ref to us (support@supabase.io) with more details about your launch. We'll keep an eye on your project. +- If you are expecting a surge in traffic (for a big launch) [contact support](https://app.supabase.com/support/new) with more details about your launch. We'll keep an eye on your project. ## Availability @@ -47,14 +47,14 @@ After developing your project and deciding it's Production Ready, you should run - Database backups are not available for download on the free tier. - You can set up your own backup systems using tools like [pg_dump](https://www.postgresqltutorial.com/postgresql-backup-database/) or [wal-g](https://github.com/wal-g/wal-g). - Nightly backups for Pro tier projects are available on the Supabase dashboard for up to 7 days. -- Upgrading to the Supabase Pro Tier will give you access to email support on support@supabase.io +- Upgrading to the Supabase Pro Tier will give you [access to our support team](https://app.supabase.com/support/new). ## Rate Limiting, Resource Allocation, & Abuse Prevention - Supabase employs a number of safeguards against bursts of incoming traffic to prevent abuse and help maximize stability across the platform - If you're expecting high load events including production launches or heavy load testing, or prolonged high resource usage please give us at least 2 weeks notice. You can do this by opening a ticket via the [support form](https://app.supabase.com/support/new). -### Rate Limits +### Auth Rate Limits - The table below shows the rate limit quotas on the following authentication endpoints: @@ -69,6 +69,11 @@ After developing your project and deciding it's Production Ready, you should run | Token refresh requests | `/auth/v1/token` | IP Address | 360 requests per hour (with bursts up to 30 requests) | | Create or Verify an MFA challenge | `/auth/v1/factors/:id/challenge` `/auth/v1/factors/:id/verify` | IP Address | 15 requests per minute (with bursts up to 30 requests) | +### Realtime Rate Limits + +- Review the [Realtime rate limits](/docs/guides/realtime/rate-limits). +- If you need rate limits increased you can always [contact support](https://app.supabase.com/support/new). + ### Abuse Prevention - Supabase provides CAPTCHA protection on the signup, sign-in and password reset endpoints. Please refer to [our guide](/docs/guides/auth/auth-captcha) on how to protect against abuse using this method. diff --git a/apps/docs/pages/guides/platform/logs.mdx b/apps/docs/pages/guides/platform/logs.mdx index f7d0f4d14be..897f22cebda 100644 --- a/apps/docs/pages/guides/platform/logs.mdx +++ b/apps/docs/pages/guides/platform/logs.mdx @@ -1,4 +1,5 @@ import Layout from '~/layouts/DefaultGuideLayout' +import { logConstants } from 'shared-data' export const meta = { id: 'logs', @@ -59,6 +60,12 @@ Supabase provides a logging interface specific to each product. You can use simp [Realtime logs](https://app.supabase.com/project/_/logs/realtime-logs) show all server logs for your [Realtime API usage](../../guides/realtime). + + +Realtime connections are not logged by default. Turn on [Realtime connection logs per client](#logging-realtime-connections) with the `log_level` parameter. + + + ![Realtime Logs](/docs/img/guides/platform/logs/logs-realtime.png) @@ -125,6 +132,21 @@ If any permission errors are encountered when executing `alter role postgres ... +## Logging Realtime Connections + +Realtime doesn't log new WebSocket connections or Channel joins by default. Enable connection logging per client by including an `info` `log_level` parameter when instantiating the Supabase client. + +```javascript +import { createClient } from '@supabase/supabase-js' + +const options = { + realtime: { + log_level: 'info', + }, +} +const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key', options) +``` + ## Logs Explorer The [Logs Explorer](https://app.supabase.com/project/_/logs-explorer) exposes logs from each part of the Supabase stack as a separate table that can be queried and joined using SQL. @@ -168,7 +190,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 +205,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 +240,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,13 +263,41 @@ 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"; ``` +### Log Source Reference + +Refer to the full field reference for each available source below. Do note that in order to access each nested key, you would need to perform the [necessary unnesting joins](#unnesting-arrays) + + + {logConstants.schemas.map((schema) => ( + + + + + + + + {schema.fields + .sort((a, b) => a.path - b.path) + .map((field) => ( + + + + + ))} + +
      PathType
      {field.path}{field.type}
      +
      + ))} +
      + export const Page = ({ children }) => export default Page diff --git a/apps/docs/pages/guides/platform/migrating-and-upgrading-projects.mdx b/apps/docs/pages/guides/platform/migrating-and-upgrading-projects.mdx index 1b657450546..0586ea2c09c 100644 --- a/apps/docs/pages/guides/platform/migrating-and-upgrading-projects.mdx +++ b/apps/docs/pages/guides/platform/migrating-and-upgrading-projects.mdx @@ -21,42 +21,41 @@ When you pause and restore a project, the restored database includes the latest ## Migrate your project -Migrating projects can be achieved using standard PostgreSQL tooling. This is particularly useful for older projects (e.g. to use a newer Postgres version). +Migrating projects can be achieved using the Supabase CLI. This is particularly useful for older projects (e.g. to use a newer Postgres version). ### Before you begin - Install [Postgres](https://www.postgresql.org/download/) so you can run `psql` and `pg_dump`. +- Install [Supabase CLI](https://supabase.com/docs/guides/cli#installation). - Create a new [Supabase project](https://app.supabase.com). - Store the old project's database URL as `$OLD_DB_URL` and the new project's as `$NEW_DB_URL`. -### Migrate the database +### Backup your old database -1. Enable [Database Webhooks](https://app.supabase.com/project/_/database/hooks) in your new project if you enabled them in your old project. -2. In your new project, enable all extensions that were enabled in your old project. -3. Run the following command from your terminal: +1. Run the following command from your terminal: -```sh -set -euo pipefail +```bash +supabase db dump --db-url "$OLD_DB_URL" -f roles.sql --role-only +supabase db dump --db-url "$OLD_DB_URL" -f schema.sql +supabase db dump --db-url "$OLD_DB_URL" -f data.sql --data-only +``` -pg_dump \ - --clean \ - --if-exists \ - --quote-all-identifiers \ - --exclude-table-data 'storage.objects' \ - --exclude-schema 'extensions|graphql|graphql_public|net|pgbouncer|pgsodium|pgsodium_masks|realtime|supabase_functions|pg_toast|pg_catalog|information_schema' \ - --schema '*' \ - --dbname "$OLD_DB_URL" \ -| sed 's/^DROP SCHEMA IF EXISTS "auth";$/-- DROP SCHEMA IF EXISTS "auth";/' \ -| sed 's/^DROP SCHEMA IF EXISTS "storage";$/-- DROP SCHEMA IF EXISTS "storage";/' \ -| sed 's/^CREATE SCHEMA "auth";$/-- CREATE SCHEMA "auth";/' \ -| sed 's/^CREATE SCHEMA "storage";$/-- CREATE SCHEMA "storage";/' \ -| sed 's/^ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin"/-- ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin"/' \ -> dump.sql +### Restore to your new project +In your new project: + +1. Enable [Database Webhooks](https://app.supabase.com/project/_/database/hooks) if you enabled them in your old project. +2. Enable any [extensions](https://app.supabase.com/project/_/database/extensions) that were enabled in your old project. +3. Create any [custom roles](https://app.supabase.com/project/_/database/roles) that were created in your old project. + +Then run the following command from your terminal: + +```bash psql \ --single-transaction \ --variable ON_ERROR_STOP=1 \ - --file dump.sql \ + --file schema.sql \ + --file data.sql \ --dbname "$NEW_DB_URL" ``` @@ -66,7 +65,7 @@ Replication for Realtime is disabled for all tables in your new project. On the ### Migrate Storage objects -The new project has the old project's Storage buckets, but the Storage objects need to be migrated manually. Use this script to move storage objects from one project to another. If you have more than 10k objects, we can move the objects for you. Just contact us at [support@supabase.com](mailto:support@supabase.com). +The new project has the old project's Storage buckets, but the Storage objects need to be migrated manually. Use this script to move storage objects from one project to another. ```js // npm install @supabase/supabase-js@1 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/platform/spend-cap.mdx b/apps/docs/pages/guides/platform/spend-cap.mdx new file mode 100644 index 00000000000..f90254db1bb --- /dev/null +++ b/apps/docs/pages/guides/platform/spend-cap.mdx @@ -0,0 +1,55 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + title: 'Spend cap', + description: 'Learn about the cost control of Supabase.', +} + +Supabase offers a "Spend Cap" on each project to manage your usage and costs. + +This Spend Cap determines whether your project can exceed the free quota allowance of any [line item](#what-can-i-be-charged-for) in a given billing cycle. +If you exceed the free quota allowance of a line item, and the Spend Cap is **"off"**, then you will be billed for any additional usage on that line item. + +## What additional usage can I be charged for? + +Supabase charges for certain usage line items in your project. Those line items include: + +- Database Size +- Storage Size +- Egress +- Asset transformations +- Realtime concurrent peak connections +- Realtime messages +- Edge function invocations +- Number of Edge functions + +You can view the pricing, and free quota allowance of each line item on the [pricing page](/pricing). + +You can monitor your usage and costs using the [Usage section of your project](https://app.supabase.com/project/_/settings/billing/usage) in the dashboard. +This will show you how much you have spent so far in the current billing cycle, as well as an estimate of your expected total cost for the cycle based on your current usage. + +## How can I turn the Spend Cap on or off? + +To set up a Spend Cap in your project, navigate to the [Subscription section](https://app.supabase.com/project/_/settings/billing/subscription) of your project. +From there, you can click on "Change subscription" to select the plan you want to use and, at the same time, turn on or off the Spend Cap. + +## What happens when the Spend Cap is on? + +Once you use all your free quota allowance for a [line item](#what-can-i-be-charged-for), your project will not allow any further usage on that line item until the next billing cycle begins. + +This can help you to avoid unexpected costs and manage your expenses more effectively. + +## What happens when Spend Cap is off? + +Your project will continue to operate after using all the free quota allowance for each [line item](#what-can-i-be-charged-for). +This additional usage will be charged based on the line item's cost per unit outlined on the [pricing page](/pricing). + + + When your Spend Cap is **off**, we recommend monitoring your consumption and costs using the [Usage + section of your project](https://app.supabase.com/project/_/settings/billing/usage) in the + dashboard. + + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/realtime/architecture.mdx b/apps/docs/pages/guides/realtime/architecture.mdx new file mode 100644 index 00000000000..5a4173bdb88 --- /dev/null +++ b/apps/docs/pages/guides/realtime/architecture.mdx @@ -0,0 +1,58 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'architecture', + title: 'Realtime Architecture', + description: 'Architecture of the Supabase Realtime service', + sidebar_label: 'Architecture', +} + +Realtime is a globally distributed Elixir cluster. Clients can connect to any node in the cluster via WebSockets and send messages to any other client connected to the cluster. + +Realtime is written in [Elixir](https://elixir-lang.org/), which compiles to [Erlang](https://www.erlang.org/), and utilizes many tools the [Phoenix Framework](https://www.phoenixframework.org/) provides out of the box. + +![Global Architecture](/docs/img/guides/realtime/realtime-arch.png) + +## Elixir & Phoenix + +Phoenix is fast and able to handle millions of concurrent connections. + +Phoenix can handle many concurrent connections because Elixir provides lightweight processes (not OS processes) to work with. + +Client-facing WebSocket servers need to handle many concurrent connections. Elixir & Phoenix let the Supabase Realtime cluster do this easily. + +## Global Cluster + +Presence is an in-memory key-value store backed by a CRDT. When a user is connected to the cluster the state of that user is sent to all connected Realtime nodes. + +Broadcast lets you send a message from any connected client to a Channel. Any other client connected to that same Channel will receive that message. + +This works globally. A client connected to a Realtime node in the United States can send a message to another client connected to a node in Singapore. Simply connect two clients to the same Realtime Channel and they'll all receive the same messages. + +Broadcast is useful for getting messages to users in the same location very quickly. If a group of clients are connected to a node in Singapore, the message only needs to go to that Realtime node in Singapore and back down. If users are close to a Realtime node they'll get Broadcast messages in the time it takes to ping the cluster. + +Thanks to the Realtime cluster, you (an amazing Supabase user) don't have to think about which regions your clients are connected to. + +If you're using Broadcast, Presence, or streaming database changes, messages will always get to your users via the shortest path possible. + +## Connecting to a Database + +Realtime allows you to listen to changes from your Postgres database. When a new client connects to Realtime and initializes the `postgres_changes` Realtime Extension the cluster will connect to your Postgres database and start streaming changes from a replication slot. + +Realtime knows the region your database is in, and connects to it from the closest region possible. + +Every Realtime region has at least two nodes so if one node goes offline the other node should reconnect and start streaming changes again. + +## Streaming the Write-Ahead Log + +A Postgres logical replication slot is acquired when connecting to your database. + +Realtime delivers changes by polling the replication slot and appending channel subscription IDs to each wal record. + +Subscription IDs are Erlang processes representing underlying sockets on the cluster. These IDs are globally unique and messages to processes are routed automatically by the Erlang virtual machine. + +After receiving results from the polling query, with subscription IDs appended, Realtime delivers records to those clients. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/realtime/bring-your-own-database.mdx b/apps/docs/pages/guides/realtime/bring-your-own-database.mdx new file mode 100644 index 00000000000..57600f9cf2c --- /dev/null +++ b/apps/docs/pages/guides/realtime/bring-your-own-database.mdx @@ -0,0 +1,107 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'bring-your-own-database', + title: 'Bring Your Own Database', + description: 'Bring your own Postgres database to subscribe to Realtime changes', + sidebar_label: 'Bring Your Own Database', +} + +Realtime Database Changes works with any Postgres database that has logical replication enabled and the [wal2json](https://github.com/eulerto/wal2json) extension installed. + + + +After setting up your database for Realtime, [submit a support ticket](https://app.supabase.com/support/new) if you want to use your database with hosted Supabase Realtime. + + + +The following steps will make sure that your database is properly set up to work with Realtime. + +## `wal2json` Extension + +Realtime relies on the `wal2json` Postgres extension to format database changes to JSON which are then sent to Realtime subscribers. + +Postgres databases managed by AWS RDS and Google Cloud SQL should already have `wal2json` installed. +Please check to make sure this is indeed the case with your Postgres database. + +## Logical Replication Configuration + +Realtime relies on Postgres' logical replication functionality to get database changes. Please enable logical replication on your database and configure the following settings: + +- `max_replication_slots`: we recommend `10` because Realtime requires a few slots plus the slots you'll need for your non-Realtime logical replication needs. +- `max_slot_wal_keep_size`: we recommend `1024` (MB) so Realtime can attempt to deliver more database changes stored in Postgres. + +## Realtime Database Setup + +### `supabase_realtime` Publication + +Create `supabase_realtime` publication and add tables you want Realtime to listen to: + +```sql +create publication supabase_realtime with (publish = 'insert, update, delete'); + +alter publication supabase_realtime add table messages, users; +``` + + + +If the payload contains a `401 Unauthorized` then you will need to grant `select` privileges to the table for the database role you want to authorize to receive database changes: + +```sql +grant select on table messages to anon; +``` + + + +### `realtime` Schema + +Create a `realtime` schema: + +```sql +create schema realtime; +``` + +### `supabase_realtime_admin` Role + +Create a `supabase_realtime_admin` database role and grant it replication permissions: + +```sql +create role supabase_realtime_admin with noinherit login password 'secure-password'; +``` + +Make sure to grant `supabase_realtime_admin` role with replication permissions. This step will vary based on your database provider. + +For example, if your database is managed by AWS RDS then you can run: + +```sql +grant rds_replication to supabase_realtime_admin; +``` + +### `supabase_realtime_admin` Privileges + +Grant `supabase_realtime_admin` privileges for `realtime` schema and all related Realtime objects: + +```sql +grant all on schema realtime to supabase_realtime_admin; +grant all on all tables in schema realtime to supabase_realtime_admin; +grant all on all sequences in schema realtime to supabase_realtime_admin; +grant all on all routines in schema realtime to supabase_realtime_admin; +``` + +### `authenticated` Role + +Create an `authenticated` role: + +```sql +create role authenticated nologin noinherit; +``` + + + +This role is hardcoded into Realtime migrations so it's required for the time being. + + + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/realtime/channels.mdx b/apps/docs/pages/guides/realtime/channels.mdx new file mode 100644 index 00000000000..b016f4bf6f6 --- /dev/null +++ b/apps/docs/pages/guides/realtime/channels.mdx @@ -0,0 +1,22 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'channels', + title: 'Realtime Channels', + description: 'WebSockets are Channels on Supabase Realtime', + sidebar_label: 'Channels', +} + +You can uniquely reference a channel by its `topic` you define when you initialize your Supabase Realtime client. Everyone connected to the same Channel topic will receive the same messages. + +Clients can send and receive messages bi-directionally over a Channel. The Realtime backend can also push messages to all clients connected to the same Channel. + +A single client can receive change records from Postgres, Broadcast messages from other clients and Presence updates all over the same Channel. + +Channels are implemented over [Phoenix Channels](https://hexdocs.pm/phoenix/channels.html) which uses [Phoenix.PubSub](https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html) with the default `Phoenix.PubSub.PG2` adapter. + +The PG2 adapter utilizes Erlang [process groups](https://www.erlang.org/docs/18/man/pg2.html) to implement the PubSub model where a publisher can send messages to many subscribers. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/realtime/extensions.mdx b/apps/docs/pages/guides/realtime/extensions.mdx new file mode 100644 index 00000000000..3f9db9c4702 --- /dev/null +++ b/apps/docs/pages/guides/realtime/extensions.mdx @@ -0,0 +1,27 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'extensions', + title: 'Realtime Extensions', + description: + 'Extensions enable powerful Realtime features like Presence, Broadcast and database change streaming', + sidebar_label: 'Extensions', +} + +Supabase Realtime provides multiplayer features so that you can build real-time applications. The server has several built-in extensions to solve specific tasks. + +- [Broadcast](/docs/guides/realtime/broadcast): for sending rapid, ephemeral messages to other connected clients. A typical use-case is tracking mouse movements. +- [Presence](/docs/guides/realtime/presence): for sending user state between connected clients. A typical use-case is simply an "online" status, which will disappear when a user is disconnected. +- [Postgres Changes](/docs/guides/realtime/postgres-changes): for receiving database changes in real-time. + +## Choosing between Broadcast and Presence + +Defaulting to using Broadcast is a good idea, and then use Presence sparingly. The Presence extension merges all changes into a shared state for every client connected to the same "channel". If you _do_ use Presence, it's best to throttle your changes so that you are sending updates less frequently. + +## Future possibilities + +Realtime extensions are still under development. We have many more extensions planned. You can follow our progress by watching the [Realtime](https://github.com/supabase/realtime) repo on GitHub. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/realtime/broadcast.mdx b/apps/docs/pages/guides/realtime/extensions/broadcast.mdx similarity index 100% rename from apps/docs/pages/guides/realtime/broadcast.mdx rename to apps/docs/pages/guides/realtime/extensions/broadcast.mdx diff --git a/apps/docs/pages/guides/realtime/postgres-changes.mdx b/apps/docs/pages/guides/realtime/extensions/postgres-changes.mdx similarity index 51% rename from apps/docs/pages/guides/realtime/postgres-changes.mdx rename to apps/docs/pages/guides/realtime/extensions/postgres-changes.mdx index 0e4e9638855..aa93a0482cf 100644 --- a/apps/docs/pages/guides/realtime/postgres-changes.mdx +++ b/apps/docs/pages/guides/realtime/extensions/postgres-changes.mdx @@ -15,7 +15,9 @@ Clients can choose to receive `INSERT`, `UPDATE`, `DELETE`, or `*` (all) changes Postgres Changes works out of the box for tables in the `public` schema. You can listen to tables in your private schemas by granting table `SELECT` permissions to the database role found in your access token. You can run a query similar to the following: ```sql -GRANT SELECT ON "private_schema"."table" TO authenticated; +grant +select + on "private_schema"."table" to authenticated; ``` @@ -29,15 +31,19 @@ You can do this in the [Replication](https://app.supabase.com/project/_/database ```sql begin; - -- remove the supabase_realtime publication - drop publication if exists supabase_realtime; - -- re-create the supabase_realtime publication with no tables - create publication supabase_realtime; +-- remove the supabase_realtime publication +drop + publication if exists supabase_realtime; + +-- re-create the supabase_realtime publication with no tables +create publication supabase_realtime; + commit; -- add a table to the publication -alter publication supabase_realtime add table messages; +alter + publication supabase_realtime add table messages; ``` ### Full `old` Record @@ -46,9 +52,16 @@ By default, only `new` record changes are sent but if you want to receive the `o you can set the `replica identity` of your table to `full`: ```sql -alter table messages replica identity full; +alter table + messages replica identity full; ``` + + +RLS policies are not applied to `DELETE` statements. When RLS is enabled and `replica identity` is set to `full` on a table, the `old` record contains only the primary key(s). + + + ## Schema Changes To listen to all changes in the `public` schema: @@ -102,13 +115,17 @@ const channel = supabase ## Filter Changes -To listen to changes when a column's value in a table matches a client-specified value: +Realtime offers filters so you can specify the data your client receives at a more granular level. + +### eq + +To listen to changes when a column's value in a table equals a client-specified value: ```js // Supabase client setup const channel = supabase - .channel('value-db-changes') + .channel('changes') .on( 'postgres_changes', { @@ -122,6 +139,166 @@ const channel = supabase .subscribe() ``` + + This filter uses Postgres' `=`. + + +### neq + +To listen to changes when a column's value in a table does not equal a client-specified value: + +```js +// Supabase client setup + +const channel = supabase + .channel('changes') + .on( + 'postgres_changes', + { + event: 'INSERT', + schema: 'public', + table: 'messages', + filter: 'body=neq.bye', + }, + (payload) => console.log(payload) + ) + .subscribe() +``` + + + This filter uses Postgres' `!=`. + + +### lt + +To listen to changes when a column's value in a table is less than a client-specified value: + +```js +// Supabase client setup + +const channel = supabase + .channel('changes') + .on( + 'postgres_changes', + { + event: 'INSERT', + schema: 'public', + table: 'messages', + filter: 'id=lt.100', + }, + (payload) => console.log(payload) + ) + .subscribe() +``` + + + This filter uses Postgres' `<` so it works for non-numeric types but make sure to check the expected behavior of the compared data's type. + + +### lte + +To listen to changes when a column's value in a table is less than or equal to a client-specified value: + +```js +// Supabase client setup + +const channel = supabase + .channel('changes') + .on( + 'postgres_changes', + { + event: 'UPDATE', + schema: 'public', + table: 'profiles', + filter: 'age=lte.65', + }, + (payload) => console.log(payload) + ) + .subscribe() +``` + + + This filter uses Postgres' `<=` so it works for non-numeric types but make sure to check the expected behavior of the compared data's type. + + +### gt + +To listen to changes when a column's value in a table is greater than a client-specified value: + +```js +// Supabase client setup + +const channel = supabase + .channel('changes') + .on( + 'postgres_changes', + { + event: 'INSERT', + schema: 'public', + table: 'products', + filter: 'quantity=gt.10', + }, + (payload) => console.log(payload) + ) + .subscribe() +``` + + + This filter uses Postgres' `>` so it works for non-numeric types but make sure to check the expected behavior of the compared data's type. + + +### gte + +To listen to changes when a column's value in a table is greater than or equal to a client-specified value: + +```js +// Supabase client setup + +const channel = supabase + .channel('changes') + .on( + 'postgres_changes', + { + event: 'INSERT', + schema: 'public', + table: 'products', + filter: 'quantity=gte.10', + }, + (payload) => console.log(payload) + ) + .subscribe() +``` + + + This filter uses Postgres' `>=` so it works for non-numeric types but make sure to check the expected behavior of the compared data's type. + + +### in + +To listen to changes when a column's value in a table equals any client-specified values: + +```js +// Supabase client setup + +const channel = supabase + .channel('changes') + .on( + 'postgres_changes', + { + event: 'INSERT', + schema: 'public', + table: 'colors', + filter: 'name=in.(red, blue, yellow)', + }, + (payload) => console.log(payload) + ) + .subscribe() +``` + + + This filter uses Postgres' `= ANY`. Realtime allows a maximum of 100 values for this filter. + + ## Combination Changes To listen to different events and schema/tables/filters combinations with the same channel: @@ -158,7 +335,8 @@ const channel = supabase You may choose to sign your own tokens to customize claims that can be checked in your RLS policies. In order for this to work you must pass `apikey` in both Realtime's `headers` and `params` when creating the client. -The `apikey` in `headers` must be either the `anon` or `service_role` token that Supabase signed. +The `apikey` in `headers` must be either the `anon` or `service_role` token that Supabase provides for every project. +You can find these tokens under [Project API keys](https://app.supabase.com/project/_/settings/api) in your project's dashboard. This will authenticate your request in the API gateway. @@ -185,6 +363,18 @@ const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY }) ``` +### Refreshed Tokens + +You will need to refresh tokens on your own, but once generated, you can pass them to Realtime. + +For example, if you're using the `supabase-js` `v2` client then you can pass your token like this: + +```js +// Client setup + +supabase.realtime.setAuth('fresh-token') +``` + export const Page = ({ children }) => export default Page diff --git a/apps/docs/pages/guides/realtime/presence.mdx b/apps/docs/pages/guides/realtime/extensions/presence.mdx similarity index 100% rename from apps/docs/pages/guides/realtime/presence.mdx rename to apps/docs/pages/guides/realtime/extensions/presence.mdx diff --git a/apps/docs/pages/guides/realtime/protocol.mdx b/apps/docs/pages/guides/realtime/protocol.mdx new file mode 100644 index 00000000000..488d2975612 --- /dev/null +++ b/apps/docs/pages/guides/realtime/protocol.mdx @@ -0,0 +1,194 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'protocol', + title: 'Realtime Protocol', + description: 'Understanding Realtime Protocol', +} + +The Realtime Protocol is a set of message formats used for communication over a WebSocket connection between a Realtime client and server. These messages are used to initiate a connection, update access tokens, receive system status updates, and receive real-time updates from the Postgres database. + +## Connection + +In the initial message, the client sends a message specifying the features they want to use (Broadcast, Presence, Postgres Changes). + +```ts +{ + "event": "phx_join", + "topic": string, + "payload": { + "config": { + "broadcast": { + "self": boolean + }, + "presence": { + "key": string + }, + "postgres_changes": [ + { + "event": "*" | "INSERT" | "UPDATE" | "DELETE", + "schema": string, + "table": string, + "filter": string + '=' + "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" + '.' + string + } + ] + } + }, + "ref": string +} +``` + + +The `in` filter has the format `COLUMN_NAME=in.(value1,value2,value3)`. However, other filters use the format `COLUMN_NAME=FILTER_NAME.value`. + + +In response, the server sends the Postgres configuration with a unique ID. With this ID, the client should route incoming changes to the appropriate callback. + +```ts +{ + "event": "phx_reply", + "topic": string, + "payload": { + "response": { + "postgres_changes": [ + { + "id": number, + "event": "*" | "INSERT" | "UPDATE" | "DELETE", + "schema": string, + "table": string, + "filter": string + '=' + "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" + '.' + string + } + ] + }, + "status": "ok" | "error" + }, + "ref": string +} +``` + +## System messages + +System message are used to inform a client about the status of the Postgres subscription. The `payload.status` indicates if the subscription successful or not. +The body of the `payload.message` can be "Subscribed to PostgreSQL" or "Subscribing to PostgreSQL failed" with subscription params. + +```ts +{ + "event": "system", + "topic": string, + "payload":{ + "channel": string, + "extension": "postgres_changes", + "message": "Subscribed to PostgreSQL" | "Subscribing to PostgreSQL failed", + "status": "ok" | "error" + }, + "ref": null, +} +``` + +## Heartbeat + +The heartbeat message should be sent every 30 seconds to avoid a connection timeout. + +```ts +{ + "event": "heartbeat", + "topic": "phoenix", + "payload": {}, + "ref": string +} +``` + +## Access Token + +To update the access token, you need to send to the server a message specifying a new token in the `payload.access_token` value. + +```ts +{ + "event": "access_token", + "topic": string, + "payload":{ + "access_token": string + }, + "ref": string +} +``` + +## Postgres CDC message + +Realtime sends a message with the following structure + +```ts +{ + "event": "postgres_changes", + "topic": string, + "payload": { + "data": { + "columns": Array<{name: string, type: string}>, + "commit_timestamp": string, + "errors": null | string, + "old_record": {"id": number | string}, + "record": {[key: string]: boolean | number | string | null}, + "type": "*" | "INSERT" | "UPDATE" | "DELETE", + "schema": string, + "table": string + }, + "ids": Array + }, + "ref": null +} +``` + +## Broadcast message + +Structure of the broadcast event + +```ts +{ + "event": "broadcast", + "topic": string, + "payload": { + "event": string, + "payload": {[key: string]: boolean | number | string | null | undefined}, + "type": "broadcast" + }, + "ref": null +} +``` + +## Presence message + +The Presence events allow clients to monitor the online status of other clients in real-time. + +### State Update + +After joining, the server sends a `presence_state` message to a client with presence information. The payload field contains keys in UUID format, where each key represents a client and its value is a JSON object containing information about that client. + +```ts +{ + "event": "presence_state", + "topic": string, + "payload": { + [key: string]: {metas: Array<{phx_ref: string, name: string, t: float}>} + }, + "ref": null +} +``` + +### Diff Update + +After a change to the presence state, such as a client joining or leaving, the server sends a presence_diff message to update the client's view of the presence state. The payload field contains two keys, `joins` and `leaves`, which represent clients that have joined and left, respectively. The values associated with each key are UUIDs of the clients. + +```ts +{ + "event": "presence_diff", + "topic": string, + "payload": { + "joins": {metas: Array<{phx_ref: string, name: string, t: float}>}, + "leaves": {metas: Array<{phx_ref: string, name: string, t: float}>} + }, + "ref": null +} +``` + +export const Page = ({ children }) => +export default Page diff --git a/apps/docs/pages/guides/realtime/realtime-with-nextjs.mdx b/apps/docs/pages/guides/realtime/realtime-with-nextjs.mdx new file mode 100644 index 00000000000..e60afb4f74a --- /dev/null +++ b/apps/docs/pages/guides/realtime/realtime-with-nextjs.mdx @@ -0,0 +1,24 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'realtime-with-nextjs', + title: 'Using Realtime with Next.js', + description: 'Client & Server Components in Next.js with Realtime Updates', + sidebar_label: 'Videos', +} + +In this guide we explore the best ways to receive realtime Postgres changes with your Next.js application. +We'll show both client and serverside updates, and explore the which option is best. + +
      + +
      + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/realtime/subscribing-to-database-changes.mdx b/apps/docs/pages/guides/realtime/subscribing-to-database-changes.mdx new file mode 100644 index 00000000000..f22764ac7c8 --- /dev/null +++ b/apps/docs/pages/guides/realtime/subscribing-to-database-changes.mdx @@ -0,0 +1,80 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'subscribing-to-database-changes', + title: 'Subscribing to Database Changes', + description: 'Listen to database changes in real-time from your website or application.', + sidebar_label: 'Videos', +} + +Supabase allows you to subscribe to real-time changes on your database from your client application. + +You can listen to database changes using the [Postgres Changes](/docs/guides/realtime/postgres-changes) extension. + +## Demo + +
      + +
      + +## Streaming inserts + +You can use the `INSERT` event to stream all new rows. + +```js +const { createClient } = require('@supabase/supabase-js') + +const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY) + +const channel = supabase + .channel('schema-db-changes') + .on( + 'postgres_changes', + { + event: 'INSERT', + schema: 'public', + }, + (payload) => console.log(payload) + ) + .subscribe() +``` + +## Streaming updates + +You can use the `UPDATE` event to stream all updated rows. + +```js +const { createClient } = require('@supabase/supabase-js') + +const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY) + +const channel = supabase + .channel('schema-db-changes') + .on( + 'postgres_changes', + { + event: 'UPDATE', + schema: 'public', + }, + (payload) => console.log(payload) + ) + .subscribe() +``` + +## More resources + +- Learn more about the [Postgres Changes](/docs/guides/realtime/postgres-changes) extension. +- Client Libraries: + - [JavaScript](/docs/reference/javascript/subscribe) + - [Flutter](/docs/reference/dart/stream) + - [Python](/docs/reference/python/subscribe) + - [C#](/docs/reference/csharp/subscribe) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/resources.mdx b/apps/docs/pages/guides/resources.mdx index c5242d1aef4..c2f2e4aa2a3 100644 --- a/apps/docs/pages/guides/resources.mdx +++ b/apps/docs/pages/guides/resources.mdx @@ -144,6 +144,12 @@ export const migrationGuides = [ ] export const postgres = [ + { + title: 'Managing Indexes', + hasLightIcon: true, + href: '/guides/resources/postgres/indexes', + description: 'Improve query performance using various index types in Postgres.', + }, { title: 'Drop all tables in schema', hasLightIcon: true, diff --git a/apps/docs/pages/guides/resources/examples.mdx b/apps/docs/pages/guides/resources/examples.mdx index 23b102fb216..ae03bc48105 100644 --- a/apps/docs/pages/guides/resources/examples.mdx +++ b/apps/docs/pages/guides/resources/examples.mdx @@ -64,7 +64,7 @@ Build a basic Todo List with Supabase and your favorite frontend framework: - [Svelte Todo List.](https://github.com/supabase/supabase/tree/master/examples/todo-list/sveltejs-todo-list) - [Vue 3 Todo List (Typescript).](https://github.com/supabase/examples/tree/main/supabase-js-v1/todo-list/vue3-ts-todo-list) - [Angular Todo List.](https://github.com/supabase/examples/tree/main/supabase-js-v1/todo-list/angular-todo-list) -- [Nuxt 3 Todo List.](https://github.com/nuxt-community/supabase-module/tree/main/demo) +- [Nuxt 3 Todo List.](https://github.com/nuxt-modules/supabase/tree/main/demo) ### Auth examples @@ -92,8 +92,8 @@ Build a basic Todo List with Supabase and your favorite frontend framework: - `useSupabase`: Supabase React Hooks. [GitHub](https://github.com/gbibeaul/use-supabase) - `vue-supabase`: Supabase Vue wrapper. [GitHub](https://github.com/supabase/vue-supabase) - `vue-3-supabase`: Supabase Vue 3 wrapper. [GitHub](https://github.com/DidoMarchet/vue-3-supabase) -- `nuxt-supabase`: Supabase Nuxt wrapper. [GitHub](https://github.com/supabase/nuxt-supabase) -- `@nuxtjs/supabase`: Supabase Nuxt 3 module. [GitHub](https://github.com/nuxt-community/supabase-module) +- `nuxt-supabase`: Supabase Nuxt wrapper. [GitHub](https://github.com/supabase-community/nuxt-supabase) +- `@nuxtjs/supabase`: Supabase Nuxt 3 module. [GitHub](https://github.com/nuxt-modules/supabase) - `react-supabase`: Supabase React Hooks. [Docs](https://react-supabase.vercel.app) [GitHub](https://github.com/tmm/react-supabase) - ` @triniwiz/nativescript-supabase`: Supabase NativeScript. [GitHub](https://github.com/triniwiz/nativescript-plugins/tree/master/packages/nativescript-supabase) @@ -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 @@ -132,10 +133,11 @@ Build a basic Todo List with Supabase and your favorite frontend framework: - Supabase + Nuxt3 (Nuxt Starter Kit) [GitHub](https://github.com/one-aalam/nuxt-starter-kit) - Supabase + Remix (Remix Starter Kit) [GitHub](https://github.com/one-aalam/remix-starter-kit) - Supabase + Angular (Angular Starter Kit) [GitHub](https://github.com/one-aalam/ng-starter-kit) -- Supabase + Nuxt3 + nuxtjs/supabase [Github](https://github.com/nuxt-community/supabase-module/tree/main/demo) +- Supabase + Nuxt3 + nuxtjs/supabase [Github](https://github.com/nuxt-modules/supabase/tree/main/demo) - Supabase + Ory Kratos & Ory Oathkeeper [Github](https://github.com/ory/examples/tree/master/kratos-keto-oathkeeper-supabase) - Supabase + Ory Cloud [Github](https://github.com/ory/examples/tree/master/supabase-ory-cloud) - Supabase Auth with React Native + Next.js (Monorepo) [Github](https://github.com/mateoguzmana/react-native-next-supabase-auth-monorepo) +- Supabase Auth with Nuxt3 [Github](https://github.com/zackha/supaAuth) ### Blog Posts diff --git a/apps/docs/pages/guides/self-hosting.mdx b/apps/docs/pages/guides/self-hosting.mdx index cdd7b69cc57..9c80c496788 100644 --- a/apps/docs/pages/guides/self-hosting.mdx +++ b/apps/docs/pages/guides/self-hosting.mdx @@ -54,6 +54,7 @@ We recommend installing all extensions into an `extensions` schema. This will ke since all tables in the `public` schema are exposed via the API. ```sql +{/* prettier-ignore */} create schema if not exists extensions; create extension if not exists "uuid-ossp" with schema extensions; create extension if not exists pgcrypto with schema extensions; diff --git a/apps/docs/pages/guides/self-hosting/analytics/config.mdx b/apps/docs/pages/guides/self-hosting/analytics/config.mdx new file mode 100644 index 00000000000..8267f6a913e --- /dev/null +++ b/apps/docs/pages/guides/self-hosting/analytics/config.mdx @@ -0,0 +1,38 @@ +import Layout from '~/layouts/DefaultGuideLayout' +import { getAnalyticsConfigV0 } from '~/lib/mdx/getConfig' +import Param from '~/components/Params' + +export const spec = getAnalyticsConfigV0() + +export const meta = { + title: 'Analytics Self-hosting Config', + description: 'How to configure and deploy Supabase Analytics.', +} + +
      {spec.info.description}
      + +
      +{spec.info.tags.map(tag => { + return ( + <> +

      {tag.title}

      +

      {tag.description}

      +
      +
      Parameters
      +
        + {spec.parameters.filter(param => param.tags.includes(tag.id)).map((param) => { + return + })} + +
      +
      + + ) + +})} + +
      + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/self-hosting/realtime/config.mdx b/apps/docs/pages/guides/self-hosting/realtime/config.mdx index a52dc2c111a..f576951b201 100644 --- a/apps/docs/pages/guides/self-hosting/realtime/config.mdx +++ b/apps/docs/pages/guides/self-hosting/realtime/config.mdx @@ -11,6 +11,8 @@ export const meta = {
      {spec.info.description}
      +See [this guide](/docs/guides/realtime/bring-your-own-database) if you want to configure your database to work with Realtime. +
      {spec.info.tags.map(tag => { return ( diff --git a/apps/docs/pages/guides/storage/quickstart.mdx b/apps/docs/pages/guides/storage/quickstart.mdx index 7309f2730af..118bd2513dd 100644 --- a/apps/docs/pages/guides/storage/quickstart.mdx +++ b/apps/docs/pages/guides/storage/quickstart.mdx @@ -7,9 +7,7 @@ export const meta = { sidebar_label: 'Quickstart', } -This guide shows the basic functionality of Supabase Storage. Find a full [example application on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management) or deploy it with Vercel for a preview: - -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fuser-management%2Fnextjs-ts-user-management&project-name=supabase-user-management&repository-name=supabase-user-management&demo-title=Supabase%20User%20Management&demo-description=An%20example%20web%20app%20using%20Supabase%20and%20Next.js&demo-url=https%3A%2F%2Fsupabase-nextjs-ts-user-management.vercel.app&demo-image=https%3A%2F%2Fi.imgur.com%2FZ3HkQqe.png&integration-ids=oac_jUduyjQgOyzev1fjrW83NYOv&external-id=nextjs-user-management) +This guide shows the basic functionality of Supabase Storage. Find a full [example application on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management) or deploy it with [Vercel for a preview](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fuser-management%2Fnextjs-ts-user-management&project-name=supabase-user-management&repository-name=supabase-user-management&demo-title=Supabase%20User%20Management&demo-description=An%20example%20web%20app%20using%20Supabase%20and%20Next.js&demo-url=https%3A%2F%2Fsupabase-nextjs-ts-user-management.vercel.app&demo-image=https%3A%2F%2Fi.imgur.com%2FZ3HkQqe.png&integration-ids=oac_jUduyjQgOyzev1fjrW83NYOv&external-id=nextjs-user-management). @@ -41,8 +39,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/index.mdx b/apps/docs/pages/index.mdx index d8aebeb4157..f1a68b30401 100644 --- a/apps/docs/pages/index.mdx +++ b/apps/docs/pages/index.mdx @@ -310,6 +310,13 @@ export const apis = [ href: 'reference/self-hosting-realtime/introduction', description: 'Self-host your own Realtime server.', }, + { + title: 'Self-Hosting Analytics', + icon: '/docs/img/icons/menu/reference-analytics', + hasLightIcon: true, + href: 'reference/self-hosting-analytics/introduction', + description: 'Self-host your own Analytics server.', + }, ] export const platform = [ 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/pages/reference/analytics/[...slug].tsx b/apps/docs/pages/reference/analytics/[...slug].tsx new file mode 100644 index 00000000000..3fcf010110f --- /dev/null +++ b/apps/docs/pages/reference/analytics/[...slug].tsx @@ -0,0 +1,167 @@ +import specFile from '~/../../spec/transforms/analytics_v0_openapi_deparsed.json' assert { type: 'json' } +import { gen_v3, enrichedOperation } from '~/lib/refGenerator/helpers' +import { Tabs, CodeBlock } from 'ui' + +import RefSubLayout from '~/layouts/ref/RefSubLayout' + +export type AcceptedValue = { + id: string + name: string + type: 'string' | 'boolean' | 'object' + description?: string +} + +export type Flag = { + id: string + name: string + description: string + default_value: string + accepted_values: AcceptedValue[] +} + +export type ApiParameter = { + example: string + in: string + name: string + required: boolean + schema: { + type: string + example: string + } +} +// @ts-ignore +const generatedSpec = gen_v3(specFile, 'wat', { apiUrl: 'apiv0' }) + +export default function Config() { + return ( + +
      +
      +
      +

      {generatedSpec.info.title}

      +

      {generatedSpec.info.description}

      +
      + +
      + {generatedSpec.operations.map((operation: any) => ( +
      + + +
      + + {operation.operation} + {operation.fullPath} + +
      + {/* Path Parameters */} + {operation.parameters && + operation.parameters.filter((parameter) => parameter.in === 'path').length > + 0 && ( +
      +

      Path parameters

      +
        + {operation.parameters && + operation.parameters + .filter((parameter: any) => parameter.in === 'path') + .map((parameter: any) => ( +
      • +
        +
        + {parameter.name} +
        + {parameter.required && ( +
        + REQUIRED +
        + )} +
        +
        +

        {parameter.description}

        +
        + {parameter.example && ( +
        + Example: + + {parameter.example} + +
        + )} +
      • + ))} +
      +
      + )} + + {/* Header Parameters */} + {operation.parameters && + operation.parameters.filter((parameter) => parameter.in === 'header').length > + 0 && ( +
      +

      Header parameters

      +
        + {operation.parameters && + operation.parameters + .filter((parameter: any) => parameter.in === 'header') + .map((parameter: any) => ( +
      • +
        + {parameter.name} + + {parameter.required && 'required'} + +
        +
        + Example: + + {parameter.example} + +
        +
      • + ))} +
      +
      + )} +
      + + {operation.responseList.length > 0 && ( + <> +

      Responses

      + + {operation.responseList.map((response: any) => ( + +

      {response.description}

      + {response?.content && response?.content['application/json'] && ( +
      + + {JSON.stringify(response.content['application/json'], null, 2)} + +
      + )} +
      + ))} +
      + + )} +
      +
      +
      + ))} +
      +
      +
      +
      + ) +} diff --git a/apps/docs/pages/reference/analytics/config.tsx b/apps/docs/pages/reference/analytics/config.tsx new file mode 100644 index 00000000000..e22dfe2e911 --- /dev/null +++ b/apps/docs/pages/reference/analytics/config.tsx @@ -0,0 +1,115 @@ +import fs from 'fs' +import matter from 'gray-matter' +import components from '~/components/index' +import { MDXRemote } from 'next-mdx-remote' +import { serialize } from 'next-mdx-remote/serialize' + +// @ts-expect-error +import specFile from '~/../../spec/analytics_v0_config.yaml' assert { type: 'yml' } +import { Parameter } from '~/lib/refGenerator/refTypes' + +import ReactMarkdown from 'react-markdown' + +// Parameters are grouped on the page by tag +const TAGS = ['general', 'database'] + +export default function Config(props) { + return ( +
      + ) +} +export async function getServerSideProps() { + // an array of ids of the intro sections for this library + const introPages = ['analytics'] + + const pages = [...introPages] + + // Grab custom markdown intro page files + const allMarkdownDocs = await Promise.all( + pages.map(async (x: any, i) => { + const pathName = `docs/ref/analytics/${x}.mdx` + + const markdownExists = fs.existsSync(pathName) + + const fileContents = markdownExists ? fs.readFileSync(pathName, 'utf8') : '' + const { data, content } = matter(fileContents) + + return { + id: x, + title: x, + // ...content, + meta: data, + introPage: introPages.includes(x), + content: content ? await serialize(content || '') : null, + } + }) + ) + + return { + props: { + docs: allMarkdownDocs, + }, + } +} diff --git a/apps/docs/pages/reference/auth/[...slug].tsx b/apps/docs/pages/reference/auth/[...slug].tsx index ad57871576b..68603c86b07 100644 --- a/apps/docs/pages/reference/auth/[...slug].tsx +++ b/apps/docs/pages/reference/auth/[...slug].tsx @@ -1,9 +1,7 @@ +import { CodeBlock, Tabs } from 'ui' import specFile from '~/../../spec/transforms/auth_v1_openapi_deparsed.json' assert { type: 'json' } -import { gen_v3, enrichedOperation } from '~/lib/refGenerator/helpers' -import { Tabs } from '~/../../packages/ui' +import { gen_v3 } from '~/lib/refGenerator/helpers' -// @ts-ignore -import CodeBlock from '~/components/CodeBlock/CodeBlock' import RefSubLayout from '~/layouts/ref/RefSubLayout' export type AcceptedValue = { diff --git a/apps/docs/pages/reference/auth/config.tsx b/apps/docs/pages/reference/auth/config.tsx index 3530cef71c6..c024dbe9ecc 100644 --- a/apps/docs/pages/reference/auth/config.tsx +++ b/apps/docs/pages/reference/auth/config.tsx @@ -2,67 +2,73 @@ import specFile from '~/../../spec/gotrue_v1_config.yaml' assert { type: 'yml' } import { Parameter } from '~/lib/refGenerator/refTypes' import ReactMarkdown from 'react-markdown' +import Head from 'next/head' // Parameters are grouped on the page by tag const TAGS = ['general'] export default function Config() { return ( -
      -
      -
      -

      {specFile.info.title} Configuration

      - {specFile.info.description} -
      - {TAGS.map((tag) => - specFile.parameters - .filter((param: Parameter) => param.tags[0] === tag) - .map((parameter: Parameter, index) => ( -
      - {index === 0 &&

      {tag}

      } -
      -
      -

      - $ - {parameter.title} -

      -
      -
      -
      -

      - {parameter.description} -

      -
      -
      - Required: {parameter.required.toString()} -
      -
      -
      + ) } diff --git a/apps/docs/pages/reference/cli/config.tsx b/apps/docs/pages/reference/cli/config.tsx index ca8c6969f28..a40295b3802 100644 --- a/apps/docs/pages/reference/cli/config.tsx +++ b/apps/docs/pages/reference/cli/config.tsx @@ -4,6 +4,7 @@ import { Parameter } from '~/lib/refGenerator/refTypes' import ReactMarkdown from 'react-markdown' import GuidesTableOfContents from '~/components/GuidesTableOfContents' import { Heading } from '~/components/CustomHTMLElements' +import Head from 'next/head' // Parameters are grouped on the page by tag const TAGS = ['general', 'auth', 'api', 'database', 'dashboard', 'local', 'edge-functions'] @@ -21,69 +22,74 @@ const tocList = TAGS.map((tag) => export default function Config() { return ( -
      -
      -
      -

      CLI configuration

      -
      - {specFile.info.description} -
      - {TAGS.map((tag) => - specFile.parameters - .filter((param: Parameter) => param.tags[0] === tag) - .map((parameter: Parameter, index) => ( -
      - {index === 0 &&

      {tag}

      } -
      -
      - - {parameter.title} - -
      -
      -
      -

      - {parameter.description} -

      -
      -
      - Required: {parameter.required.toString()} -
      -
      -
      -
      - - On this page - - -
      -
      -
      + ) } diff --git a/apps/docs/pages/reference/self-hosting-analytics/[...slug].tsx b/apps/docs/pages/reference/self-hosting-analytics/[...slug].tsx new file mode 100644 index 00000000000..5f78d5830ff --- /dev/null +++ b/apps/docs/pages/reference/self-hosting-analytics/[...slug].tsx @@ -0,0 +1,24 @@ +import analyticsSpec from '~/../../spec/transforms/analytics_v0_openapi_deparsed.json' assert { type: 'json' } +import selfHostingAnalyticsCommonSections from '~/../../spec/common-self-hosting-analytics-sections.json' +import RefSectionHandler from '~/components/reference/RefSectionHandler' +import { flattenSections } from '~/lib/helpers' +import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' +import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' +import { gen_v3 } from '~/lib/refGenerator/helpers' + +const sections = flattenSections(selfHostingAnalyticsCommonSections) + +// @ts-ignore +const spec = gen_v3(analyticsSpec, 'wat', { apiUrl: 'apiv0' }) + +export default function JSReference(props) { + return +} + +export async function getStaticProps() { + return handleRefStaticProps(sections, '/self-hosting-analytics') +} + +export function getStaticPaths() { + return handleRefGetStaticPaths() +} diff --git a/apps/docs/pages/reference/storage/[...slug].tsx b/apps/docs/pages/reference/storage/[...slug].tsx index 9068c3008c7..bfd42d2719a 100644 --- a/apps/docs/pages/reference/storage/[...slug].tsx +++ b/apps/docs/pages/reference/storage/[...slug].tsx @@ -1,9 +1,7 @@ +import { CodeBlock, Tabs } from 'ui' import specFile from '~/../../spec/transforms/storage_v0_openapi_deparsed.json' assert { type: 'json' } -import { gen_v3, enrichedOperation } from '~/lib/refGenerator/helpers' -import { Tabs } from '~/../../packages/ui' +import { gen_v3 } from '~/lib/refGenerator/helpers' -// @ts-ignore -import CodeBlock from '~/components/CodeBlock/CodeBlock' import RefSubLayout from '~/layouts/ref/RefSubLayout' export type AcceptedValue = { diff --git a/apps/docs/pages/reference/swift/[...slug].tsx b/apps/docs/pages/reference/swift/[...slug].tsx new file mode 100644 index 00000000000..d0991316c1f --- /dev/null +++ b/apps/docs/pages/reference/swift/[...slug].tsx @@ -0,0 +1,21 @@ +import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json' +// @ts-expect-error +import spec from '~/../../spec/supabase_swift_v1.yml' assert { type: 'yml' } +import RefSectionHandler from '~/components/reference/RefSectionHandler' +import { flattenSections } from '~/lib/helpers' +import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' +import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' + +const sections = flattenSections(clientLibsCommonSections) + +export default function JSReference(props) { + return +} + +export async function getStaticProps() { + return handleRefStaticProps(sections, '/swift') +} + +export function getStaticPaths() { + return handleRefGetStaticPaths() +} diff --git a/apps/docs/pages/reference/swift/crawlers/[...slug].tsx b/apps/docs/pages/reference/swift/crawlers/[...slug].tsx new file mode 100644 index 00000000000..00d29a772db --- /dev/null +++ b/apps/docs/pages/reference/swift/crawlers/[...slug].tsx @@ -0,0 +1,44 @@ +import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json' +import typeSpec from '~/../../spec/enrichments/tsdoc_v2/combined.json' +// @ts-expect-error +import spec from '~/../../spec/supabase_swift_v1.yml' assert { type: 'yml' } +import RefSectionHandler from '~/components/reference/RefSectionHandler' +import { flattenSections } from '~/lib/helpers' +import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' +import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' +import { useRouter } from 'next/router' +import RefSEO from '~/components/reference/RefSEO' + +const sections = flattenSections(clientLibsCommonSections) + +export default function JSReference(props) { + const router = useRouter() + const slug = router.query.slug[0] + const filteredSection = sections.filter((section) => section.id === slug) + + const pageTitle = filteredSection[0]?.title + ? `${filteredSection[0]?.title} | Supabase` + : 'Supabase' + + return ( + <> + + + + + ) +} + +export async function getStaticProps() { + return handleRefStaticProps(sections, '/swift') +} + +export function getStaticPaths() { + return handleRefGetStaticPaths() +} diff --git a/apps/docs/pages/reference/swift/v1/[...slug].tsx b/apps/docs/pages/reference/swift/v1/[...slug].tsx new file mode 100644 index 00000000000..1ffdebde8c3 --- /dev/null +++ b/apps/docs/pages/reference/swift/v1/[...slug].tsx @@ -0,0 +1,21 @@ +import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json' +// @ts-expect-error +import spec from '~/../../spec/supabase_swift_v1.yml' assert { type: 'yml' } +import RefSectionHandler from '~/components/reference/RefSectionHandler' +import { flattenSections } from '~/lib/helpers' +import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' +import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' + +const sections = flattenSections(clientLibsCommonSections) + +export default function JSReference(props) { + return +} + +export async function getStaticProps() { + return handleRefStaticProps(sections, '/swift/v1') +} + +export function getStaticPaths() { + return handleRefGetStaticPaths() +} diff --git a/apps/docs/pages/reference/swift/v1/crawlers/[...slug].tsx b/apps/docs/pages/reference/swift/v1/crawlers/[...slug].tsx new file mode 100644 index 00000000000..13ae4435ddd --- /dev/null +++ b/apps/docs/pages/reference/swift/v1/crawlers/[...slug].tsx @@ -0,0 +1,43 @@ +import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json' +import typeSpec from '~/../../spec/enrichments/tsdoc_v2/combined.json' +// @ts-expect-error +import spec from '~/../../spec/supabase_swift_v1.yml' assert { type: 'yml' } +import RefSectionHandler from '~/components/reference/RefSectionHandler' +import { flattenSections } from '~/lib/helpers' +import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' +import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' +import { useRouter } from 'next/router' +import RefSEO from '~/components/reference/RefSEO' + +const sections = flattenSections(clientLibsCommonSections) + +export default function JSReference(props) { + const router = useRouter() + const slug = router.query.slug[0] + const filteredSection = sections.filter((section) => section.id === slug) + + const pageTitle = filteredSection[0]?.title + ? `${filteredSection[0]?.title} | Supabase` + : 'Supabase' + return ( + <> + + + + + ) +} + +export async function getStaticProps() { + return handleRefStaticProps(sections, '/swift/v1') +} + +export function getStaticPaths() { + return handleRefGetStaticPaths() +} diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index 2daf85cdf42..a87525bceb6 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -17,6 +17,8 @@ Dave Wilson Div Arora Egor Romanov Fabrizio Fenoglio +Francesco Sansalvadore +Greg Richardson Haydn Maley Hieu Pham Inian P diff --git a/apps/docs/public/img/b-tree-example.png b/apps/docs/public/img/b-tree-example.png new file mode 100644 index 00000000000..b9274c9e43d Binary files /dev/null and b/apps/docs/public/img/b-tree-example.png differ 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/img/guides/realtime/realtime-arch.png b/apps/docs/public/img/guides/realtime/realtime-arch.png new file mode 100644 index 00000000000..fca216142b1 Binary files /dev/null and b/apps/docs/public/img/guides/realtime/realtime-arch.png differ diff --git a/apps/docs/public/img/icons/menu/analytics-light.svg b/apps/docs/public/img/icons/menu/analytics-light.svg new file mode 100644 index 00000000000..3cea218cd72 --- /dev/null +++ b/apps/docs/public/img/icons/menu/analytics-light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/docs/public/img/icons/menu/analytics.svg b/apps/docs/public/img/icons/menu/analytics.svg new file mode 100644 index 00000000000..2c3be34319e --- /dev/null +++ b/apps/docs/public/img/icons/menu/analytics.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/docs/public/img/icons/menu/reference-analytics-light.svg b/apps/docs/public/img/icons/menu/reference-analytics-light.svg new file mode 100644 index 00000000000..840fb813f92 --- /dev/null +++ b/apps/docs/public/img/icons/menu/reference-analytics-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/docs/public/img/icons/menu/reference-analytics.svg b/apps/docs/public/img/icons/menu/reference-analytics.svg new file mode 100644 index 00000000000..840fb813f92 --- /dev/null +++ b/apps/docs/public/img/icons/menu/reference-analytics.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/docs/public/img/icons/menu/reference-swift-light.svg b/apps/docs/public/img/icons/menu/reference-swift-light.svg new file mode 100644 index 00000000000..d11ab08bb58 --- /dev/null +++ b/apps/docs/public/img/icons/menu/reference-swift-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/docs/public/img/icons/menu/reference-swift.svg b/apps/docs/public/img/icons/menu/reference-swift.svg new file mode 100644 index 00000000000..d11ab08bb58 --- /dev/null +++ b/apps/docs/public/img/icons/menu/reference-swift.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/docs/public/img/icons/menu/swift.svg b/apps/docs/public/img/icons/menu/swift.svg new file mode 100644 index 00000000000..d11ab08bb58 --- /dev/null +++ b/apps/docs/public/img/icons/menu/swift.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/docs/public/img/icons/swift-icon.svg b/apps/docs/public/img/icons/swift-icon.svg new file mode 100644 index 00000000000..d11ab08bb58 --- /dev/null +++ b/apps/docs/public/img/icons/swift-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/docs/public/img/integrations/logos/openai_logo.png b/apps/docs/public/img/integrations/logos/openai_logo.png new file mode 100644 index 00000000000..c5596878b0b Binary files /dev/null and b/apps/docs/public/img/integrations/logos/openai_logo.png differ diff --git a/apps/docs/public/img/supabase-oauth-logins.png b/apps/docs/public/img/supabase-oauth-logins.png index 8b92d40169d..825ae97fb76 100644 Binary files a/apps/docs/public/img/supabase-oauth-logins.png and b/apps/docs/public/img/supabase-oauth-logins.png differ diff --git a/apps/docs/public/sitemap.xml b/apps/docs/public/sitemap.xml index 35bf4d7ff55..5af30326761 100644 --- a/apps/docs/public/sitemap.xml +++ b/apps/docs/public/sitemap.xml @@ -19,7 +19,19 @@ - https://supabase.com/docs/guides/database/api + https://supabase.com/docs/handbook/contributing + weekly + 0.5 + + + + https://supabase.com/docs/handbook/introduction + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth weekly 0.5 @@ -36,12 +48,6 @@ 0.5 - - https://supabase.com/docs/guides/examples - weekly - 0.5 - - https://supabase.com/docs/guides/functions weekly @@ -97,37 +103,31 @@ - https://supabase.com/docs/handbook/contributing + https://supabase.com/docs/learn/auth-deep-dive/auth-deep-dive-jwts weekly 0.5 - https://supabase.com/docs/handbook/introduction + https://supabase.com/docs/learn/auth-deep-dive/auth-google-oauth weekly 0.5 - https://supabase.com/docs/new/auth + https://supabase.com/docs/learn/auth-deep-dive/auth-gotrue weekly 0.5 - https://supabase.com/docs/reference/index + https://supabase.com/docs/learn/auth-deep-dive/auth-policies weekly 0.5 - https://supabase.com/docs/tutorials/nextjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/api/generating-types + https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security weekly 0.5 @@ -169,13 +169,13 @@ - https://supabase.com/docs/guides/auth/managing-user-data + https://supabase.com/docs/guides/auth/enterprise-sso weekly 0.5 - https://supabase.com/docs/guides/auth/overview + https://supabase.com/docs/guides/auth/managing-user-data weekly 0.5 @@ -216,6 +216,90 @@ 0.5 + + https://supabase.com/docs/guides/functions/auth + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/cicd-workflow + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/connect-to-postgres + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/cors + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/debugging + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/global-deployments + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/import-maps + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/local-development + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/quickstart + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/schedule-functions + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/secrets + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/storage-caching + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/typescript-support + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/api + weekly + 0.5 + + https://supabase.com/docs/guides/database/arrays weekly @@ -318,60 +402,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 @@ -396,12 +426,6 @@ 0.5 - - https://supabase.com/docs/guides/integrations/dashibase - weekly - 0.5 - - https://supabase.com/docs/guides/integrations/dhiwise weekly @@ -559,7 +583,7 @@ - https://supabase.com/docs/guides/platform/database-usage + https://supabase.com/docs/guides/platform/database-size weekly 0.5 @@ -612,6 +636,18 @@ 0.5 + + https://supabase.com/docs/guides/platform/spend-cap + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/ssl-enforcement + weekly + 0.5 + + https://supabase.com/docs/guides/platform/sso weekly @@ -625,7 +661,25 @@ - https://supabase.com/docs/guides/realtime/postgres-changes + https://supabase.com/docs/guides/realtime/architecture + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/channels + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/extensions + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/protocol weekly 0.5 @@ -642,6 +696,24 @@ 0.5 + + https://supabase.com/docs/guides/realtime/realtime-with-nextjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/subscribing-to-database-changes + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/examples + weekly + 0.5 + + https://supabase.com/docs/guides/resources/glossary weekly @@ -684,210 +756,6 @@ 0.5 - - https://supabase.com/docs/learn/auth-deep-dive/auth-deep-dive-jwts - weekly - 0.5 - - - - https://supabase.com/docs/learn/auth-deep-dive/auth-google-oauth - weekly - 0.5 - - - - https://supabase.com/docs/learn/auth-deep-dive/auth-gotrue - weekly - 0.5 - - - - https://supabase.com/docs/learn/auth-deep-dive/auth-policies - weekly - 0.5 - - - - https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/extensions/http - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/extensions/pgcron - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/extensions/pgnet - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/extensions/pgtap - 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/uuid-ossp - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/flutter - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/nextjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/nuxtjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/reactjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/solidjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/sveltekit - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/vue - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-angular - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-expo - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-flutter - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-angular - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-react - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-vue - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-nextjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-nuxt-3 - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-react - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-redwoodjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-solidjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-svelte - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-sveltekit - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-vue-3 - weekly - 0.5 - - https://supabase.com/docs/guides/auth/auth-helpers/auth-ui weekly @@ -936,108 +804,6 @@ 0.5 - - https://supabase.com/docs/guides/functions/examples/cloudflare-turnstile - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/connect-to-postgres - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/github-actions - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/og-image - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/storage-caching - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/stripe-webhooks - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/telegram-bot - weekly - 0.5 - - - - https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-auth - weekly - 0.5 - - - - https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-storage - weekly - 0.5 - - - - https://supabase.com/docs/guides/resources/migrating-to-supabase/firestore-data - weekly - 0.5 - - - - https://supabase.com/docs/guides/resources/migrating-to-supabase/heroku - weekly - 0.5 - - - - https://supabase.com/docs/guides/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 - weekly - 0.5 - - - - https://supabase.com/docs/guides/self-hosting/auth/config - weekly - 0.5 - - - - https://supabase.com/docs/guides/self-hosting/storage/config - weekly - 0.5 - - https://supabase.com/docs/guides/auth/social-login/auth-apple weekly @@ -1140,6 +906,468 @@ 0.5 + + https://supabase.com/docs/guides/auth/sso/auth-sso-saml + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/cloudflare-turnstile + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/discord-bot + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/github-actions + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/og-image + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/openai + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/rate-limiting + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/screenshots + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/stripe-webhooks + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/telegram-bot + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/upstash-redis + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/api/generating-types + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/dropping-all-tables-in-schema + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/first-row-in-group + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/indexes + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/which-version-of-postgres + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/flutter + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/nextjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/nuxtjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/reactjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/solidjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/sveltekit + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/vue + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/http + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/hypopg + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg-safeupdate + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg_graphql + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg_hashids + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg_jsonschema + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg_plan_filter + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg_stat_monitor + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pg_stat_statements + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgaudit + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgcron + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgjwt + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgnet + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgrepack + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgroonga + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgrouting + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgsodium + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgtap + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/pgvector + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/plpgsql_check + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/plv8 + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/postgis + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/rum + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/timescaledb + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/uuid-ossp + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions/wrappers + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/sso/azure + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/sso/gsuite + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/sso/okta + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-angular + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-expo + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-flutter + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-angular + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-react + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-vue + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-nextjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-nuxt-3 + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-react + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-redwoodjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-solidjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-svelte + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-sveltekit + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-vue-3 + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/extensions/broadcast + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/extensions/postgres-changes + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/extensions/presence + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-auth + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-storage + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/firestore-data + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/heroku + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/render + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting/auth/config + 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/reference/javascript/initializing weekly @@ -1562,6 +1790,12 @@ 0.5 + + https://supabase.com/docs/reference/javascript/db-returns + weekly + 0.5 + + https://supabase.com/docs/reference/javascript/functions-invoke weekly @@ -3404,6 +3638,24 @@ 0.5 + + https://supabase.com/docs/reference/cli/supabase-ssl-enforcement + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-ssl-enforcement-update + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-ssl-enforcement-get + weekly + 0.5 + + https://supabase.com/docs/reference/cli/supabase-secrets weekly @@ -3867,4 +4119,4 @@ weekly 0.5 - \ No newline at end of file + diff --git a/apps/docs/scripts/generate-embeddings.ts b/apps/docs/scripts/generate-embeddings.ts index 41b5a25ecbd..520450083e3 100644 --- a/apps/docs/scripts/generate-embeddings.ts +++ b/apps/docs/scripts/generate-embeddings.ts @@ -3,17 +3,25 @@ import { createHash } from 'crypto' import dotenv from 'dotenv' import { ObjectExpression } from 'estree' import { readdir, readFile, stat } from 'fs/promises' +import GithubSlugger from 'github-slugger' +import yaml from 'js-yaml' import { Content, Root } from 'mdast' import { fromMarkdown } from 'mdast-util-from-markdown' import { mdxFromMarkdown, MdxjsEsm } from 'mdast-util-mdx' import { toMarkdown } from 'mdast-util-to-markdown' +import { toString } from 'mdast-util-to-string' import { mdxjs } from 'micromark-extension-mdxjs' import 'openai' import { Configuration, OpenAIApi } from 'openai' -import { join } from 'path' +import { OpenAPIV3 } from 'openapi-types' +import { basename, dirname, join } from 'path' import { u } from 'unist-builder' import { filter } from 'unist-util-filter' import { inspect } from 'util' +import { ICommonFunc, IFunctionDefinition, ISpec } from '../components/reference/Reference.types' +import { CliCommand, CliSpec } from '../generator/types/CliSpec' +import { flattenSections } from '../lib/helpers' +import { enrichedOperation, gen_v3 } from '../lib/refGenerator/helpers' dotenv.config() @@ -105,10 +113,16 @@ function splitTreeBy(tree: Root, predicate: (node: Content) => boolean) { type Meta = ReturnType +type Section = { + content: string + heading?: string + slug?: string +} + type ProcessedMdx = { checksum: string meta: Meta - sections: string[] + sections: Section[] } /** @@ -149,7 +163,20 @@ function processMdxForSearch(content: string): ProcessedMdx { const sectionTrees = splitTreeBy(mdTree, (node) => node.type === 'heading') - const sections = sectionTrees.map((tree) => toMarkdown(tree)) + const slugger = new GithubSlugger() + + const sections = sectionTrees.map((tree) => { + const [firstNode] = tree.children + + const heading = firstNode.type === 'heading' ? toString(firstNode) : undefined + const slug = heading ? slugger.slug(heading) : undefined + + return { + content: toMarkdown(tree), + heading, + slug, + } + }) return { checksum, @@ -158,17 +185,33 @@ function processMdxForSearch(content: string): ProcessedMdx { } } -async function walk(dir: string): Promise { +type WalkEntry = { + path: string + parentPath?: string +} + +async function walk(dir: string, parentPath?: string): Promise { const immediateFiles = await readdir(dir) const recursiveFiles = await Promise.all( immediateFiles.map(async (file) => { - const filePath = join(dir, file) - const stats = await stat(filePath) + const path = join(dir, file) + const stats = await stat(path) if (stats.isDirectory()) { - return walk(filePath) + // Keep track of document hierarchy (if this dir has corresponding doc file) + const docPath = `${basename(path)}.mdx` + + return walk( + path, + immediateFiles.includes(docPath) ? join(dirname(path), docPath) : parentPath + ) } else if (stats.isFile()) { - return [filePath] + return [ + { + path: path, + parentPath, + }, + ] } else { return [] } @@ -180,10 +223,182 @@ async function walk(dir: string): Promise { [] ) - return flattenedFiles + return flattenedFiles.sort((a, b) => a.path.localeCompare(b.path)) } +abstract class BaseEmbeddingSource { + checksum?: string + meta?: Meta + sections?: Section[] + + constructor(public source: string, public path: string, public parentPath?: string) {} + + abstract load(): Promise<{ checksum: string; meta?: Meta; sections: Section[] }> +} + +class MarkdownEmbeddingSource extends BaseEmbeddingSource { + type: 'markdown' = 'markdown' + + constructor(source: string, public filePath: string, public parentFilePath?: string) { + const path = filePath.replace(/^pages/, '').replace(/\.mdx?$/, '') + const parentPath = parentFilePath?.replace(/^pages/, '').replace(/\.mdx?$/, '') + + super(source, path, parentPath) + } + + async load() { + const contents = await readFile(this.filePath, 'utf8') + + const { checksum, meta, sections } = processMdxForSearch(contents) + + this.checksum = checksum + this.meta = meta + this.sections = sections + + return { + checksum, + meta, + sections, + } + } +} + +abstract class ReferenceEmbeddingSource extends BaseEmbeddingSource { + type: 'reference' = 'reference' + + constructor( + source: string, + path: string, + public meta: Meta, + public specFilePath: string, + public sectionsFilePath: string + ) { + super(source, path) + } + + async load() { + const specContents = await readFile(this.specFilePath, 'utf8') + const refSectionsContents = await readFile(this.sectionsFilePath, 'utf8') + + const refSections: ICommonFunc[] = JSON.parse(refSectionsContents) + const flattenedRefSections = flattenSections(refSections) + + const checksum = createHash('sha256') + .update(specContents + refSectionsContents) + .digest('base64') + + const specSections = this.getSpecSections(specContents) + + const sections = flattenedRefSections + .map((refSection) => { + const specSection = this.matchSpecSection(specSections, refSection.id) + + if (!specSection) { + return + } + + return { + heading: refSection.title, + slug: refSection.slug, + content: `${this.meta.title} for ${refSection.title}:\n${this.formatSection( + specSection, + refSection + )}`, + } + }) + .filter((section) => !!section) + + this.checksum = checksum + this.sections = sections + + return { + checksum, + sections, + meta: this.meta, + } + } + + abstract getSpecSections(specContents: string): SpecSection[] + abstract matchSpecSection(specSections: SpecSection[], id: string): SpecSection + abstract formatSection(specSection: SpecSection, refSection: ICommonFunc): string +} + +class OpenApiEmbeddingSource extends ReferenceEmbeddingSource { + getSpecSections(specContents: string): enrichedOperation[] { + const spec: OpenAPIV3.Document<{}> = JSON.parse(specContents) + + const generatedSpec = gen_v3(spec, '', { + apiUrl: 'apiv0', + }) + + return generatedSpec.operations + } + matchSpecSection(operations: enrichedOperation[], id: string): enrichedOperation { + return operations.find((operation) => operation.operationId === id) + } + formatSection(specOperation: enrichedOperation) { + const { summary, description, operation, path, tags } = specOperation + return JSON.stringify({ + summary, + description, + operation, + path, + tags, + }) + } +} + +class ClientLibEmbeddingSource extends ReferenceEmbeddingSource { + getSpecSections(specContents: string): IFunctionDefinition[] { + const spec = yaml.load(specContents) as ISpec + + return spec.functions + } + matchSpecSection(functionDefinitions: IFunctionDefinition[], id: string): IFunctionDefinition { + return functionDefinitions.find((functionDefinition) => functionDefinition.id === id) + } + formatSection(functionDefinition: IFunctionDefinition, refSection: ICommonFunc): string { + const { title } = refSection + const { description, title: functionName } = functionDefinition + + return JSON.stringify({ + title, + description, + functionName, + }) + } +} + +class CliEmbeddingSource extends ReferenceEmbeddingSource { + getSpecSections(specContents: string): CliCommand[] { + const spec = yaml.load(specContents) as CliSpec + + return spec.commands + } + matchSpecSection(cliCommands: CliCommand[], id: string): CliCommand { + return cliCommands.find((cliCommand) => cliCommand.id === id) + } + formatSection(cliCommand: CliCommand): string { + const { summary, description, usage } = cliCommand + return JSON.stringify({ + summary, + description, + usage, + }) + } +} + +type EmbeddingSource = + | MarkdownEmbeddingSource + | OpenApiEmbeddingSource + | ClientLibEmbeddingSource + | CliEmbeddingSource + async function generateEmbeddings() { + // TODO: use better CLI lib like yargs + const args = process.argv.slice(2) + const shouldRefresh = args.includes('--refresh') + if ( !process.env.NEXT_PUBLIC_SUPABASE_URL || !process.env.SUPABASE_SERVICE_ROLE_KEY || @@ -196,27 +411,82 @@ async function generateEmbeddings() { const supabaseClient = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL, - process.env.SUPABASE_SERVICE_ROLE_KEY + process.env.SUPABASE_SERVICE_ROLE_KEY, + { + auth: { + persistSession: false, + autoRefreshToken: false, + }, + } ) - const markdownFiles = (await walk('pages')) - .filter((fileName) => /\.mdx?$/.test(fileName)) - .filter((fileName) => !ignoredFiles.includes(fileName)) + const embeddingSources: EmbeddingSource[] = [ + new OpenApiEmbeddingSource( + 'api', + '/reference/api', + { title: 'Management API Reference' }, + '../../spec/transforms/api_v0_openapi_deparsed.json', + '../../spec/common-api-sections.json' + ), + new ClientLibEmbeddingSource( + 'js-lib', + '/reference/javascript', + { title: 'JavaScript Reference' }, + '../../spec/supabase_js_v2.yml', + '../../spec/common-client-libs-sections.json' + ), + new ClientLibEmbeddingSource( + 'dart-lib', + '/reference/dart', + { title: 'Dart Reference' }, + '../../spec/supabase_dart_v1.yml', + '../../spec/common-client-libs-sections.json' + ), + new ClientLibEmbeddingSource( + 'python-lib', + '/reference/python', + { title: 'Python Reference' }, + '../../spec/supabase_py_v2.yml', + '../../spec/common-client-libs-sections.json' + ), + new ClientLibEmbeddingSource( + 'csharp-lib', + '/reference/csharp', + { title: 'C# Reference' }, + '../../spec/supabase_csharp_v0.yml', + '../../spec/common-client-libs-sections.json' + ), + new CliEmbeddingSource( + 'cli', + '/reference/cli', + { title: 'CLI Reference' }, + '../../spec/cli_v1_commands.yaml', + '../../spec/common-cli-sections.json' + ), + ...(await walk('pages')) + .filter(({ path }) => /\.mdx?$/.test(path)) + .filter(({ path }) => !ignoredFiles.includes(path)) + .map((entry) => new MarkdownEmbeddingSource('guide', entry.path)), + ] - console.log(`Discovered ${markdownFiles.length} pages`) - console.log('Checking which pages are new or have changed') + console.log(`Discovered ${embeddingSources.length} pages`) + + if (!shouldRefresh) { + console.log('Checking which pages are new or have changed') + } else { + console.log('Refresh flag set, re-generating all pages') + } + + for (const embeddingSource of embeddingSources) { + const { type, source, path, parentPath } = embeddingSource - for (const markdownFile of markdownFiles) { - const path = markdownFile.replace(/^pages/, '').replace(/\.mdx?$/, '') try { - const contents = await readFile(markdownFile, 'utf8') - - const { checksum, meta, sections } = processMdxForSearch(contents) + const { checksum, meta, sections } = await embeddingSource.load() // Check for existing page in DB and compare checksums const { error: fetchPageError, data: existingPage } = await supabaseClient .from('page') - .select() + .select('id, path, checksum, parentPage:parent_page_id(id, path)') .filter('path', 'eq', path) .limit(1) .maybeSingle() @@ -225,17 +495,48 @@ async function generateEmbeddings() { throw fetchPageError } + type Singular = T extends any[] ? undefined : T + // We use checksum to determine if this page & its sections need to be regenerated - if (existingPage?.checksum === checksum) { + if (!shouldRefresh && existingPage?.checksum === checksum) { + const existingParentPage = existingPage?.parentPage as Singular< + typeof existingPage.parentPage + > + + // If parent page changed, update it + if (existingParentPage?.path !== parentPath) { + console.log(`[${path}] Parent page has changed. Updating to '${parentPath}'...`) + const { error: fetchParentPageError, data: parentPage } = await supabaseClient + .from('page') + .select() + .filter('path', 'eq', parentPath) + .limit(1) + .maybeSingle() + + if (fetchParentPageError) { + throw fetchParentPageError + } + + const { error: updatePageError } = await supabaseClient + .from('page') + .update({ parent_page_id: parentPage?.id }) + .filter('id', 'eq', existingPage.id) + + if (updatePageError) { + throw updatePageError + } + } continue } - console.log({ checksum, path, meta }) - if (existingPage) { - console.log( - `Docs have changed for '${path}', removing old page sections and their embeddings` - ) + if (!shouldRefresh) { + console.log( + `[${path}] Docs have changed, removing old page sections and their embeddings` + ) + } else { + console.log(`[${path}] Refresh flag set, removing old page sections and their embeddings`) + } const { error: deletePageSectionError } = await supabaseClient .from('page_section') @@ -247,11 +548,32 @@ async function generateEmbeddings() { } } + const { error: fetchParentPageError, data: parentPage } = await supabaseClient + .from('page') + .select() + .filter('path', 'eq', parentPath) + .limit(1) + .maybeSingle() + + if (fetchParentPageError) { + throw fetchParentPageError + } + // Create/update page record. Intentionally clear checksum until we // have successfully generated all page sections. const { error: upsertPageError, data: page } = await supabaseClient .from('page') - .upsert({ checksum: null, path, meta }, { onConflict: 'path' }) + .upsert( + { + checksum: null, + path, + type, + source, + meta, + parent_page_id: parentPage?.id, + }, + { onConflict: 'path' } + ) .select() .limit(1) .single() @@ -260,10 +582,10 @@ async function generateEmbeddings() { throw upsertPageError } - console.log(`Adding ${sections.length} page sections (with embeddings) for '${path}'`) - for (const section of sections) { + console.log(`[${path}] Adding ${sections.length} page sections (with embeddings)`) + for (const { slug, heading, content } of sections) { // OpenAI recommends replacing newlines with spaces for best results (specific to embeddings) - const input = section.replace(/\n/g, ' ') + const input = content.replace(/\n/g, ' ') try { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY }) @@ -284,7 +606,9 @@ async function generateEmbeddings() { .from('page_section') .insert({ page_id: page.id, - content: section, + slug, + heading, + content, token_count: embeddingResponse.data.usage.total_tokens, embedding: responseData.embedding, }) diff --git a/apps/docs/styles/algolia-search.scss b/apps/docs/styles/algolia-search.scss deleted file mode 100644 index 56ac843d5d2..00000000000 --- a/apps/docs/styles/algolia-search.scss +++ /dev/null @@ -1,962 +0,0 @@ -// ---------------- -// 1. CSS Variables -// 2. Dark Mode -// 3. Autocomplete -// 4. Panel -// 5. Sources -// 6. Hit Layout -// 7. Panel Header -// 8. Panel Footer -// 9. Detached Mode -// 10. Gradients -// 11. Utilities -// ---------------- - -// Note: -// This theme reflects the markup structure of autocomplete with SCSS indentation. -// We use the SASS `@at-root` function to keep specificity low. - -// ---------------- -// 1. CSS Variables -// ---------------- -:root { - // Input - --aa-search-input-height: 38px; - --aa-input-icon-size: 20px; - - // Size and spacing - --aa-base-unit: 16; - --aa-spacing-factor: 1; - --aa-spacing: calc(var(--aa-base-unit) * var(--aa-spacing-factor) * 1px); - --aa-spacing-half: calc(var(--aa-spacing) / 2); - --aa-panel-max-height: 650px; - - // Z-index - --aa-base-z-index: 9999; - - // Font - --aa-font-size: calc(var(--aa-base-unit) * 1px); - --aa-font-family: inherit; - --aa-font-weight-medium: 500; - --aa-font-weight-semibold: 600; - --aa-font-weight-bold: 700; - - // Icons - --aa-icon-size: 20px; - --aa-icon-stroke-width: 1.6; - --aa-icon-color-rgb: 119, 119, 163; - --aa-icon-color-alpha: 1; - --aa-action-icon-size: 20px; - - // Text colors - --aa-text-color-rgb: 38, 38, 39; - --aa-text-color-alpha: 1; - --aa-primary-color-rgb: 62, 52, 211; - --aa-primary-color-alpha: 0.2; - --aa-muted-color-rgb: 128, 126, 163; - --aa-muted-color-alpha: 0.6; - - // Border colors - --aa-panel-border-color-rgb: 128, 126, 163; - --aa-panel-border-color-alpha: 0.3; - --aa-input-border-color-rgb: 128, 126, 163; - --aa-input-border-color-alpha: 0.8; - - // Background colors - --aa-background-color-rgb: 255, 255, 255; - --aa-background-color-alpha: 1; - --aa-input-background-color-rgb: 255, 255, 255; - --aa-input-background-color-alpha: 1; - --aa-selected-color-rgb: 179, 173, 214; - --aa-selected-color-alpha: 0.205; - --aa-description-highlight-background-color-rgb: 245, 223, 77; - --aa-description-highlight-background-color-alpha: 0.5; - - // Detached mode - --aa-detached-media-query: (max-width: 680px); - --aa-detached-modal-media-query: (min-width: 680px); - --aa-detached-modal-max-width: 680px; - --aa-detached-modal-max-height: 500px; - --aa-overlay-color-rgb: 115, 114, 129; - --aa-overlay-color-alpha: 0.4; - - // Shadows - --aa-panel-shadow: 0 0 0 1px rgba(35, 38, 59, 0.1), 0 6px 16px -4px rgba(35, 38, 59, 0.15); - - // Scrollbar - --aa-scrollbar-width: 13px; - --aa-scrollbar-track-background-color-rgb: 234, 234, 234; - --aa-scrollbar-track-background-color-alpha: 1; - --aa-scrollbar-thumb-background-color-rgb: var(--aa-background-color-rgb); - --aa-scrollbar-thumb-background-color-alpha: 1; - - // Touch screens - @media (hover: none) and (pointer: coarse) { - --aa-spacing-factor: 1.2; - --aa-action-icon-size: 22px; - } -} - -// ---------------- -// 2. Dark Mode -// ---------------- -body { - /* stylelint-disable selector-no-qualifying-type, selector-class-pattern */ - &[data-theme='dark'], - &.dark { - // Text colors - --aa-text-color-rgb: 183, 192, 199; - --aa-primary-color-rgb: 146, 138, 255; - --aa-muted-color-rgb: 146, 138, 255; - - // Background colors - --aa-input-background-color-rgb: 0, 3, 9; - --aa-background-color-rgb: 21, 24, 42; - --aa-selected-color-rgb: 146, 138, 255; - --aa-selected-color-alpha: 0.25; - --aa-description-highlight-background-color-rgb: 0 255 255; - --aa-description-highlight-background-color-alpha: 0.25; - - // Icons - --aa-icon-color-rgb: 119, 119, 163; - - // Shadows - --aa-panel-shadow: inset 1px 1px 0 0 rgb(44, 46, 64), 0 3px 8px 0 rgb(0, 3, 9); - - // Scrollbar - --aa-scrollbar-track-background-color-rgb: 44, 46, 64; - --aa-scrollbar-thumb-background-color-rgb: var(--aa-background-color-rgb); - } - /* stylelint-enable selector-no-qualifying-type, selector-class-pattern */ -} - -// Reset for `@extend` -%reset { - box-sizing: border-box; -} - -// Init for `@extend` -%init { - color: rgba(var(--aa-text-color-rgb), var(--aa-text-color-alpha)); - font-family: var(--aa-font-family); - font-size: var(--aa-font-size); - font-weight: normal; - line-height: 1em; - margin: 0; - padding: 0; - text-align: left; -} - -// ---------------- -// 3. Autocomplete -// ---------------- -.aa-Autocomplete, -.aa-DetachedFormContainer { - @extend %init; - * { - @extend %reset; - } - // Search box - @at-root .aa-Form { - @apply transition bg-scale-200 border border-scale-500; - align-items: center; - border-radius: 3px; - display: flex; - line-height: 1em; - margin: 0; - position: relative; - width: 100%; - &:focus-within { - @apply border-brand-900; - outline: currentColor none medium; - } - @at-root .aa-InputWrapperPrefix { - align-items: center; - display: flex; - flex-shrink: 0; - height: var(--aa-search-input-height); - order: 1; - // Container for search and loading icons - @at-root .aa-Label, - .aa-LoadingIndicator { - cursor: initial; - flex-shrink: 0; - height: 100%; - padding: 0; - text-align: left; - svg { - @apply text-brand-900; - height: auto; - max-height: var(--aa-input-icon-size); - stroke-width: var(--aa-icon-stroke-width); - width: var(--aa-input-icon-size); - } - } - @at-root .aa-SubmitButton, - .aa-LoadingIndicator { - height: 100%; - padding-left: calc(var(--aa-spacing) * 0.75 - 1px); - padding-right: var(--aa-spacing-half); - width: calc(var(--aa-spacing) * 1.75 + var(--aa-icon-size) - 1px); - @media (hover: none) and (pointer: coarse) { - padding-left: calc(var(--aa-spacing-half) / 2 - 1px); - width: calc(var(--aa-icon-size) + (var(--aa-spacing) * 1.25) - 1px); - } - } - @at-root .aa-SubmitButton { - appearance: none; - background: none; - border: 0; - margin: 0; - } - @at-root .aa-LoadingIndicator { - align-items: center; - display: flex; - justify-content: center; - &[hidden] { - display: none; - } - } - } - @at-root .aa-InputWrapper { - order: 3; - position: relative; - width: 100%; - // Search box input (with placeholder and query) - @at-root .aa-Input { - @apply text-sm text-scale-1200; - appearance: none; - background: none; - border: 0; - height: var(--aa-search-input-height); - padding: 0; - width: 100%; - &::placeholder { - @apply text-scale-700; - opacity: 1; - } - // Focus is set and styled on the parent, it isn't necessary here - &:focus { - border-color: none; - box-shadow: none; - outline: none; - } - // Remove native appearence - &::-webkit-search-decoration, - &::-webkit-search-cancel-button, - &::-webkit-search-results-button, - &::-webkit-search-results-decoration { - appearance: none; - } - } - } - @at-root .aa-InputWrapperSuffix { - align-items: center; - display: flex; - height: var(--aa-search-input-height); - order: 4; - // Accelerator to clear the query - @at-root .aa-ClearButton { - align-items: center; - background: none; - border: 0; - color: rgba(var(--aa-muted-color-rgb), var(--aa-muted-color-alpha)); - cursor: pointer; - display: flex; - height: 100%; - margin: 0; - padding: 0 calc(var(--aa-spacing) * (5 / 6) - 0.5px); - @media (hover: none) and (pointer: coarse) { - padding: 0 calc(var(--aa-spacing) * (2 / 3) - 0.5px); - } - &:hover, - &:focus { - color: rgba(var(--aa-text-color-rgb), var(--aa-text-color-alpha)); - } - &[hidden] { - display: none; - } - svg { - stroke-width: var(--aa-icon-stroke-width); - width: var(--aa-icon-size); - } - } - } - } -} - -// ---------------- -// 4. Panel -// ---------------- -.aa-Panel { - @extend %init; - - @apply bg-scale-200; - border-radius: calc(var(--aa-spacing) / 4); - box-shadow: var(--aa-panel-shadow); - margin: 8px 0 0; - overflow: hidden; - position: absolute; - transition: opacity 200ms ease-in, filter 200ms ease-in; - @media screen and (prefers-reduced-motion) { - transition: none; - } - * { - @extend %reset; - } - button { - appearance: none; - background: none; - border: 0; - margin: 0; - padding: 0; - } - - @at-root .aa-PanelLayout { - @apply bg-scale-200; - height: 100%; - margin: 0; - max-height: var(--aa-panel-max-height); - overflow-y: auto; - padding: 0; - position: relative; - text-align: left; - - @at-root .aa-PanelLayoutColumns--twoGolden { - display: grid; - grid-template-columns: 39.2% auto; - overflow: hidden; - padding: 0; - } - @at-root .aa-PanelLayoutColumns--two { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - overflow: hidden; - padding: 0; - } - @at-root .aa-PanelLayoutColumns--three { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - overflow: hidden; - padding: 0; - } - } - - // When a request isn't resolved yet - @at-root .aa-Panel--stalled { - .aa-Source { - filter: grayscale(1); - opacity: 0.8; - } - } - - @at-root .aa-Panel--scrollable { - margin: 0; - max-height: var(--aa-panel-max-height); - overflow-x: hidden; - overflow-y: auto; - padding: var(--aa-spacing-half); - scrollbar-color: rgba( - var(--aa-scrollbar-thumb-background-color-rgb), - var(--aa-scrollbar-thumb-background-color-alpha) - ) - rgba( - var(--aa-scrollbar-track-background-color-rgb), - var(--aa-scrollbar-track-background-color-alpha) - ); - scrollbar-width: thin; - - &::-webkit-scrollbar { - width: var(--aa-scrollbar-width); - } - &::-webkit-scrollbar-track { - background-color: rgba( - var(--aa-scrollbar-track-background-color-rgb), - var(--aa-scrollbar-track-background-color-alpha) - ); - } - &::-webkit-scrollbar-thumb { - background-color: rgba( - var(--aa-scrollbar-thumb-background-color-rgb), - var(--aa-scrollbar-thumb-background-color-alpha) - ); - border-color: rgba( - var(--aa-scrollbar-track-background-color-rgb), - var(--aa-scrollbar-track-background-color-alpha) - ); - border-radius: 9999px; - border-style: solid; - border-width: 3px 2px 3px 3px; - } - } -} - -// ---------------- -// 5. Sources -// Each source can be styled independently -// ---------------- -.aa-Source { - margin: 0; - padding: 0; - position: relative; - width: 100%; - &:empty { - // Hide empty section - display: none; - } - @at-root .aa-SourceNoResults { - font-size: 1em; - margin: 0; - padding: var(--aa-spacing); - } - // List of results inside the source - @at-root .aa-List { - list-style: none; - margin: 0; - padding: 0; - position: relative; - } - // Source title - @at-root .aa-SourceHeader { - margin: var(--aa-spacing-half) 0.5em var(--aa-spacing-half) 0; - padding: 0; - position: relative; - // Hide empty header - &:empty { - display: none; - } - // Title typography - @at-root .aa-SourceHeaderTitle { - background: rgba(var(--aa-background-color-rgb), var(--aa-background-color-alpha)); - color: rgba(var(--aa-primary-color-rgb), 1); - display: inline-block; - font-size: 0.8em; - font-weight: var(--aa-font-weight-semibold); - margin: 0; - padding: 0 var(--aa-spacing-half) 0 0; - position: relative; - z-index: var(--aa-base-z-index); - } - // Line separator - @at-root .aa-SourceHeaderLine { - border-bottom: solid 1px rgba(var(--aa-primary-color-rgb), 1); - display: block; - height: 2px; - left: 0; - margin: 0; - opacity: 0.3; - padding: 0; - position: absolute; - right: 0; - top: var(--aa-spacing-half); - z-index: calc(var(--aa-base-z-index) - 1); - } - } - // See all button - @at-root .aa-SourceFooterSeeAll { - background: linear-gradient( - 180deg, - rgba(var(--aa-background-color-rgb), var(--aa-background-color-alpha)), - rgba(128, 126, 163, 0.14) - ); - border: 1px solid rgba(var(--aa-muted-color-rgb), var(--aa-muted-color-alpha)); - border-radius: 5px; - box-shadow: inset 0 0 2px #fff, 0 2px 2px -1px rgba(76, 69, 88, 0.15); - color: inherit; - font-size: 0.95em; - font-weight: var(--aa-font-weight-medium); - padding: 0.475em 1em 0.6em; - text-decoration: none; - &:focus, - &:hover { - border: 1px solid rgba(var(--aa-primary-color-rgb), 1); - color: rgba(var(--aa-primary-color-rgb), 1); - } - } -} - -// ---------------- -// 6. Hit Layout -// ---------------- -.aa-Item { - @apply px-4 py-4; - align-items: center; - border-radius: 3px; - cursor: pointer; - display: grid; - min-height: calc(var(--aa-spacing) * 2.5); - // When the result is active - &[aria-selected='true'] { - @apply bg-scale-400; - .aa-ItemActionButton, - .aa-ActiveOnly { - visibility: visible; - } - } - // The result type icon inlined SVG or image - @at-root .aa-ItemIcon { - align-items: center; - background: rgba(var(--aa-background-color-rgb), var(--aa-background-color-alpha)); - border-radius: 3px; - box-shadow: inset 0 0 0 1px - rgba(var(--aa-panel-border-color-rgb), var(--aa-panel-border-color-alpha)); - color: rgba(var(--aa-icon-color-rgb), var(--aa-icon-color-alpha)); - display: flex; - flex-shrink: 0; - font-size: 0.7em; - height: calc(var(--aa-icon-size) + var(--aa-spacing-half)); - justify-content: center; - overflow: hidden; - stroke-width: var(--aa-icon-stroke-width); - text-align: center; - width: calc(var(--aa-icon-size) + var(--aa-spacing-half)); - img { - height: auto; - max-height: calc(var(--aa-icon-size) + var(--aa-spacing-half) - 8px); - max-width: calc(var(--aa-icon-size) + var(--aa-spacing-half) - 8px); - width: auto; - } - svg { - height: var(--aa-icon-size); - width: var(--aa-icon-size); - } - @at-root .aa-ItemIcon--alignTop { - align-self: flex-start; - } - @at-root .aa-ItemIcon--noBorder { - background: none; - box-shadow: none; - } - @at-root .aa-ItemIcon--picture { - height: 96px; - width: 96px; - img { - max-height: 100%; - max-width: 100%; - padding: var(--aa-spacing-half); - } - } - } - @at-root .aa-ItemContent { - @apply flex flex-col space-y-1; - cursor: pointer; - line-height: 1.25em; - overflow: hidden; - &:empty { - display: none; - } - mark { - @apply text-brand-900; - background: none; - font-style: normal; - font-weight: var(--aa-font-weight-bold); - } - .aa-ItemTitle { - @apply text-scale-1200 text-sm; - } - @at-root .aa-ItemContent--dual { - display: flex; - flex-direction: column; - justify-content: space-between; - text-align: left; - .aa-ItemContentTitle, - .aa-ItemContentSubtitle { - display: block; - } - } - @at-root .aa-ItemContent--indented { - padding-left: calc(var(--aa-icon-size) + var(--aa-spacing)); - } - @at-root .aa-ItemContentBody { - display: grid; - gap: calc(var(--aa-spacing-half) / 2); - } - @at-root .aa-ItemContentTitle { - display: inline-block; - margin: 0 0.5em 0 0; - max-width: 100%; - overflow: hidden; - padding: 0; - text-overflow: ellipsis; - white-space: nowrap; - } - @at-root .aa-ItemContentSubtitle { - @apply text-scale-1000 text-sm truncate; - @at-root .aa-ItemContentSubtitleIcon { - &::before { - border-color: rgba(var(--aa-muted-color-rgb), 0.64); - border-style: solid; - content: ''; - display: inline-block; - left: 1px; - position: relative; - top: -3px; - } - } - @at-root .aa-ItemContentSubtitle--inline { - .aa-ItemContentSubtitleIcon { - &::before { - border-width: 0 0 1.5px; - margin-left: var(--aa-spacing-half); - margin-right: calc(var(--aa-spacing-half) / 2); - width: calc(var(--aa-spacing-half) + 2px); - } - } - } - @at-root .aa-ItemContentSubtitle--standalone { - align-items: center; - color: rgba(var(--aa-text-color-rgb), var(--aa-text-color-alpha)); - display: grid; - gap: var(--aa-spacing-half); - grid-auto-flow: column; - justify-content: start; - .aa-ItemContentSubtitleIcon { - &::before { - border-radius: 0 0 0 3px; - border-width: 0 0 1.5px 1.5px; - height: var(--aa-spacing-half); - width: var(--aa-spacing-half); - } - } - } - @at-root .aa-ItemContentSubtitleCategory { - color: rgba(var(--aa-muted-color-rgb), 1); - font-weight: 500; - } - } - @at-root .aa-ItemContentDescription { - color: rgba(var(--aa-text-color-rgb), var(--aa-text-color-alpha)); - font-size: 0.85em; - max-width: 100%; - overflow-x: hidden; - text-overflow: ellipsis; - &:empty { - display: none; - } - mark { - background: rgba( - var(--aa-description-highlight-background-color-rgb), - var(--aa-description-highlight-background-color-alpha) - ); - color: rgba(var(--aa-text-color-rgb), var(--aa-text-color-alpha)); - font-style: normal; - font-weight: var(--aa-font-weight-medium); - } - } - @at-root .aa-ItemContentDash { - color: rgba(var(--aa-muted-color-rgb), var(--aa-muted-color-alpha)); - display: none; - opacity: 0.4; - } - @at-root .aa-ItemContentTag { - background-color: rgba(var(--aa-primary-color-rgb), var(--aa-primary-color-alpha)); - border-radius: 3px; - margin: 0 0.4em 0 0; - padding: 0.08em 0.3em; - } - } - // wrap hit with url but we don't need to see it - @at-root .aa-ItemWrapper, - .aa-ItemLink { - align-items: center; - color: inherit; - gap: calc(var(--aa-spacing-half) / 2); - grid-auto-flow: column; - justify-content: space-between; - width: 100%; - } - @at-root .aa-ItemLink { - color: inherit; - text-decoration: none; - } - // Secondary click actions - @at-root .aa-ItemActions { - display: grid; - grid-auto-flow: column; - height: 100%; - justify-self: end; - margin: 0 calc(var(--aa-spacing) / -3); - padding: 0 2px 0 0; - } - @at-root .aa-ItemActionButton { - align-items: center; - background: none; - border: 0; - color: rgba(var(--aa-muted-color-rgb), var(--aa-muted-color-alpha)); - cursor: pointer; - display: flex; - flex-shrink: 0; - padding: 0; - &:hover, - &:focus { - svg { - color: rgba(var(--aa-text-color-rgb), var(--aa-text-color-alpha)); - @media (hover: none) and (pointer: coarse) { - color: inherit; - } - } - } - svg { - color: rgba(var(--aa-muted-color-rgb), var(--aa-muted-color-alpha)); - margin: 0; - margin: calc(var(--aa-spacing) / 3); - stroke-width: var(--aa-icon-stroke-width); - width: var(--aa-action-icon-size); - } - } - @at-root .aa-ActiveOnly { - visibility: hidden; - } -} - -//---------------- -// 7. Panel Header -//---------------- -.aa-PanelHeader { - align-items: center; - background: rgba(var(--aa-primary-color-rgb), 1); - color: #fff; - display: grid; - height: var(--aa-modal-header-height); - margin: 0; - padding: var(--aa-spacing-half) var(--aa-spacing); - position: relative; - - &::after { - background-image: linear-gradient( - rgba(var(--aa-background-color-rgb), 1), - rgba(var(--aa-background-color-rgb), 0) - ); - bottom: calc(var(--aa-spacing-half) * -1); - content: ''; - height: var(--aa-spacing-half); - left: 0; - pointer-events: none; - position: absolute; - right: 0; - z-index: var(--aa-base-z-index); - } -} - -//---------------- -// 8. Panel Footer -//---------------- -.aa-PanelFooter { - background-color: rgba(var(--aa-background-color-rgb), var(--aa-background-color-alpha)); - box-shadow: inset 0 1px 0 - rgba(var(--aa-panel-border-color-rgb), var(--aa-panel-border-color-alpha)); - display: flex; - justify-content: space-between; - margin: 0; - padding: var(--aa-spacing); - position: relative; - z-index: var(--aa-base-z-index); - &::after { - background-image: linear-gradient( - rgba(var(--aa-background-color-rgb), 0), - rgba(var(--aa-muted-color-rgb), var(--aa-muted-color-alpha)) - ); - content: ''; - height: var(--aa-spacing); - left: 0; - opacity: 0.12; - pointer-events: none; - position: absolute; - right: 0; - top: calc(var(--aa-spacing) * -1); - z-index: calc(var(--aa-base-z-index) - 1); - } -} - -//---------------- -// 9. Detached Mode -//---------------- -.aa-DetachedContainer { - @apply bg-scale-200 border border-scale-500; - bottom: 0; - box-shadow: var(--aa-panel-shadow); - display: flex; - flex-direction: column; - left: 0; - margin: 0; - overflow: hidden; - padding: 0; - position: fixed; - right: 0; - top: 0; - z-index: var(--aa-base-z-index); - &::after { - height: 32px; - } - .aa-SourceHeader { - margin: var(--aa-spacing-half) 0 var(--aa-spacing-half) 2px; - } - .aa-Panel { - @apply bg-scale-200; - border-radius: 0; - box-shadow: none; - flex-grow: 1; - margin: 0; - padding: 0; - position: relative; - } - .aa-PanelLayout { - bottom: 0; - box-shadow: none; - left: 0; - margin: 0; - max-height: none; - overflow-y: auto; - position: absolute; - right: 0; - top: 0; - width: 100%; - } - @at-root .aa-DetachedFormContainer { - @apply bg-scale-200; - border-bottom: solid 1px - rgba(var(--aa-panel-border-color-rgb), var(--aa-panel-border-color-alpha)); - display: flex; - flex-direction: row; - justify-content: space-between; - margin: 0; - padding: var(--aa-spacing-half); - @at-root .aa-DetachedCancelButton { - @apply text-scale-1100 text-sm; - background: none; - border: 0; - border-radius: 3px; - cursor: pointer; - margin: 0 0 0 var(--aa-spacing-half); - padding: 0 var(--aa-spacing-half); - &:hover, - &:focus { - box-shadow: inset 0 0 0 1px - rgba(var(--aa-panel-border-color-rgb), var(--aa-panel-border-color-alpha)); - } - } - } - @at-root .aa-DetachedContainer--modal { - border-radius: 6px; - bottom: inherit; - height: auto; - margin: 0 auto; - max-width: var(--aa-detached-modal-max-width); - position: absolute; - top: 3%; - .aa-PanelLayout { - max-height: var(--aa-detached-modal-max-height); - padding-bottom: var(--aa-spacing-half); - position: static; - } - } -} -// Search Button -.aa-DetachedSearchButton { - @apply rounded-md bg-scaleA-200 border border-scale-700; - align-items: center; - color: rgba(var(--aa-muted-color-rgb), var(--aa-muted-color-alpha)); - cursor: pointer; - display: flex; - font: inherit; - font-family: var(--aa-font-family); - font-size: var(--aa-font-size); - height: var(--aa-search-input-height); - margin: 0; - padding: 0 calc(var(--aa-search-input-height) / 8); - position: relative; - text-align: left; - width: 100%; - &:focus { - @apply border-brand-900; - outline: currentColor none medium; - } - @at-root .aa-DetachedSearchButtonIcon { - @apply text-scale-1100; - align-items: center; - cursor: initial; - display: flex; - height: 100%; - justify-content: center; - width: calc(var(--aa-icon-size) + var(--aa-spacing)); - } - .aa-DetachedSearchButtonPlaceholder { - @apply text-sm text-scale-800; - } -} - -// Remove scroll on `body` -.aa-Detached { - height: 100vh; - overflow: hidden; -} - -.aa-DetachedOverlay { - @apply backdrop-blur-sm; - background-color: rgba(0, 0, 0, 0.4); - height: 100vh; - left: 0; - margin: 0; - padding: 0; - position: fixed; - right: 0; - top: 0; - z-index: calc(var(--aa-base-z-index) - 1); -} - -//---------------- -// 10. Gradients -//---------------- -.aa-GradientTop, -.aa-GradientBottom { - height: var(--aa-spacing-half); - left: 0; - pointer-events: none; - position: absolute; - right: 0; - z-index: var(--aa-base-z-index); -} - -.aa-GradientTop { - // background-image: linear-gradient( - // rgba(var(--aa-background-color-rgb), 1), - // rgba(var(--aa-background-color-rgb), 0) - // ); - top: 0; -} - -.aa-GradientBottom { - // background-image: linear-gradient( - // rgba(var(--aa-background-color-rgb), 0), - // rgba(var(--aa-background-color-rgb), 1) - // ); - // border-bottom-left-radius: calc(var(--aa-spacing) / 4); - // border-bottom-right-radius: calc(var(--aa-spacing) / 4); - bottom: 0; -} - -//---------------- -// 11. Utilities -//---------------- -.aa-DesktopOnly { - @media (hover: none) and (pointer: coarse) { - display: none; - } -} - -.aa-TouchOnly { - @media (hover: hover) { - display: none; - } -} - -// search bar -input[type='search'].DocSearch-Input { - background-color: var(--colors-scale1); - box-shadow: none; -} - -.dark .DocSearch-Input { - background-color: var(--colors-scale2); - color: white; -} diff --git a/apps/docs/styles/docsearch.scss b/apps/docs/styles/docsearch.scss deleted file mode 100644 index 569965efcd9..00000000000 --- a/apps/docs/styles/docsearch.scss +++ /dev/null @@ -1,535 +0,0 @@ -/* search modal */ - -.DocSearch--active { - @apply overflow-hidden #{!important}; -} - -.DocSearch-Container { - background: rgba(0, 0, 0, 0.3); - @apply fixed top-0 left-0 z-[200] flex h-screen w-screen flex-col p-4 backdrop-blur-sm sm:p-6 md:p-[10vh] lg:p-[12vh]; - @apply cursor-auto #{!important}; -} - -.DocSearch-LoadingIndicator svg { - @apply hidden; -} - -.DocSearch-LoadingIndicator { - display: none; - width: 1.5rem; - height: 1.5rem; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'%3E%3Ccircle cx='12' cy='12' r='9' stroke-width='2' stroke='%23cffafe' /%3E%3Cpath d='M3,12a9,9 0 1,0 18,0a9,9 0 1,0 -18,0' stroke-width='2' stroke='%2306b6d4' stroke-dasharray='56.5486677646' stroke-dashoffset='37.6991118431' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - background-size: 100% 100%; -} - -.DocSearch-Container--Stalled .DocSearch-LoadingIndicator { - @apply block; -} - -.DocSearch-Modal { - @apply dark:bg-scale-200 bg-scale-100; - margin: 0 auto; - width: 100%; - max-width: 47.375rem; - display: flex; - flex-direction: column; - min-height: 0; - border-radius: theme('borderRadius.lg'); - box-shadow: theme('boxShadow.lg'); -} - -.DocSearch-SearchBar { - z-index: 1; - padding: 0 1rem; - @apply border-scale-400 relative flex flex-none items-center border-b; -} - -.DocSearch-Form { - @apply flex min-w-0 flex-auto items-center; -} - -.DocSearch-Dropdown-Container { - @apply pb-6; -} - -.DocSearch-Dropdown { - @apply flex-auto overflow-auto; -} - -.DocSearch-Hit--Result { - @apply relative mx-6; -} - -.DocSearch-Hit--Result.DocSearch-Hit--Child { - @apply ml-12; -} - -.DocSearch-Hit--Result.DocSearch-Hit--Child::before { - content: ''; - position: absolute; - top: -0.25rem; - bottom: -0.25rem; - left: -1rem; - width: 1px; - background: theme('colors.scale.400'); - - // @apply dark:bg-slate-700; -} - -.DocSearch-Hit > a { - position: relative; - font-size: 0.875rem; - // border-bottom: 1px solid theme('colors.slate.100'); - - // @apply dark:border-slate-200; -} - -.DocSearch-Hit--Result { - // @apply border-b-0 #{!important}; -} - -.DocSearch-MagnifierLabel { - @apply h-6 w-6 flex-none; - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m19 19-3.5-3.5' stroke='%23475569' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Ccircle cx='11' cy='11' r='6' stroke='%23475569' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - - .dark & { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m19 19-3.5-3.5' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Ccircle cx='11' cy='11' r='6' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - } -} - -.DocSearch-MagnifierLabel svg { - display: none; -} - -.DocSearch-Container--Stalled .DocSearch-MagnifierLabel { - display: none; -} - -.DocSearch-Input { - @apply text-scale-1200 placeholder-scale-900; - appearance: none; - background: transparent; - height: 3.5rem; - font-size: 1rem; - margin-left: 0.75rem; - margin-right: 1rem; - flex: auto; - min-width: 0; - border: none; -} - -@screen sm { - .DocSearch-Input { - font-size: 0.875rem; - } -} - -.DocSearch-Input:focus { - outline: 2px dotted transparent; -} - -.DocSearch-Input::-webkit-search-cancel-button, -.DocSearch-Input::-webkit-search-decoration, -.DocSearch-Input::-webkit-search-results-button, -.DocSearch-Input::-webkit-search-results-decoration { - display: none; -} - -.DocSearch-Reset { - display: none; -} - -button[type='reset'].DocSearch-Cancel { - appearance: none; - flex: none; - font-size: 0; - border-radius: 0.375rem; - padding: 0.25rem 0.375rem; - @apply ring-scale-900 hover:ring-scale-900 dark:bg-scale-600 shadow-sm ring-1 hover:shadow dark:ring-0; - width: 1.75rem; - height: 1.5rem; - /* esc */ - background-image: url("data:image/svg+xml,%3Csvg width='16' height='7' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M.506 6h3.931V4.986H1.736v-1.39h2.488V2.583H1.736V1.196h2.69V.182H.506V6ZM8.56 1.855h1.18C9.721.818 8.87.102 7.574.102c-1.276 0-2.21.705-2.205 1.762-.003.858.602 1.35 1.585 1.585l.634.159c.633.153.986.335.988.727-.002.426-.406.716-1.03.716-.64 0-1.1-.295-1.14-.878h-1.19c.03 1.259.931 1.91 2.343 1.91 1.42 0 2.256-.68 2.259-1.745-.003-.969-.733-1.483-1.744-1.71l-.523-.125c-.506-.117-.93-.304-.92-.722 0-.375.332-.65.934-.65.588 0 .949.267.994.724ZM15.78 2.219C15.618.875 14.6.102 13.254.102c-1.537 0-2.71 1.086-2.71 2.989 0 1.898 1.153 2.989 2.71 2.989 1.492 0 2.392-.992 2.526-2.063l-1.244-.006c-.117.623-.606.98-1.262.98-.883 0-1.483-.656-1.483-1.9 0-1.21.591-1.9 1.492-1.9.673 0 1.159.389 1.253 1.028h1.244Z' fill='%23334155'/%3E%3C/svg%3E"); - background-position: center; - background-repeat: no-repeat; - background-size: 57.1428571429% auto; - - .dark & { - background-image: url("data:image/svg+xml,%3Csvg width='16' height='7' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M.506 6h3.931V4.986H1.736v-1.39h2.488V2.583H1.736V1.196h2.69V.182H.506V6ZM8.56 1.855h1.18C9.721.818 8.87.102 7.574.102c-1.276 0-2.21.705-2.205 1.762-.003.858.602 1.35 1.585 1.585l.634.159c.633.153.986.335.988.727-.002.426-.406.716-1.03.716-.64 0-1.1-.295-1.14-.878h-1.19c.03 1.259.931 1.91 2.343 1.91 1.42 0 2.256-.68 2.259-1.745-.003-.969-.733-1.483-1.744-1.71l-.523-.125c-.506-.117-.93-.304-.92-.722 0-.375.332-.65.934-.65.588 0 .949.267.994.724ZM15.78 2.219C15.618.875 14.6.102 13.254.102c-1.537 0-2.71 1.086-2.71 2.989 0 1.898 1.153 2.989 2.71 2.989 1.492 0 2.392-.992 2.526-2.063l-1.244-.006c-.117.623-.606.98-1.262.98-.883 0-1.483-.656-1.483-1.9 0-1.21.591-1.9 1.492-1.9.673 0 1.159.389 1.253 1.028h1.244Z' fill='%2394a3b8'/%3E%3C/svg%3E"); - } -} - -.DocSearch-Reset svg { - display: none; -} - -.DocSearch-Hit-source { - line-height: 1.5rem; - font-weight: 400; - color: theme('colors.scale.900'); - padding-top: 2.5rem; - margin: 0 1.5rem 1rem; -} - -.DocSearch-Hits:first-child .DocSearch-Hit-source { - padding-top: 1.5rem; -} - -.DocSearch-Hit-Container { - display: flex; - align-items: center; -} - -.DocSearch-Hit-Tree { - display: none; -} - -.DocSearch-Hit-icon { - display: none; -} - -.DocSearch-Hit--Result .DocSearch-Hit-icon { - display: block; - flex: none; - margin-right: 1rem; - width: 1.5rem; - height: 1.5rem; - border-radius: theme('borderRadius.md'); - /* hash icon */ - background-image: url("data:image/svg+xml,%3Csvg width='12' height='12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3.75 1v10M8.25 1v10M1 3.75h10M1 8.25h10' stroke='%2394A3B8' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: center center; - - @apply ring-scale-300 bg-white shadow-sm ring-1 dark:bg-slate-700 dark:ring-0; -} - -.DocSearch-Hit--Parent.DocSearch-Hit--Guide .DocSearch-Hit-icon { - /* book icon */ - background-image: url("data:image/svg+xml,%3Csvg width='14' height='10' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M3.5 0c1.093 0 2.117.27 3 .743V10a6.344 6.344 0 0 0-3-.743c-1.093 0-2.617.27-3.5.743V.743C.883.27 2.407 0 3.5 0Z' fill='%23BAE6FD'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.5 0c1.093 0 2.617.27 3.5.743V10c-.883-.473-2.407-.743-3.5-.743s-2.117.27-3 .743V.743a6.344 6.344 0 0 1 3-.743Z' fill='%2338BDF8'/%3E%3C/svg%3E"); -} - -.DocSearch-Hit-icon svg { - display: none; -} - -.DocSearch-Hit-content-wrapper { - flex: auto; - display: flex; - flex-direction: column-reverse; - min-width: 0; - z-index: 1; -} - -.DocSearch-Hit-path { - align-self: flex-start; - font-size: 0.75rem; - line-height: 1.5rem; - font-weight: 400; - color: theme('colors.scale.1100'); - border-radius: 999px; - padding: 0 0.75rem; - @apply bg-scale-300 dark:bg-scale-500; -} - -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-icon { - // @apply bg-transparent shadow-none ring-1 ring-white/30 dark:ring-white/20; - // background-image: url("data:image/svg+xml,%3Csvg width='12' height='12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3.75 1v10M8.25 1v10M1 3.75h10M1 8.25h10' stroke='%23fff' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E"); -} - -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit--Parent .DocSearch-Hit-icon { - /* book icon */ - // background-image: url("data:image/svg+xml,%3Csvg width='14' height='10' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M3.5 0c1.093 0 2.117.27 3 .743V10a6.344 6.344 0 0 0-3-.743c-1.093 0-2.617.27-3.5.743V.743C.883.27 2.407 0 3.5 0Z' fill='%23fff'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.5 0c1.093 0 2.617.27 3.5.743V10c-.883-.473-2.407-.743-3.5-.743s-2.117.27-3 .743V.743a6.344 6.344 0 0 1 3-.743Z' fill='%23fff'/%3E%3C/svg%3E"); -} - -.DocSearch-Hit-title { - @apply text-scale-1100 dark:text-scale-1100 truncate leading-6; -} - -.DocSearch-Hit-title + .DocSearch-Hit-path { - @apply mb-1; -} - -.DocSearch-Hit-action { - @apply ml-3.5 flex-none; -} - -.DocSearch-Hit--Result { - .DocSearch-Hit-action svg { - @apply hidden; - } - - .DocSearch-Hit-action { - // @apply h-6 w-6; - // background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m11 9 3 3-3 3' stroke='%23475569' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - } - - [aria-selected='true'] & .DocSearch-Hit-action { - // background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m11 9 3 3-3 3' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - } - - .DocSearch-Hit[aria-selected='true'] & .DocSearch-Hit-path { - // @apply bg-scale-400 dark:bg-scale-400; - } - - .DocSearch-Hit[aria-selected='true'] & .DocSearch-Hit-title { - // @apply text-white; - } -} - -.DocSearch-Hit-action-button { - @apply flex; -} - -.DocSearch-Hit-action + .DocSearch-Hit-action { - @apply ml-2; -} - -.DocSearch-Hit-action path { - stroke-width: 2px; - stroke: #71717a; -} - -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-action path { - stroke: #777; - - .dark & { - stroke: #aaa; - } -} - -.DocSearch-Hit > a { - @apply block py-4 px-6; -} - -.DocSearch-Hit--Result { - @apply py-3 px-4 #{!important}; -} - -.DocSearch-Hit { - @apply relative; -} - -.DocSearch-Hit:first-child > a { - // @apply border-t border-slate-100 dark:border-slate-200; -} - -.DocSearch-Hit--Result { - // @apply border-t-0 #{!important}; -} - -.DocSearch-Hit + .DocSearch-Hit .DocSearch-Hit--Result { - @apply mt-2; -} - -.DocSearch-Hit--Result { - @apply dark:bg-scale-300 border-scale-400 rounded-lg border bg-white transition-all; -} - -.DocSearch-Hit[aria-selected='true'] > a { - @apply bg-slate-50 dark:bg-slate-700; -} - -.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit--Result { - @apply bg-scale-200 dark:bg-scale-600; -} - -.DocSearch-Hit--FirstChild::before { - @apply top-0 #{!important}; -} - -.DocSearch-Hit--LastChild::before { - @apply bottom-0 #{!important}; -} - -.DocSearch-Hits mark { - background: none; - @apply text-scale-1200 border-scale-900 border-b-2 font-semibold; -} - -.DocSearch-Hit-path mark { - @apply border-0; -} - -.DocSearch-Hit[aria-selected='true'] mark { - @apply text-scale-1200; - border-bottom-color: currentColor; -} - -.DocSearch-Footer { - @apply flex flex-none justify-end border-t border-slate-100 py-4 px-6 dark:border-slate-200; -} - -.DocSearch-Commands { - @apply hidden; -} - -.DocSearch-Logo a { - @apply text-scale-900 flex items-center text-xs font-medium; -} - -.DocSearch-Logo svg { - @apply text-scale-900 ml-3 text-[#5468ff]; -} - -.DocSearch-Hit--deleting, -.DocSearch-Hit--favoriting { - opacity: 0; - transition: all 250ms linear; -} - -.DocSearch-NoResults .DocSearch-Screen-Icon { - @apply hidden; -} - -.DocSearch-Title { - @apply mb-10 text-lg leading-6; -} - -.DocSearch-Title strong { - @apply text-slate-1200 dark:text-scale-1200 font-normal; -} - -.DocSearch-NoResults-Prefill-List { - .DocSearch-Help { - color: var(--colors-scale11) !important; - } -} - -.DocSearch-StartScreen { - .DocSearch-Help { - @apply text-center py-16; - } -} - -.DocSearch-Help { - @apply text-scale-1100; - font-size: 0.875rem !important; - a { - color: var(--colors-brand9) !important; - font-weight: 400 !important; - } -} - -.DocSearch-NoResults { - @apply px-4 pt-10 pb-8; -} - -.DocSearch-NoResults .DocSearch-Title { - text-align: center; - color: theme('colors.scale.1100'); -} - -.DocSearch-NoResults-Prefill-List .DocSearch-Help { - @apply text-scale-900 dark:text-scale-200 mb-3 text-sm leading-6; -} - -.DocSearch-NoResults-Prefill-List ul { - @apply border-scale-300 dark:border-scale-400 dark:bg-scale-300 overflow-hidden rounded-lg border bg-white; -} - -.DocSearch-NoResults-Prefill-List button { - padding: 0.5rem 0.75rem; - display: block; - width: 100%; - text-align: left; - font-size: theme('fontSize.sm'); - line-height: theme('lineHeight.6'); - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m11 9 3 3-3 3' stroke='%23475569' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 0.75rem center; - - @apply text-scale-900 hover:text-slate-1200 dark:bg-scale-300 hover:bg-scale-200 dark:hover:bg-scale-400 bg-white; -} - -.DocSearch-NoResults-Prefill-List li + li button { - @apply border-scale-300 border-t dark:border-slate-400; -} - -.DocSearch-NoResults-Prefill-List + .DocSearch-Help { - @apply mt-3 text-xs leading-6; -} - -.DocSearch-NoResults-Prefill-List + .DocSearch-Help a { - @apply text-scale-900 dark:text-scale-900 font-semibold; -} - -.DocSearch-Hit-action [data-cy] { - width: 1.5rem; - height: 1.5rem; -} - -.DocSearch-Hit-action [data-cy] svg { - display: none; -} - -.DocSearch-Hit-action [data-cy='fav-recent'] { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 5 2 5h5l-4 4 2 5-5-3-5 3 2-5-4-4h5l2-5Z' stroke='%23CBD5E1' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - - .dark & { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 5 2 5h5l-4 4 2 5-5-3-5 3 2-5-4-4h5l2-5Z' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - } -} - -.DocSearch-Hit-action [data-cy='fav-recent']:hover { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 5 2 5h5l-4 4 2 5-5-3-5 3 2-5-4-4h5l2-5Z' stroke='%230ea5e9' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); -} - -.DocSearch-Hit-action [data-cy='remove-recent'] { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17 7 7 17M7 7l10 10' stroke='%2394A3B8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - - .dark & { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17 7 7 17M7 7l10 10' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - } -} - -.DocSearch-Hit-action [data-cy='remove-recent']:hover { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17 7 7 17M7 7l10 10' stroke='%23475569' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - - .dark & { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17 7 7 17M7 7l10 10' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - } -} - -.DocSearch-Hit-action [data-cy='remove-fav']::before { - @apply pointer-events-none absolute inset-0 bg-sky-50 content-[''] dark:bg-slate-700; -} - -.DocSearch-Hit[aria-selected='true'] [data-cy='remove-fav']::before { - @apply bg-sky-100 dark:bg-slate-700; -} - -.DocSearch-Hit-action [data-cy='remove-fav']::after { - content: ''; - position: absolute; - pointer-events: none; - width: calc(4.5rem + 1px); - height: 1.5rem; - margin-left: calc(-3rem - 1px); - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 5 2 5h5l-4 4 2 5-5-3-5 3 2-5-4-4h5l2-5Z' fill='%230EA5E9' stroke='%230EA5E9' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"), - url("data:image/svg+xml,%3Csvg width='1' height='1' fill='%23e2e8f0' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='1' height='1'/%3E%3C/svg%3E"), - url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17 7 7 17M7 7l10 10' stroke='%2394A3B8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - background-repeat: no-repeat, repeat-y, no-repeat; - background-position: left, center, right; - - .dark & { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 5 2 5h5l-4 4 2 5-5-3-5 3 2-5-4-4h5l2-5Z' fill='%230EA5E9' stroke='%230EA5E9' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"), - url("data:image/svg+xml,%3Csvg width='1' height='1' fill='%23e2e8f0' opacity='0.05' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='1' height='1'/%3E%3C/svg%3E"), - url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17 7 7 17M7 7l10 10' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - } -} - -.DocSearch-Hit-action [data-cy='remove-fav']:hover::after { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 5 2 5h5l-4 4 2 5-5-3-5 3 2-5-4-4h5l2-5Z' fill='%230EA5E9' stroke='%230EA5E9' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"), - url("data:image/svg+xml,%3Csvg width='1' height='1' fill='%23e2e8f0' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='1' height='1'/%3E%3C/svg%3E"), - url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17 7 7 17M7 7l10 10' stroke='%23475569' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - - .dark & { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 5 2 5h5l-4 4 2 5-5-3-5 3 2-5-4-4h5l2-5Z' fill='%230EA5E9' stroke='%230EA5E9' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"), - url("data:image/svg+xml,%3Csvg width='1' height='1' fill='%23e2e8f0' opacity='0.05' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='1' height='1'/%3E%3C/svg%3E"), - url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17 7 7 17M7 7l10 10' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - } -} - -.DocSearch-Hit-action + .DocSearch-Hit-action { - // @apply ml-3 border-l border-slate-200 pl-3 dark:border-slate-200; -} diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json index ce8de4c02e1..c87a0e51e07 100644 --- a/apps/docs/tsconfig.json +++ b/apps/docs/tsconfig.json @@ -6,7 +6,7 @@ "paths": { "~/*": ["./*"] }, - "target": "es5", + "target": "es2015", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, diff --git a/apps/temp-community-forum/.eslintrc.js b/apps/temp-community-forum/.eslintrc.js deleted file mode 100644 index e0b422e408e..00000000000 --- a/apps/temp-community-forum/.eslintrc.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('config/eslint-preset') diff --git a/apps/temp-community-forum/next-env.d.ts b/apps/temp-community-forum/next-env.d.ts deleted file mode 100644 index 4f11a03dc6c..00000000000 --- a/apps/temp-community-forum/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/temp-community-forum/next.config.js b/apps/temp-community-forum/next.config.js deleted file mode 100644 index 6e4744fdc65..00000000000 --- a/apps/temp-community-forum/next.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const withTM = require('next-transpile-modules')(['common']) - -module.exports = withTM({ - reactStrictMode: true, -}) diff --git a/apps/temp-community-forum/package.json b/apps/temp-community-forum/package.json deleted file mode 100644 index c5984bf7e45..00000000000 --- a/apps/temp-community-forum/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "forum", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "next dev --port 3002", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@octokit/graphql": "^4.8.0", - "colorette": "^1.4.0", - "common": "*", - "next": "12.0.8", - "react": "17.0.2", - "react-dom": "17.0.2", - "string-hash": "^1.1.3" - }, - "devDependencies": { - "@types/node": "^17.0.12", - "@types/react": "17.0.37", - "config": "*", - "eslint": "7.32.0", - "mini-svg-data-uri": "^1.4.4", - "next-transpile-modules": "9.0.0", - "tsconfig": "*", - "typescript": "^4.5.3" - } -} diff --git a/apps/temp-community-forum/postcss.config.js b/apps/temp-community-forum/postcss.config.js deleted file mode 100644 index 29c447cb54b..00000000000 --- a/apps/temp-community-forum/postcss.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('config/postcss.config') diff --git a/apps/temp-community-forum/src/pages/_app.tsx b/apps/temp-community-forum/src/pages/_app.tsx deleted file mode 100644 index 2d0cd5807e9..00000000000 --- a/apps/temp-community-forum/src/pages/_app.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' -import Main from '.' -import '../styles/tailwind.css' - -const Index = () => { - return
      -} - -export default Index diff --git a/apps/temp-community-forum/src/pages/index.tsx b/apps/temp-community-forum/src/pages/index.tsx deleted file mode 100644 index c7381379165..00000000000 --- a/apps/temp-community-forum/src/pages/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { fetchDiscussions } from '../utils/api' -import { Button, Layouts } from 'common' -import { useEffect, useState } from 'react' - -export default function Main() { - const [discussionList, setDiscussionList] = useState([] as any[]) - - useEffect(() => { - async function fetchData() { - const discussions = await fetchDiscussions() - setDiscussionList(discussions) - } - fetchData() - }, []) - - return ( - -
      Categories
      -
      One
      -
      Two
      -
      - } - > -

      Discussions

      - {discussionList.map((x) => ( -
      -

      {x.title}

      -

      {x.body}

      -
      - ))} - - ) -} diff --git a/apps/temp-community-forum/src/styles/tailwind.css b/apps/temp-community-forum/src/styles/tailwind.css deleted file mode 100644 index b5c61c95671..00000000000 --- a/apps/temp-community-forum/src/styles/tailwind.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/apps/temp-community-forum/src/utils/api.ts b/apps/temp-community-forum/src/utils/api.ts deleted file mode 100644 index dfdb13906fb..00000000000 --- a/apps/temp-community-forum/src/utils/api.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { graphql } from '@octokit/graphql' - -const query = ` -{ - repository(owner: "supabase", name: "supabase") { - discussions(first: 10) { - nodes { - id - author { - avatarUrl - url - login - } - body - title - answer { - author { - avatarUrl - login - } - body - createdAt - } - createdAt - } - totalCount - pageInfo { - startCursor - endCursor - hasNextPage - hasPreviousPage - } - } - } -} -` -export async function fetchDiscussions() { - // @ts-ignore - const { repository } = await graphql(query, { - headers: { - authorization: `token ${process.env.NEXT_PUBLIC_DISCUSSIONS_TOKEN}`, - }, - }) - return repository.discussions.nodes -} diff --git a/apps/temp-community-forum/tailwind.config.js b/apps/temp-community-forum/tailwind.config.js deleted file mode 100644 index 9c2af498997..00000000000 --- a/apps/temp-community-forum/tailwind.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('config/tailwind.config') diff --git a/apps/temp-community-forum/tsconfig.json b/apps/temp-community-forum/tsconfig.json deleted file mode 100644 index a355365ba65..00000000000 --- a/apps/temp-community-forum/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "tsconfig/nextjs.json", - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] -} diff --git a/apps/temp-community-tutorials/.eslintrc.js b/apps/temp-community-tutorials/.eslintrc.js deleted file mode 100644 index e0b422e408e..00000000000 --- a/apps/temp-community-tutorials/.eslintrc.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('config/eslint-preset') diff --git a/apps/temp-community-tutorials/README.md b/apps/temp-community-tutorials/README.md deleted file mode 100644 index 1d67ece731b..00000000000 --- a/apps/temp-community-tutorials/README.md +++ /dev/null @@ -1,30 +0,0 @@ -## Getting Started - -First, run the development server: - -```bash -yarn dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. - -[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. - -The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/apps/temp-community-tutorials/next-env.d.ts b/apps/temp-community-tutorials/next-env.d.ts deleted file mode 100644 index 4f11a03dc6c..00000000000 --- a/apps/temp-community-tutorials/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/temp-community-tutorials/next.config.js b/apps/temp-community-tutorials/next.config.js deleted file mode 100644 index 6e4744fdc65..00000000000 --- a/apps/temp-community-tutorials/next.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const withTM = require('next-transpile-modules')(['common']) - -module.exports = withTM({ - reactStrictMode: true, -}) diff --git a/apps/temp-community-tutorials/package.json b/apps/temp-community-tutorials/package.json deleted file mode 100644 index 473ca829340..00000000000 --- a/apps/temp-community-tutorials/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "tutorials", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "next dev --port 3003", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "colorette": "^1.4.0", - "common": "*", - "next": "12.0.8", - "react": "17.0.2", - "react-dom": "17.0.2" - }, - "devDependencies": { - "@types/node": "^17.0.12", - "@types/react": "17.0.37", - "config": "*", - "eslint": "7.32.0", - "next-transpile-modules": "9.0.0", - "tsconfig": "*", - "typescript": "^4.5.3" - } -} diff --git a/apps/temp-community-tutorials/postcss.config.js b/apps/temp-community-tutorials/postcss.config.js deleted file mode 100644 index 29c447cb54b..00000000000 --- a/apps/temp-community-tutorials/postcss.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('config/postcss.config') diff --git a/apps/temp-community-tutorials/src/pages/_app.tsx b/apps/temp-community-tutorials/src/pages/_app.tsx deleted file mode 100644 index 2d0cd5807e9..00000000000 --- a/apps/temp-community-tutorials/src/pages/_app.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' -import Main from '.' -import '../styles/tailwind.css' - -const Index = () => { - return
      -} - -export default Index diff --git a/apps/temp-community-tutorials/src/pages/index.tsx b/apps/temp-community-tutorials/src/pages/index.tsx deleted file mode 100644 index 2e5771c0ae1..00000000000 --- a/apps/temp-community-tutorials/src/pages/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { fetchPosts, DevPost } from '../utils/api' -import { Button, Layouts } from 'common' -import { useEffect, useState } from 'react' - -export default function Main() { - const [postList, setPostList] = useState([] as DevPost[]) - - useEffect(() => { - async function fetchData() { - const posts = await fetchPosts() - setPostList(posts) - } - fetchData() - }, []) - - return ( - Shared Sidebar
      }> -

      Tutorials

      - {postList.map((x) => ( -
      -

      {x.title}

      -

      {x.description}

      -
      - ))} - - ) -} diff --git a/apps/temp-community-tutorials/src/styles/tailwind.css b/apps/temp-community-tutorials/src/styles/tailwind.css deleted file mode 100644 index b5c61c95671..00000000000 --- a/apps/temp-community-tutorials/src/styles/tailwind.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/apps/temp-community-tutorials/src/utils/api.ts b/apps/temp-community-tutorials/src/utils/api.ts deleted file mode 100644 index baa1b180f00..00000000000 --- a/apps/temp-community-tutorials/src/utils/api.ts +++ /dev/null @@ -1,55 +0,0 @@ -const username = 'supabase' -const postsUrl = `https://dev.to/api/articles?username=${username}` - -// Defines a Post returned from the DEV API -export type DevPost = { - id: Number - title: string - description: string - slug: string - url: string - path: string - readable_publish_date: string - published_at: string - tags: string - tag_list: string[] - comments_count: Number - public_reactions_count: Number - positive_reactions_count: Number - published: boolean - first_published_at: string - edited_at: string - user: { - username: string - name: string - profile_image_90: string - cover_image_90: string - profile_image_original: string - cover_image_original: string - location: string - website: string - bio: string - twitter_username: string - github_username: string - dribbble_username: string - path: string - } - organization: { - id: Number - name: string - slug: string - profile_image_90: string - profile_imag: string - } -} - -export async function fetchPosts() { - try { - const res = await fetch(postsUrl) - const data: DevPost[] = await res.json() - return data - } catch (err) { - console.log(err) - return [] - } -} diff --git a/apps/temp-community-tutorials/tailwind.config.js b/apps/temp-community-tutorials/tailwind.config.js deleted file mode 100644 index 9c2af498997..00000000000 --- a/apps/temp-community-tutorials/tailwind.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('config/tailwind.config') diff --git a/apps/temp-community-tutorials/tsconfig.json b/apps/temp-community-tutorials/tsconfig.json deleted file mode 100644 index a355365ba65..00000000000 --- a/apps/temp-community-tutorials/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "tsconfig/nextjs.json", - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] -} diff --git a/apps/www/_blog/2020-05-01-supabase-alpha-april-2020.mdx b/apps/www/_blog/2020-05-01-supabase-alpha-april-2020.mdx index 4081fb5f644..964cfe71934 100644 --- a/apps/www/_blog/2020-05-01-supabase-alpha-april-2020.mdx +++ b/apps/www/_blog/2020-05-01-supabase-alpha-april-2020.mdx @@ -18,15 +18,15 @@ Now in [Supabase](https://app.supabase.com): - Query your database directly from the dashboard - Analyze your queries and make them faster :rocket: - +
      + +
      ## Important Notes diff --git a/apps/www/_blog/2020-06-01-supabase-alpha-may-2020.mdx b/apps/www/_blog/2020-06-01-supabase-alpha-may-2020.mdx index afacbae0458..fc9d49b9ba6 100644 --- a/apps/www/_blog/2020-06-01-supabase-alpha-may-2020.mdx +++ b/apps/www/_blog/2020-06-01-supabase-alpha-may-2020.mdx @@ -14,15 +14,15 @@ video: https://www.youtube.com/v/e4qXmcEFaUs It has been a monster month at [Supabase](/) and we're back with a video update. It's a longer video this month because we have a couple of housekeeping items. - +
      + +
      ## Housekeeping diff --git a/apps/www/_blog/2020-07-01-supabase-alpha-june-2020.mdx b/apps/www/_blog/2020-07-01-supabase-alpha-june-2020.mdx index f84f144d0ea..4baf3f31a58 100644 --- a/apps/www/_blog/2020-07-01-supabase-alpha-june-2020.mdx +++ b/apps/www/_blog/2020-07-01-supabase-alpha-june-2020.mdx @@ -17,66 +17,66 @@ We're now 4 months into building [Supabase](/), which means another major update Watch a full demo: - +
      + +
      ### View relational data We're sometimes asked how we will make Postgres as simple as Firebase, since Postgres is a relational database. This month we're making our first steps to prove that relational databases can be even easier to use than document stores. We're releasing an excel-like editing interface which can drill down into your relational data. - +
      + +
      ### Manage JSON data Postgres is an amazing database, giving the flexibility of a document store with the power of a RDBMS. If you use `JSON` data in Postgres, then we want to make that easy too. Supabase detects when your column is `JSON` or `JSONB`, and provides an easy way to edit and view your data. More improvements coming soon for this feature! - +
      + +
      ### Choose your region If you noticed a bit of latency on Supabase, it's because your projects were previously set up in Singapore. It was always our intention that you'd be able to choose your database region, and this month we've delivered it. In the next releases we'll even allow you to go multi-region, instantly replicating your database close to your customers. - +
      + +
      ### Backups A guiding principle at Supabase is zero lock-in. So this month we are exposing your daily database backups on the dashboard, giving you a simple way to migrate off Supabase. We have a lot more to build in this space (`WAL-G!`), so watch this space. - +
      + +
      ### Kaizen diff --git a/apps/www/_blog/2020-08-02-supabase-alpha-july-2020.mdx b/apps/www/_blog/2020-08-02-supabase-alpha-july-2020.mdx index 6d5847735c1..e93c38d00f3 100644 --- a/apps/www/_blog/2020-08-02-supabase-alpha-july-2020.mdx +++ b/apps/www/_blog/2020-08-02-supabase-alpha-july-2020.mdx @@ -17,14 +17,14 @@ After 5 months of building, we're releasing one of our most anticipated features Watch a full demo: - +
      + +
      ### Auth diff --git a/apps/www/_blog/2020-09-03-supabase-alpha-august-2020.mdx b/apps/www/_blog/2020-09-03-supabase-alpha-august-2020.mdx index e20bf8a6a58..1a63a1a4bb9 100644 --- a/apps/www/_blog/2020-09-03-supabase-alpha-august-2020.mdx +++ b/apps/www/_blog/2020-09-03-supabase-alpha-august-2020.mdx @@ -17,14 +17,14 @@ We're 6 months into building our hosted database platform and we've made some ma Watch a full demo: - +
      + +
      ### Easily create tables diff --git a/apps/www/_blog/2020-10-03-supabase-alpha-september-2020.mdx b/apps/www/_blog/2020-10-03-supabase-alpha-september-2020.mdx index f30c537eb23..43a2b4bc41a 100644 --- a/apps/www/_blog/2020-10-03-supabase-alpha-september-2020.mdx +++ b/apps/www/_blog/2020-10-03-supabase-alpha-september-2020.mdx @@ -18,14 +18,14 @@ We're now 7 months into building Supabase. This update is a big one! Watch a full demo: - +
      + +
      ### Third-party logins diff --git a/apps/www/_blog/2020-11-02-supabase-alpha-october-2020.mdx b/apps/www/_blog/2020-11-02-supabase-alpha-october-2020.mdx index 9e4cc3dc193..158b81d22d5 100644 --- a/apps/www/_blog/2020-11-02-supabase-alpha-october-2020.mdx +++ b/apps/www/_blog/2020-11-02-supabase-alpha-october-2020.mdx @@ -19,15 +19,15 @@ We're now 8 months into building Supabase. We're focused on performance, stabili Watch a full demo: - +
      + +
      ### Supabase.js 1.0 diff --git a/apps/www/_blog/2020-12-01-supabase-alpha-november-2020.mdx b/apps/www/_blog/2020-12-01-supabase-alpha-november-2020.mdx index 7910f61bc42..a4bb7858a82 100644 --- a/apps/www/_blog/2020-12-01-supabase-alpha-november-2020.mdx +++ b/apps/www/_blog/2020-12-01-supabase-alpha-november-2020.mdx @@ -19,15 +19,15 @@ We've been building for 9 months now, and we're getting even closer to Beta. Watch a full demo: - +
      + +
      ### Add users diff --git a/apps/www/_blog/2020-12-02-case-study-monitoro.mdx b/apps/www/_blog/2020-12-02-case-study-monitoro.mdx index 4bfceaf4e42..0c628e8c394 100644 --- a/apps/www/_blog/2020-12-02-case-study-monitoro.mdx +++ b/apps/www/_blog/2020-12-02-case-study-monitoro.mdx @@ -46,12 +46,12 @@ Thanks to Supabase, Monitoro could deliver the features required needed faster t Learn more about Monitoro: - +
      + +
      diff --git a/apps/www/_blog/2021-01-02-supabase-beta-december-2020.mdx b/apps/www/_blog/2021-01-02-supabase-beta-december-2020.mdx index 91bcdeb34ee..223849745f9 100644 --- a/apps/www/_blog/2021-01-02-supabase-beta-december-2020.mdx +++ b/apps/www/_blog/2021-01-02-supabase-beta-december-2020.mdx @@ -20,15 +20,15 @@ After 10 hectic months of building, Supabase is now in Beta. Watch a full demo: - +
      + +
      ### Supabase is now in Beta diff --git a/apps/www/_blog/2021-02-02-supabase-beta-january-2021.mdx b/apps/www/_blog/2021-02-02-supabase-beta-january-2021.mdx index 10e86ed878e..976c96f08b6 100644 --- a/apps/www/_blog/2021-02-02-supabase-beta-january-2021.mdx +++ b/apps/www/_blog/2021-02-02-supabase-beta-january-2021.mdx @@ -20,15 +20,15 @@ New year, new features. We've been busy at Supabase during January and our commu Watch a full demo: - +
      + +
      ### Count functionality diff --git a/apps/www/_blog/2021-03-02-supabase-beta-february-2021.mdx b/apps/www/_blog/2021-03-02-supabase-beta-february-2021.mdx index 2a14b71e615..2fe5da5efef 100644 --- a/apps/www/_blog/2021-03-02-supabase-beta-february-2021.mdx +++ b/apps/www/_blog/2021-03-02-supabase-beta-february-2021.mdx @@ -19,15 +19,15 @@ Supabase is an open source Firebase alternative. We've now been building for one Watch a full demo: - +
      + +
      ### Dashboard Sidebars diff --git a/apps/www/_blog/2021-03-11-using-supabase-replit.mdx b/apps/www/_blog/2021-03-11-using-supabase-replit.mdx index e0856ab9753..ead6386295d 100644 --- a/apps/www/_blog/2021-03-11-using-supabase-replit.mdx +++ b/apps/www/_blog/2021-03-11-using-supabase-replit.mdx @@ -22,15 +22,15 @@ They support a ton of different languages and execution environments and even re As a Replit user, if you want to access larger amounts of data direct from your repl, or if you fancy accessing some super-powerful query tools, at some point you may want to start interacting with a relational database. Supabase is a good fit here; just like Replit, you don't need to worry about servers, and hosting, you can just click a few buttons and get a fully featured relational database which you can start communicating with directly from javacript, using supabase-js. - +
      + +
      Here's how to start a Supabase + Node.js repl: diff --git a/apps/www/_blog/2021-04-06-supabase-beta-march-2021.mdx b/apps/www/_blog/2021-04-06-supabase-beta-march-2021.mdx index ba28fa69618..b12b9071c0b 100644 --- a/apps/www/_blog/2021-04-06-supabase-beta-march-2021.mdx +++ b/apps/www/_blog/2021-04-06-supabase-beta-march-2021.mdx @@ -19,15 +19,15 @@ Launch week, Storage, Supabase CLI, Connection Pooling, Supabase UI, and Pricing Watch a full demo: - +
      + +
      ## Supabase Storage diff --git a/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx b/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx index fb40d53ea65..96bde96182e 100644 --- a/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx +++ b/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx @@ -20,15 +20,15 @@ Check out what we were working on below, as well as some incredible Community co Watch a full demo: - +
      + +
      ## Light Mode @@ -124,15 +124,15 @@ $$ language plv8; Fireship reviewed Supabase last week, and despite being a (self-proclaimed) Firebase fan-boy, the review was very impartial. - +
      + +
      ## Community diff --git a/apps/www/_blog/2021-06-02-supabase-beta-may-2021.mdx b/apps/www/_blog/2021-06-02-supabase-beta-may-2021.mdx index 6469fa46f1d..7106971fe2f 100644 --- a/apps/www/_blog/2021-06-02-supabase-beta-may-2021.mdx +++ b/apps/www/_blog/2021-06-02-supabase-beta-may-2021.mdx @@ -19,15 +19,15 @@ Apple & Twitter Logins, Supabase Grid, Go & Swift Libraries. Lots of great stuff Watch a full demo: - +
      + +
      ## Apple logins are now available @@ -88,15 +88,15 @@ We started a weekly 1-hour live stream where we build in public. - [Build in Public 001](https://www.youtube.com/watch?v=p561ogKZ63o): Building a "Next.js + Supabase" Tutorial - [Build in Public 002](https://twitter.com/p_phiri/status/1397815806990372865?s=20): `maybeSingle()` and "React + Supabase" Tutorial - +
      + +
      ## Community diff --git a/apps/www/_blog/2021-07-02-supabase-beta-june-2021.mdx b/apps/www/_blog/2021-07-02-supabase-beta-june-2021.mdx index d42f3c7bbb6..eb4413eab73 100644 --- a/apps/www/_blog/2021-07-02-supabase-beta-june-2021.mdx +++ b/apps/www/_blog/2021-07-02-supabase-beta-june-2021.mdx @@ -20,15 +20,15 @@ Supabase is gearing up for another Launch Week on July the 26th. Until then, her Watch a full demo of this month's releases - +
      + +
      ## Vercel integration @@ -113,15 +113,15 @@ We run a weekly 1-hour live stream where we build in public. - [Build in Public 006](https://youtu.be/LHYrqBb4q9I): Supabase Studio - [Build in Public 007](https://youtu.be/R4gJhX_JFTo): Supabase Studio - API Docs - +
      + +
      ## Community diff --git a/apps/www/_blog/2021-07-28-supabase-auth-passwordless-sms-login.mdx b/apps/www/_blog/2021-07-28-supabase-auth-passwordless-sms-login.mdx index 72e22de1d6a..10728c41e42 100644 --- a/apps/www/_blog/2021-07-28-supabase-auth-passwordless-sms-login.mdx +++ b/apps/www/_blog/2021-07-28-supabase-auth-passwordless-sms-login.mdx @@ -55,15 +55,15 @@ Simply plug your Twilio credentials into your Auth Settings in the Supabase Dash Check out the [documentation](/docs/guides/auth/phone-login/twilio) to get started with Mobile OTPs, or watch the [Youtube guide](https://youtu.be/akScoPO01bc). - +
      + +
      ### Multi-Factor Auth coming soon diff --git a/apps/www/_blog/2021-08-12-supabase-beta-july-2021.mdx b/apps/www/_blog/2021-08-12-supabase-beta-july-2021.mdx index c38bb4d977c..244b30497a8 100644 --- a/apps/www/_blog/2021-08-12-supabase-beta-july-2021.mdx +++ b/apps/www/_blog/2021-08-12-supabase-beta-july-2021.mdx @@ -20,15 +20,15 @@ July ended with our second "Launch Week", where we shipped something every day f Watch a full demo of this month's releases. - +
      + +
      ## Launch Week II: The SQL diff --git a/apps/www/_blog/2021-09-10-supabase-beta-august-2021.mdx b/apps/www/_blog/2021-09-10-supabase-beta-august-2021.mdx index 1bc892b92a2..545489aebc5 100644 --- a/apps/www/_blog/2021-09-10-supabase-beta-august-2021.mdx +++ b/apps/www/_blog/2021-09-10-supabase-beta-august-2021.mdx @@ -20,15 +20,15 @@ We've raised $30M and shipped a bunch of features. Let's dive into what's been h Watch a recap of this month's release. - +
      + +
      ## We raised $30 million @@ -90,15 +90,15 @@ We now provide a handy copy utility for various database connection strings beca RLS can be a bit foreign for developers getting started with Postgres. This video by [@\_dijonmusters](https://twitter.com/_dijonmusters) demystifies it. If you find the video a useful medium for learning, consider [subscribing to our channel](https://www.youtube.com/c/supabase). - +
      + +
      ## Community diff --git a/apps/www/_blog/2021-10-04-supabase-beta-sept-2021.mdx b/apps/www/_blog/2021-10-04-supabase-beta-sept-2021.mdx index 587aa3d2207..893881ea4c7 100644 --- a/apps/www/_blog/2021-10-04-supabase-beta-sept-2021.mdx +++ b/apps/www/_blog/2021-10-04-supabase-beta-sept-2021.mdx @@ -67,15 +67,15 @@ We've revamped the Auth docs - The docs are now broken down into [Authentication Low Code demo, Using Supabase with [Clutch.io](http://clutch.io/) - [@\_dijonmusters](https://twitter.com/_dijonmusters) ran a [session at General Assembly](https://www.youtube.com/watch?t=642&v=5fsKMTeBKKY) showing how to use these two tools together to create apps using a low code approach. - +
      + +
      ## Community diff --git a/apps/www/_blog/2021-11-05-supabase-beta-october-2021.mdx b/apps/www/_blog/2021-11-05-supabase-beta-october-2021.mdx index ca0dc8ca935..b6e75148a87 100644 --- a/apps/www/_blog/2021-11-05-supabase-beta-october-2021.mdx +++ b/apps/www/_blog/2021-11-05-supabase-beta-october-2021.mdx @@ -20,15 +20,15 @@ We released three new Auth providers, multi-schema support, and we're gearing up Watch a recap of this month's release. - +
      + +
      ## Slack, Spotify, and MessageBird logins @@ -63,43 +63,43 @@ We have a TON of new guides, with videos too. Learn about PostgreSQL Functions. [Docs](https://supabase.com/docs/guides/database/functions). - +
      + +
      ### Auth Overview Learn about all the exciting features of Auth within Supabase. [Docs](https://supabase.com/docs/guides/auth). - +
      + +
      ### API Features Learn more about the power of PostgREST for RESTful APIs. [Docs](https://supabase.com/docs/guides/database/api#restful-api). - +
      + +
      ### And more @@ -118,29 +118,29 @@ There was a lot of activity this month. Supabase attended the Jamstack conf. Watch us catch up with Matt, the cofounder of Netlify (minute 8). - +
      + +
      ### Supabase at Next.js conf And [Jon](https://twitter.com/_dijonmusters) made a guest appearance at this year's amazing [Next.js Conf](https://nextjs.org/conf). - +
      + +
      ### Community Highlights diff --git a/apps/www/_blog/2021-11-29-community-day-lw3.mdx b/apps/www/_blog/2021-11-29-community-day-lw3.mdx index 3bebaafceac..eda838497c6 100644 --- a/apps/www/_blog/2021-11-29-community-day-lw3.mdx +++ b/apps/www/_blog/2021-11-29-community-day-lw3.mdx @@ -27,15 +27,15 @@ Supabase provides a whole bunch of features that are made up of a [collection of As a user of our hosted offering you don't really need to know all this, but we love showcasing the amazing open-source tools that we work with, and so we've invited [Vik Gamov](https://twitter.com/gAmUssA) to give you a little intro to [Kong](https://github.com/Kong/kong): - +
      + +
      ## PostgreSQL 14 updates @@ -118,15 +118,15 @@ Anytime we scroll through [madewithsupabase.com](https://www.madewithsupabase.co So we reached out to some of our users, requesting a short video to learn more and we were overwhelmed with your gracious submissions. We've started a user stories playlist and we are so excited to see many more of these stories in the future. 💚 - +
      + +
      And with that, we officially declare Launch Week as open 🥳 Check back [here](/blog) every day this week to see what new things we are shipping. We can't wait to share them with you 🚀 diff --git a/apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx b/apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx index b8a3c3e6e40..cf849781aa6 100644 --- a/apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx +++ b/apps/www/_blog/2021-12-01-realtime-row-level-security-in-postgresql.mdx @@ -22,15 +22,15 @@ Today, we're announcing security improvements to Realtime, where database change ## Demo - +
      + +
      ## Overview diff --git a/apps/www/_blog/2021-12-02-supabase-acquires-logflare.mdx b/apps/www/_blog/2021-12-02-supabase-acquires-logflare.mdx index 4e3a9e61dea..41b88a7a033 100644 --- a/apps/www/_blog/2021-12-02-supabase-acquires-logflare.mdx +++ b/apps/www/_blog/2021-12-02-supabase-acquires-logflare.mdx @@ -106,15 +106,15 @@ In just the past 10 days, we've been working on a few of the new Dashboard featu Over the next few weeks, we plan to release these features (once we have a chance to polish them up), and you'll be able to query your logs with your favorite language - SQL 😀. - +
      + +
      ## Open source diff --git a/apps/www/_blog/2022-01-20-supabase-beta-december-2021.mdx b/apps/www/_blog/2022-01-20-supabase-beta-december-2021.mdx index 11cafb38ef9..39b36792c28 100644 --- a/apps/www/_blog/2022-01-20-supabase-beta-december-2021.mdx +++ b/apps/www/_blog/2022-01-20-supabase-beta-december-2021.mdx @@ -38,43 +38,43 @@ Enable it from the Database > Extensions tab in the [Supabase Dashboard](https:/ Jon Meyers walks you through how to write more complex queries and logic in your database, and invoke it via the API. - +
      + +
      ### Call any API using PostgreSQL functions We recently added the ability to call any external HTTP endpoint from your postgres functions. Jon shows you how! - +
      + +
      ### Create PostgreSQL Functions with Supabase Triggers allow you to execute SQL in response to any table changes. Learn how to write PostgreSQL triggers with Jon! - +
      + +
      ### Hiring @@ -90,15 +90,15 @@ There was a lot of activity this month. Let Jason and Jon show you how to build an app with Next.js and Supabase on top of Netlify! - +
      + +
      ### Divjoy integration diff --git a/apps/www/_blog/2022-02-22-supabase-beta-january-2022.mdx b/apps/www/_blog/2022-02-22-supabase-beta-january-2022.mdx index 81c0e8d98b1..a8b72dc03bc 100644 --- a/apps/www/_blog/2022-02-22-supabase-beta-january-2022.mdx +++ b/apps/www/_blog/2022-02-22-supabase-beta-january-2022.mdx @@ -109,15 +109,15 @@ There's no stopping [Jon Meyers](https://jonmeyers.io/videos)! He’s back with You'll learn how to listen to _any_ database change, then send those changes [in a payload](https://supabase.com/blog/supabase-functions-updates#hook-payload) via HTTP request. - +
      + +
      ## Set your own support ticket priority @@ -141,43 +141,43 @@ Are you maintaining multiple environments? Snaplet helps you copy a production d Check out the [Supabase clone environments](https://docs.snaplet.dev/tutorials/supabase-clone-environments) tutorial on the Snaplet docs. - +
      + +
      ### Supabase + Retool Retool has put together a brilliant 10 minute admin panel setup using Supabase with Retool. - +
      + +
      ### Learn with Jason Jason Lengstorf caught up with our very own [Jon Meyers](https://jonmeyers.io/) on his awesome show, [Learn with Jason](https://www.youtube.com/watch?v=8vqY1KT4TLU), to talk about building an app [with Supabase and NextJS](https://supabase.com/docs/guides/with-nextjs). - +
      + +
      ### New article highlights diff --git a/apps/www/_blog/2022-03-25-supabase-launch-week-four.mdx b/apps/www/_blog/2022-03-25-supabase-launch-week-four.mdx index 8edfcd91dde..e0c8dbfed49 100644 --- a/apps/www/_blog/2022-03-25-supabase-launch-week-four.mdx +++ b/apps/www/_blog/2022-03-25-supabase-launch-week-four.mdx @@ -35,15 +35,15 @@ to keep updated by following the hashtag [#SupaLaunchWeek](https://twitter.com/h Watch us as we kick off Launch Week, shipping this blog post. - +
      + +
      ## Monday: Community Day diff --git a/apps/www/_blog/2022-03-28-community-day-lw4.mdx b/apps/www/_blog/2022-03-28-community-day-lw4.mdx index 4600e2b03bb..d0245acace2 100644 --- a/apps/www/_blog/2022-03-28-community-day-lw4.mdx +++ b/apps/www/_blog/2022-03-28-community-day-lw4.mdx @@ -157,15 +157,15 @@ As always, all of this is open-source. Feedback, feature requests, and contribut For our community day open source spotlight feature, we’re delighted to have [Bashbunni](https://twitter.com/bash_bunni) give us an overview of what’s possible with Charm: - +
      + +
      ## New courses and learning materials diff --git a/apps/www/_blog/2022-03-31-supabase-edge-functions.mdx b/apps/www/_blog/2022-03-31-supabase-edge-functions.mdx index 03eb07a82b7..55d19fb6f92 100644 --- a/apps/www/_blog/2022-03-31-supabase-edge-functions.mdx +++ b/apps/www/_blog/2022-03-31-supabase-edge-functions.mdx @@ -108,15 +108,15 @@ To round off the end-to-end experience, `supabase-js` now works in Deno, making ## Quickstart - +
      + +
      Can't wait to get started? Develop, test, and deploy your first Supabase Edge Function in less than 7 minutes with our [Quickstart Video Tutorial](https://youtu.be/rzglqRdZUQE)! Check the [Edge Functions Docs](https://supabase.com/docs/guides/functions) for the steps. diff --git a/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx b/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx index 584e9d26dc8..f03ab864253 100644 --- a/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx +++ b/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx @@ -16,15 +16,15 @@ Today is the 1st of April, or April Fool's Day, but I like to think of today as Here's the next version of Realtime, with multiplayer features. - +
      + +
      We've made quite a few iterations on Realtime in the past. The first major version of Realtime listened to a single database by streaming changes from PostgreSQL's Write-Ahead Log and sending them to clients via WebSockets. The second major version introduced better security via WALRUS (Write Ahead Log Realtime Unified Security) which adheres to a database's RLS policies to determine authorized clients. However, this version continues to be single tenant as it can only poll one database at a time. diff --git a/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx b/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx index aaf5e4d21b0..b4a0503a236 100644 --- a/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx +++ b/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx @@ -23,15 +23,15 @@ As with anything at Supabase, the Partner Gallery is built on top of the [Supaba # Partner showcase - new integrations and expert services - +
      + +
      ## Expert services @@ -43,15 +43,15 @@ Please join us in welcoming our first two experts! ### Morrow - +
      + +
      Morrow is an app development agency headquartered in Bristol, UK. Their mission is to consult, build and support cutting-edge innovation. @@ -77,15 +77,15 @@ We’re starting off with a selection across auth, developer tools, and low-code ### Clerk - +
      + +
      Clerk authenticates users, manages session tokens, and provides user management functionality that can be used in combination with the authorization logic available in Supabase through PostgreSQL Row Level Security (RLS) policies. @@ -99,15 +99,15 @@ Stytch provides an all-in-one platform for passwordless auth. Stytch makes it ea ### Prisma - +
      + +
      Prisma is an open source next-generation ORM. It consists of the following parts: @@ -117,15 +117,15 @@ Prisma is an open source next-generation ORM. It consists of the following parts ### Snaplet - +
      + +
      Snaplet is a developer tool that copies a Postgres database, transforming personal information, so that you can safely code against actual data. This functionality makes it possible to easily achieve environment parity in Supabase. @@ -137,15 +137,15 @@ Vercel enables developers to build and publish wonderful things. They build prod ### pgMustard - +
      + +
      pgMustard speeds up your journey from knowing which query is a problem to working out what can be done about it. @@ -157,29 +157,29 @@ Even if you know how, Postgres includes so much useful information, it is easy t ### Clutch - +
      + +
      What can I achieve with Clutch & Supabase? Anything you can think of where a database would be necessary for your project. Examples include building a blog, real-time chat app, booking platform, and todo list, Supabase, and Clutch provides all the necessary tools to build your projects quickly and efficiently. ### Appsmith - +
      + +
      Appsmith is an open-source framework for building internal tools. It lets you drag-and-drop UI components to build pages, connect to any API, database or GraphQL source and write logic with JavaScript objects. @@ -191,28 +191,28 @@ Draftbit is a "pro-code" low-code mobile app building platform. Draftbit exports ### Plasmic - +
      + +
      By connecting Supabase with Plasmic, a visual builder for the web, you can build data-backed applications without code. While many users leverage Plasmic to quickly launch and iterate on landing pages, Plasmic can be used as a general-purpose visual builder for React, which can be used to design and implement fully featured read-write applications. ### Teta - +
      + +
      Teta is an online cooperative app builder. It allows designers and developers to collaborate in real-time on the design and development of mobile applications, through a convenient visual interface that allows the export of Flutter code. diff --git a/apps/www/_blog/2022-07-13-supabase-auth-helpers-with-sveltekit-support.mdx b/apps/www/_blog/2022-07-13-supabase-auth-helpers-with-sveltekit-support.mdx index 8fcd5b9ff1d..95162e6418e 100644 --- a/apps/www/_blog/2022-07-13-supabase-auth-helpers-with-sveltekit-support.mdx +++ b/apps/www/_blog/2022-07-13-supabase-auth-helpers-with-sveltekit-support.mdx @@ -46,15 +46,15 @@ Having good documentation is great, especially when there are [examples](https:/ Remix Auth Helpers are in the works. You can follow the development of this on our [GitHub repo](https://github.com/supabase-community/auth-helpers), or subscribe to our Supabase YouTube channel as we build it live on air. - +
      + +
      ## Contribute diff --git a/apps/www/_blog/2022-08-10-launch-week-5-hackathon.mdx b/apps/www/_blog/2022-08-10-launch-week-5-hackathon.mdx index dd12a99bee8..8146287897b 100644 --- a/apps/www/_blog/2022-08-10-launch-week-5-hackathon.mdx +++ b/apps/www/_blog/2022-08-10-launch-week-5-hackathon.mdx @@ -24,15 +24,15 @@ There's a $1500 GitHub sponsorship and [gold Supabase caps](https://store.supaba You can access the supabase-js pre-release, including the new interface by running `npm install @supabase/supabase-js@rc`. - +
      + +
      ## Key Facts diff --git a/apps/www/_blog/2022-08-12-supabase-series-b.mdx b/apps/www/_blog/2022-08-12-supabase-series-b.mdx index 0e6c33cc749..5a38458ec7a 100644 --- a/apps/www/_blog/2022-08-12-supabase-series-b.mdx +++ b/apps/www/_blog/2022-08-12-supabase-series-b.mdx @@ -113,15 +113,15 @@ pgsodium: [2.0](https://www.postgresql.org/about/news/pgsodium-200-modern-crypto and [3.0](https://github.com/michelp/pgsodium/releases/tag/v3.0.0) to support [Transparent Column Encryption](https://github.com/michelp/pgsodium#transparent-column-encryption). - +
      + +
      diff --git a/apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx b/apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx index 5bc9888e6ee..f316a907770 100644 --- a/apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx +++ b/apps/www/_blog/2022-08-15-supabase-cli-v1-and-admin-api-beta.mdx @@ -17,16 +17,16 @@ In addition, we are releasing a Management API (in beta). The Management API is (Note: The Management API was previously called the Admin API.) - +
      + +
      Watch the CLI team announcing the new features. diff --git a/apps/www/_blog/2022-08-16-supabase-js-v2.mdx b/apps/www/_blog/2022-08-16-supabase-js-v2.mdx index 5ca0414db64..6708ecaaf54 100644 --- a/apps/www/_blog/2022-08-16-supabase-js-v2.mdx +++ b/apps/www/_blog/2022-08-16-supabase-js-v2.mdx @@ -316,16 +316,16 @@ We'll continuing merging security fixes to v1, with maintenance patches for the ## Announcement video and discussion - +
      + +
      ## supabase-js v2 resources diff --git a/apps/www/_blog/2022-08-17-supabase-soc2.mdx b/apps/www/_blog/2022-08-17-supabase-soc2.mdx index 7febf307779..c3f4fa8ff6a 100644 --- a/apps/www/_blog/2022-08-17-supabase-soc2.mdx +++ b/apps/www/_blog/2022-08-17-supabase-soc2.mdx @@ -92,16 +92,16 @@ Getting the Type 1 certification is just the start, and we will be working on ge ## Announcement video and discussion - +
      + +
      ## More Launch Week 5 diff --git a/apps/www/_blog/2022-08-18-supabase-realtime-multiplayer-general-availability.mdx b/apps/www/_blog/2022-08-18-supabase-realtime-multiplayer-general-availability.mdx index a30b10b8d00..78da8824758 100644 --- a/apps/www/_blog/2022-08-18-supabase-realtime-multiplayer-general-availability.mdx +++ b/apps/www/_blog/2022-08-18-supabase-realtime-multiplayer-general-availability.mdx @@ -224,16 +224,16 @@ We also want to thank [José Valim](https://twitter.com/josevalim), and everyone ## Announcement video and discussion - +
      + +
      ## More Launch Week 5 diff --git a/apps/www/_blog/2022-08-19-launch-week-5-community-day.mdx b/apps/www/_blog/2022-08-19-launch-week-5-community-day.mdx index dda88680ffd..9f22394c4a7 100644 --- a/apps/www/_blog/2022-08-19-launch-week-5-community-day.mdx +++ b/apps/www/_blog/2022-08-19-launch-week-5-community-day.mdx @@ -46,16 +46,16 @@ _Our limited edition swag is approved by professional swag testers like Peanut!_ ### Launch Week Landing Page & Golden Tickets - +
      + +
      We now have a shiny new [Launch Week page](https://supabase.com/launch-week), your one-stop shop for all things Launch Week, where you can find each day's feature as well as highlights from the past Launch Weeks. @@ -69,16 +69,16 @@ We've added a Golden Ticket mechanic, distributing some special edition launch w ### pg_jsonschema - +
      + +
      For our Open Source Spotlight we're hearing from [Dmitry Dygalo](https://twitter.com/Stranger6667) from [schemathesis.io](https://schemathesis.io/) who works on a multitude of open-source projects. He's the author and maintainer of [jsonschema-rs](https://github.com/Stranger6667/jsonschema-rs), @@ -122,16 +122,16 @@ All that in only 28 mins — or 14 mins on 2x speed! ### Realtime Trello Board with Angular by Simon Grimm - +
      + +
      [Simon Grimm](https://twitter.com/schlimmson) from [ionicacademy.com](http://ionicacademy.com/) & [devdactic.com](http://devdactic.com/) is releasing a new course for all the Angular fans out there! @@ -146,16 +146,16 @@ Since releasing our [Partners](/partners) page last Launch Week, we've had a lot ### CodeSandbox Projects Beta - +
      + +
      The new [CodeSandbox Projects Beta](https://projects.codesandbox.io/) let's you run your projects and turn them into a link you can share with anyone. Developers visiting your link can not only see your running code, they can click “fork” and get an exact copy of that environment so they can easily contribute back to your masterpiece. CodeSandbox integrates directly with GitHub, so you get a unique development environment for every PR you and your contributors open! @@ -163,31 +163,31 @@ You can do all of this from a web browser, from [within VSCode](https://marketpl ### Dashibase - +
      + +
      [Dashibase](https://dashibase.com/) is a no-code internal tools builder with a Notion-like UI. With the latest update, you can easily build CRUD dashboards on top of your Supabase projects, set read/write access on your data sources, and share your dashboards with your team. Stop building dashboards from scratch. ### Replicache - +
      + +
      Replicache is a JavaScript framework for building high performance, collaborative, offline-first web apps. @@ -197,16 +197,16 @@ Replicache works with Supabase for storage, and Supabase Realtime for its [poke ### [Zapp.run](http://Zapp.run) by Invertase - +
      + +
      Zapp is a free online sandbox environment for building Flutter applications in the browser. With Zapp, you can enjoy building Flutter apps as though in your favorite IDE, especially if you are a VSCode user; you should feel at home. @@ -214,16 +214,16 @@ You can install any pub packages and start developing, of course including Supab ### Feelantera - +
      + +
      Feelantera is a creative digital agency based in Indonesia. They craft web applications with cutting-edge technology, and are one of our new [Supabase Experts](https://supabase.com/partners/experts). They [recently open-sourced](https://twitter.com/feelantera/status/1556909234478059521) their Supabase Edge Functions powered URL shortener on [GitHub](https://github.com/feelantera/fltr-link). @@ -233,16 +233,16 @@ Are you also experienced in building with Supabase and interested in helping oth ### Snappify - +
      + +
      snappify is a powerful design tool that helps you to explain and share your code snippets in many beautiful ways. @@ -260,16 +260,16 @@ To explore the infrastructure backing the project, check out the repository [her ## Hackathon - +
      + +
      We hope all of these awesome projects and stories gave you plenty of inspiration and you're ready to build in a weekend and scale to millions, as our [hackathon is still running until Sunday](https://supabase.com/blog/launch-week-5-hackathon) night PT! diff --git a/apps/www/_blog/2022-08-19-pg-jsonschema-a-postgres-extension-for-json-validation.mdx b/apps/www/_blog/2022-08-19-pg-jsonschema-a-postgres-extension-for-json-validation.mdx index 52f14bd8c01..296165cc10d 100644 --- a/apps/www/_blog/2022-08-19-pg-jsonschema-a-postgres-extension-for-json-validation.mdx +++ b/apps/www/_blog/2022-08-19-pg-jsonschema-a-postgres-extension-for-json-validation.mdx @@ -6,6 +6,7 @@ image: lw5-pg_jsonschema/jsonschema-thumb.jpg thumb: lw5-pg_jsonschema/jsonschema-thumb.jpg tags: - launch-week + - planetpg date: '2022-08-19' toc_depth: 3 --- diff --git a/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx b/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx index c0e20a82629..92ccd3f4ee0 100644 --- a/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx +++ b/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx @@ -27,15 +27,16 @@ Along our jurney we will: - implement **magic link sign in** and user authentication with Angular - make use of **the realtime capabilities**! - Watch the video version of the tutorial. +
      + +
      +Watch the video version of the tutorial. Since there are quite some code snippets we need I've put together the [full source code on Github](https://github.com/saimon24/supabase-trello-angular) so you can easily run the project yourself! diff --git a/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx b/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx index 78bae4b10ed..92688641ed8 100644 --- a/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx +++ b/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx @@ -6,6 +6,7 @@ image: lw6-day-1/lw6-day1-docs.jpg thumb: lw6-day-1/lw6-day1-docs.jpg tags: - launch-week + - docs date: '2022-12-12' toc_depth: 3 video: https://www.youtube.com/v/Q1Amk6iDlF8 diff --git a/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx b/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx index 70e89a15e82..eff5501b53a 100644 --- a/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx +++ b/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx @@ -17,8 +17,10 @@ Today, we’re releasing Multi Factor Authentication for everyone. Additionally, in preparation for releasing SAML, we're "dogfooding" the feature with the introduction of Single Sign On (SSO) on our dashboard. Contact us at [growth@supabase.com](mailto:growth@supabase.com) if you want to [enable this on your Enterprise plan](https://supabase.com/docs/guides/platform/sso). - +
      + +
      ## What is MFA? diff --git a/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx b/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx index 28beba1875d..fa8bd3a668e 100644 --- a/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx +++ b/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx @@ -6,6 +6,7 @@ image: launch-week-6/wrappers/wrappers-og.png thumb: launch-week-6/wrappers/wrappers-og.png tags: - launch-week + - planetpg date: '2022-12-15' toc_depth: 3 video: https://www.youtube.com/v/QA2qC5F-4OU @@ -245,15 +246,15 @@ If you're interested in getting involved or building your own Wrapper, don't hes - View the documentation: [supabase.github.io/wrappers](https://supabase.github.io/wrappers/) - Supabase Launch Week: [supabase.com/launch-week](https://supabase.com/launch-week) - +
      + +
      ## More Launch Week 6 diff --git a/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx b/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx index 78facd9830a..ed8855ba1e2 100644 --- a/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx +++ b/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx @@ -20,16 +20,16 @@ While we've been working hard on new features, the community has been developing ### FlutterFlow - +
      + +
      [FlutterFlow](https://flutterflow.io/) is a low-code builder for developing native mobile applications using Flutter. You can use the simple drag-and-drop interface to build your app faster than traditional development. @@ -41,16 +41,16 @@ Watch John from the FlutterFlow team demonstrate how you can [get started with F ### OneSignal - +
      + +
      [OneSignal](https://onesignal.com/) is a messaging platform allowing you to deliver push notifications, in-app messages, SMS, and emails to your users. @@ -62,16 +62,16 @@ In the video above, William from OneSignal will show you how you can easily inte ### NextAuth - +
      + +
      [NextAuth.js](https://next-auth.js.org/) is an open source authentication solution for [Next.js](https://nextjs.org/). It allows you to bring your own Database so that you own your user data. diff --git a/apps/www/_blog/2022-12-16-launch-week-6-wrap-up.mdx b/apps/www/_blog/2022-12-16-launch-week-6-wrap-up.mdx index a2a6c96a94d..0e42f6dcdf8 100644 --- a/apps/www/_blog/2022-12-16-launch-week-6-wrap-up.mdx +++ b/apps/www/_blog/2022-12-16-launch-week-6-wrap-up.mdx @@ -157,15 +157,15 @@ We've released a new multilingual Full Text Search extension, PGroonga. It's fas ## Flutterflow for Flutter App Development - +
      + +

      @@ -178,15 +178,15 @@ We partnered with the team at [Flutterflow](https://flutterflow.io/) to bring yo ## OneSignal for Push Notifications - +
      + +

      diff --git a/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx b/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx index 2b02cda5cb3..aafb2bfa88f 100644 --- a/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx +++ b/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx @@ -58,15 +58,15 @@ Decrypted data can be accessed by a special view that is automatically created w ## Deep Dive on How The Vault works - +
      + +
      As we mentioned, the Vault uses pgsodium's Transparent Column Encryption (TCE) to store secrets in an authenticated encrypted form. There are some details around that you may be curious about, what does authenticated mean, and where are encryption keys store? This section explains those details. diff --git a/apps/www/_blog/2023-02-03-openai-embeddings-postgres-vector.mdx b/apps/www/_blog/2023-02-03-openai-embeddings-postgres-vector.mdx index c04183c99b0..e6c7aa2fe4f 100644 --- a/apps/www/_blog/2023-02-03-openai-embeddings-postgres-vector.mdx +++ b/apps/www/_blog/2023-02-03-openai-embeddings-postgres-vector.mdx @@ -6,7 +6,7 @@ image: embeddings/og_pgvector.png thumb: embeddings/og_pgvector.png tags: - postgres - - ai + - AI date: '2023-02-06' toc_depth: 3 --- diff --git a/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx b/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx index 6c1b29a9e70..85b061947c7 100644 --- a/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx +++ b/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx @@ -5,13 +5,13 @@ author: paul_copplestone image: docsgpt/og_supabase_clippy.png thumb: docsgpt/og_supabase_clippy.png tags: - - ai + - AI - docs date: '2023-02-07' toc_depth: 3 --- -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. @@ -55,13 +55,13 @@ It's built with Supabase/Postgres, and consists of several key parts: All the changes we made are in [this](https://github.com/supabase/supabase/pull/12056) Pull Request. Greg is here to explain every step in full detail:
      - +
      You can find a full write up in our previous blog post: [Storing OpenAI embeddings in Postgres with pgvector](https://supabase.com/blog/openai-embeddings-postgres-vector). @@ -90,13 +90,13 @@ A few weeks ago I received an email, drawing our attention to a [Pull Request](h After we merged the PR, I reached out to see if Greg was interested in helping with some documentation or a blog post to help the community to use pgvector. On the call, Greg surprised me with an initial prototype where he had ingested the Supabase docs for a ChatGPT-like experience. I was suitably impressed and suggested that we could explore ways to work together to build it into the Supabase docs. He wasted no time:
      - +
      ### A friendly collaboration diff --git a/apps/www/_blog/2023-02-14-flutter-real-time-multiplayer-game.mdx b/apps/www/_blog/2023-02-14-flutter-real-time-multiplayer-game.mdx index 60dde37916e..796f60340f8 100644 --- a/apps/www/_blog/2023-02-14-flutter-real-time-multiplayer-game.mdx +++ b/apps/www/_blog/2023-02-14-flutter-real-time-multiplayer-game.mdx @@ -14,7 +14,7 @@ toc_depth: 3 Flutter is a UI library to build apps that run on any platform, but it can also build interactive games thanks to an open-source game engine built on top of Flutter called [Flame](https://flame-engine.org/). Flame takes care of things like collision detection or loading image sprites to bring game development to all the Flutter devs. We can take it a step further to introduce real-time communication features so that players can play against each other in real-time. -In this article, we will use Flutter, Flame, and Supabase's real-time features to build a real-time multiplayer shooting game. You can find the complete code of this tutorial [here](https://github.com/supabase/supabase/tree/master/examples/realtime/examples/flutter-multiplayer-shooting-game). +In this article, we will use Flutter, Flame, and Supabase's real-time features to build a real-time multiplayer shooting game. You can find the complete code of this tutorial [here](https://github.com/supabase/supabase/tree/master/examples/realtime/flutter-multiplayer-shooting-game). ## Overview of the final game @@ -833,7 +833,7 @@ We learned how to create an interactive shooting game. We took advantage of Flut ## More Flutter Resources -- [Complete code of this article](https://github.com/supabase/supabase/tree/master/examples/realtime/examples/flutter-multiplayer-shooting-game) +- [Complete code of this article](https://github.com/supabase/supabase/tree/master/examples/realtime/flutter-multiplayer-shooting-game) - [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app) - [Flutter Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls) - [Generate Flame template using Very Good CLI](https://verygood.ventures/blog/generate-a-game-with-our-new-template) diff --git a/apps/www/_blog/2023-02-16-case-study-happyteams.mdx b/apps/www/_blog/2023-02-16-case-study-happyteams.mdx new file mode 100644 index 00000000000..cd7366094c0 --- /dev/null +++ b/apps/www/_blog/2023-02-16-case-study-happyteams.mdx @@ -0,0 +1,58 @@ +--- +title: HappyTeams unlocks better performance and reduces cost with Supabase +description: How a bootstrapped startup migrated from Heroku to Supabase in 30 minutes and never looked back +author: rory_wilding +image: 2023-02-16-case-study-happyteams/case-study-happyteams.png +thumb: 2023-02-16-case-study-happyteams/case-study-happyteams.png +tags: + - case-study +date: '2023-02-16' +toc_depth: 3 +--- + +## The Challenge  + +> Finding a painless and hands-free database experience to support growth + +[HappyTeams](https://happyteams.io/) is a growing startup with an ambitious vision. The company is bootstrapped, so technical resources are scarce. Devs need to do more with less. This means staying laser-focused on delivering features, rather than database timeouts or performance issues. + +The company launched its app on Heroku Postgres. But one year in, they were experiencing scaling issues. They needed to handle thousands of users while also shipping new features and building out integrations. Issues with database slowness and concurrent connections were holding them back. + +Michael Dever, the founder and CTO of HappyTeams, realized it was time for a change. A large overhaul of the app in early 2022 presented the opportunity to do so: "When you're in that first sprint of building a product, you're kind of locked with that first database decision you make. When we did a rebuild, it was time to look at something like Supabase." + +![HappyTeams Home Page](/images/blog/2023-02-16-case-study-happyteams/happyteams-home-page.png) + +## Why Supabase? + +There were four main factors that led HappyTeams to switch from Heroku Postgres to Supabase: + +1. **Better performance**. The team was experiencing uneven performance with Heroku, which was sluggish at times. Supabase was consistently fast. + +2. **Enabling a large number of direct connections and connection pooling with PgBouncer**. HappyTeams was constantly hitting limits on the number of database connections. They could solve this problem with Supabase's [flexible plans](https://supabase.com/pricing) and [support for PgBouncer](https://supabase.com/blog/supabase-pgbouncer). + +3. **Quality of support, documentation, and onboarding**: While Heroku provided no support at the tier they were on, the Supabase team was readily available: "Responses were powerful and they answered all my questions, even on a Sunday. That just further sold the product for me." He was also impressed with the [documentation](https://supabase.com/docs) on the Supabase website and the onboarding process. + +4. **Lower costs**. Moving to Supabase reduced HappyTeams's costs: The base plan comes with everything they need to scale the product. + + + Great documentation, great product, great pricing. It sells itself, really. + + +## Supabase provides a fast and reliable production database + +After a 30 minute migration, HappyTeams can iterate quickly, add new features, and improve their product based on customer feedback + +Once they made the decision, migrating from Heroku to Supabase was simple. HappyTeams completed their database migration in less than an hour using pg_dump and pg_restore. + +Performance is no longer an issue. "Supabase just worked", says Michael. Pooling connections using PgBouncer was effortless - solving a recurring pain point for HappyTeams. + +"Supabase is so fast. Way, way, way faster than our Heroku Postgres database, which I think was located in the same region." + +Today, Supabase is an integral part of HappyTeams's tech stack. It has allowed the company to move fast and rapidly release new features - with minimal tech resources devoted to their backend infrastructure. + +Thanks to Supabase's ease of use and reliability, HappyTeams can iterate quickly based on feature requests and deliver more value to their user base. They are planning to expand their usage, exploring functionality such as [Edge Functions](https://supabase.com/edge-functions) and the [API client](https://supabase.com/docs/guides/api). + + + When we needed to add a comments feature to our app, we could get it done in just two days. + Supabase is really like a dream come true for a developer. + diff --git a/apps/www/_blog/2023-02-17-type-constraints-in-65-lines-of-sql.mdx b/apps/www/_blog/2023-02-17-type-constraints-in-65-lines-of-sql.mdx new file mode 100644 index 00000000000..262c4630353 --- /dev/null +++ b/apps/www/_blog/2023-02-17-type-constraints-in-65-lines-of-sql.mdx @@ -0,0 +1,293 @@ +--- +title: Type Constraints in 65 lines of SQL +description: Creating validated data types in Postgres +author: oli_rice +image: 2023-jan/semver-thumb.jpg +thumb: 2023-jan/semver-thumb.jpg +tags: + - postgres + - planetpg +date: '2023-02-17' +toc_depth: 3 +--- + +PostgreSQL has a rich and extensible type system. Beyond [enums](https://www.postgresql.org/docs/current/datatype-enum.html) and [composite](https://www.postgresql.org/docs/current/rowtypes.html) types, we can: + +- apply data validation rules +- override comparison operators like `=` / `+` / `-` +- create custom aggregations +- define casting rules between types + +With a little effort, a user-defined type can feel indistinguishable from a built-in. In this article we focus on validation and ergonomics while quickly touching on a few other concepts. + +To illustrate, we’ll create an `semver` data type to represent [Semantic Versioning](https://semver.org) values. We’ll then add validation rules to make invalid states unrepresentable. + +## SemVer + +A (very) loose primer on SemVer: + +SemVer is a specification for representing software versions that communicate information about backwards compatibility. The type is typically represented as a string with 5 components. + +![image](/images/blog/2023-jan/semver-spec.jpg) + +Where `pre-release` and `metadata` are optional. + +The intent of each component is outside the scope of this article but, as an example, incrementing the major version number notifies users that the release includes at least one backwards incompatible change. + +For a concise representation of the full spec, [check out the grammar](https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions). + +## SQL + +For our purposes, we’ll assume that the SemVer type is a critical component of the application that needs to be queried flexibly and efficiently. + +### Storing Components + +To that end, we’ll store each component of the version as a separate field on a [composite type](https://www.postgresql.org/docs/current/rowtypes.html). + +{/* prettier-ignore */} +```sql +create type semver_components as ( + major int, + minor int, + patch int, + pre_release text[], + build_metadata text[] +); +``` + +We can create an instance of this type in SQL by casting a tuple as the `semver_components` type. + +{/* prettier-ignore */} +```sql +select + (1, 2, 3, array['beta', '1'], array['meta'])::semver_components +-- returns: (1,2,3,{'beta','1'},{'meta'}) +``` + +Unfortunately, our definition is far too permissive. + +{/* prettier-ignore */} +```sql +select + (null, -500, null, array['?'], array[''])::semver_components +-- returns: (,-500,,{'?'},{'' +``` + +Our data type has no problem accepting invalid components. To list a few of the SemVer rules we violated: + +- Major version must not be null +- Minor version must be ≥ 0 +- Patch version must not be null +- Pre-release elements must only include characters [A-z0-9] +- Build metadata elements may not be empty strings + +We need to add some validation rules to meet our “make invalid states unrepresentable” goal. + +### Validation + +[Domains](https://www.postgresql.org/docs/current/sql-createdomain.html) are Postgres’ solution for optionally layering constraints over a data type. Domains are to types what [check constraints](https://www.postgresql.org/docs/current/ddl-constraints.html) are to tables. If you’re not familiar with check constraints, you can think of them as equivalent to zod/pydantic in javascript/python. + +Let's codify some SemVer rules, layer them on the `semver_components` type, and give the new domain a friendly name. + +{/* prettier-ignore */} +```sql +create domain semver + as semver_components + check ( + -- major: non-null positive integer + (value).major is not null and (value).major >= 0 + -- minor: non-null positive integer + and (value).minor is not null and (value).minor >= 0 + -- patch: non-null positive integer + and (value).patch is not null and (value).patch >= 0 + and semver_elements_match_regex( + (value).pre_release, + '^[A-z0-9]{1,255}$' + ) + and semver_elements_match_regex( + (value).build_metadata, + '^[A-z0-9\.]{1,255}$' + ) + ); +``` + +which references a helper function: + +{/* prettier-ignore */} +```sql +create or replace function semver_elements_match_regex( + parts text[], + regex text +) +returns bool +language sql +as $$ + -- validates that *parts* nullable array of non-empty strings + -- where each element of *parts* matches *regex* + select + $1 is null + or ( + ( + select ( + bool_and(pr_arr.elem is not null) + and bool_and(pr_arr.elem ~ $2) + ) + from + unnest($1) pr_arr(elem) + ) + and array_length($1, 1) > 0 + ) +$$; +``` + +Now, if we repeat our positive and negative test cases using the `semver` type (vs `semver_components`) we still accept valid states: + +{/* prettier-ignore */} +```sql +-- Success Case +select + (1, 2, 3, array['beta', '1'], array['meta'])::semver, +-- returns: (1,2,3,{'beta','1'},{'meta'}) +``` + +while invalid states are rejected with an error: + +{/* prettier-ignore */} +```sql +-- Failure Case +select + (null, -500, null, array['?'], array[''])::semver +-- ERROR: value for domain semver violates check constraint "semver_check" +-- SQL state: 23514 +``` + +### Testing + +Our validation doesn’t have to be called manually. The `semver` domain can be used anywhere you’d use the `semver_components` type and the validations are automatically applied. + +{/* prettier-ignore */} +```sql +-- A table with a semver column +create table package_version( + id bigserial primary key, + package_name text not null, + package_semver semver not null -- semver column +); + +-- Insert some valid records +insert into package_version( package_name, package_semver ) +values + ('supabase-js', (2, 2, 3, null, null)), + ('supabase-js', (2, 0, 0, array['rc', '1'], null) +); + +-- Attempt to insert an invalid record (major is null) +insert into package_version( package_name, package_semver ) +values + ('invalid-js', (null, 1, 0, array['asdf'], null)); +-- ERROR: value for domain semver violates check constraint "semver_check" +``` + +Good stuff! + +We’re 48 lines of SQL in and have solved for making invalid states unrepresentable. Now lets think about ergonomics. + +### Displaying + +Now that our data type is well constrained, you might notice that selecting values from a `semver` typed column returns a tuple, rather than the SemVer string we’re used to seeing. + +{/* prettier-ignore */} +```sql +select + * +from + package_version +/* +id | package_name | package_semver +------------------------------------- + 1 | supabase-js | (2,2,3,,) + 2 | supabase-js | (2,0,0,"{rc,1}",) +*/ +``` + +For example: `(2,0,0,"{rc,1}",)` vs `2.0.0-rc.1` + +We could work around that problem with some [custom casts](https://www.postgresql.org/docs/current/sql-createcast.html), but I’d recommend keeping everything explicit with a function call. + +{/* prettier-ignore */} +```sql +create or replace function semver_to_text(semver) + returns text + immutable + language sql +as $$ + select + format('%s.%s.%s', $1.major, $1.minor, $1.patch) + || case + when $1.pre_release is null then '' + else format('-%s', array_to_string($1.pre_release, '.')) + end + || case + when $1.build_metadata is null then '' + else format('+%s', array_to_string($1.build_metadata, '.')) + end +$$; +``` + +Which allows us to query the `package_version` table and retrieve a string representation of the data. + +{/* prettier-ignore */} +```sql +select + id, + package_name, + semver_to_text(package_semver) as ver -- cast as text +from + package_version +/* +id | package_name | ver +------------------------------ + 1 | supabase-js | 2.2.3 + 2 | supabase-js | 2.0.0-rc.1 +*/ +``` + +Or, better yet, use a [generated column](https://www.postgresql.org/docs/current/ddl-generated-columns.html) + +{/* prettier-ignore */} +```sql +create table package_version( + id bigserial primary key, + package_name text not null, + package_semver semver not null, + semver_text text generated always as (semver_to_text(package_semver)) stored +); +``` + +so the text representation is persisted along with the `semver` type and incurs no query/filter penalty. + +### Other Tricks + +Postgres provides all the tools you could want to make your data types/domains work with SQL as seamlessly as builtins. + +For example, you could: + +- add convenience functions to parse a [semver type from text](https://github.com/supabase/dbdev/blob/ca338584203d9b2eb7a4a378f5724674c15b9c25/supabase/migrations/20220117141507_semver.sql#L78) +- [override the equality operator](https://github.com/supabase/dbdev/blob/ca338584203d9b2eb7a4a378f5724674c15b9c25/supabase/migrations/20220117141507_semver.sql#L37-L63) (`=`) to correctly reflect that versions differing only in build metadata are considered equal +- [add a `max` function](https://github.com/supabase/dbdev/blob/ca338584203d9b2eb7a4a378f5724674c15b9c25/supabase/migrations/20220117141507_semver.sql#L122-L140) to efficiently query for the newest version of each package from within the database + +to name a few. + +Aligning the right parts of your business’ logic with the database can dramatically improve throughput, decrease IO, and simplify application code. + +### Conclusion + +Admittedly, building performant and ergonomic custom data types in Postgres involves a lot of ceremony. + +That said, in cases where: + +- the type’s data integrity is critical +- the type is well specified +- the type’s spec does not change (or changes infrequently) + +Teaching Postgres to have first class support for your custom type can be transformative for data integrity and performance. 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/_blog/2023-03-09-supabase-beta-update-february-2023.mdx b/apps/www/_blog/2023-03-09-supabase-beta-update-february-2023.mdx new file mode 100644 index 00000000000..2f6f20dbf6a --- /dev/null +++ b/apps/www/_blog/2023-03-09-supabase-beta-update-february-2023.mdx @@ -0,0 +1,146 @@ +--- +title: Supabase Beta February 2023 +description: There is something for everybody this month - AI, Auth, Database, Edge Functions, GraphQL … you name it! +author: ant_wilson +image: 2023-03-09-beta-update-february/supabase-beta-update-february-2023.jpg +thumb: 2023-03-09-beta-update-february/supabase-beta-update-february-2023.jpg +tags: + - release-notes +date: '2023-03-09' +toc_depth: 3 +--- + +There’s something for everybody this month: AI, Auth, Database, Edge Functions, GraphQL … you name it! + +## GraphiQL editor in the dashboard + +![GraphiQL editor in the dashboard](/images/blog/2023-03-09-beta-update-february/graphiql-dashboard.png) + +The most popular GraphQL IDE/API explorer is now built into the dashboard! You can now explore and query your GraphQL API produced by `pg_graphql`. + +[Try it now](https://app.supabase.com/project/_/api/graphiql). + +## Supabase + OpenAI search + +![Supabase + OpenAI search](/images/blog/2023-03-09-beta-update-february/openai-supabase.png) + +We've updated our Docs search functionality to use `pgvector` + OpenAI. Still no cease and desist from Microsoft, so you can continue to ask Clippy any Supabase-specific questions 📎💚 + +[Ask Clippy.](https://supabase.com/docs) + +## Serve all the functions! + +![Serve all the functions!](/images/blog/2023-03-09-beta-update-february/supabase-cli-edge-functions.png) + +Do you use multiple Edge Functions in your project? Then celebrate! Supabase CLI 1.36+ now supports serving multiple Edge Functions at the same time. + +To enable the feature, just run `supabase functions serve` in your project. + +[Check the docs.](https://supabase.com/docs/reference/cli/supabase-functions-serve) + +## Smaller Postgres docker images for everyone + +![Smaller Postgres docker images for everyone](/images/blog/2023-03-09-beta-update-february/postgres-docker-images.png) + +We rewrote the Postgres Dockerfile with multi-stage builds so that each extension is compiled in its own separate stage. This reduces the size of the image from 1.3GB to 250MB, enabling a much faster boot time. + +[See it yourself](https://hub.docker.com/r/supabase/postgres/tags) + +## New UI for Postgres Roles + +![New UI for Postgres Roles](/images/blog/2023-03-09-beta-update-february/postgres-roles.png) + +We've improved database role management. You can create, update, and delete database roles through the dashboard. Just one small step towards column-level security + +[Check it out.](https://app.supabase.com/project/_/editor) + +## API docs in the table editor + +![API docs in the table editor](/images/blog/2023-03-09-beta-update-february/api-docs.png) + +API docs got a light touchup and were moved to the table editor. You can now look up API methods and generate & download type files right there ✨ + +[Check it out.](https://app.supabase.com/project/_/editor) + +## Quick product updates + +- **Postgres Extensions**: We're rolling out some fixes for several Postgres extensions. Check your Dashboard notifications to see if you need to take any actions. + +- **Auth**: Added full OpenAPI 3.0 spec which provides a comprehensive overview of the API with documentation on each request. [PR](https://github.com/supabase/gotrue/pull/918) + +- **Database**: supabase-js now infers the response type from your query. If the inferred type is incorrect, you can use `.returns()` to override it. [Doc](https://supabase.com/docs/reference/javascript/db-returns) + +- **Dashboard**: Improved database roles management, you can now create, update and delete database roles through the dashboard. [Dashboard](https://app.supabase.com/project/_/database/roles) + +- **Dashboard**: We've provided a reference panel showing all available paths that can be queried from each respective source that improves the Logs Explorer experience. [Dashboard](https://app.supabase.com/project/_/logs/explorer) + +- **Edge Functions**: upgraded to Deno 1.30.3, that supports TypeScript 4.9.x and introduces `satisfies`. Thanks to [Benjamin Dobell](https://github.com/Benjamin-Dobell) 🙏. [PR](https://github.com/supabase/functions-relay/pull/30) + +- Realtime: Broadcast only primary key(s) for deleting records when RLS is enabled and replica identity is full. [PR](https://github.com/supabase/realtime/pull/461) + +## Next.js with Supabase + +![Next.js with Supabase](/images/blog/2023-03-09-beta-update-february/nextjs-supabase.png) + +Building the next big thing with Supabase and Next.js? Then make sure to check Jon's new video series to help get you (and your app directory) off the ground. + +- [Fetching and Caching data in Next.js Async Server Components](https://www.youtube.com/watch?v=GniRj1jIhFw) + +- [Client vs Server Components in Next.js app directory // Merging server state with realtime updates](https://www.youtube.com/watch?v=YR-xP6PPXXA) + +- [Delicious Server Component Cookies // Auth in the Next.js app directory](https://www.youtube.com/watch?v=Bh1TOpOcGJQ) + +![Made with Supabase](/images/blog/2022-12-07-november-beta-update/madewithsupabase.jpg) + +[Domain-specific ChatGPT](https://github.com/gannonh/gpt3.5-turbo-pgvector) | This starter app uses embeddings coupled with vector search to show how the OpenAI API can be used to create a conversational interface to domain-specific knowledge. Built by [Gannon Hall](https://github.com/gannonh) with Next.js, Vercel, gpt-3.5-turbo, pgvector, and Supabase. + +Discover 100+ other apps in this [tweet](https://twitter.com/supabase/status/1628843899996209155). + +## Extended Community Highlights + +![Community](/images/blog/2022-june/community.jpg) + +- Langchain supports our database as a vector store, using pgvector. [Doc](https://hwchase17.github.io/langchainjs/docs/modules/indexes/vector_stores/supabase/)
      + +- refine week: building a CRUD app with refine & Supabase. [Full series](https://refine.dev/week-of-refine/) + +- Build an Instagram Web App with Supabase and Next.js. [Tutorial](https://livecycle.io/blogs/nextjs-supabase-instagram/)
      + +- Supabase GeoQueries with PostGIS and Storage Upload. [Video](https://www.youtube.com/watch?v=-_Wpr7rwU8I)
      + +- How I used Supabase to build a winning hackathon project. [Article](https://blog.matt.lgbt/how-i-used-supabase-to-build-a-winning-hackathon-project) + +- Using a Supabase API within FlutterFlow. [Video](https://www.youtube.com/watch?v=YzSXwlA3vWM)
      + +- Getting started with Supabase in Android. [Part 1](https://www.youtube.com/watch?v=SGr73sWMX6w) | [Part 2](https://www.youtube.com/watch?v=NWaIIRfVpuo) + +- Streaming User Events from PostgreSQL (Supabase) to Serverless Kafka. [Article](https://upstash.com/blog/postgre-supabase-connector)
      + +- Building a CRUD API with FastAPI and Supabase: A Step-by-Step Guide. [Tutorial](https://blog.theinfosecguy.xyz/building-a-crud-api-with-fastapi-and-supabase-a-step-by-step-guide)
      + +- How to build a collaborative SaaS product using Next.js and ZenStack's access control policy. [Tutorial](https://zenstack.dev/blog/saas-demo)
      + +- Nuxt 3, Tailwind, and Supabase Crash Course. [Course](https://dev.to/jacobandrewsky/nuxt-3-tailwind-and-supabase-crash-course-1482)
      + +- A sneak peek at the Supabase and Next.js RSC SaaS kit. [Article](https://makerkit.dev/blog/changelog/nextjs-supabase-kit-update)
      + +- Build a Fullstack serverless application using React, Chakra UI, Hookstate, and Supabase. [Tutorial](https://clx.hashnode.dev/build-a-fullstack-serverless-application-using-react-chakra-ui-hookstate-and-supabase)
      + +- Implement Sign in with Google using Supabase Auth in NextJS. [Tutorial](https://dev.to/irwanphan/implement-sign-in-with-google-using-supabase-auth-in-nextjs-1jj1)
      + +## We're Hiring + +Come join one of the fastest-growing open source projects ever 🤗 + +- [Technical partnerships lead (Next.js, Node,js, Supabase)](https://boards.greenhouse.io/supabase/jobs/4775849004)\ +- [Junior Developer Advocate (SMM & Community)](https://boards.greenhouse.io/supabase/jobs/4777008004) + +## Meme Zone + +As always, one of our favorite memes from last month. [Follow us on Twitter](https://twitter.com/supabase) for more. + +![Best meme from February 2023](/images/blog/2023-03-09-beta-update-february/beta-update-february-meme.jpeg) + +## It's happenning! Coming in April + +![Supabase Launch Week 7](/images/blog/2023-03-09-beta-update-february/supabase-launch-week-7.png) diff --git a/apps/www/_blog/2023-04-01-supaclub.mdx b/apps/www/_blog/2023-04-01-supaclub.mdx new file mode 100644 index 00000000000..9d52ef8a387 --- /dev/null +++ b/apps/www/_blog/2023-04-01-supaclub.mdx @@ -0,0 +1,38 @@ +--- +title: SupaClub +description: The Worlds First Software Engineering Nightclub. +author: ant_wilson +image: supaclub/supaclub-thumb.png +thumb: supaclub/supaclub-thumb.png +tags: + - announcements +date: '2023-04-01' +toc_depth: 3 +--- + +Announcing SupaClub (pronounced “Super-Clurb”), the world’s first Software Engineering Nightclub. + +Located on the Indonesian island of Bali, SupaClub is where the digital nomads of South East Asia are mixing Hardcore Techno with Hardcore Engineering. + +![Main Dance Floor](/images/blog/supaclub/supaclub1.png) + +In March this year, some members of the Supabase team took advantage of the fact that they can work fully remotely, and spent a week working in Bali. The island is a great place to relax in coffee shops during the day, but when night time rolled around our team faced a major problem - the large established nightclubs of Bali would not allow our engineers to bring their laptops into the club. They have strict rules against EMACS on the dance floor, killing our dream of fixing bugs whilst jamming to 90s club anthems. + +Our team got together and decided that we would do what we do best at Supabase. We’d ship a fix, and today that fix becomes a reality. + +![coding in the club](/images/blog/supaclub/supaclub2.png) + +SupaClub, situated near the beach in the Canggu neighborhood of Bali, will open its doors this coming Friday. It can house 4000 developers across 4 floors, each with a dedicated DJ and breakout rooms. It has UltraSpeed 1Gbps WiFi and bookable standing desks on every floor. VIP [SupaBrew bottle service](https://supabase.com/blog/supabrew) is available inside private conference rooms, complete with whiteboards and UltraWide monitors for full immersion (Think WeWork-meets-Pacha). + +![dancefloor meets coding](/images/blog/supaclub/supaclub3.png) + +In terms of beverage selection, [SupaBrew](https://supabase.com/blog/supabrew) will of course be available behind the bar, along with a cocktail list that includes: + +- **Bjarne Pisco Stroustrup** - A back-to-basics tantalizing mix of tart, sweet, and silky pisco brandy that won't leave your pointers dangling +- **The Linus** - A spicy twist on the Bloody Mary, this aggressive blend will have you shouting for more +- **Mojito-js** - we would list the ingredients but our mixologists keep changing the spec +- **HTML Highball** - Not a real cocktail, more of a mocktail + +SupaClub was made possible with the help of infamous Fyre Fest investor Ja Rule and “Super Bass” songstress Niki Minaj, both of which will also be performing on the opening night. + +If you would like to reserve a spot on the guest list for the opening night please email us at noreply@supabase.com with a link to your GitHub account and "Re: april fools" in the subject line. See you there. diff --git a/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx b/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx new file mode 100644 index 00000000000..406cc058b93 --- /dev/null +++ b/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx @@ -0,0 +1,337 @@ +--- +title: Infinite scroll with Next.js, Framer Motion, and Supabase +description: Lazy load and paginate data on scroll with Next.js and a sprinkle of Framer Motion magic ✨ +author: fsansalvadore +image: infinite-scroll-thumb.jpg +thumb: infinite-scroll-thumb.jpg +tags: + - tutorial + - launch-week + - Next-js +date: '2023-04-04' +toc_depth: 6 +--- + +Every Launch Week, we strive to approach things differently as part of our Kaizen mindset. For [Launch Week 7](https://www.supabase.com/launch-week), we decided to add some twists to our popular ticket system. While we made several changes, this post will focus on one specific modification (we'll share more about the rest this week!). + +Our goal was to feature all the amazing people who generated tickets on a [single page](https://supabase.com/launch-week/tickets). However, displaying them all at once could take a toll on the loading time and negatively affect the user experience. That's where lazy loading smaller subsets via infinite scrolling came in as the solution. + +With infinite scrolling, users can effortlessly scroll through content while only loading the necessary data as they go. This not only enhances performance but also creates a seamless and enjoyable user experience. + +In this post, we'll detail our process of implementing infinite scrolling using Next.js, Framer Motion, and Supabase. + +## Step 0. Load the first batch + +First things first, let's install the dependencies we'll need. + +```bash +npm install @supabase/supabase-js lodash framer-motion +``` + +Set up the supabase-js client and fetch the first 20 tickets through `getServerSideProps` so we don't start with an empty screen. + +We'll assume our table in the db is called `my_tickets_table`. + +```jsx +import { useEffect, useState } from 'react' +import { createClient } from '@supabase/supabase-js' + +const supabase = createClient('supabase-url', 'supabase-key') + +export default function TicketsPage({ tickets }) { + const [loadedTickets, setLoadedTickets] = useState(tickets) + + return ( +
      + {loadedTickets.map((ticket, index) => ( + {/* We'll get to this part later */} + ))} +
      + ) + +export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { + const { data: tickets } = await supabase! + .from('my_tickets_table') + .select('*') + .order('createdAt', { ascending: false }) + .limit(20) + + return { + props: { + tickets, + }, + } +} +``` + +## Step 1. Listen to the scroll + +To detect if the user is scrolling we can listen to the window `scroll` event. + +If you try to `console.log()` the event returned in the listener callback, you'll notice that it fires on every single pixel scrolled. We want to avoid triggering like crazy, so we'll use a [lodash debounce function](https://www.geeksforgeeks.org/lodash-_-debounce-method/) to limit how often we call this event to once every 200 milliseconds. + +Here's what it looks like: + +```jsx +import { useEffect } from 'react' +import { debounce } from 'lodash' + +const handleScroll = () => { + // Do stuff when scrolling +} + +useEffect(() => { + const handleDebouncedScroll = debounce(() => handleScroll(), 200) + window.addEventListener('scroll', handleDebouncedScroll) + return () => { + window.removeEventListener('scroll', handleDebouncedScroll) + } +}, []) +``` + +## Step 2. Check if the container intersects the viewport + +Next, we want to check if the bottom of the tickets container intersects with the bottom of the viewport. We can use the `getBoundingClientRect` method to get the position of the container and then compare it with the height of the viewport. + +```jsx +import { useRef, useState } from 'react' + +// ... + +const containerRef = useRef(null) +const [offset, setOffset] = useState(1) +const [isInView, setIsInView] = useState(false) + +const handleScroll = (container) => { + if (containerRef.current && typeof window !== 'undefined') { + const container = containerRef.current + const { bottom } = container.getBoundingClientRect() + const { innerHeight } = window + setIsInView((prev) => bottom <= innerHeight) + } +} + +useEffect(() => { + if (isInView) { + loadMoreUsers(offset) + } +}, [isInView]) + +return
      {/* List of loaded tickets */}
      +``` + +## Step 3. Load tickets based on the offset + +Now we can load more tickets based on the current offset. We'll use the [range](https://supabase.com/docs/reference/javascript/range) method from the supabase-js library to easily work with the pagination logic. + +```jsx +export default function TicketsPage() { + const PAGE_COUNT = 20 + const [offset, setOffset] = useState(1) + const [isLoading, setIsLoading] = useState(false) + const [isInView, setIsInView] = useState(false) + + useEffect(() => { + if (isInView) { + loadMoreTickets(offset) + } + }, [isInView]) + + const loadMoreTickets = async (offset: number) => { + setIsLoading(true) + // Every time we fetch, we want to increase + // the offset to load fresh tickets + setOffset((prev) => prev + 1) + const { data: newTickets } = await fetchTickets(offset, PAGE_COUNT) + // Merge new tickets with all previously loaded + setLoadedTickets((prevTickets) => [...prevTickets, ...newTickets]) + setIsLoading(false) + } + + const fetchTickets = async (offset, limit) => { + const from = offset * PAGE_COUNT + const to = from + PAGE_COUNT - 1 + + const { data } = await supabase! + .from('my_tickets_table') + .select('*') + .range(from, to) + .order('createdAt', { ascending: false }) + + + return data + } +} +``` + +## Step 4. Animate the tickets + +Now that we have our tickets loaded, we want to add some animation to make them pop like dominos as they appear on the screen. For this, we're going to use the [Framer Motion](https://www.framer.com/motion/) library. + +We'll wrap each ticket in a motion component and add a transition effect to stagger their appearance on the screen: + +```jsx +import { motion } from 'framer-motion' + +// ... + +{ + loadedTickets.map((ticket, index) => { + // each ticket will be delayed based on it's index + // but we need to subtract the delay from all the previously loaded tickets + const recalculatedDelay = i >= PAGE_COUNT * 2 ? (i - PAGE_COUNT * (offset - 1)) / 15 : i / 15 + + return ( + + {/* Actual ticket component */} + + ) + }) +} +``` + +With this code, each ticket will start with an opacity of 0 and a y position of 20. As it animates into view, it will fade in and move up to its final position. The delay for each ticket will be based on its index in the array, creating a nice staggered effect. + +## Step 5. Stop listening when finished + +Once all the tickets have been loaded, we want to stop listening to the scroll event to avoid unnecessary requests. We can do this by setting a state variable called `isLast` to true whenever the length of the response will be less than `PAGE_COUNT`: + +```jsx +if (newTickets.length < PAGE_COUNT) { + setIsLast(true) +} +``` + +We'll use this code to conditionally remove the event listener: + +```jsx +useEffect(() => { + const handleDebouncedScroll = debounce(() => !isLast && handleScroll(), 200) + window.addEventListener('scroll', handleScroll) + return () => { + window.removeEventListener('scroll', handleScroll) + } +}, []) +``` + +Now, when the `isLast` state variable is true, the event listener will be removed and the component will stop listening to the scroll event. + +## Wrap up + +That's it! We hope this post enabled you to build the next awesome infinite scroll. + +Here's the complete code: + +```jsx +import { useEffect, useState, useRef } from 'react' +import { createClient } from '@supabase/supabase-js' +import { debounce } from 'lodash' +import { motion } from 'framer-motion' + +const supabase = createClient('supabase-url', 'supabase-key') + +export default function TicketsPage({ tickets }) { + const PAGE_COUNT = 20 + const containerRef = useRef(null) + const [loadedTickets, setLoadedTickets] = useState(tickets) + const [offset, setOffset] = useState(1) + const [isLoading, setIsLoading] = useState(false) + const [isInView, setIsInView] = useState(false) + + const handleScroll = (container) => { + if (containerRef.current && typeof window !== 'undefined') { + const container = containerRef.current + const { bottom } = container.getBoundingClientRect() + const { innerHeight } = window + setIsInView((prev) => bottom <= innerHeight) + } + } + + useEffect(() => { + const handleDebouncedScroll = debounce(() => !isLast && handleScroll(), 200) + window.addEventListener('scroll', handleScroll) + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, []) + + useEffect(() => { + if (isInView) { + loadMoreTickets(offset) + } + }, [isInView]) + + const loadMoreTickets = async (offset: number) => { + setIsLoading(true) + setOffset((prev) => prev + 1) + const { data: newTickets } = await fetchTickets(offset, PAGE_COUNT) + setLoadedTickets((prevTickets) => [...prevTickets, ...newTickets]) + setIsLoading(false) + } + + const fetchTickets = async (offset) => { + const from = offset * PAGE_COUNT + const to = from + PAGE_COUNT - 1 + + const { data } = await supabase! + .from('my_tickets_table') + .select('*') + .range(from, to) + .order('createdAt', { ascending: false }) + + return data + } + + return ( +
      + { + loadedTickets.map((ticket, index) => { + const recalculatedDelay = + i >= PAGE_COUNT * 2 ? (i - PAGE_COUNT * (offset - 1)) / 15 : i / 15 + + return ( + + {/* Actual ticket component */} + + ) + }) + } +
      + ) + +export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { + const { data: tickets } = await supabase! + .from('my_tickets_table') + .select('*') + .order('createdAt', { ascending: false }) + .limit(20) + + return { + props: { + tickets, + }, + } +} +``` + +If you also want to feature in the endless tickets page and have a chance to win cool swag, you're invited to [generate your unique ticket](https://www.supabase.com/launch-week) until April 16th. + +See you at Launch Week! 👋 diff --git a/apps/www/_customers/epsilon3.mdx b/apps/www/_customers/epsilon3.mdx new file mode 100644 index 00000000000..6c6c94f7ac4 --- /dev/null +++ b/apps/www/_customers/epsilon3.mdx @@ -0,0 +1,97 @@ +--- +title: Epsilon3 digitize paper-based procedures in the space industry using telemetry data, simplifying testing and operations. +name: Epsilon3 +description: Epsilon3 uses Supabase to help teams execute secure and reliable operations in an industry where project spend runs into the billions. +author: rory_wilding +author_title: Supabase +author_url: https://github.com/kiwicopple +author_image_url: https://avatars2.githubusercontent.com/u/10214025?s=400&u=c6775be2ae667e2acae3ccd347fed62bb3f5b3e7&v=4 +authorURL: https://github.com/kiwicopple +logo: /images/customers/logos/epsilon3.png +logo_inverse: /images/customers/logos/light/epsilon3.png +og_image: /images/customers/og/epsilon3.jpg +tags: + - supabase +date: '2023-02-14' +company_url: 'https://epsilon3.io' +stats: + [ + { stat: '00,000', label: Example stat }, + { stat: '00,000', label: Example stat }, + { stat: '00,000', label: Example stat }, + ] +misc: [{ label: 'Founded', text: Los Angeles, California }] +about: Epsilon3 is the OS for spacecraft and complex operations. Epsilon3’s software platform manages complex operational procedures, saving operators time and reducing errors. +--- + +Epsilon3 is a USA-based Y Combinator startup. They digitize paper-based procedures in the space industry using telemetry data, +simplifying testing and operations. Their product is rapidly becoming the OS for space projects and complex operations. + +## Space Operations + +Epsilon3's customers run testing and operational procedures in real-time. Epsilon3's solution allows online collaboration as a team, +instead of being siloed and separated which is an increasingly important requirement in a post-pandemic working world. +Epsilon3 is powering an industry where reliability is paramount. In the space industry, missions cost billions and need to run reliably. + +![Epsilon3 digitizes paper-based processes.](/images/blog/epsilon3/epsilon3-product.gif) + +Epsilon3's software saves operators time and reduces errors to improve complex operations. Their customers are using their software and +successfully sending satellites into space and are about to use Epsilon3's software for an operational Rocket Launch. As a result, their +reputation for improving the quality standard for complex operations has led to demand from other industries. The team is receiving +enquiries from clients in the robotics, logistics and medical equipment sectors who need a structured and standardized way of running +procedures and keeping audit trails. + +## Moving fast in regulated markets + +As a startup, Epsilon3 needs to move fast and scale. They require security, reliability, and performance. + +The team has a background in engineering from Northrop, Google, and SpaceX and understands the technical benefits Postgres offers as a +battle-tested open-source relational database. + +Real-time functionality is also a key for their customers to see how running procedures are progressing as the changes occur. + +The space industry is highly regulated, adding complexity. For example, in the USA, deployments must be in an ITAR-compliant way +(for instance AWS GovCloud supports ITAR compliance) while customers in countries outside the USA may need to host their data within their own +country. These customer requirements mean Epsilon3 must be able to offer flexible hosting options, including on-premises deployment. + + +

      Billion dollar missions need to run reliably and securely.

      +

      + We didn’t just want something that works, we wanted a solution implementing best practices. We + also needed a setup that we could self-host to meet compliance needs and allow on-premises + deployment options for our customers. +

      +

      + We use Supabase because they give us an open-source scalable back-end built by database experts + that we can self-host. +

      +

      + Supabase takes out the mental effort from our back-end infrastructure so we can focus on our + customers needs. +

      +
      + +## Supabase gives Postgres users superpowers + +The team implemented a self-hosted Supabase setup. They use AWS with a self-hosted Supabase API powering an RDS Postgres instance. +This setup gives a balance of rapid development, reduced DevOps, and flexible hosting options. As Supabase uniquely offers [real-time](https://supabase.com/realtime) for +Postgres, it allows their customers to see updates from running procedures. Supabase offers well-supported client libraries that make +Postgres Row Level Security easy to use, which is a critical item on the Epsilon3 roadmap. + +All this was easy for the team to set up, even with self-hosting, so the team was able to focus on what they do best—helping their customers +run complex operations smoothly. With this implementation, Epsilon3 gets all the benefits of Postgres, plus the smooth developer experience and +support that Supabase is known for, without compromising their compliance needs. + +![Epsilon3 uses Supabase for hosting their critical infrastructure.](/images/blog/epsilon3/supabase-epsilon3-architecture.png) + +## Self-hosted Supabase, a superpower + +Self-hosting Supabase allows the crew at Epsilon3 to move fast. They are revolutionizing space operations securely and reliably in real-time whilst +staying compliant. The team is now seeing demand from other industries and is confident in their scaling capability because they know it's +just Postgres under the hood. + +Check out the [Epsilon3 website](https://www.epsilon3.io/) to learn more about the team and their OS for space operations. If you are interested +in self-hosting Supabase then you can **[contact us](https://forms.supabase.com/enterprise)** to learn more. diff --git a/apps/www/_customers/happyteams.mdx b/apps/www/_customers/happyteams.mdx new file mode 100644 index 00000000000..9a36c5ba610 --- /dev/null +++ b/apps/www/_customers/happyteams.mdx @@ -0,0 +1,72 @@ +--- +title: HappyTeams unlocks better performance and reduces cost with Supabase. +name: HappyTeams +description: How a bootstrapped startup migrated from Heroku to Supabase in 30 minutes and never looked back. +author: rory_wilding +author_title: Supabase +author_url: https://github.com/kiwicopple +author_image_url: https://avatars2.githubusercontent.com/u/10214025?s=400&u=c6775be2ae667e2acae3ccd347fed62bb3f5b3e7&v=4 +authorURL: https://github.com/kiwicopple +logo: /images/customers/logos/happyteams.png +logo_inverse: /images/customers/logos/light/happyteams.png +og_image: /images/customers/og/happyteams.jpg +tags: + - supabase +date: '2023-02-16' +company_url: 'https://happyteams.io/' +stats: + [ + { stat: '00,000', label: Example stat }, + { stat: '00,000', label: Example stat }, + { stat: '00,000', label: Example stat }, + ] +misc: [{ label: 'Founded', text: 'Dublin, Ireland' }] +about: HappyTeams is a purpose-built solution that empowers management to unlock valuable team insights. +--- + +## The Challenge + +> Finding a painless and hands-free database experience to support growth + +[HappyTeams](https://happyteams.io/) is a growing startup with an ambitious vision. The company is bootstrapped, so technical resources are scarce. Devs need to do more with less. This means staying laser-focused on delivering features, rather than database timeouts or performance issues. + +The company launched its app on Heroku Postgres. But one year in, they were experiencing scaling issues. They needed to handle thousands of users while also shipping new features and building out integrations. Issues with database slowness and concurrent connections were holding them back. + +Michael Dever, the founder and CTO of HappyTeams, realized it was time for a change. A large overhaul of the app in early 2022 presented the opportunity to do so: "When you're in that first sprint of building a product, you're kind of locked with that first database decision you make. When we did a rebuild, it was time to look at something like Supabase." + +![HappyTeams Home Page](/images/blog/2023-02-16-case-study-happyteams/happyteams-home-page.png) + +## Why Supabase? + +There were four main factors that led HappyTeams to switch from Heroku Postgres to Supabase: + +1. **Better performance**. The team was experiencing uneven performance with Heroku, which was sluggish at times. Supabase was consistently fast. + +2. **Enabling a large number of direct connections and connection pooling with PgBouncer**. HappyTeams was constantly hitting limits on the number of database connections. They could solve this problem with Supabase's [flexible plans](https://supabase.com/pricing) and [support for PgBouncer](https://supabase.com/blog/supabase-pgbouncer). + +3. **Quality of support, documentation, and onboarding**: While Heroku provided no support at the tier they were on, the Supabase team was readily available: "Responses were powerful and they answered all my questions, even on a Sunday. That just further sold the product for me." He was also impressed with the [documentation](https://supabase.com/docs) on the Supabase website and the onboarding process. + +4. **Lower costs**. Moving to Supabase reduced HappyTeams's costs: The base plan comes with everything they need to scale the product. + + + Great documentation, great product, great pricing. It sells itself, really. + + +## Supabase provides a fast and reliable production database + +After a 30 minute migration, HappyTeams can iterate quickly, add new features, and improve their product based on customer feedback + +Once they made the decision, migrating from Heroku to Supabase was simple. HappyTeams completed their database migration in less than an hour using pg_dump and pg_restore. + +Performance is no longer an issue. "Supabase just worked", says Michael. Pooling connections using PgBouncer was effortless - solving a recurring pain point for HappyTeams. + +"Supabase is so fast. Way, way, way faster than our Heroku Postgres database, which I think was located in the same region." + +Today, Supabase is an integral part of HappyTeams's tech stack. It has allowed the company to move fast and rapidly release new features - with minimal tech resources devoted to their backend infrastructure. + +Thanks to Supabase's ease of use and reliability, HappyTeams can iterate quickly based on feature requests and deliver more value to their user base. They are planning to expand their usage, exploring functionality such as [Edge Functions](https://supabase.com/edge-functions) and the [API client](https://supabase.com/docs/guides/api). + + + When we needed to add a comments feature to our app, we could get it done in just two days. + Supabase is really like a dream come true for a developer. + diff --git a/apps/www/_customers/mobbin.mdx b/apps/www/_customers/mobbin.mdx new file mode 100644 index 00000000000..06774869a3e --- /dev/null +++ b/apps/www/_customers/mobbin.mdx @@ -0,0 +1,97 @@ +--- +title: How Mobbin migrated 200,000 users from Firebase for a better authentication experience. +name: Mobbin +description: Mobbin helps over 200,000 creators globally search and view the latest design patterns from well-known apps. +author: rory_wilding +author_title: Supabase +author_url: https://github.com/kiwicopple +author_image_url: https://avatars2.githubusercontent.com/u/10214025?s=400&u=c6775be2ae667e2acae3ccd347fed62bb3f5b3e7&v=4 +authorURL: https://github.com/kiwicopple +logo: /images/customers/logos/mobbin.png +logo_inverse: /images/customers/logos/light/mobbin.png +og_image: /images/customers/og/mobbin.jpg +tags: + - supabase +date: '2023-02-14' +company_url: 'https://mobbin.design' +stats: + [ + { stat: '00,000', label: Example stat }, + { stat: '00,000', label: Example stat }, + { stat: '00,000', label: Example stat }, + ] +misc: [{ label: 'Founded', text: Singapore }] +about: Mobbin is a comprehensive, expert-curated library of interfaces from the best-in-class digital products that helps product designers find relevant design inspiration and references with significantly less time and effort. +--- + +Mobbin helps over 200,000 creators globally search and view the latest design patterns from well-known apps. +Their platform allows creators to search screenshots of mobile apps to inspire product development. +Creators use Mobbin to combine inspiration and develop new experiences. + +## From Idea to 30,000 users in one Product Hunt launch + +The initial concept for Mobbin was just a Dropbox full of screenshots. After sharing with their early users, +the team tried organizing them into folders but this quickly became unscalable. Clear interest from users encouraged them to build a full website. They used Firebase for the backend and launched on +[Product Hunt](https://www.producthunt.com/posts/mobbin-1). + +Mobbin went viral, and within the first week, they had 30,000 users. They have now more than 400,000 +registered users through word of mouth. Their curation continues to attract designers and creators globally. + +![Mobbin cover](/images/blog/mobbin/mobbin-cover.png) + +## Scaling issues put Mobbin growth at risk + +Firebase helped the team launch quickly, but at scale, they found Firebase lacked the functionality required for a high-quality user experience. + +The first problem was duplicate user logins. Firebase Authentication doesn't automatically merge users with the same email address. +For example, users cannot log in with Google and Facebook if they share the same email address. +Users struggled to upgrade to a paid account because they couldn't remember which authentication provider they signed up with. + +Next, they had issues with data integrity. While Firebase has 99.95% uptime, application code can (and does) fail. +Mobbin didn't want to implement idempotence for every mutative Firebase Function. Between a failed Function execution and a retry, +a document state might change without strict validation. The team could never be confident their data had the level of integrity they required. + +Finally, the team grew concerned with their increasing Firebase bill. Because of the document-based data structure, +their API requests required several round trips. At one point, they were making 5 million API requests per month. +These round trips made their website slow and led to a notable time investment hacking together a fix. + +![Mobbing screenshots](/images/blog/mobbin/mobbin-screenshots.jpg) + +## Migrating to Supabase + +Mobbin's initial product gave them a comprehensive understanding of customer requirements. For their second version, +they needed a better authentication experience, fairer pricing, and a relational database to deliver a quality +application sustainable for their business model. + +The team found Supabase and realized it covered all the bases. Not only that, their team had experience with the +tools open source tools that Supabase is built with. + +[Authentication](https://supabase.com/auth), built using [GoTrue](https://github.com/supabase/gotrue), instantly solved their users' login issues. +Under the hood, Supabase is just Postgres, and Jian Jie (co-founder and CTO of Mobbin) knows this is one of the best battle-tested +open source relational databases. + +They decided that moving to Supabase was a no-brainer. The process itself was smooth - the real challenge was validating their +data integrity due to historical failures on their serverless functions. + +Mobbin is now successfully running on top of Supabase, with superior performance compared to their old Firebase setup. +Because Supabase does not charge based on API requests, Mobbin significantly reduced their monthly spending without maintaining a custom backend. + + +

      Migrating to Supabase meant that we could instantly fix our Auth problems and save money.

      +

      + Just being on Supabase alone gives us confidence we can deliver on whatever users need in the + future. +

      +

      + The cool thing is now we know under the hood it's just Postgres, it really does feel like + anything is possible for the future of our product +

      +
      + +## Supabase saves Mobbin time and money + +Migrating to Supabase helped the team at Mobbin instantly improve the end-user experience and save costs. +They now add new features with confidence, and they continue to help creators all over the globe find inspiration for their next project. +You can [check out Mobbin on their website](https://mobbin.design/browse/ios/apps). + +[Sign up](https://app.supabase.com/) for Supabase's free tier and set up a scalable backend in less than 2 minutes. diff --git a/apps/www/_customers/replenysh.mdx b/apps/www/_customers/replenysh.mdx new file mode 100644 index 00000000000..d6454cde443 --- /dev/null +++ b/apps/www/_customers/replenysh.mdx @@ -0,0 +1,87 @@ +--- +title: Replenysh uses Supabase to implement OTP in less than 24 hours. +name: Replenysh +description: With Supabase, Replenysh gets a slick auth experience, reduces DevOps overhead, and continues to scale with Postgres. +author: rory_wilding +author_title: Supabase +author_url: https://github.com/kiwicopple +author_image_url: https://avatars2.githubusercontent.com/u/10214025?s=400&u=c6775be2ae667e2acae3ccd347fed62bb3f5b3e7&v=4 +authorURL: https://github.com/kiwicopple +logo: /images/customers/logos/replenysh.png +logo_inverse: /images/customers/logos/light/replenysh.png +og_image: /images/customers/og/replenysh.jpg +tags: + - supabase +date: '2023-02-14' +company_url: https://replenysh.com/ +stats: + [ + { stat: '00,000', label: Example stat }, + { stat: '00,000', label: Example stat }, + { stat: '00,000', label: Example stat }, + ] +misc: [{ label: 'Founded', text: Southern California }] +about: Replenysh is a digital platform that connects global brands with local communities to recover materials and eliminate the concept of waste. +--- + +Replenysh connects brands who want to recover their products and packaging with communities looking to monetize used materials. + +Learn how Replenysh uses Supabase to power the circular economy, redefining how brands interact with their customers & products. + +## Material recovery offers brands a new way to connect to their customers + +At the end of a linear economy, products usually end up in landfills or the ocean. Every year, 8 million metric tons of plastics enter our ocean. This is on top of the estimated 150 million metric tons that currently circulate our marine environments. In the USA alone, every year, 139 million tons of waste is sent to landfills. + +The problem is our current waste and recycling infrastructure can't support that demand. With less than 10% of material actually getting recycled, the vast majority just ends up in a landfill or as pollution. + +Replenysh is building a solution to this problem by creating entirely new infrastructure to help brands get their materials back. They're building an ecosystem of tools to enable any community in the world to do the right thing. + +Whenever material is dropped off at a host, brands have the opportunity to engage with that person, resulting in a win-win situation for the planet, the brands, and the individual who all want to make a positive impact. Anyone in the community can take their used products and materials to the hosts — with that material being tracked all the way back to the brands and manufacturers who will reuse it. Brands have the opportunity to engage with that person, resulting in a win-win situation for the planet, the brands, and the individual who all want to make a positive impact. This turns a traditional consumption model into an opportunity for brands and customers to participate in the circular economy. + +Brands are adopting Replenysh to demonstrate their commitment to making a positive environmental impact, while seeking to engage with consumers in a new, authentic way. Replenysh provides the technology to communities that are helping build the future infrastructure where all materials are traceable and reused. + +![Linear vs Circular economy](/images/blog/replenysh/circular-economy.png) + +## Refactoring code leads to a better way forward + +Historically the team created their own backend infrastructure, setting up APIs and Authentication to run their own OTP system with Twilio and passwords. Postgres is essential to their requirements because Replenysh use Row Level Security for authorization. Before adopting Supabase, the team used a Haskell server with a different Postgres instance deployed on Heroku. + +The team decided to do a large refactor of their codebase and began investigating alternative hosting options to help speed up development time and reduce maintenance. + +Firebase seemed appealing due to its ability to get started quickly, but the team didn't want to migrate away from a relational database. Specifically, Postgres and RLS fit their requirements. + +When the team discovered Supabase on Hacker News they felt excitement. Supabase has a reputation for a smooth developer experience, providing Postgres and Row Level Security as a core feature. + +As a bonus, Supabase provided more than just a database, reducing their DevOps overhead. After reading the [Supabase Auth documentation](https://supabase.com/docs/guides/auth/overview) the team determined they can have the functionality they need, like SMS OTP. When they saw [Mobbin are using Supabase at scale](https://supabase.com/case-studies-mobbin), they decided to migrate to Supabase. + +## Globally recognized brands need quality at scale + +Less than a day after beginning development with Supabase, the team completed their SMS OTP implementation, and deployed their app to the app store a few weeks later. + +![Replenysh-Mobile-App](/images/blog/replenysh/Replenysh-Mobile-App.png) + +Replenysh clients include brands with global recognition and millions of customers. With their reputation at stake, major brands have a high-quality threshold. If a global brand asks its customers to adopt a technology, the user experience must be high quality and reliable. Their customers' primary interaction point is mobile-first. This means a robust and smooth mobile Auth experience is non-negotiable. After switching to Supabase, when they demo, clients are instantly impressed with just how easy it is for users to log in with an SMS OTP and start participating in the circular economy. As a result, some of the world's largest retailers and beverage companies have recently joined Replenysh and the circular economy. + +![Replenysh-large-screen-view](/images/blog/replenysh/Replenysh-large-screen-view.png) + +## Supabase means faster development and reduced maintenance + + +

      + We saw the Auth update for launch week we thought it was the perfect time to get going with + Supabase. +

      +

      + We implemented & pushed live phone (OTP) auth and had something ready to deploy to the app store + in less than 24-hours! +

      +

      + Supabase's developer experience lived up to its reputation. Our app is ready to launch with + major brands to help them reach sustainability goals and connect with their customers in a + meaningful way +

      +
      + +## Supabase help Replenysh continue to scale + +Supabase turbo-charged Replenysh's development time with a seamless end-user mobile login experience. They are ready to help major brands engage with their users through participation in the circular economy. With Supabase, the team knows they have a slick auth experience, reduced DevOps overhead, and can continue to scale with Postgres. You can test out the Supabase developer experience today by [starting a new project on the free tier!.](https://app.supabase.com) diff --git a/apps/www/_customers/xendit.mdx b/apps/www/_customers/xendit.mdx new file mode 100644 index 00000000000..7f3b8a131f7 --- /dev/null +++ b/apps/www/_customers/xendit.mdx @@ -0,0 +1,43 @@ +--- +title: Xendit use Supabase and create a full solution shipped to production in less than one week. +name: Xendit +description: As a payment processor, Xendit are responsible for verifying that all transactions are legal. +author: rory_wilding +author_title: Supabase +author_url: https://github.com/kiwicopple +author_image_url: https://avatars2.githubusercontent.com/u/10214025?s=400&u=c6775be2ae667e2acae3ccd347fed62bb3f5b3e7&v=4 +authorURL: https://github.com/kiwicopple +logo: /images/customers/logos/xendit.png +logo_inverse: /images/customers/logos/light/xendit.png +og_image: /images/customers/og/xendit.jpg +company_url: https://xendit.co +tags: + - supabase +date: '2023-02-14' +stats: + [ + { stat: '120', label: Staff count }, + { stat: '00,000', label: Example stat }, + { stat: '00,000', label: Example stat }, + ] +misc: [{ label: 'Founded', text: Jakarta Selatan }] +about: Xendit is a financial technology company that provides payment solutions and simplifies the payment process for businesses in Indonesia, the Philippines, and Southeast Asia, from SMEs and e-commerce startups to large enterprises. +--- + +## Challenge + +As a payment processor, Xendit are responsible for verifying that all transactions are legal. Any transactions which are suspicions must be verified against a strict set of criteria, and the parties involved need to be checked against international sanctions lists. This is a critical anti-money-laundering operation and needs to be performed in realtime to prevent any delays on legitimate payments. + +## Why they chose Supabase + +Xendit needed something fast. Something that was cheaper than using the global players like Worldcheck or Refinitiv. Xendit already uses Postgres for a lot of their critical infrastructure, and so Xendit team are familiar with the technology and comfortable in it's ability to scale. + +## What they built + +Xendit parses international sanctions lists from the UN and the Indonesian government and loads them into Supabase. Since Supabase provides a full Postgres server, they can then use the [Trigram](https://www.postgresql.org/docs/current/pgtrgm.html) extension to perform full-text search on the lists, with a relevance score on every search. + +Supabase was perfect for their use case, as they needed something built fast. The full solution was built and in production in less than one week. + +> The full solution was built and in production in less than one week. + +Xendit created a database function for searching, which they are able to call directly using their Python clients. They have plans to iterate on the current implementation using more advanced techniques, like machine learning, but for now the Supabase system has been in Production for 9 months without a problem. diff --git a/apps/www/components/BuiltWithSupabase/index.tsx b/apps/www/components/BuiltWithSupabase/index.tsx index 48d746c84c1..b467d87e570 100644 --- a/apps/www/components/BuiltWithSupabase/index.tsx +++ b/apps/www/components/BuiltWithSupabase/index.tsx @@ -16,18 +16,22 @@ const BuiltExamples = () => {

      There are many example apps and starter projects to get going

      diff --git a/apps/www/components/Carousels/SplitCodeBlockCarousel.tsx b/apps/www/components/Carousels/SplitCodeBlockCarousel.tsx index c8f6c619d70..51d62b25d1e 100644 --- a/apps/www/components/Carousels/SplitCodeBlockCarousel.tsx +++ b/apps/www/components/Carousels/SplitCodeBlockCarousel.tsx @@ -126,9 +126,7 @@ function SplitCodeBlockCarousel(props: SplitCodeBlockCarousel) { {extension.url && ( - + )} diff --git a/apps/www/components/CustomerStories/index.tsx b/apps/www/components/CustomerStories/index.tsx new file mode 100644 index 00000000000..c08d20b92cb --- /dev/null +++ b/apps/www/components/CustomerStories/index.tsx @@ -0,0 +1,54 @@ +import SectionHeader from 'components/UI/SectionHeader' +import PostTypes from '~/types/post' +import SectionContainer from '../Layouts/SectionContainer' +import Link from 'next/link' +import { GlassPanel } from 'ui' +import TextLink from '../TextLink' + +const CustomerStories = ({ customerStories }: any) => { + const caseStudyThumbs = customerStories.map((blog: PostTypes, idx: number) => { + return { + logo: blog.logo, + logoInverse: blog.logo_inverse, + title: blog.title, + link: blog.url, + } + }) + + return ( + +
      + +
      +
      + {caseStudyThumbs.map((caseStudy: any, i: number) => ( + + + + {caseStudy.description} + + + + ))} +
      +
      + +
      +
      + ) +} + +export default CustomerStories diff --git a/apps/www/components/DarkModeToggle.tsx b/apps/www/components/DarkModeToggle.tsx index 789bdd7a5a4..4b3b03cc7aa 100644 --- a/apps/www/components/DarkModeToggle.tsx +++ b/apps/www/components/DarkModeToggle.tsx @@ -4,14 +4,6 @@ import { IconSun, IconMoon } from 'ui' function DarkModeToggle() { const { isDarkMode, toggleTheme } = useTheme() - const toggleDarkMode = () => { - localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString()) - toggleTheme() - - const key = localStorage.getItem('supabaseDarkMode') - document.documentElement.className = key === 'true' ? 'dark' : '' - } - return (
      @@ -24,7 +16,7 @@ function DarkModeToggle() { isDarkMode ? 'bg-scale-500' : 'bg-scale-900' } mx-5 `} - onClick={() => toggleDarkMode()} + onClick={() => toggleTheme()} > Toggle Themes { const { isDarkMode } = useTheme() + const { pathname } = useRouter() + const isLaunchWeekPage = pathname.includes('launch-week') return (
      diff --git a/apps/www/components/ImageGrid.tsx b/apps/www/components/ImageGrid.tsx index fb4782c985b..49e6c28217f 100644 --- a/apps/www/components/ImageGrid.tsx +++ b/apps/www/components/ImageGrid.tsx @@ -1,7 +1,7 @@ -import { useRouter } from 'next/router' import Image from 'next/image' - import Link from 'next/link' +import { motion } from 'framer-motion' + interface iImages { name: string image: string @@ -19,6 +19,7 @@ interface iImageGrid { lgCols?: colSizes padding?: paddingSizes className?: string + animated?: boolean } const ImageGrid = ({ @@ -28,6 +29,7 @@ const ImageGrid = ({ lgCols = 8, padding = 8, className, + animated = false, }: iImageGrid) => { const smBreakpoint = { 3: 'grid-cols-3', @@ -70,7 +72,7 @@ const ImageGrid = ({ if (x.link) { return ( -
      +
      {children}
      @@ -80,9 +82,22 @@ const ImageGrid = ({ } } + const MaybeAnimatedDiv = ({ children, ...rest }: any) => + animated ? ( + + {children} + + ) : ( +
      {children}
      + ) + return ( -
      -
      + ) })} diff --git a/apps/www/components/LaunchWeek/Banners/CountdownBanner.tsx b/apps/www/components/LaunchWeek/Banners/CountdownBanner.tsx new file mode 100644 index 00000000000..12fe5c3738c --- /dev/null +++ b/apps/www/components/LaunchWeek/Banners/CountdownBanner.tsx @@ -0,0 +1,85 @@ +import React from 'react' +import Link from 'next/link' +import Countdown from 'react-countdown' +import _announcement from '~/data/Announcement.json' +import { AnnouncementProps } from '../../Nav/Announcement' +import { useRouter } from 'next/router' + +interface CountdownStepProps { + value: string | number + unit: string +} + +const announcement = _announcement as AnnouncementProps + +function CountdownStep({ value, unit }: CountdownStepProps) { + return ( +
      +
      + {value} + {unit} +
      +
      + ) +} + +function CountdownBanner() { + const { pathname } = useRouter() + const isLaunchWeekPage = pathname === '/launch-week' + const isLaunchWeekSection = pathname.includes('launch-week') + + const renderer = ({ days, hours, minutes, seconds, completed }: any) => { + if (completed) { + // Render a completed state + return ( +
      +

      Supabase Launch Week 7

      + +
      + ) + } else { + // Render countdown + return ( +
      +

      + Supabase Launch Week 7 +

      +
      + : + : + : + +
      + {!isLaunchWeekPage && ( + + )} +
      + ) + } + } + + return ( +
      + +
      + ) +} + +export default CountdownBanner diff --git a/apps/www/components/LaunchWeek/LW7BgGraphic.tsx b/apps/www/components/LaunchWeek/LW7BgGraphic.tsx new file mode 100644 index 00000000000..f5b29f01eb6 --- /dev/null +++ b/apps/www/components/LaunchWeek/LW7BgGraphic.tsx @@ -0,0 +1,45 @@ +import { motion, useScroll, useTransform } from 'framer-motion' +import Image from 'next/image' +import { useMobileViewport } from '../../hooks/useMobileViewport' +import styles from './lw7-bg-graphic.module.css' + +export default function LW7BgGraphic() { + const { scrollYProgress } = useScroll() + const isMobile = useMobileViewport(768) + + const graphicsY = useTransform( + scrollYProgress, + // Map scrollYProgress from these values: + [0, 0.5], + // Into these values: + [0, isMobile ? 70 : 200] + ) + + return ( +
      + + + +
      + ) +} diff --git a/apps/www/components/LaunchWeek/LabelBadge.tsx b/apps/www/components/LaunchWeek/LabelBadge.tsx new file mode 100644 index 00000000000..43960ce0a53 --- /dev/null +++ b/apps/www/components/LaunchWeek/LabelBadge.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +interface Props { + text: string + gradientText?: boolean +} + +export default function LabelBadge({ text, gradientText = false }: Props) { + return ( + + {text} + + ) +} diff --git a/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekLogoHeader.tsx b/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekLogoHeader.tsx new file mode 100644 index 00000000000..99880d97369 --- /dev/null +++ b/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekLogoHeader.tsx @@ -0,0 +1,33 @@ +import classNames from 'classnames' +import Image from 'next/image' +import { motion } from 'framer-motion' +import { useMobileViewport } from '../../../hooks/useMobileViewport' + +export function LaunchWeekLogoHeader() { + const isMobile = useMobileViewport(640) + + return ( +
      + +

      + Launch week + + + +

      +

      + April 10th – 14th at 7AM PT | 10AM ET +

      +
      +
      + ) +} diff --git a/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekPrizeCard.tsx b/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekPrizeCard.tsx new file mode 100644 index 00000000000..057e74db4c5 --- /dev/null +++ b/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekPrizeCard.tsx @@ -0,0 +1,68 @@ +import Image from 'next/image' +import React from 'react' +import { motion } from 'framer-motion' + +type animateFromType = 'left' | 'right' | 'up' | 'down' | 'opacityOnly' + +export default function LaunchWeekPrizeCard({ + imageUrl = '', + imageWrapperClassName, + content, + className, + animateFrom, + imgObjectPosition = 'center', +}: { + imageUrl: string + imageWrapperClassName?: string + content: any + className?: string + animateFrom?: animateFromType + imgObjectPosition?: string +}) { + const finalState = { x: 0, y: 0, opacity: 1 } + + function getAnimationFrom(animationDirection: animateFromType) { + switch (animationDirection) { + case 'left': + return { x: -20, opacity: 0 } + case 'up': + return { y: -20, opacity: 0 } + case 'right': + return { x: 20, opacity: 0 } + case 'down': + return { y: 20, opacity: 0 } + case 'opacityOnly': + return { opacity: 0 } + } + } + + return ( + +
      +
      + +
      +
      {content && content}
      +
      +
      + ) +} diff --git a/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekPrizeSection.tsx b/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekPrizeSection.tsx new file mode 100644 index 00000000000..05f73843e1e --- /dev/null +++ b/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekPrizeSection.tsx @@ -0,0 +1,122 @@ +import React from 'react' +import LabelBadge from '../LabelBadge' +import LaunchWeekPrizeCard from './LaunchWeekPrizeCard' +import { motion } from 'framer-motion' + +export default function LaunchWeekPrizeSection({ className }: { className?: string }) { + const finalAnimationState = { y: 0, opacity: 1 } + + return ( +
      +
      +
      + +
      + + + + + + + + + +
      +

      + Get your winning ticket +

      +

      + Join us on April 16th for Launch Week 7's final day +
      and find out if you are one of the lucky + winners. Get sharing! +

      +
      +
      +
      +
      + +

      + Main award +

      +

      + Mechanical Keyboard +

      +

      + Increase your chances of winning limited edition 62-Key ISO Custom Mechanical + Keyboard by sharing your ticket on Twitter and LinkedIn. +

      + + } + /> + +
      +
      + + Supabase T-shirt + + } + animateFrom="up" + /> + + Supabase Socks + + } + animateFrom="right" + /> +
      + + Supabase Sticker Pack + + } + /> +
      +
      +
      +
      +
      + ) +} diff --git a/apps/www/components/LaunchWeek/LaunchSection/TicketBrick.tsx b/apps/www/components/LaunchWeek/LaunchSection/TicketBrick.tsx new file mode 100644 index 00000000000..50e0438868d --- /dev/null +++ b/apps/www/components/LaunchWeek/LaunchSection/TicketBrick.tsx @@ -0,0 +1,62 @@ +import React, { useState } from 'react' +import TicketHeader from '../Ticket/TicketHeader' +import TicketNumber from '../Ticket/TicketNumber' +import { UserData } from '../Ticket/hooks/use-conf-data' +import Image from 'next/image' + +interface Props { + user: UserData +} + +export function TicketBrick({ user }: Props) { + const [isLoading, setLoading] = useState(true) + + const golden = user.sharedOnLinkedIn && user.sharedOnTwitter + + // reg_bg_57.png + const baseImagePath = `https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw7/tickets_bg/` + + const randomNumber = Math.floor(Math.random() * 200) + 1 + const goldRandomNumber = Math.floor(Math.random() * 56) + 1 + + return ( + <> +
      + setLoading(false)} + /> +
      + +
      + {user.username} +
      +

      + {user.name ? user.name : user.username} +

      + {user.name &&

      @{user.username}

      } +
      +
      + +
      +
      + + ) +} diff --git a/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWall.tsx b/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWall.tsx new file mode 100644 index 00000000000..e499c4c40ff --- /dev/null +++ b/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWall.tsx @@ -0,0 +1,49 @@ +import Link from 'next/link' +import React from 'react' +import { Button } from 'ui' +import { useMobileViewport } from '../../../hooks/useMobileViewport' +import { UserData } from '../Ticket/hooks/use-conf-data' +import { TicketBrickWallSlider } from './TicketBrickWallSlider' + +type user = UserData + +interface Props { + users: user[] +} + +export default function TicketBrickWall({ users }: Props) { + const isMobile = useMobileViewport(768) + return ( +
      +
      +
      +

      + Explore the unique tickets generated by our community +

      +
      +
      + + + {isMobile && } +
      + +
      +
      + ) +} diff --git a/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx b/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx new file mode 100644 index 00000000000..59271c61e43 --- /dev/null +++ b/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx @@ -0,0 +1,92 @@ +import { useEffect, useRef, useState } from 'react' +import Image from 'next/image' +import Link from 'next/link' +import { UserData } from '../Ticket/hooks/use-conf-data' +import { useWindowSize } from 'react-use' + +import { Swiper, SwiperSlide } from 'swiper/react' +import SwiperCore, { Autoplay } from 'swiper' +import 'swiper/swiper.min.css' + +SwiperCore.use([Autoplay]) + +interface Props { + users: UserData[] + reverse?: boolean + speed?: number +} + +export function TicketBrickWallSlider({ users, reverse, speed = 10000 }: Props) { + const ref = useRef(null) + const [swiperInstance, setSwiperInstance] = useState(null) + const { width } = useWindowSize() + const STORAGE_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw7' + const BUCKET_FOLDER_VERSION = 'v3' + const getOgUrl = (username: string, isGold: boolean) => + `${STORAGE_URL}/tickets/gallery/${ + isGold ? 'golden' : 'regular' + }/${BUCKET_FOLDER_VERSION}/${username}.png` + + useEffect(() => { + // trigger autoplay if viewport resize + if (swiperInstance) swiperInstance?.autoplay?.run() + }, [width]) + + return ( +
      +
      + setSwiperInstance(swiper)} + centeredSlides={true} + spaceBetween={10} + slidesPerView={5} + speed={speed} + loop={true} + watchOverflow + threshold={2} + updateOnWindowResize + allowTouchMove={false} + autoplay={{ + delay: 0, + disableOnInteraction: true, + reverseDirection: reverse, + }} + breakpoints={{ + 320: { + slidesPerView: 2.5, + }, + 720: { + slidesPerView: 3.5, + }, + 1440: { + slidesPerView: 5, + }, + }} + > + {users.map((user, i) => ( + + + +
      +
      + {user.username} +
      +
      +
      + +
      + ))} +
      +
      +
      + ) +} diff --git a/apps/www/components/LaunchWeek/LaunchSection/brick-wall.module.css b/apps/www/components/LaunchWeek/LaunchSection/brick-wall.module.css new file mode 100644 index 00000000000..c81740ed399 --- /dev/null +++ b/apps/www/components/LaunchWeek/LaunchSection/brick-wall.module.css @@ -0,0 +1,6 @@ +/* mask overlay on left & right */ +.brick-wall-mask--left, +.brick-wall-mask--right { + background: linear-gradient(358.88deg, #1c1c1c 24.57%, #1c1c1c00 87.73%), + linear-gradient(358.88deg, #1c1c1c 24.57%, #1c1c1c00 87.73%); +} diff --git a/apps/www/components/LaunchWeek/LaunchSection/ticket-brick-wall-slider.module.css b/apps/www/components/LaunchWeek/LaunchSection/ticket-brick-wall-slider.module.css new file mode 100644 index 00000000000..164e683b3a2 --- /dev/null +++ b/apps/www/components/LaunchWeek/LaunchSection/ticket-brick-wall-slider.module.css @@ -0,0 +1,3 @@ +.swiper-wrapper .swiper-slide { + width: 300px !important; +} diff --git a/apps/www/components/LaunchWeek/ReferralIndicator.tsx b/apps/www/components/LaunchWeek/ReferralIndicator.tsx new file mode 100644 index 00000000000..fd3a7f4c3fd --- /dev/null +++ b/apps/www/components/LaunchWeek/ReferralIndicator.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data' +import useWinningChances from './Ticket/hooks/useWinningChances' + +export default function ReferralIndicator() { + const winningChances = useWinningChances() + + return ( +
      +
      + {winningChances}x chance +
      +
      + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/ActualTicket.tsx b/apps/www/components/LaunchWeek/Ticket/ActualTicket.tsx new file mode 100644 index 00000000000..e687a263c48 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ActualTicket.tsx @@ -0,0 +1,189 @@ +import cn from 'classnames' +import { useRef, useState } from 'react' +import styles from './ticket.module.css' +import styleUtils from './utils.module.css' +import TicketVisual from './TicketVisual' +import TicketActions from './TicketActions' +import TicketCopy from './ticket-copy' + +import { UserData } from './hooks/use-conf-data' +import ReferralIndicator from '../ReferralIndicator' +import useWinningChances from './hooks/useWinningChances' +import { SITE_URL } from '~/lib/constants' +import { useMobileViewport } from '../../../hooks/useMobileViewport' + +type TicketGenerationState = 'default' | 'loading' + +type Props = { + username: UserData['username'] + ticketNumber: UserData['ticketNumber'] + name: UserData['name'] + golden: UserData['golden'] + bgImageId: UserData['bg_image_id'] + referrals: number + sharePage?: boolean +} + +export default function Ticket({ + username, + name, + ticketNumber, + sharePage, + golden, + bgImageId, + referrals, +}: Props) { + const isMobile = useMobileViewport(1023) + const [ticketGenerationState, setTicketGenerationState] = + useState('default') + const divRef = useRef(null) + const winningChances = useWinningChances() + + return ( +
      +
      +
      + + {username && ( +
      + +
      + )} +
      +
      +
      +
      +

      + {!sharePage ? ( + <> + {name ? ( + <> + {winningChances === 1 && ( + + You're in the draw!
      + Now make it gold. +
      + )} + {winningChances === 2 && ( + + You've doubled your{' '} +
      + chance! +
      Almost{' '} + gold. +
      + )} + {winningChances === 3 && ( + + You're gold!
      + You've maxed your
      chances of winning! +
      + )} + + ) : ( + + Generate your ticket.
      + Win the SupaKeyboard. +
      + )} + + ) : ( + + {name ? name : username}'s
      + unique ticket +
      + )} +

      + +
      + {!sharePage ? ( + <> + {golden ? ( +

      + Join us on April 16th for Launch Week 7's final day and find out if you are one + of the lucky winners. +

      + ) : username ? ( +

      + Why stop there? Increase your chances of winning by sharing your unique ticket. + Get sharing! +

      + ) : ( +

      + We have some fantastic swag up for grabs, including 3 limited-edition mechanical + keyboards that you won't want to miss. +

      + )} + + ) : ( + <> +

      + Get yours and win some fantastic swag, including a limited-edition mechanical + keyboard that you won't want to miss. +

      + + + + )} +
      + + {!sharePage && username && } +
      +
      + {username && ( + + )} +
      +
      +
      + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/TicketActions.tsx b/apps/www/components/LaunchWeek/Ticket/TicketActions.tsx new file mode 100644 index 00000000000..ee351e58f04 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/TicketActions.tsx @@ -0,0 +1,122 @@ +import { useEffect, useRef, useState } from 'react' +import { SITE_URL, TWEET_TEXT, TWEET_TEXT_GOLDEN } from '~/lib/constants' +import { IconCheckCircle } from 'ui' +import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data' +import { useParams } from '~/hooks/useParams' +import TicketForm from './TicketForm' + +type TicketGenerationState = 'default' | 'loading' + +type Props = { + username: string + golden?: boolean + ticketGenerationState?: TicketGenerationState + setTicketGenerationState: (ticketGenerationState: TicketGenerationState) => void +} + +export default function TicketActions({ + username, + golden = false, + ticketGenerationState, + setTicketGenerationState, +}: Props) { + const [imgReady, setImgReady] = useState(false) + const [loading, setLoading] = useState(false) + const downloadLink = useRef() + const permalink = (medium: string) => + encodeURIComponent(`${SITE_URL}/tickets/${username}?lw=7${golden ? `&golden=true` : ''}`) + const text = encodeURIComponent(golden ? TWEET_TEXT_GOLDEN : TWEET_TEXT) + const { userData } = useConfData() + const tweetUrl = `https://twitter.com/intent/tweet?url=${permalink( + 'twitter' + )}&via=supabase&text=${text}` + const linkedInUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${permalink('linkedin')}` + const downloadUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/lw7-ticket-og?username=${encodeURIComponent( + username + )}` + const params = useParams() + const sharePage = params.username + + useEffect(() => { + setImgReady(false) + + const img = new Image() + + img.src = downloadUrl + img.onload = () => { + setImgReady(true) + setLoading(false) + if (downloadLink.current) { + downloadLink.current.click() + downloadLink.current = undefined + } + } + }, [downloadUrl]) + + return ( +
      + {!sharePage ? ( + <> +
      +
      +
      + +
      + Connect with Github +
      +
      + + + + ) : ( + !username && ( + + ) + )} +
      + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx b/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx index f658037f136..3eeb644ca6a 100644 --- a/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx +++ b/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx @@ -1,7 +1,6 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { PageState, ConfDataContext, UserData } from './hooks/use-conf-data' -import Ticket from './ticket' -import ConfContainer from './conf-container' +import Ticket from './ActualTicket' import Form from './form' import { SupabaseClient, Session } from '@supabase/supabase-js' @@ -23,6 +22,10 @@ export default function Conf({ const [userData, setUserData] = useState(defaultUserData) const [pageState, setPageState] = useState(defaultPageState) + useEffect(() => { + localStorage.setItem('isGolden', !!userData?.golden ? 'true' : 'false') + }, [userData]) + return ( )} diff --git a/apps/www/components/LaunchWeek/Ticket/TicketForm.tsx b/apps/www/components/LaunchWeek/Ticket/TicketForm.tsx new file mode 100644 index 00000000000..0e2bed3c4aa --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/TicketForm.tsx @@ -0,0 +1,176 @@ +import { useState, useRef, useEffect } from 'react' +import { useRouter } from 'next/router' +import cn from 'classnames' +import { SITE_ORIGIN } from '~/lib/constants' +import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data' +import formStyles from './form.module.css' +import ticketFormStyles from './ticket-form.module.css' +import { Button, IconCheckCircle, IconLoader } from 'ui' + +type FormState = 'default' | 'loading' | 'error' +type TicketGenerationState = 'default' | 'loading' + +type Props = { + defaultUsername?: string + ticketGenerationState?: TicketGenerationState + setTicketGenerationState: any +} + +export default function TicketForm({ defaultUsername = '', setTicketGenerationState }: Props) { + const [username, setUsername] = useState(defaultUsername) + const [formState, setFormState] = useState('default') + const [errorMsg] = useState('') + const { supabase, session, setUserData, setPageState, userData } = useConfData() + const [realtimeChannel, setRealtimeChannel] = useState | null>(null) + const formRef = useRef(null) + const router = useRouter() + + useEffect(() => { + if (session?.user && !userData.id) { + document.body.classList.add('ticket-generated') + const username = session.user.user_metadata.user_name + setUsername(username) + const name = session.user.user_metadata.full_name + const email = session.user.email + supabase + .from('lw7_tickets') + .insert({ email, name, username, referred_by: router.query?.referral ?? null }) + .eq('email', email) + .select() + .single() + .then(async ({ error }) => { + // If error because of duplicate email, ignore and proceed, otherwise sign out. + if (error && error?.code !== '23505') return supabase.auth.signOut() + const { data } = await supabase + .from('lw7_tickets_golden') + .select('*') + .eq('username', username) + .single() + if (data) { + setUserData(data) + } + setFormState('default') + + // Prefetch GitHub avatar + new Image().src = `https://github.com/${username}.png` + + // Prefetch the twitter share URL to eagerly generate the page + fetch(`/launch-week/tickets/${username}`).catch((_) => {}) + // Prefetch ticket og image. + fetch( + `https://obuldanrptloktxcffvn.functions.supabase.co/lw7-ticket-og?username=${encodeURIComponent( + username ?? '' + )}` + ).catch((_) => {}) + + setPageState('ticket') + + // Listen to realtime changes + if (!realtimeChannel && !data?.golden) { + const channel = supabase + .channel('changes') + .on( + 'postgres_changes', + { + event: 'UPDATE', + schema: 'public', + table: 'lw7_tickets', + filter: `username=eq.${username}`, + }, + (payload) => { + const golden = !!payload.new.sharedOnTwitter && !!payload.new.sharedOnLinkedIn + setUserData({ + ...payload.new, + golden, + }) + if (golden) { + channel.unsubscribe() + } + } + ) + .subscribe() + setRealtimeChannel(channel) + } + }) + } + return () => { + // Cleanup realtime subscription on unmount + realtimeChannel?.unsubscribe() + } + }, [session]) + + return formState === 'error' ? ( +
      +
      +
      +
      {errorMsg}
      + +
      +
      +
      + ) : ( +
      { + e.preventDefault() + + if (formState !== 'default') { + setTicketGenerationState('default') + setFormState('default') + return + } + + setFormState('loading') + setTicketGenerationState('loading') + + await supabase.auth.signInWithOAuth({ + provider: 'github', + options: { + redirectTo: `${SITE_ORIGIN}/launch-week/${ + userData.username ? '?referral=' + userData.username : '' + }`, + }, + }) + }} + className="flex flex-col items-center xl:block relative z-20" + id="wayfinding--connect-with-github-form" + > +
      +
      + +
      + {/* {!session &&

      Only public info will be used.

      } */} +
      +
      + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/TicketHeader.tsx b/apps/www/components/LaunchWeek/Ticket/TicketHeader.tsx new file mode 100644 index 00000000000..da3d1e79401 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/TicketHeader.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import { DATE } from '~/lib/constants' + +interface Props { + size?: 'default' | 'small' +} +export default function TicketHeader({ size }: Props) { + return ( +
      +
      + + {DATE} +
      +
      + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/TicketNumber.tsx b/apps/www/components/LaunchWeek/Ticket/TicketNumber.tsx new file mode 100644 index 00000000000..f06b8d6fd1c --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/TicketNumber.tsx @@ -0,0 +1,95 @@ +type Props = { + number: number | undefined + size?: 'default' | 'small' +} + +export default function TicketNumber({ number, size }: Props) { + const numDigits = `${number}`.length + const prefix = `00000000`.slice(numDigits) + const ticketNumberText = `№ ${prefix}${number}` + + return ( + <> +
      +
      + {/* Mobile line */} +
      + + + + + + + + + + +
      + {/* Vertical line: desktop */} +
      + + + + + + + + + + +
      +
      + {ticketNumberText} +
      +
      +
      + + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/TicketProfile.tsx b/apps/www/components/LaunchWeek/Ticket/TicketProfile.tsx new file mode 100644 index 00000000000..965171407f5 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/TicketProfile.tsx @@ -0,0 +1,86 @@ +// import GithubIcon from '~/components/LaunchWeek/Ticket/icons/icon-github' +import cn from 'classnames' +import TicketForm from './TicketForm' +// import IconAvatar from '~/components/LaunchWeek/Ticket/icons/icon-avatar' +import styles from './ticket-profile.module.css' +import Image from 'next/image' + +type TicketGenerationState = 'default' | 'loading' +type Props = { + name?: string + username?: string + size?: number + ticketGenerationState?: TicketGenerationState + setTicketGenerationState?: (ticketGenerationState: TicketGenerationState) => void + golden?: boolean +} + +// The middle part of the ticket +// avatar / Yourn Name / Username +export default function TicketProfile({ + name, + username, + size = 1, + ticketGenerationState, + setTicketGenerationState, + golden = false, +}: Props) { + return ( +
      + {username && ( + + {username ? ( + {username} + ) : ( + <> + {/* + { } + */} + + )} + + )} +
      + {username ? ( +

      +

      + {name || username || 'Your Name'} +

      {name &&

      @{username}

      }

      +
      +

      + ) : ( + + )} +
      +
      + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/TicketVisual.tsx b/apps/www/components/LaunchWeek/Ticket/TicketVisual.tsx new file mode 100644 index 00000000000..be31e8c231c --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/TicketVisual.tsx @@ -0,0 +1,130 @@ +import styles from './ticket-visual.module.css' +import TicketProfile from './TicketProfile' +import TicketNumber from './TicketNumber' +import Tilt from 'vanilla-tilt' +import useConfData from '~/components/LaunchWeek/Ticket/hooks/use-conf-data' +import TicketHeader from './TicketHeader' +import { useEffect, useRef, useState } from 'react' +import Image from 'next/image' + +type TicketGenerationState = 'default' | 'loading' +type Props = { + size?: number + name?: string + ticketNumber?: number + bgImageId?: number + username?: string + ticketGenerationState?: TicketGenerationState + setTicketGenerationState?: any + golden?: boolean +} + +export default function TicketVisual({ + size = 1, + name, + username, + bgImageId, + ticketNumber, + ticketGenerationState = 'default', + setTicketGenerationState, + golden = false, +}: Props) { + const { session } = useConfData() + const [imageIsLoading, setImageIsLoading] = useState(true) + const ticketRef = useRef(null) + const storageBaseFilepath = `https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw7/tickets_bg` + + const ticketBg = { + regular: { + image: `${storageBaseFilepath}/blurred/regular/jpg/reg_bg_${bgImageId}.jpg`, + overlay: `/images/launchweek/seven/ticket-overlay-reg.png`, + }, + gold: { + image: `${storageBaseFilepath}/blurred/golden/jpg/gold_bg_${bgImageId}.jpg`, + overlay: `/images/launchweek/seven/ticket-overlay-gold.png`, + }, + } + + useEffect(() => { + if (ticketRef.current && !window.matchMedia('(pointer: coarse)').matches) { + Tilt.init(ticketRef.current, { + glare: !golden, + max: 4, + gyroscope: true, + 'max-glare': 0.3, + 'full-page-listening': true, + }) + } + }, [ticketRef]) + + return ( +
      +
      +
      + {username && } +
      + +
      +
      + +
      + {username && ( + + )} + setImageIsLoading(false)} + /> +
      +
      +
      + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/TicketsGrid.tsx b/apps/www/components/LaunchWeek/Ticket/TicketsGrid.tsx new file mode 100644 index 00000000000..4377b09b8c8 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/TicketsGrid.tsx @@ -0,0 +1,90 @@ +import { motion } from 'framer-motion' +import Image from 'next/image' +import Link from 'next/link' +import { useMobileViewport } from '../../../hooks/useMobileViewport' +import { UserData } from './hooks/use-conf-data' + +interface Props { + loadedUsers: UserData[] + isLoading: boolean + pageCount: number + offset: number +} + +export default function TicketsGrid({ loadedUsers, isLoading, pageCount, offset }: Props) { + const STORAGE_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw7' + const BUCKET_FOLDER_VERSION = 'v3' + const getOgUrl = (username: string, isGold: boolean) => + `${STORAGE_URL}/tickets/gallery/${ + isGold ? 'golden' : 'regular' + }/${BUCKET_FOLDER_VERSION}/${username}.png` + const isMobile = useMobileViewport(768) + const isTablet = useMobileViewport(1024) + + return ( +
      + {loadedUsers?.map((user, i) => { + const rowTickets = isMobile ? 2 : isTablet ? 3 : 5 + const divider = Math.floor(i / rowTickets) + const isOddRow = divider % 2 === 0 + // Delay should be no more than pageCount for new loaded users + const recalculatedDelay = i >= pageCount * 2 ? (i - pageCount * (offset - 1)) / 15 : i / 15 + + return ( + + +
      + {user.username} +
      +
      + + ) + })} + {isLoading && + Array.from({ length: pageCount }, (_, i) => ( + +
      +
      +
      + + ))} +
      + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/form.tsx b/apps/www/components/LaunchWeek/Ticket/form.tsx index 59dbcb902ac..d8a9c9dbc3d 100644 --- a/apps/www/components/LaunchWeek/Ticket/form.tsx +++ b/apps/www/components/LaunchWeek/Ticket/form.tsx @@ -1,13 +1,11 @@ -import { useState, useCallback, useEffect } from 'react' +import { useState, useCallback } from 'react' import cn from 'classnames' import useConfData from '~/components/LaunchWeek/Ticket/hooks/use-conf-data' import { useRouter } from 'next/router' -import LoadingDots from './loading-dots' import styleUtils from './utils.module.css' import styles from './form.module.css' import useEmailQueryParam from '~/components/LaunchWeek/Ticket/hooks/use-email-query-param' import { IconLoader } from '~/../../packages/ui' -// import Captcha, { useCaptcha } from './captcha' type FormState = 'default' | 'loading' | 'error' @@ -37,45 +35,7 @@ export default function Form({ sharePage, align = 'Center' }: Props) { const router = useRouter() const isCaptchaEnabled = false - useEffect(() => { - if (session?.user) { - document.body.classList.add('ticket-generated') - const username = session.user.user_metadata.user_name - const name = session.user.user_metadata.full_name - const email = session.user.email - supabase - .from('lw6_tickets') - .upsert({ email, name, username }, { onConflict: 'email', ignoreDuplicates: false }) - .eq('email', email) - .select() - .single() - .then(async ({ error }) => { - if (error) return supabase.auth.signOut() - const { data } = await supabase - .from('lw6_tickets_golden') - .select('*') - .eq('username', username) - .single() - setUserData(data) - setFormState('default') - - // Prefetch GitHub avatar - new Image().src = `https://github.com/${username}.png` - - // Prefetch the twitter share URL to eagerly generate the page - fetch(`/launch-week/tickets/${username}`).catch((_) => {}) - // Prefetch ticket og image. - fetch( - `https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?username=${encodeURIComponent( - username ?? '' - )}` - ).catch((_) => {}) - - setPageState('ticket') - }) - } - }, [session]) - + // NOTE: this is not in use anymore as we're only allowing GitHub signups since LW7! async function register(email: string, token?: string): Promise { const { error } = await supabase!.from('lw6_tickets').insert({ email }) if (error) { diff --git a/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts b/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts index 4855f4f9a56..fc8d1df8fbb 100644 --- a/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts +++ b/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts @@ -9,6 +9,10 @@ export type UserData = { username?: string name?: string golden?: boolean + referrals?: number + bg_image_id?: number + sharedOnTwitter?: string + sharedOnLinkedIn?: string } type ConfDataContextType = { diff --git a/apps/www/components/LaunchWeek/Ticket/hooks/useWinningChances.ts b/apps/www/components/LaunchWeek/Ticket/hooks/useWinningChances.ts new file mode 100644 index 00000000000..b843fd0f5fe --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/hooks/useWinningChances.ts @@ -0,0 +1,25 @@ +import { UserData } from './use-conf-data' +import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data' + +function calculateWinChances(userData: UserData): number { + // Just signing up gives you 1 + // Then signing up with LinkedIn and Twitter get you one more each for a total of 3 + // When the chance === 3, you have a golden ticket + let numChances = 1 + + if (userData.sharedOnLinkedIn) { + numChances++ + } + if (userData.sharedOnTwitter) { + numChances++ + } + + return numChances +} + +export default function useWinningChances() { + const { userData } = useConfData() + + const chances = calculateWinChances(userData) + return chances +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-actions.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-actions.tsx deleted file mode 100644 index fd7c62842a4..00000000000 --- a/apps/www/components/LaunchWeek/Ticket/ticket-actions.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useEffect, useRef, useState } from 'react' -import { SITE_URL, TWEET_TEXT, TWEET_TEXT_GOLDEN } from '~/lib/constants' -import { IconDownload, IconLinkedin, IconTwitter } from 'ui' -import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data' -import LoadingDots from './loading-dots' - -type Props = { - username: string - golden?: boolean -} - -export default function TicketActions({ username, golden = false }: Props) { - const [imgReady, setImgReady] = useState(false) - const [loading, setLoading] = useState(false) - const downloadLink = useRef() - const permalink = encodeURIComponent(`${SITE_URL}/tickets/${username}?v=6`) - const text = encodeURIComponent(golden ? TWEET_TEXT_GOLDEN : TWEET_TEXT) - const { userData } = useConfData() - const tweetUrl = `https://twitter.com/intent/tweet?url=${permalink}&via=supabase&text=${text}` - const linkedInUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${permalink}` - const downloadUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?username=${encodeURIComponent( - userData.username ?? '' - )}` - - useEffect(() => { - setImgReady(false) - - const img = new Image() - - img.src = downloadUrl - img.onload = () => { - setImgReady(true) - setLoading(false) - if (downloadLink.current) { - downloadLink.current.click() - downloadLink.current = undefined - } - } - }, [downloadUrl]) - - const ActionStyle = ({ children }: any) => { - return ( -
      - {children} -
      - ) - } - - return ( - <> - - -
      - -
      - Tweet it -
      -
      - - -
      - -
      - Share on Linkedin -
      -
      - - { - if (imgReady) return - - e.preventDefault() - downloadLink.current = e.currentTarget - // Wait for the image download to finish - setLoading(true) - }} - download="ticket.png" - className="flex items-center gap-2" - > -
      - -
      - Download - {loading ? : <>{/* Download */}} -
      -
      - - ) -} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx index 997ada1729f..c228e815a67 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx +++ b/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx @@ -2,46 +2,32 @@ import { useEffect, useState, useRef } from 'react' import cn from 'classnames' import { SITE_URL } from '~/lib/constants' import styleUtils from './utils.module.css' -// import IconCopy from '~/components/LaunchWeek/Ticket/icons/icon-copy' -import styles from './ticket-copy.module.css' import { IconCopy, IconCheck } from 'ui' type Props = { username: string + isGolden?: boolean } -export default function TicketCopy({ username }: Props) { +export default function TicketCopy({ username, isGolden }: Props) { const [fadeOpacity, setFadeOpacity] = useState(1) const [scrolling, setScrolling] = useState(false) const [copyEnabled, setCopyEnabled] = useState(false) const [copied, setCopied] = useState(false) const scrollRef = useRef(null) const buttonRef = useRef(null) - const url = `${SITE_URL}/tickets/${username}` + const url = `${SITE_URL}/tickets/${username}?lw=7${isGolden ? `&golden=true` : ''}` useEffect(() => { if (navigator.clipboard) { setCopyEnabled(true) } }, []) - const copiedText = Copied! - - // background: none; - // outline: none; - // border: none; - // z-index: 2; - // border-radius: var(--space-2x); - // width: 40px; - // display: flex; - // align-items: center; - // justify-content: center; - // margin-right: -10px; - const copyButton = ( ) @@ -67,14 +57,14 @@ export default function TicketCopy({ username }: Props) { className={cn( styleUtils.appear, styleUtils['appear-third'], - 'bg-scaleA-200 h-8 rounded border border-scale-400 w-full' + 'bg-scaleA-200 h-8 rounded-full border border-[#E3E3E370] w-full overflow-hidden' )} + id="wayfinding--ticket-copy" > -
      -
      Your ticket URL:
      +

      { if (!scrolling) { @@ -95,10 +85,7 @@ export default function TicketCopy({ username }: Props) { {url}

      -
      - {copied && copiedText} - {copyButton} -
      +
      {copyButton}
      ) diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx deleted file mode 100644 index 8ded0d8cdf1..00000000000 --- a/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { useState, useRef } from 'react' -// import { scrollTo } from '@lib/smooth-scroll'; -import cn from 'classnames' -// import GithubIcon from '~/components/LaunchWeek/Ticket/icons/icon-github' -// import CheckIcon from '~/components/LaunchWeek/Ticket/icons/icon-check' -import { SITE_ORIGIN } from '~/lib/constants' -import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data' -import LoadingDots from './loading-dots' -import formStyles from './form.module.css' -import ticketFormStyles from './ticket-form.module.css' - -type FormState = 'default' | 'loading' | 'error' -type TicketGenerationState = 'default' | 'loading' - -type Props = { - defaultUsername?: string - setTicketGenerationState: React.Dispatch> -} - -export default function Form({ defaultUsername = '', setTicketGenerationState }: Props) { - const [username] = useState(defaultUsername) - const [formState, setFormState] = useState('default') - const [errorMsg] = useState('') - const { supabase } = useConfData() - const formRef = useRef(null) - - return formState === 'error' ? ( -
      -
      -
      -
      {errorMsg}
      - -
      -
      -
      - ) : ( -
      { - e.preventDefault() - - if (formState !== 'default') { - setTicketGenerationState('default') - setFormState('default') - return - } - - setFormState('loading') - setTicketGenerationState('loading') - - await supabase.auth.signInWithOAuth({ - provider: 'github', - options: { redirectTo: `${SITE_ORIGIN}/launch-week/tickets` }, - }) - }} - className="flex flex-col items-center xl:block" - > -
      -
      - -
      - {!username &&

      Only public info will be used.

      } -
      -
      - ) -} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-image.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-image.tsx index 43fe8a2f68f..b9ba7f91e87 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket-image.tsx +++ b/apps/www/components/LaunchWeek/Ticket/ticket-image.tsx @@ -1,6 +1,6 @@ import { useRouter } from 'next/router' import Head from 'next/head' -import TicketVisual from './ticket-visual' +import TicketVisual from './TicketVisual' import styles from './ticket-image.module.css' export default function TicketImage() { @@ -16,7 +16,7 @@ export default function TicketImage() { rel="stylesheet" /> - + /> */}
      ) diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-info.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-info-footer.tsx similarity index 80% rename from apps/www/components/LaunchWeek/Ticket/ticket-info.tsx rename to apps/www/components/LaunchWeek/Ticket/ticket-info-footer.tsx index 8437aa6eddd..7dd3b821630 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket-info.tsx +++ b/apps/www/components/LaunchWeek/Ticket/ticket-info-footer.tsx @@ -5,12 +5,12 @@ import { DATE, SITE_URL } from '~/lib/constants' const siteUrl = new URL(SITE_URL) const siteUrlForTicket = `${siteUrl.host}${siteUrl.pathname}`.replace(/\/$/, '') -export default function TicketInfo({ +export default function TicketInfoFooter({ logoTextSecondaryColor = 'var(--accents-5)', golden = false, }) { return ( -
      + ) diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-info.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-info.module.css index 9611ea8d805..ff1dc0539a2 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket-info.module.css +++ b/apps/www/components/LaunchWeek/Ticket/ticket-info.module.css @@ -1,8 +1,4 @@ .info { - display: flex; - gap: 0px; - margin-bottom: 64px; - padding: 32px; } @media (min-width: 768px) { diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-mono-mobile.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-mono-mobile.tsx index f55de57df16..2e7663bef28 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket-mono-mobile.tsx +++ b/apps/www/components/LaunchWeek/Ticket/ticket-mono-mobile.tsx @@ -12,7 +12,7 @@ export default function TicketMonoMobile({ golden = false }: { golden?: boolean diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-mono.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-mono.tsx index c4e5eb46b0f..e867cd8835d 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket-mono.tsx +++ b/apps/www/components/LaunchWeek/Ticket/ticket-mono.tsx @@ -6,10 +6,11 @@ export default function TicketMono({ golden = false }: { golden?: boolean }) { return ( - ) } diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-number.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-number.tsx deleted file mode 100644 index 473a8749b03..00000000000 --- a/apps/www/components/LaunchWeek/Ticket/ticket-number.tsx +++ /dev/null @@ -1,14 +0,0 @@ -type Props = { - number: number -} - -export default function TicketNumber({ number }: Props) { - const numDigits = `${number}`.length - const prefix = `00000000`.slice(numDigits) - return ( - <> - № {prefix} - {number} - - ) -} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-profile.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-profile.module.css index b4e44f9f345..ac08447a22a 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket-profile.module.css +++ b/apps/www/components/LaunchWeek/Ticket/ticket-profile.module.css @@ -1,30 +1,14 @@ -.profile { - display: flex; - align-items: center; -} - .image { - width: 60px; - height: 60px; + width: 50px; + height: 50px; border-radius: 50%; border: 1px solid #999; } @media (min-width: 768px) { .image { - width: calc(80px * var(--size)); - height: calc(80px * var(--size)); - } -} - -.text { - flex: 1; - margin-left: 10px; -} - -@media (min-width: 768px) { - .text { - margin-left: calc(15px * var(--size)); + width: calc(100px * var(--size)); + height: calc(100px * var(--size)); } } @@ -34,7 +18,6 @@ line-height: 1.15; letter-spacing: -0.02em; margin: 0; - max-width: 300px; } .name-golden { @@ -102,12 +85,12 @@ background-position: -200% 0; } } - +/* .skeleton { display: flex; align-items: center; - border-radius: 5px; -} + border-radius: 25px; +} */ .skeleton.loaded { width: unset !important; @@ -152,16 +135,15 @@ .rounded, .rounded.wrapper::before { - width: 62px; - height: 62px; + width: calc(80px * var(--size)); + height: calc(80px * var(--size)); border-radius: 50% !important; } @media (min-width: 768px) { .rounded, .rounded.wrapper::before { - width: calc(82px * var(--size)); - height: calc(82px * var(--size)); - border-radius: 50% !important; + width: calc(110px * var(--size)); + height: calc(110px * var(--size)); } } diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-profile.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-profile.tsx deleted file mode 100644 index 6a920bde73d..00000000000 --- a/apps/www/components/LaunchWeek/Ticket/ticket-profile.tsx +++ /dev/null @@ -1,86 +0,0 @@ -// import GithubIcon from '~/components/LaunchWeek/Ticket/icons/icon-github' -import cn from 'classnames' -// import IconAvatar from '~/components/LaunchWeek/Ticket/icons/icon-avatar' -import styles from './ticket-profile.module.css' - -type TicketGenerationState = 'default' | 'loading' -type Props = { - name?: string - username?: string - size?: number - ticketGenerationState: TicketGenerationState - golden?: boolean -} - -export default function TicketProfile({ - name, - username, - size = 1, - ticketGenerationState, - golden = false, -}: Props) { - return ( -
      - - {username ? ( - {username} - ) : ( - - {/* */} - - )} - -
      -

      - - {name || username || 'Your Name'} - -

      -

      - - - {/* */} - {username ? `@${username}` : <>username} - - - Supabase disconnected badge - -

      -
      -
      - ) -} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-visual.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-visual.module.css index 83e5f1d92ac..9056698878f 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket-visual.module.css +++ b/apps/www/components/LaunchWeek/Ticket/ticket-visual.module.css @@ -1,90 +1,62 @@ .visual { - position: relative; transform: translateZ(0); - display: flex; - flex-direction: column; - justify-content: space-between; - /* background-color: black; */ - border: 2px solid rgba(0, 0, 0, 0.1); + background-image: linear-gradient(154.77deg, #cb88ff 29.4%, #cb88ff 120.47%); + box-shadow: 0px 1px 27px #9e44ef10, 0 0 0 1px rgba(121, 89, 134, 0.237), + inset 0 0px 0 -2px rgba(0, 0, 0, 0.55); + /* border: 1px solid rgba(0, 0, 0, 0.05); */ border-radius: 16px; - background-color: rgba(0, 0, 0, 1); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); - /* background: linear-gradient(#fff000, var(--gold-secondary)); */ - border: 1px solid #1c5f52; - animation: blank-svg-shadow 2s ease-in-out infinite alternate; + /* border: 1px solid #1c1c1c; */ + /* animation: 2s ease-in-out infinite alternate; */ - background-image: url('/images/launchweek/regular-ticket-background.png'); background-repeat: no-repeat; background-size: contain; } .visual--gold { - /* border: 10px solid; - border-image-slice: 1; - border-width: 2px; - border-radius: 16px; */ - background-color: linear-gradient(#fff000, #dea513); - background-color: #dea513; + background: radial-gradient(#fcdf9d, #dbb333); +} + +.visual--gold::before { + content: ' '; + position: absolute; + inset: 0; + filter: drop-shadow(0 0 6px #fff) drop-shadow(0 0 6px #d99f0c) drop-shadow(0 0 9px #dbb333); + background: radial-gradient(#fcdf9d, #dbb333); border: 1px solid rgba(255, 255, 0, 0.629); - animation: svg-shadow 2s ease-in-out infinite alternate; - background-image: url('/images/launchweek/gold-ticket-background.png'); - /* filter: drop-shadow(2.45223px 8.5828px 73.5669px #FFBD14); */ - /* border-image-source: linear-gradient(to left, var(--gold-secondary), var(--gold-accent)); */ + animation: opacity-pulse 2s ease-in-out infinite alternate; } -@media (max-width: 768px) { - .visual { - background-size: cover; - - background-image: url('/images/launchweek/2x-regular-ticket-background.jpg'); +@keyframes opacity-pulse { + from { + opacity: 0.5; } - .visual--gold { - background-image: url('/images/launchweek/2x-gold-ticket-background.jpg'); + to { + opacity: 1; } } -/* .svg-shadow { -} */ - @keyframes svg-shadow { from { filter: drop-shadow(0 0 2px #fff) drop-shadow(0 0 4px #d99f0c) drop-shadow(0 0 6px #d99f0c); } to { - filter: drop-shadow(0 0 3px #fff) drop-shadow(0 0 6px #d99f0c) drop-shadow(0 0 9px #d99f0c); + filter: drop-shadow(0 0 10px #fff) drop-shadow(0 0 6px #d99f0c) drop-shadow(0 0 9px #d99f0c); } } @keyframes blank-svg-shadow { from { - filter: drop-shadow(0 0 2px #1c5f52) drop-shadow(0 0 4px #1c5f52) drop-shadow(0 0 6px #1c5f52); + filter: drop-shadow(0 0 2px #bada55) drop-shadow(0 0 2px #bada55) drop-shadow(0 0 2px #bada55); } to { - filter: drop-shadow(0 0 3px #1c5f52) drop-shadow(0 0 6px #1c5f52) drop-shadow(0 0 9px #1c5f52); - } -} - -.horizontal-ticket { - display: none; -} - -.vertical-ticket { - display: flex; -} - -@media (min-width: 768px) { - .horizontal-ticket { - display: flex; - } - - .vertical-ticket { - display: none; + filter: drop-shadow(0 0 3px #bada55) drop-shadow(0 0 2px #bada55) drop-shadow(0 0 2px #bada55); } } @@ -94,22 +66,18 @@ top: 0; width: 100%; height: 100%; - /* padding: 46px 29px;/ */ } .logo img { - /* margin-top: 64px; */ margin-top: calc(64px * var(--size)); margin-left: calc(32px * var(--size)); margin-left: calc(32px * var(--size)); - /* width: 272px; */ width: calc(272px * var(--size)); } @media (min-width: 768px) { .logo img { margin-top: calc(32px * var(--size)); - /* width: 280px; */ width: calc(280px * var(--size)); margin-left: calc(160px * var(--size)); } @@ -140,48 +108,10 @@ display: flex; flex-direction: column; justify-content: flex-end; - /* padding: calc(46px * var(--size)) calc(29px * var(--size)); */ } @media (min-width: 768px) { .info { bottom: 0; - /* padding: 0 calc(160px * var(--size)) calc(44px * var(--size)) - calc(63px * var(--size)); */ - } -} - -.ticket-number-wrapper { - position: absolute; - bottom: 40px; - right: 0; -} - -@media (min-width: 768px) { - .ticket-number-wrapper { - bottom: 0; - } -} - -.ticket-number { - font-size: calc(40px * var(--size)); - line-height: 1; - width: calc(330px * var(--size)); - text-align: center; - background-clip: text; -} - -.ticket-number-golden { - color: var(--gold-primary); -} - -@media (min-width: 768px) { - .ticket-number { - transform: rotate(90deg) translateY(calc(100px * var(--size))); - transform-origin: bottom right; - } - - .ticket-number-golden { - color: var(--gold-accent); } } diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-visual.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-visual.tsx deleted file mode 100644 index 55278d18b5a..00000000000 --- a/apps/www/components/LaunchWeek/Ticket/ticket-visual.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import styles from './ticket-visual.module.css' -import TicketProfile from './ticket-profile' -import TicketNumber from './ticket-number' -import TicketMono from './ticket-mono' -import TicketInfo from './ticket-info' -import TicketMonoMobile from './ticket-mono-mobile' -import cn from 'classnames' -import { useRouter } from 'next/router' - -type TicketGenerationState = 'default' | 'loading' -type Props = { - size?: number - name?: string - ticketNumber?: number - username?: string - ticketGenerationState?: TicketGenerationState - golden?: boolean -} - -export default function TicketVisual({ - size = 1, - name, - username, - ticketNumber, - ticketGenerationState = 'default', - golden = false, -}: Props) { - // golden = true - - const router = useRouter() - const basePath = router.basePath - - return ( - <> -
      -
      - {/* {username ? : } */} - - -
      -
      - {/* {username ? ( - - ) : ( - - )} */} - -
      -
      - -
      -
      - -
      -
      - -
      - {ticketNumber && ( -
      -
      - -
      -
      - )} -
      - - ) -} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket.module.css b/apps/www/components/LaunchWeek/Ticket/ticket.module.css index aebe6c9c713..aa126cb7070 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket.module.css +++ b/apps/www/components/LaunchWeek/Ticket/ticket.module.css @@ -1,3 +1,16 @@ +.ticket-hero { + background: #c58cd2; + background: linear-gradient( + 137.46deg, + rgba(255, 255, 255, 0.1) 6.19%, + rgba(255, 255, 255, 0.08) 103.29% + ); + z-index: 10; + box-shadow: 0 1px 1px rgba(131, 6, 184, 0.08), 0 2px 2px rgba(128, 6, 184, 0.08), + inset 0 0.5px 0px 0.5px rgba(255, 255, 255, 0.14), inset 0 -3px 0 -2px rgba(255, 255, 255, 0.08), + 0px 4px 20px rgba(118, 7, 170, 0.15); +} + .hero { line-height: 1.15; letter-spacing: -0.05em; @@ -9,10 +22,6 @@ @media (min-width: 768px) { .hero { line-height: 1; - /* font-weight: 800; */ - /* max-width: 800px; */ - /* margin-left: auto; - margin-right: auto; */ } } @@ -58,26 +67,6 @@ margin-bottom: auto; } -.ticket-visual { - position: relative; - border-radius: calc(16px * var(--size)); - /* overflow: hidden; */ - width: 330px; - margin: 0 auto; -} - -/* .ticket-visual:before, -.ticket-visual:after { */ -/* content: ''; - display: block; - width: 72px; - height: 58px; */ -/* background-color: #1C1C1C; */ -/* position: absolute; - z-index: 100; - border-radius: 50%; */ -/* } */ - .ticket-visual:before { left: 50%; top: -7.5%; @@ -91,10 +80,6 @@ } @media (min-width: 768px) { - .ticket-visual { - width: 650px; - } - .ticket-visual:before { top: 50%; left: -7%; @@ -147,6 +132,16 @@ } .ticket-visual-wrapper { - border-radius: calc(16px * var(--size)); width: 100%; } + +.gold-text { + color: #f4cf72; + text-shadow: 0 0 1px rgba(0, 0, 0, 0.5), 0 0 4px rgba(0, 0, 0, 0.1); +} + +@media (min-width: 768px) { + .gold-text { + color: #e4b641; + } +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket.tsx b/apps/www/components/LaunchWeek/Ticket/ticket.tsx deleted file mode 100644 index 4cc479bd591..00000000000 --- a/apps/www/components/LaunchWeek/Ticket/ticket.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import cn from 'classnames' -import Tilt from 'vanilla-tilt' -import { useRef, useEffect, useState } from 'react' -// import { TicketGenerationState } from '@lib/constants' -// import {isMobileOrTablet} from '~/lib/helpers' -// import { scrollTo } from '@lib/smooth-scroll' -import styles from './ticket.module.css' -import styleUtils from './utils.module.css' -import TicketForm from './ticket-form' -import TicketVisual from './ticket-visual' -import TicketActions from './ticket-actions' -import TicketCopy from './ticket-copy' -import { DATE, SITE_NAME } from '~/lib/constants' -import Form from './form' -import { UserData } from './hooks/use-conf-data' - -type TicketGenerationState = 'default' | 'loading' -type Props = { - username: UserData['username'] - ticketNumber: UserData['ticketNumber'] - name: UserData['name'] - golden: UserData['golden'] - sharePage?: boolean -} - -export default function Ticket({ username, name, ticketNumber, sharePage, golden }: Props) { - const ticketRef = useRef(null) - const [ticketGenerationState, setTicketGenerationState] = - useState('default') - const divRef = useRef(null) - - useEffect(() => { - if (ticketRef.current && !window.matchMedia('(pointer: coarse)').matches) { - Tilt.init(ticketRef.current, { - glare: true, - max: 5, - 'max-glare': 0.16, - 'full-page-listening': true, - }) - } - }, [ticketRef]) - - // useEffect(() => { - // if (!sharePage && divRef && divRef.current && isMobileOrTablet()) { - // scrollTo(divRef.current, -30) - // } - // }, [divRef, sharePage]) - // golden = true - - return ( -
      -
      -
      -

      - {sharePage ? ( - name ? ( -

      {name}'s ticket

      - ) : ( -

      {SITE_NAME}

      - ) - ) : golden ? ( -

      Congratulations!

      - ) : ( -

      You have a ticket!

      - )} -

      -

      - {sharePage ? ( -

      - Join {name ?? 'us'} on {DATE}. -

      - ) : golden ? ( - <> - You got a Golden ticket. This means you’re in, and you also won a Supabase goodie - bag. Tweet it to redeem your swag pack! - - ) : username ? ( - <>Here is your unique ticket image for bragging on socials! - ) : ( - <> - This means you're in. Generate a unique ticket image with your GitHub profile, cause - a few of the lucky attendees will get a limited edition Supabase goodie bag. Make - sure you don't skip your chance. - - )} -

      -
      -
      - {!sharePage ? ( - <> - - - ) : ( -
      - )} -
      -
      -
      -
      - -
      - {!sharePage && ( - <> - {username ? ( -
      -
      - -
      -
      - -
      -
      - ) : ( -
      - )} - - )} -
      -
      - ) -} diff --git a/apps/www/components/LaunchWeek/lw7-bg-graphic.module.css b/apps/www/components/LaunchWeek/lw7-bg-graphic.module.css new file mode 100644 index 00000000000..04776532987 --- /dev/null +++ b/apps/www/components/LaunchWeek/lw7-bg-graphic.module.css @@ -0,0 +1,5 @@ +.gradient-overlay::before { + background: linear-gradient(0deg, #1c1c1c 0%, #1c1c1c20 60%, transparent 100%), + linear-gradient(0deg, #1c1c1c 0%, #1c1c1c80 20%, #1c1c1c60 40%, #1c1c1c05 75%, transparent 100%), + linear-gradient(0deg, #1c1c1c 0%, #1c1c1c80 15%, #1c1c1c50 40%, #1c1c1c05 65%, transparent 100%); +} diff --git a/apps/www/components/LaunchWeek/lw7_days.json b/apps/www/components/LaunchWeek/lw7_days.json new file mode 100644 index 00000000000..e961beefa62 --- /dev/null +++ b/apps/www/components/LaunchWeek/lw7_days.json @@ -0,0 +1,114 @@ +[ + { + "title": "Docs Updated", + "shipped": true, + "date": "12 Dec 2022", + "description": "Supabase Docs", + "d": 1, + "dd": "Monday", + "youtube_id": "OpPOaJI_Z28", + "blogpost": "https://supabase.com/blog/new-supabase-docs-built-with-nextjs", + "docs": "https://supabase.com/docs", + "steps": [ + { + "title": "Supabase Docs", + "blog": "/blog/new-supabase-docs-built-with-nextjs", + "docs": "/docs", + "isNew": true, + "description": "" + } + ] + }, + { + "title": "Image Processing: Resize V2 + CDN", + "shipped": true, + "date": "13 Dec 2022", + "description": "Image Transformations", + "d": 2, + "dd": "Tuesday", + "youtube_id": "iqZlPtl_b-I", + "blogpost": "https://supabase.com/blog", + "docs": "https://supabase.com/docs", + "steps": [ + { + "title": "Image Transformations", + "blog": "/blog/storage-image-resizing-smart-cdn", + "docs": "/docs/guides/storage/image-transformations", + "isNew": true, + "description": "" + }, + { + "title": "Smart CDN", + "description": "Faster asset delivery, now even faster.", + "blog": "/blog/storage-image-resizing-smart-cdn", + "docs": "/docs/guides/storage/cdn", + "isNew": true + } + ] + }, + { + "title": "Multi-factor Authentication", + "shipped": true, + "date": "14 Dec 2022", + "description": "Multi-factor Authentication", + "d": 3, + "dd": "Wednesday", + "youtube_id": "CGZr5tybW18", + "blogpost": "https://supabase.com/blog", + "docs": "https://supabase.com/docs", + "steps": [ + { + "title": "Multi-factor Authentication", + "blog": "/blog/mfa-auth-via-rls", + "docs": "/docs/guides/auth/auth-mfa", + "isNew": false, + "description": "" + } + ] + }, + { + "title": "Supabase Wrappers - FDW Framework", + "shipped": true, + "date": "15 Dec 2022", + "description": "Supabase Wrappers", + "d": 4, + "dd": "Thursday", + "youtube_id": "QA2qC5F-4OU", + "blogpost": "/blog/postgres-foreign-data-wrappers-rust", + "docs": "https://supabase.com/docs", + "steps": [ + { + "title": "Supabase Wrappers", + "blog": "/blog/postgres-foreign-data-wrappers-rust", + "docs": "https://supabase.github.io/wrappers/", + "isNew": true, + "description": "Connect Postgres to external systems with Postgres Foreign Data Wrappers" + } + ] + }, + { + "title": "Supabase Vault Release", + "shipped": true, + "date": "16 Dec 2022", + "description": "Vault Release", + "d": 5, + "dd": "Friday", + "youtube_id": "6bGQotxisoY", + "blogpost": "https://supabase.com/blog", + "docs": "https://supabase.com/docs", + "steps": [ + { + "title": "Vault Release", + "blog": "/blog/vault-now-in-beta", + "isNew": true, + "description": "" + }, + { + "title": "Transparent Column Encryption", + "description": "Faster asset delivery, now even faster.", + "blog": "/blog/transparent-column-encryption-with-postgres", + "isNew": true + } + ] + } +] diff --git a/apps/www/components/Nav/Announcement.tsx b/apps/www/components/Nav/Announcement.tsx index a85d9c524f2..60595a309be 100644 --- a/apps/www/components/Nav/Announcement.tsx +++ b/apps/www/components/Nav/Announcement.tsx @@ -1,26 +1,37 @@ import React, { useEffect, useState } from 'react' import _announcement from '~/data/Announcement.json' -import { IconChevronRight, IconX } from 'ui' +import { IconX } from 'ui' import { useRouter } from 'next/router' +import { PropsWithChildren } from 'react' -interface AnnouncementProps { +export interface AnnouncementProps { show: boolean text: string - cta: string + launchDate: string link: string badge?: string } const announcement = _announcement as AnnouncementProps -const Announcement = () => { +interface AnnouncementComponentProps { + show?: boolean + className?: string +} + +const Announcement = ({ + show = true, + className, + children, +}: PropsWithChildren) => { const [hidden, setHidden] = useState(true) const router = useRouter() + const isLaunchWeekSection = router.pathname.includes('launch-week') // override to hide announcement - if (!announcement.show) return null + if (!show || !announcement.show) return null // construct the key for the announcement, based on the title text const announcementKey = 'announcement_' + announcement.text.replace(/ /g, '') @@ -42,50 +53,23 @@ const Announcement = () => { function handleLink() { router.push(announcement.link) - window.localStorage.setItem(announcementKey, 'hidden') } - if (hidden) { + // Always show if on LW section + if (!isLaunchWeekSection && hidden) { return null } else { return ( -
      -
      - {announcement.text} - - {announcement.badge && ( -
      - {announcement.badge} -
      - )} -
      - - {announcement.cta} - -
      -
      - -
      +
      + {!isLaunchWeekSection && ( +
      + +
      + )} + {children}
      ) } diff --git a/apps/www/components/Nav/Developers.tsx b/apps/www/components/Nav/Developers.tsx index 9840c0a060d..fe7e30ebb31 100644 --- a/apps/www/components/Nav/Developers.tsx +++ b/apps/www/components/Nav/Developers.tsx @@ -62,23 +62,23 @@ const Developers = () => {

      Latest announcements

        - {AnnouncementsData.map((caseStudy: any, idx: number) => ( + {AnnouncementsData.map((announcement: any, idx: number) => (
      • - +
        caseStudyThumb

        - {caseStudy.title} + {announcement.title}

        -

        {caseStudy.description}

        +

        {announcement.description}

        diff --git a/apps/www/components/Nav/Product.tsx b/apps/www/components/Nav/Product.tsx index efa294b1644..d2085722e6e 100644 --- a/apps/www/components/Nav/Product.tsx +++ b/apps/www/components/Nav/Product.tsx @@ -1,11 +1,12 @@ import React from 'react' import SolutionsData from 'data/Solutions.json' -import CaseStudiesData from 'data/CaseStudies.json' +import CustomerStoriesData from 'data/CustomerStories.json' import { useRouter } from 'next/router' import ProductIcon from '../ProductIcon' import { Badge } from 'ui' import Image from 'next/image' import Link from 'next/link' +import TextLink from '../TextLink' const Product = () => { const { basePath } = useRouter() @@ -46,30 +47,31 @@ const Product = () => {
        {iconSections}
        -
        -

        Latest case studies

        +
        +

        Latest customer stories

        +
        + +
        diff --git a/apps/www/components/Nav/index.tsx b/apps/www/components/Nav/index.tsx index 98782862c93..7baf842c5f5 100644 --- a/apps/www/components/Nav/index.tsx +++ b/apps/www/components/Nav/index.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react' import Link from 'next/link' +import { useRouter } from 'next/router' import { Button, Badge, IconStar, IconChevronDown } from 'ui' import FlyOut from '~/components/UI/FlyOut' @@ -11,18 +12,23 @@ import Solutions from '~/components/Nav/Product' import Developers from '~/components/Nav/Developers' import Announcement from '~/components/Nav/Announcement' -import { useTheme } from 'common/Providers' +import { useIsLoggedIn, useTheme } from 'common' import TextLink from '../TextLink' import Image from 'next/image' import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png' import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png' +import CountdownBanner from '../LaunchWeek/Banners/CountdownBanner' const Nav = () => { const { isDarkMode } = useTheme() + const { pathname } = useRouter() const [open, setOpen] = useState(false) - const [openProduct, setOpenProduct] = useState(false) const [openDevelopers, setOpenDevelopers] = useState(false) + const isLoggedIn = useIsLoggedIn() + + const isLaunchWeekPage = pathname.includes('launch-week') + const showLaunchWeekNavMode = isLaunchWeekPage && !open && !openProduct && !openDevelopers React.useEffect(() => { if (open) { @@ -47,12 +53,12 @@ const Nav = () => { const { name, description, icon, label, url } = solution const content = ( -
        +
        -
        +
        {/* */} { )}

        -

        {description}

        +

        {description}

        {url && ( -

        +

        )} @@ -85,14 +91,14 @@ const Nav = () => { ) return url ? ( - + {content} ) : (
        {content}
        @@ -101,6 +107,7 @@ const Nav = () => { type HamburgerButtonProps = { toggleFlyOut: Function + showLaunchWeekNavMode?: boolean } const HamburgerButton = (props: HamburgerButtonProps) => ( @@ -109,17 +116,20 @@ const Nav = () => { onClick={() => props.toggleFlyOut()} >
        @@ -180,20 +195,42 @@ const Nav = () => { return ( <> - {/* */} -
        -
        -
      • @@ -103,9 +103,7 @@ export function Group({ children, icon, title }: GroupProps) { const __styles = styleHandler('menu') const { type } = useMenuContext() return ( -
        +
        {icon && {icon}} {title} {children} diff --git a/packages/ui/src/components/Modal/Modal.tsx b/packages/ui/src/components/Modal/Modal.tsx index 4c536622e39..0b4cbd29be9 100644 --- a/packages/ui/src/components/Modal/Modal.tsx +++ b/packages/ui/src/components/Modal/Modal.tsx @@ -125,7 +125,12 @@ const Modal = ({ - @@ -154,8 +159,9 @@ const Modal = ({ {header &&
        {header}
        } {/*
        ( side="bottom" align="end" size="content" - portalled showClose header={
        diff --git a/packages/ui/src/components/Popover/Popover.tsx b/packages/ui/src/components/Popover/Popover.tsx index 010e5f591ce..5c47d177927 100644 --- a/packages/ui/src/components/Popover/Popover.tsx +++ b/packages/ui/src/components/Popover/Popover.tsx @@ -16,7 +16,6 @@ interface RootProps { onOpenChange?: RadixPopoverTypes.PopoverProps['onOpenChange'] open?: boolean overlay?: React.ReactNode - portalled?: boolean showClose?: boolean side?: RadixPopoverTypes.PopoverContentProps['side'] sideOffset?: RadixPopoverTypes.PopoverContentProps['sideOffset'] @@ -37,7 +36,6 @@ function Popover({ onOpenChange, open, overlay, - portalled, side = 'bottom', sideOffset = 6, style, @@ -67,24 +65,25 @@ function Popover({ {children} - - {arrow && ( - - )} - {header &&
        {header}
        } - {overlay} - {footer &&
        {footer}
        } -
        + + + {arrow && ( + + )} + {header &&
        {header}
        } + {overlay} + {footer &&
        {footer}
        } +
        +
        ) } diff --git a/packages/ui/src/components/Radio/Radio.tsx b/packages/ui/src/components/Radio/Radio.tsx index 86265df382f..72ad375ec43 100644 --- a/packages/ui/src/components/Radio/Radio.tsx +++ b/packages/ui/src/components/Radio/Radio.tsx @@ -137,7 +137,7 @@ function RadioGroup({ interface InputProps extends Omit, 'size'> { label: string afterLabel?: string - beforeLabel?: string + beforeLabel?: string | React.ReactNode description?: string size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge' hidden?: boolean diff --git a/packages/ui/src/components/SidePanel/SidePanel.tsx b/packages/ui/src/components/SidePanel/SidePanel.tsx index e105fe85d91..6dc69c307e9 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 @@ -67,7 +67,12 @@ const SidePanel = ({ -
        @@ -121,10 +126,16 @@ export function Separator() { return
        } -export function Content({ children }: { children: React.ReactNode }) { +export function Content({ + children, + className, +}: { + children: React.ReactNode + className?: string +}) { let __styles = styleHandler('sidepanel') - return
        {children}
        + return
        {children}
        } SidePanel.Content = Content diff --git a/packages/ui/src/components/Toggle/Toggle.tsx b/packages/ui/src/components/Toggle/Toggle.tsx index 413c1725a1f..9ebbfbd6fff 100644 --- a/packages/ui/src/components/Toggle/Toggle.tsx +++ b/packages/ui/src/components/Toggle/Toggle.tsx @@ -1,8 +1,7 @@ -import React, { useEffect, useState } from 'react' +import clsx from 'clsx' +import { useEffect, useState } from 'react' import { FormLayout } from '../../lib/Layout/FormLayout' import { useFormContext } from '../Form/FormContext' - -import defaultTheme from '../../lib/theme/defaultTheme' import styleHandler from '../../lib/theme/styleHandler' interface Props extends Omit, 'size'> { @@ -10,8 +9,8 @@ interface Props extends Omit, 'size'> { disabled?: boolean layout?: 'horizontal' | 'vertical' | 'flex' error?: string - descriptionText?: string - label?: string + descriptionText?: string | React.ReactNode + label?: string | React.ReactNode afterLabel?: string beforeLabel?: string labelOptional?: string @@ -129,7 +128,7 @@ function Toggle({ type="button" id={id} name={name} - className={toggleClasses.join(' ')} + className={clsx(...toggleClasses, disabled && 'opacity-50 cursor-default')} onClick={onClick} disabled={disabled} onBlur={handleBlurEvent} diff --git a/packages/ui/src/lib/Layout/FormLayout/FormLayout.tsx b/packages/ui/src/lib/Layout/FormLayout/FormLayout.tsx index d400ed44020..9715d795b1d 100644 --- a/packages/ui/src/lib/Layout/FormLayout/FormLayout.tsx +++ b/packages/ui/src/lib/Layout/FormLayout/FormLayout.tsx @@ -86,9 +86,7 @@ export function FormLayout({ } else { dataInputContainerClasses.push(__styles.data_input_vertical_layout) if (align === 'right') { - dataInputContainerClasses.push( - __styles.data_input_vertical_layout__align_right - ) + dataInputContainerClasses.push(__styles.data_input_vertical_layout__align_right) } } @@ -128,10 +126,7 @@ export function FormLayout({ const renderDescription = descriptionText && (

        {descriptionText} @@ -153,19 +148,14 @@ export function FormLayout({ > {labelled && (

        - -
        - - {mounted && createPortal(, document.body)} + {showCustomChildren && children !== undefined ? ( + <>{children} + ) : ( + <> + +
        + + + )} + + {mounted && + createPortal(, document.body)}
        ) }) diff --git a/studio/components/grid/SupabaseGrid.utils.ts b/studio/components/grid/SupabaseGrid.utils.ts index 7f13c277e8a..2a0a4f29cb2 100644 --- a/studio/components/grid/SupabaseGrid.utils.ts +++ b/studio/components/grid/SupabaseGrid.utils.ts @@ -215,9 +215,12 @@ export function parseSupaTable( isEncrypted: encryptedColumns.includes(column.name), enum: column.enums, comment: column.comment, - targetTableSchema: null, - targetTableName: null, - targetColumnName: null, + foreignKey: { + targetTableSchema: null, + targetTableName: null, + targetColumnName: null, + deletionAction: undefined, + }, } const primaryKey = primaryKeys.find((pk) => pk.name == column.name) temp.isPrimaryKey = !!primaryKey @@ -230,9 +233,10 @@ export function parseSupaTable( ) }) if (relationship) { - temp.targetTableSchema = relationship.target_table_schema - temp.targetTableName = relationship.target_table_name - temp.targetColumnName = relationship.target_column_name + temp.foreignKey.targetTableSchema = relationship.target_table_schema + temp.foreignKey.targetTableName = relationship.target_table_name + temp.foreignKey.targetColumnName = relationship.target_column_name + temp.foreignKey.deletionAction = relationship.deletion_action } return temp }) diff --git a/studio/components/grid/components/common/ForeignTableModal/FilterHeader.tsx b/studio/components/grid/components/common/ForeignTableModal/FilterHeader.tsx deleted file mode 100644 index 827848222bb..00000000000 --- a/studio/components/grid/components/common/ForeignTableModal/FilterHeader.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import React, { FC, useState } from 'react' -import AwesomeDebouncePromise from 'awesome-debounce-promise' -import { Button, Input, IconChevronDown, IconSearch } from 'ui' -import { DropdownControl } from '../../common' -import { FilterOperatorOptions } from '../../header/filter' -import { Filter, FilterOperator } from '../../../types' - -interface FilterProps { - defaultColumnName?: string - defaultValue?: string - foreignColumnNames: string[] - onChange: (value: Filter) => void -} - -export const FilterHeader: FC = ({ - defaultColumnName, - defaultValue, - foreignColumnNames, - onChange, -}) => { - const [columnName, setColumnName] = useState( - defaultColumnName - ? defaultColumnName - : foreignColumnNames.length > 0 - ? foreignColumnNames[0] - : '' - ) - const [condition, setCondition] = useState(FilterOperatorOptions[0].value) - const [filterText, setFilterText] = useState(defaultValue ?? '') - - const columnOptions = - foreignColumnNames.map((x) => { - return { value: x, label: x } - }) || [] - - function triggerOnChange(columnName: string, condition: string, filterText: string) { - onFilterChangeDebounced( - { - column: columnName, - operator: condition as FilterOperator, - value: filterText, - }, - onChange - ) - } - - function onColumnChange(value: string | number) { - const str = value + '' - setColumnName(str) - triggerOnChange(str, condition, filterText) - } - - function onConditionChange(value: string | number) { - const str = value + '' - setCondition(str) - triggerOnChange(columnName, str, filterText) - } - - function onFilterChange(event: React.ChangeEvent) { - const value = event.target.value - setFilterText(value) - triggerOnChange(columnName, condition, value) - } - - return ( -
        - - - - - - - } - /> -
        - ) -} - -const onFilterChangeDebounced = AwesomeDebouncePromise( - (filter: Filter, onChange: (value: Filter) => void) => { - onChange(filter) - }, - 500 -) diff --git a/studio/components/grid/components/common/ForeignTableModal/RowItem.tsx b/studio/components/grid/components/common/ForeignTableModal/RowItem.tsx deleted file mode 100644 index d27314724bb..00000000000 --- a/studio/components/grid/components/common/ForeignTableModal/RowItem.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import * as React from 'react' -import { Menu } from 'ui' -import { Dictionary } from '../../../types' - -interface RowItemProps { - item: Dictionary - onSelect: (item: Dictionary) => void - columnNames: string[] -} - -export const RowItem: React.FC = ({ item, onSelect, columnNames }) => { - return ( -
        - onSelect(item)} style={{ minWidth: 'min-content' }}> -
        - {columnNames.map((key, j) => { - // - // limit to 5 attributes - // - // this could be improved so the user could pick which attributes to display - // @mildtomato - if (j > 5) return null - - return ( -
        -

        - {key} -

        -

        - {item[key] - ? typeof item[key] === 'object' - ? JSON.stringify(item[key]) - : item[key] - : '[null]'} -

        -
        - ) - })} -
        -
        -
        - ) -} diff --git a/studio/components/grid/components/common/ForeignTableModal/index.tsx b/studio/components/grid/components/common/ForeignTableModal/index.tsx deleted file mode 100644 index 78e1bdb56f0..00000000000 --- a/studio/components/grid/components/common/ForeignTableModal/index.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import * as React from 'react' -import { Button, Modal, Menu, IconExternalLink, IconTrash } from 'ui' - -import { RowItem } from './RowItem' -import { FilterHeader } from './FilterHeader' -import Query from 'components/grid/query' -import { useTrackedState } from 'components/grid/store' -import { Dictionary, Filter } from 'components/grid/types' - -type ForeignTableModalProps = { - columnName?: string - defaultValue?: string - onChange: (value: any | null) => void -} - -export const ForeignTableModal: React.FC = ({ - columnName, - defaultValue, - onChange, -}) => { - const [visible, setVisible] = React.useState(false) - const [foreignColumnNames, setForeignColumnNames] = React.useState([]) - const [rows, setRows] = React.useState[] | null>(null) - const state = useTrackedState() - const columnDefinition = state.table?.columns.find((x) => x.name == columnName) - - React.useEffect(() => { - if (!visible) return - - fetchColumns() - - if (defaultValue && columnDefinition) { - fetchData({ - column: columnDefinition.targetColumnName!, - operator: '=', - value: defaultValue, - }) - } else { - fetchData() - } - }, [visible]) - - async function fetchColumns() { - if ( - !state.metaService || - !columnDefinition?.targetTableName || - !columnDefinition?.targetTableSchema - ) - return - - const { data } = await state.metaService.fetchColumns( - columnDefinition?.targetTableName, - columnDefinition?.targetTableSchema - ) - if (data && data.length > 0) { - const columnNames = data.map((x) => x.name) - setForeignColumnNames(columnNames) - } - } - - async function fetchData(filter?: Filter) { - if (!state.onSqlQuery || !columnDefinition?.targetTableName) return - - const query = new Query() - let queryChains = query - .from(columnDefinition?.targetTableName, columnDefinition?.targetTableSchema ?? undefined) - .select() - - if (filter && filter.value && filter.value != '') { - const { column, operator, value } = filter - queryChains = queryChains.filter(column, operator, value) - } - - // TODO: How to let users know that filter result limit at 20 results - // should we allow a higher value? - const sql = queryChains.range(0, 20).toSql() - const { data, error } = await state.onSqlQuery(sql) - if (error) { - setRows(null) - } else { - setRows(data) - } - } - - function toggle(event: React.MouseEvent) { - if (event && event.stopPropagation) event.stopPropagation() - setVisible(!visible) - } - - function onItemSelect(item: Dictionary) { - if (item && columnDefinition && columnDefinition.targetColumnName) { - const value = item[columnDefinition.targetColumnName] - onChange(value) - } - - setVisible(false) - } - - function renderRows() { - if (!rows) return null - const temp = rows.map((x, i) => { - return ( - - ) - }) - return {temp} - } - - function onFilterChange(filter: Filter) { - if (filter && filter.value && filter.value != '') fetchData(filter) - } - - function onClearValueClick() { - onChange(null) - setVisible(false) - } - - return ( - <> - - } - > - - - - - -
        -
        - {renderRows()} -
        -
        -
        - - - ) -} diff --git a/studio/components/grid/components/editor/BooleanEditor.tsx b/studio/components/grid/components/editor/BooleanEditor.tsx index 8505b5a5901..052ba484a86 100644 --- a/studio/components/grid/components/editor/BooleanEditor.tsx +++ b/studio/components/grid/components/editor/BooleanEditor.tsx @@ -1,30 +1,46 @@ -import * as React from 'react' +import { Select } from 'ui' import { EditorProps } from '@supabase/react-data-grid' +import { useTrackedState } from 'components/grid/store' + +interface Props extends EditorProps { + isNullable?: boolean +} export function BooleanEditor({ row, column, + isNullable, onRowChange, onClose, -}: EditorProps) { - function onChange(event: React.ChangeEvent) { - onRowChange({ ...row, [column.key]: event.target.checked }) - } +}: Props) { + const state = useTrackedState() + const gridColumn = state.gridColumns.find((x) => x.name == column.key) + const value = row[column.key as keyof TRow] as unknown as string - function onBlur() { - onClose(true) + const onBlur = () => onClose(false) + const onChange = (event: any) => { + const value = event.target.value + if (value === 'null') { + onRowChange({ ...row, [column.key]: null }, true) + } else { + onRowChange({ ...row, [column.key]: value === 'true' }, true) + } } return ( -
        - -
        + ) } diff --git a/studio/components/grid/components/editor/JsonEditor.tsx b/studio/components/grid/components/editor/JsonEditor.tsx index d068097412a..93ea44dbc9a 100644 --- a/studio/components/grid/components/editor/JsonEditor.tsx +++ b/studio/components/grid/components/editor/JsonEditor.tsx @@ -104,17 +104,19 @@ export function JsonEditor({
        - - -
        - Expand editor -
        -
        + + + +
        + Expand editor +
        +
        +
        diff --git a/studio/components/grid/components/editor/NullableBooleanEditor.tsx b/studio/components/grid/components/editor/NullableBooleanEditor.tsx deleted file mode 100644 index 22996cf1dc6..00000000000 --- a/studio/components/grid/components/editor/NullableBooleanEditor.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useEffect } from 'react' -import { EditorProps } from '@supabase/react-data-grid' -import { Button, IconX } from 'ui' - -export function NullableBooleanEditor({ - row, - column, - onRowChange, - onClose, -}: EditorProps) { - const value = row[column.key as keyof TRow] as unknown as boolean | null - - useEffect(() => { - // if value is null, set it to false on initial render - if (value === null) { - onRowChange({ ...row, [column.key]: false }) - } - }, []) - - function onChange(event: React.ChangeEvent) { - onRowChange({ ...row, [column.key]: event.target.checked }) - } - - function onBlur() { - onClose(true) - } - - function onClear() { - onRowChange({ ...row, [column.key]: null }, true) - } - - return ( -
        - -
        - ) -} diff --git a/studio/components/grid/components/editor/TextEditor.tsx b/studio/components/grid/components/editor/TextEditor.tsx index 2b0fa077dc3..09b98506491 100644 --- a/studio/components/grid/components/editor/TextEditor.tsx +++ b/studio/components/grid/components/editor/TextEditor.tsx @@ -2,13 +2,14 @@ import { useState, useCallback } from 'react' import { EditorProps } from '@supabase/react-data-grid' import { useTrackedState } from '../../store' import { BlockKeys, MonacoEditor, NullValue, EmptyValue } from '../common' -import { Popover } from 'ui' +import { Button, Popover } from 'ui' export function TextEditor({ row, column, + isNullable, onRowChange, -}: EditorProps) { +}: EditorProps & { isNullable?: boolean }) { const state = useTrackedState() const [isPopoverOpen, setIsPopoverOpen] = useState(true) const gridColumn = state.gridColumns.find((x) => x.name == column.key) @@ -59,6 +60,13 @@ export function TextEditor({

        Cancel changes

      +
      + {isNullable && ( + + )} +
      } diff --git a/studio/components/grid/components/editor/index.ts b/studio/components/grid/components/editor/index.ts index af37e035773..4869badf6da 100644 --- a/studio/components/grid/components/editor/index.ts +++ b/studio/components/grid/components/editor/index.ts @@ -2,7 +2,6 @@ export * from './BooleanEditor' export * from './DateEditor' export * from './DateTimeEditor' export * from './JsonEditor' -export * from './NullableBooleanEditor' export * from './NumberEditor' export * from './SelectEditor' export * from './TextEditor' 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/formatter/BooleanFormatter.tsx b/studio/components/grid/components/formatter/BooleanFormatter.tsx index a038fdda957..9255bd8c77b 100644 --- a/studio/components/grid/components/formatter/BooleanFormatter.tsx +++ b/studio/components/grid/components/formatter/BooleanFormatter.tsx @@ -6,5 +6,5 @@ import { NullValue } from '../common' export const BooleanFormatter = (p: React.PropsWithChildren>) => { const value = p.row[p.column.key] as boolean | null if (value === null) return - return <>{value ? 'true' : 'false'} + return <>{value ? 'TRUE' : 'FALSE'} } diff --git a/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx b/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx index e6abb30516f..cab2f3416a2 100644 --- a/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx +++ b/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx @@ -1,43 +1,75 @@ -import * as React from 'react' +import Link from 'next/link' +import { PropsWithChildren } from 'react' import { FormatterProps } from '@supabase/react-data-grid' import { SupaRow } from '../../types' import { NullValue } from '../common' -import { ForeignTableModal } from '../common/ForeignTableModal' -import { useDispatch, useTrackedState } from '../../store' -import { deepClone } from '../../utils' +import * as Tooltip from '@radix-ui/react-tooltip' +import { Button, IconArrowRight } from 'ui' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' -export const ForeignKeyFormatter = ( - p: React.PropsWithChildren> -) => { - const state = useTrackedState() - const dispatch = useDispatch() - const value = p.row[p.column.key] +export const ForeignKeyFormatter = (props: PropsWithChildren>) => { + const { ref, id } = useParams() + const { meta } = useStore() - function onRowChange(_value: any | null) { - const rowData = deepClone(p.row) - rowData[p.column.key] = _value + const { row, column } = props + const selectedTable = meta.tables.byId(id as string) + const relationship = (selectedTable?.relationships ?? []).find( + (r) => + r.source_schema === selectedTable?.schema && + r.source_table_name === selectedTable?.name && + r.source_column_name === column.name + ) + const targetTable = meta.tables + .list() + .find( + (table) => + table.schema === relationship?.target_table_schema && + table.name === relationship.target_table_name + ) - const { error } = state.rowService!.update(rowData, p.row, p.column.key) - if (error) { - if (state.onError) state.onError(error) - } else { - dispatch({ - type: 'EDIT_ROW', - payload: { row: rowData, idx: p.row.idx }, - }) - } - } - - function onChange(_value: any | null) { - onRowChange(_value) - } + const value = row[column.key] return (
      {value === null ? : value} - + {relationship !== undefined && targetTable !== undefined && value !== null && ( + + + + +
      ) } diff --git a/studio/components/grid/components/grid/ColumnHeader.tsx b/studio/components/grid/components/grid/ColumnHeader.tsx index 8b945f3fdd4..decdda4da38 100644 --- a/studio/components/grid/components/grid/ColumnHeader.tsx +++ b/studio/components/grid/components/grid/ColumnHeader.tsx @@ -1,12 +1,14 @@ import * as React from 'react' -import { IconKey, IconLink, IconLock } from 'ui' +import { IconArrowRight, IconKey, IconLink, IconLock } from 'ui' import * as Tooltip from '@radix-ui/react-tooltip' import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd' import { XYCoord } from 'dnd-core' import { useDispatch } from '../../store' -import { ColumnHeaderProps, ColumnType, DragItem } from '../../types' +import { ColumnHeaderProps, ColumnType, DragItem, GridForeignKey } from '../../types' import { ColumnMenu } from '../menu' import { useTrackedState } from '../../store' +import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants' +import { getForeignKeyDeletionAction } from 'components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils' export function ColumnHeader({ column, @@ -14,6 +16,7 @@ export function ColumnHeader({ isPrimaryKey, isEncrypted, format, + foreignKey, }: ColumnHeaderProps) { const ref = React.useRef(null) const dispatch = useDispatch() @@ -119,7 +122,7 @@ export function ColumnHeader({
      - {renderColumnIcon(columnType)} + {renderColumnIcon(columnType, { name: column.name as string, foreignKey })} {isPrimaryKey && ( @@ -127,17 +130,19 @@ export function ColumnHeader({
      - - -
      - Primary key -
      -
      + + + +
      + Primary key +
      +
      +
      )} @@ -149,17 +154,19 @@ export function ColumnHeader({ - - -
      - Encrypted column -
      -
      + + + +
      + Encrypted column +
      +
      +
      )}
      @@ -169,13 +176,47 @@ export function ColumnHeader({ ) } -function renderColumnIcon(type: ColumnType) { +function renderColumnIcon( + type: ColumnType, + columnMeta: { name?: string; foreignKey?: GridForeignKey } +) { + const { name, foreignKey } = columnMeta switch (type) { case 'foreign_key': return ( -
      - -
      + + + + + + + +
      +
      +

      Foreign key relation:

      +
      +

      {name}

      + +

      + {foreignKey?.targetTableSchema}.{foreignKey?.targetTableName}. + {foreignKey?.targetColumnName} +

      +
      + {foreignKey?.deletionAction !== FOREIGN_KEY_DELETION_ACTION.NO_ACTION && ( +

      + On delete: {getForeignKeyDeletionAction(foreignKey?.deletionAction)} +

      + )} +
      +
      +
      +
      +
      ) default: return null diff --git a/studio/components/grid/components/grid/Grid.tsx b/studio/components/grid/components/grid/Grid.tsx index 07aee22745b..98612a258d1 100644 --- a/studio/components/grid/components/grid/Grid.tsx +++ b/studio/components/grid/components/grid/Grid.tsx @@ -6,6 +6,9 @@ import { GridProps, SupaRow } from '../../types' import { useDispatch, useTrackedState } from '../../store' import RowRenderer from './RowRenderer' import AwesomeDebouncePromise from 'awesome-debounce-promise' +import { ForeignRowSelectorProps } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query' function rowKeyGetter(row: SupaRow) { return row?.idx ?? -1 @@ -13,13 +16,28 @@ function rowKeyGetter(row: SupaRow) { interface IGrid extends GridProps { rows: any[] + updateRow: (previousRow: any, updatedData: any) => void + onEditForeignKeyColumnValue: (args: { + foreignKey: NonNullable + row: any + column: 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, + onEditForeignKeyColumnValue, + }, ref: React.Ref | undefined ) => { const dispatch = useDispatch() @@ -41,18 +59,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] }) } } @@ -70,6 +77,39 @@ export const Grid = memo( }) } + const table = state.table + + const { project } = useProjectContext() + const { data } = useForeignKeyConstraintsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + schema: table?.schema ?? undefined, + }) + + function getColumnForeignKey(columnName: string) { + const { targetTableSchema, targetTableName, targetColumnName } = + table?.columns.find((x) => x.name == columnName)?.foreignKey ?? {} + + return data?.find( + (key) => + key.target_schema == targetTableSchema && + key.target_table == targetTableName && + key.target_columns == targetColumnName + ) + } + + function onRowDoubleClick(row: any, column: any) { + const foreignKey = getColumnForeignKey(column.name) + + if (foreignKey) { + onEditForeignKeyColumnValue({ + foreignKey, + row, + column, + }) + } + } + if (!columnHeaders || columnHeaders.length == 0) { return (
      +export type HeaderProps = { + table: SupaTable sorts: Sort[] filters: Filter[] + isRefetching: boolean onAddColumn?: () => void onAddRow?: () => void + onImportData?: () => void headerActions?: ReactNode + customHeader: ReactNode } -const Header: FC = ({ +const Header = ({ table, sorts, filters, onAddColumn, onAddRow, + onImportData, headerActions, -}) => { + customHeader, + isRefetching, +}: HeaderProps) => { const state = useTrackedState() const { selectedRows } = state return ( -
      - {selectedRows.size > 0 ? ( - - ) : ( - - )} -
      {headerActions}
      +
      +
      + {customHeader ? ( + <>{customHeader} + ) : ( + <> + {selectedRows.size > 0 ? ( + + ) : ( + + )} + + )} +
      {headerActions}
      +
      +
      ) } + export default Header -interface DefaultHeaderProps { - table: Partial - sorts: Sort[] - filters: Filter[] +type DefaultHeaderProps = { + table: SupaTable + isRefetching: boolean onAddColumn?: () => void onAddRow?: () => void + onImportData?: () => void } -const DefaultHeader: FC = ({ +const DefaultHeader = ({ table, - sorts, - filters, + isRefetching, onAddColumn, onAddRow, -}) => { + onImportData, +}: 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') + + const [{ filter: filters, sort: sorts }, setParams] = useUrlState({ + arrayKeys: ['sort', 'filter'], + }) + return (
      - - - + + +
      {canAddNew && ( <>
      - -
      -
      -
      + {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}

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

      Import data from CSV

      +

      Insert new rows from a CSV

      +
      + , + ] + : []), + ]} + > + + + )}
      )} @@ -163,18 +222,48 @@ 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 { 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 +281,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) @@ -238,8 +349,18 @@ const RowHeader: FC = ({ sorts, filters }) => { const rows = allRowsSelected ? await state.rowService!.fetchAllData(filters, sorts) : allRows.filter((x) => selectedRows.has(x.idx)) + const formattedRows = rows.map((row) => { + const formattedRow = row + Object.keys(row).map((column) => { + if (typeof row[column] === 'object' && row[column] !== null) + formattedRow[column] = JSON.stringify(formattedRow[column]) + }) + return formattedRow + }) - const csv = exportRowsToCsv(state.table!.columns, rows) + const csv = Papa.unparse(formattedRows, { + columns: state.table!.columns.map((column) => column.name), + }) const csvData = new Blob([csv], { type: 'text/csv;charset=utf-8;' }) saveAs(csvData, `${state.table!.name}_rows.csv`) setIsExporting(false) diff --git a/studio/components/grid/components/header/RLSBannerWarning.tsx b/studio/components/grid/components/header/RLSBannerWarning.tsx new file mode 100644 index 00000000000..e1fdeb35f48 --- /dev/null +++ b/studio/components/grid/components/header/RLSBannerWarning.tsx @@ -0,0 +1,87 @@ +import { useState } from 'react' +import { Button, IconAlertCircle, IconExternalLink, Modal } from 'ui' +import Link from 'next/link' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' +import ConfirmationModal from 'components/ui/ConfirmationModal' +import type { PostgresTable } from '@supabase/postgres-meta' + +const RLS_ACKNOWLEDGED_KEY = 'supabase-acknowledge-rls-warning' + +export default function RLSBannerWarning() { + const { meta } = useStore() + const { ref: projectRef, id: tableID } = useParams() + + const isAcknowledged = localStorage.getItem(`${RLS_ACKNOWLEDGED_KEY}-${tableID}`) === 'true' + + const [isOpen, setIsOpen] = useState(false) + + const tables: PostgresTable[] = meta.tables.list() + const currentTable = tables.find((table) => table.id.toString() === tableID) + const rlsEnabled = currentTable?.rls_enabled + const isPublicTable = currentTable?.schema === 'public' + + function handleDismissWarning() { + localStorage.setItem(`${RLS_ACKNOWLEDGED_KEY}-${tableID}`, 'true') + setIsOpen(false) + } + + return ( + <> + {!isAcknowledged && !rlsEnabled && isPublicTable ? ( +
      +
      + + Warning: This table is publicly + readable and writable.{' '} + + + Enable Row Level Security + + +
      + +
      +
      + + setIsOpen(false)} + onSelectConfirm={handleDismissWarning} + children={ + +
      +

      + Data in this table is publicly readable and writable by anyone with the + anon key. We strongly recommend creating RLS (Row-Level Security) policies to + allow access to this table. +

      + +
      +
      + } + /> +
      + ) : ( + <> + )} + + ) +} diff --git a/studio/components/grid/components/header/RefreshButton.tsx b/studio/components/grid/components/header/RefreshButton.tsx index cea80d66f2e..27bd851fcac 100644 --- a/studio/components/grid/components/header/RefreshButton.tsx +++ b/studio/components/grid/components/header/RefreshButton.tsx @@ -1,20 +1,21 @@ -import { FC, useState, useEffect } from 'react' -import { Button, IconRefreshCw, IconCheck } from 'ui' +import { useQueryClient } from '@tanstack/react-query' +import { SupaTable } from 'components/grid/types' +import { sqlKeys } from 'data/sql/keys' +import { useParams } from 'common/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 + isRefetching: boolean } -const RefreshButton: FC = ({ sorts, filters }) => { - const state = useTrackedState() - const dispatch = useDispatch() +const RefreshButton = ({ table, isRefetching }: RefreshButtonProps) => { + const { ref } = useParams() + const queryClient = useQueryClient() - const [loading, setLoading] = useState(false) + const queryKey = sqlKeys.query(ref, [table.schema, table.name]) const [status, setStatus] = useState() useEffect(() => { @@ -40,10 +41,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 ( @@ -61,7 +59,7 @@ const RefreshButton: FC = ({ sorts, filters }) => { onClick={() => { if (!status) onClick() }} - loading={loading || status === 'saving'} + loading={isRefetching || status === 'saving'} > {!status ? 'Refresh' : status === 'saved' ? 'Changes saved' : 'Saving changes'} diff --git a/studio/components/grid/components/header/filter/FilterPopover.tsx b/studio/components/grid/components/header/filter/FilterPopover.tsx index 1cdecf8f3c3..ec2b1a9b507 100644 --- a/studio/components/grid/components/header/filter/FilterPopover.tsx +++ b/studio/components/grid/components/header/filter/FilterPopover.tsx @@ -1,17 +1,21 @@ -import { FC, useState, useMemo, useCallback } from 'react' +import { useState, useMemo, useCallback } from 'react' import { isEqual } from 'lodash' import { Button, IconPlus, IconFilter, Popover } from 'ui' import update from 'immutability-helper' import { useUrlState } from 'hooks' import FilterRow from './FilterRow' -import { useTrackedState } from 'components/grid/store' import { formatFilterURLParams } from 'components/grid/SupabaseGrid.utils' -import { Filter } from 'components/grid/types' +import { Filter, SupaTable } from 'components/grid/types' import { FilterOperatorOptions } from './Filter.constants' -const FilterPopover: FC = () => { - const [{ filter: filters }]: any = useUrlState({ arrayKeys: ['filter'] }) +export interface FilterPopoverProps { + table: SupaTable + filters: string[] + setParams: ReturnType[1] +} + +const FilterPopover = ({ table, filters, setParams }: FilterPopoverProps) => { const btnText = (filters || []).length > 0 ? `Filtered by ${filters.length} rule${filters.length > 1 ? 's' : ''}` @@ -22,7 +26,7 @@ const FilterPopover: FC = () => { size="large" align="start" className="sb-grid-filter-popover" - overlay={} + overlay={} > - {newToken && } +
      +
      +
      + + { + setTokenScope('V0') + setIsOpen(true) + }} + > +
      +

      Generate token for experimental API

      +
      + , + ]} + > +
      +
      +
      + setIsOpen(!isOpen)} header={
      -
      Generate New Token
      +
      + {tokenScope === 'V0' ? 'Generate token for experimental API' : 'Generate New Token'} +
      } > @@ -72,19 +102,47 @@ const NewAccessTokenButton = observer(() => { > {({ isSubmitting }: { isSubmitting: boolean }) => (
      + {tokenScope === 'V0' && ( + + +

      + These include deleting organizations and projects which cannot be undone. As + such, be very careful when using this API. +

      + +
      +
      + )} - +
      + + +
      )} @@ -95,28 +153,3 @@ const NewAccessTokenButton = observer(() => { }) export default NewAccessTokenButton - -interface NewTokenItemProps { - data: NewAccessToken -} - -const NewTokenItem: FC = observer(({ data }) => { - return ( - -
      -

      - Do copy this access token and store it in a secure place - you will not be able to see it - again. -

      - {}} - /> -
      -
      - ) -}) diff --git a/studio/components/interfaces/Account/NewTokenBanner.tsx b/studio/components/interfaces/Account/NewTokenBanner.tsx new file mode 100644 index 00000000000..242cc88f31f --- /dev/null +++ b/studio/components/interfaces/Account/NewTokenBanner.tsx @@ -0,0 +1,30 @@ +import { Alert, Input } from 'ui' +import { observer } from 'mobx-react-lite' +import { NewAccessToken } from 'data/access-tokens/access-tokens-create-mutation' + +interface NewTokenBannerProps { + token: NewAccessToken +} + +const NewTokenBanner = observer(({ token }: NewTokenBannerProps) => { + return ( + +
      +

      + Do copy this access token and store it in a secure place - you will not be able to see it + again. +

      + {}} + /> +
      +
      + ) +}) + +export default NewTokenBanner diff --git a/studio/components/interfaces/Account/index.ts b/studio/components/interfaces/Account/index.ts index 20793536e14..4ce89542872 100644 --- a/studio/components/interfaces/Account/index.ts +++ b/studio/components/interfaces/Account/index.ts @@ -1,4 +1,5 @@ import AccessTokenList from './AccessTokenList' +import NewTokenBanner from './NewTokenBanner' import NewAccessTokenButton from './NewAccessTokenButton' -export { AccessTokenList, NewAccessTokenButton } +export { AccessTokenList, NewTokenBanner, NewAccessTokenButton } diff --git a/studio/components/interfaces/Auth/Auth.constants.ts b/studio/components/interfaces/Auth/Auth.constants.ts index 32f80bc5cb2..c9d229fb963 100644 --- a/studio/components/interfaces/Auth/Auth.constants.ts +++ b/studio/components/interfaces/Auth/Auth.constants.ts @@ -7,12 +7,12 @@ // "https://uptime-monitor-fe.vercel.app/" // Supports wildcards, port numbers at the end, paths at the end and query params -const baseDomainRegex = +const baseUrlRegex = /^((ftp|http|https):\/\/)?(www.)?(?!.*(ftp|http|https|www.))[a-zA-Z0-9_*-]+(\.[a-zA-Z0-9_*-]+)+((\/)[\w#]+)*(\/\w+\?[a-zA-Z0-9_]+=\w+(&[a-zA-Z0-9_]+=\w+)*)+(?:\.[a-z]+)*(?::\d+)?(?![^<]*(?:<\/\w+>|\/?>))(.*)?\/?(.)*?$/gm // iOS deep linking scheme https://benoitpasquier.com/deep-linking-url-scheme-ios/ const appRegex = - /^[a-z0-9]+([.][a-z0-9]+)*:\/(\/[-a-z0-9._~!$&'()*+,;=:@%]+)+(?:\.[a-z]+)*(?::\d+)?(?![^<]*(?:<\/\w+>|\/?>))(.*)?\/?(.)*?$/i + /^[a-z0-9-]+([.][a-z0-9]+)*:\/(\/[-a-z0-9._~!$&'()*+,;=:@%]+)+(?:\.[a-z]+)*(?::\d+)?(?![^<]*(?:<\/\w+>|\/?>))(.*)?\/?(.)*?$/i // Regex from https://stackoverflow.com/a/18696953/4807782 const localhostRegex = /^(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/i @@ -21,7 +21,7 @@ const localhostRegex = /^(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+ const chromeExtensionRegex = /chrome-extension:\/\/([a-zA-Z]*)/gm // combine the above regexes -export const domainRegex = new RegExp( - `(${baseDomainRegex.source})|(${localhostRegex.source})|(${appRegex.source})|(${chromeExtensionRegex.source})`, +export const urlRegex = new RegExp( + `(${baseUrlRegex.source})|(${localhostRegex.source})|(${appRegex.source})|(${chromeExtensionRegex.source})`, 'i' ) diff --git a/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx b/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx index 138d1711c28..f9ab8236697 100644 --- a/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx +++ b/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx @@ -1,6 +1,6 @@ import { observer } from 'mobx-react-lite' -import { useStore } from 'hooks' -import { PROVIDERS_SCHEMAS } from 'stores/authConfig/schema' +import { useStore, useFlag } from 'hooks' +import { PROVIDERS_SCHEMAS, PROVIDER_SAML } from 'stores/authConfig/schema' import { FormHeader } from 'components/ui/Forms' import { HorizontalShimmerWithIcon } from 'components/ui/Shimmers' import ProviderForm from './ProviderForm' @@ -8,7 +8,10 @@ import { ProviderCollapsibleClasses } from './AuthProvidersForm.constants' const AuthProvidersForm = () => { const { authConfig } = useStore() - const providers = PROVIDERS_SCHEMAS + const isSamlEnabled = useFlag('isSamlEnabled') + const providers = isSamlEnabled + ? PROVIDERS_SCHEMAS + : PROVIDERS_SCHEMAS.filter((provider) => provider !== PROVIDER_SAML) return (
      diff --git a/studio/components/interfaces/Auth/AuthProvidersForm/FormField.tsx b/studio/components/interfaces/Auth/AuthProvidersForm/FormField.tsx index f7c7d879784..f9437860cc7 100644 --- a/studio/components/interfaces/Auth/AuthProvidersForm/FormField.tsx +++ b/studio/components/interfaces/Auth/AuthProvidersForm/FormField.tsx @@ -1,3 +1,4 @@ +import { BASE_PATH } from 'lib/constants' import { FC, useState } from 'react' import ReactMarkdown from 'react-markdown' import { Button, Input, InputNumber, Toggle, Listbox, IconEye, IconEyeOff } from 'ui' @@ -102,7 +103,17 @@ const FormField: FC = ({ name, properties, formValues, disabled = false } name={name} disabled={disabled} label={properties.title} - descriptionText={properties.description} + descriptionText={ + properties.description ? ( + + {properties.description} + + ) : null + } /> ) @@ -113,7 +124,17 @@ const FormField: FC = ({ name, properties, formValues, disabled = false } name={name} disabled={disabled} label={properties.title} - descriptionText={properties.description} + descriptionText={ + properties.description ? ( + + {properties.description} + + ) : null + } defaultValue={properties.enum[0]} > {properties.enum.map((option: Enum) => { @@ -123,7 +144,9 @@ const FormField: FC = ({ name, properties, formValues, disabled = false } key={option.value} label={option.label} value={option.value} - addOnBefore={() => } + addOnBefore={() => ( + + )} > {option.label} diff --git a/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx b/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx index c438d96b468..7e70bcebd4d 100644 --- a/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx +++ b/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx @@ -5,6 +5,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { Alert, Button, Collapsible, Form, IconCheck, IconChevronUp, Input } from 'ui' import { useStore, checkPermissions } from 'hooks' +import { BASE_PATH } from 'lib/constants' import { Provider } from './AuthProvidersForm.types' import { ProviderCollapsibleClasses } from './AuthProvidersForm.constants' import FormField from './FormField' @@ -25,12 +26,23 @@ const ProviderForm: FC = ({ provider }) => { Object.keys(provider.properties).forEach((key) => { // When the key is a 'double negative' key, we must reverse the boolean before adding it to the form const isDoubleNegative = doubleNegativeKeys.includes(key) - initialValues[key] = isDoubleNegative ? !authConfig.config[key] : authConfig.config[key] ?? '' + + if (provider.title === 'SAML 2.0') { + initialValues[key] = authConfig.config[key] ?? false + } else { + initialValues[key] = isDoubleNegative + ? !authConfig.config[key] + : authConfig.config[key] ?? '' + } }) return initialValues } - const isActive = authConfig.config[`EXTERNAL_${provider?.title?.toUpperCase()}_ENABLED`] + // [Joshen] Doing this check as SAML doesn't follow the same naming structure as the other provider options + const isActive = + provider.title === 'SAML 2.0' + ? authConfig.config['SAML_ENABLED'] || false + : authConfig.config[`EXTERNAL_${provider?.title?.toUpperCase()}_ENABLED`] const INITIAL_VALUES = generateInitialValues() const onSubmit = async (values: any, { setSubmitting, resetForm }: any) => { @@ -78,7 +90,7 @@ const ProviderForm: FC = ({ provider }) => { width={14} /> {`${provider.title} @@ -162,25 +174,27 @@ const ProviderForm: FC = ({ provider }) => { {!canUpdateConfig && ( - - -
      - - You need additional permissions to update provider settings - -
      -
      + + + +
      + + You need additional permissions to update provider settings + +
      +
      +
      )}
      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 (
      -
      + = ({ - - -
      -

      - This expression will be added to queries that refer to the table if row-level - security is enabled. -

      -

      - Rows for which the expression returns true will be visible. Any rows for which - the expression returns false or null will not be visible to the user (in a - SELECT), and will not be available for modification (in an UPDATE or DELETE). -

      -

      - Such rows are silently suppressed - no error is reported. -

      -
      -
      + + + +
      +

      + This expression will be added to queries that refer to the table if + row-level security is enabled. +

      +

      + Rows for which the expression returns true will be visible. Any rows for + which the expression returns false or null will not be visible to the user + (in a SELECT), and will not be available for modification (in an UPDATE or + DELETE). +

      +

      + Such rows are silently suppressed - no error is reported. +

      +
      +
      +

      @@ -87,29 +90,31 @@ const PolicyDefinition: FC = ({ - - -

      -

      - This expression will be used in INSERT and UPDATE queries against the table if - row-level security is enabled. -

      -

      - Only rows for which the expression evaluates to true will be allowed. An error - will be thrown if the expression evaluates to false or null for any of the - records inserted or any of the records that result from the update. -

      -

      - Note that this expression is evaluated against the proposed new contents of - the row, not the original contents. -

      -
      - + + + +
      +

      + This expression will be used in INSERT and UPDATE queries against the table + if row-level security is enabled. +

      +

      + Only rows for which the expression evaluates to true will be allowed. An + error will be thrown if the expression evaluates to false or null for any of + the records inserted or any of the records that result from the update. +

      +

      + Note that this expression is evaluated against the proposed new contents of + the row, not the original contents. +

      +
      +
      +

      diff --git a/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx b/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx index b3a00e0d638..41518f2742e 100644 --- a/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx +++ b/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx @@ -82,19 +82,21 @@ const PolicyRow: FC = ({ /> {!canUpdatePolicies && ( - - -

      - - You need additional permissions to edit RLS policies - -
      - + + + +
      + + You need additional permissions to edit RLS policies + +
      +
      +
      )} )} diff --git a/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx b/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx index 82cfd727e02..b081c241095 100644 --- a/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx +++ b/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx @@ -52,19 +52,21 @@ const PolicyTableRowHeader: FC = ({ {!canCreatePolicies && ( - - -
      - - You need additional permissions to create RLS policies - -
      -
      + + + +
      + + You need additional permissions to create RLS policies + +
      +
      +
      )} @@ -78,19 +80,21 @@ const PolicyTableRowHeader: FC = ({ {!canToggleRLS && ( - - -
      - - You need additional permissions to toggle RLS - -
      -
      + + + +
      + + You need additional permissions to toggle RLS + +
      +
      +
      )}
      diff --git a/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx b/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx index cc2551da145..4b943903e45 100644 --- a/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx +++ b/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx @@ -53,9 +53,10 @@ const PolicyTableRow: FC = ({ withIcon variant="warning" className="!px-4 !py-3 !mt-3" - title="Warning: RLS is disabled" + title="Warning: RLS is disabled. Your table is publicly readable and writable." > - Anonymous access is allowed to this table + Anyone with the anon. key can modify or delete your data. You should turn on RLS and + create access policies to keep your data secure. )}
      diff --git a/studio/components/interfaces/Auth/RedirectDomains/DomainList.tsx b/studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx similarity index 82% rename from studio/components/interfaces/Auth/RedirectDomains/DomainList.tsx rename to studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx index 5e341de01e0..44f60d3b15b 100644 --- a/studio/components/interfaces/Auth/RedirectDomains/DomainList.tsx +++ b/studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx @@ -9,10 +9,10 @@ import { HorizontalShimmerWithIcon } from 'components/ui/Shimmers' interface Props { canUpdate: boolean - onSelectDomainToDelete: (domain: string) => void + onSelectUrlToDelete: (url: string) => void } -const DomainList: FC = ({ canUpdate, onSelectDomainToDelete }) => { +const RedirectUrlList: FC = ({ canUpdate, onSelectUrlToDelete }) => { const { authConfig } = useStore() const URI_ALLOW_LIST_ARRAY = authConfig.config.URI_ALLOW_LIST @@ -31,20 +31,20 @@ const DomainList: FC = ({ canUpdate, onSelectDomainToDelete }) => { ) : URI_ALLOW_LIST_ARRAY.length > 0 ? ( - URI_ALLOW_LIST_ARRAY.map((domain: string) => { + URI_ALLOW_LIST_ARRAY.map((url: string) => { return ( - +
      - {domain} + {url}
      {canUpdate && ( @@ -69,4 +69,4 @@ const DomainList: FC = ({ canUpdate, onSelectDomainToDelete }) => { ) } -export default observer(DomainList) +export default observer(RedirectUrlList) diff --git a/studio/components/interfaces/Auth/RedirectDomains/RedirectDomains.tsx b/studio/components/interfaces/Auth/RedirectUrls/RedirectUrls.tsx similarity index 60% rename from studio/components/interfaces/Auth/RedirectDomains/RedirectDomains.tsx rename to studio/components/interfaces/Auth/RedirectUrls/RedirectUrls.tsx index 14354efddd0..8483d0df26d 100644 --- a/studio/components/interfaces/Auth/RedirectDomains/RedirectDomains.tsx +++ b/studio/components/interfaces/Auth/RedirectUrls/RedirectUrls.tsx @@ -7,10 +7,10 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { checkPermissions, useStore } from 'hooks' import { FormHeader } from 'components/ui/Forms' -import { domainRegex } from '../Auth.constants' -import DomainList from './DomainList' +import { urlRegex } from '../Auth.constants' +import RedirectUrlList from './RedirectUrlList' -const RedirectDomains = () => { +const RedirectUrls = () => { const { authConfig, ui } = useStore() const URI_ALLOW_LIST_ARRAY = authConfig.config.URI_ALLOW_LIST @@ -19,24 +19,24 @@ const RedirectDomains = () => { const [open, setOpen] = useState(false) const [isDeleting, setIsDeleting] = useState(false) - const [selectedDomainToDelete, setSelectedDomainToDelete] = useState() + const [selectedUrlToDelete, setSelectedUrlToDelete] = useState() const canUpdateConfig = checkPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') - const newDomainSchema = object({ - domain: string().matches(domainRegex, 'URL is not valid').required(), + const newUrlSchema = object({ + url: string().matches(urlRegex, 'URL is not valid').required(), }) - const onAddNewDomain = async (values: any, { setSubmitting }: any) => { + const onAddNewUrl = async (values: any, { setSubmitting }: any) => { setSubmitting(true) const payload = URI_ALLOW_LIST_ARRAY - payload.push(values.domain) + payload.push(values.url) const payloadString = payload.toString() if (payloadString.length > 2 * 1024) { ui.setNotification({ - message: 'Too many redirect domains, please remove some or try to use wildcards', + message: 'Too many redirect URLs, please remove some or try to use wildcards', category: 'error', }) @@ -47,36 +47,36 @@ const RedirectDomains = () => { const { error } = await authConfig.update({ URI_ALLOW_LIST: payloadString }) if (!error) { setOpen(false) - ui.setNotification({ category: 'success', message: 'Successfully added domain' }) + ui.setNotification({ category: 'success', message: 'Successfully added URL' }) } else { ui.setNotification({ error, category: 'error', - message: `Failed to update domain: ${error?.message}`, + message: `Failed to update URL: ${error?.message}`, }) } setSubmitting(false) } - const onConfirmDeleteDomain = async (domain?: string) => { - if (!domain) return + const onConfirmDeleteUrl = async (url?: string) => { + if (!url) return setIsDeleting(true) - // Remove selectedDomain from array and update - const payload = URI_ALLOW_LIST_ARRAY.filter((e: string) => e !== domain) + // Remove selectedUrl from array and update + const payload = URI_ALLOW_LIST_ARRAY.filter((e: string) => e !== url) const { error } = await authConfig.update({ URI_ALLOW_LIST: payload.toString() }) if (!error) { - setSelectedDomainToDelete(undefined) - ui.setNotification({ category: 'success', message: 'Successfully removed domain' }) + setSelectedUrlToDelete(undefined) + ui.setNotification({ category: 'success', message: 'Successfully removed URL' }) } else { ui.setNotification({ error, category: 'error', - message: `Failed to remove domain: ${error?.message}`, + message: `Failed to remove URL: ${error?.message}`, }) } @@ -97,66 +97,63 @@ const RedirectDomains = () => { {!canUpdateConfig && ( - - -
      - - You need additional permissions to update redirect URLs - -
      -
      + + + +
      + + You need additional permissions to update redirect URLs + +
      +
      +
      )}
      - + setOpen(!open)} - header={

      Add a new domain

      } + header={

      Add a new URL

      } >
      {({ isSubmitting }: { isSubmitting: boolean }) => { return (

      - This will add a domain to a list of allowed domains that can interact with your + This will add a URL to a list of allowed URLs that can interact with your Authenticaton services for this project.

      - +
      @@ -167,18 +164,18 @@ const RedirectDomains = () => { Remove domain} - onCancel={() => setSelectedDomainToDelete(undefined)} + visible={selectedUrlToDelete !== undefined} + header={

      Remove URL

      } + onCancel={() => setSelectedUrlToDelete(undefined)} >

      Are you sure you want to remove{' '} - {selectedDomainToDelete}? + {selectedUrlToDelete}?

      -

      - This domain will no longer work with your authentication configuration. +

      + This URL will no longer work with your authentication configuration.

      @@ -187,7 +184,7 @@ const RedirectDomains = () => { block type="default" size="medium" - onClick={() => setSelectedDomainToDelete(undefined)} + onClick={() => setSelectedUrlToDelete(undefined)} > Cancel @@ -196,9 +193,9 @@ const RedirectDomains = () => { size="medium" type="warning" loading={isDeleting} - onClick={() => onConfirmDeleteDomain(selectedDomainToDelete)} + onClick={() => onConfirmDeleteUrl(selectedUrlToDelete)} > - {isDeleting ? 'Removing...' : 'Remove domain'} + {isDeleting ? 'Removing...' : 'Remove URL'}
      @@ -207,4 +204,4 @@ const RedirectDomains = () => { ) } -export default observer(RedirectDomains) +export default observer(RedirectUrls) diff --git a/studio/components/interfaces/Auth/RedirectDomains/ValueContainer.tsx b/studio/components/interfaces/Auth/RedirectUrls/ValueContainer.tsx similarity index 100% rename from studio/components/interfaces/Auth/RedirectDomains/ValueContainer.tsx rename to studio/components/interfaces/Auth/RedirectUrls/ValueContainer.tsx diff --git a/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx b/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx index 18d7ec4d7b1..4014bc69c75 100644 --- a/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx +++ b/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx @@ -13,7 +13,7 @@ import { FormSectionLabel, } from 'components/ui/Forms' import { useStore, checkPermissions } from 'hooks' -import { domainRegex } from './../Auth.constants' +import { urlRegex } from './../Auth.constants' import { defaultDisabledSmtpFormValues } from './SmtpForm.constants' import { generateFormValues, isSmtpEnabled } from './SmtpForm.utils' @@ -55,7 +55,7 @@ const SmtpForm = () => { }, then: (schema) => schema - .matches(domainRegex, 'Must be a valid URL or IP address') + .matches(urlRegex, 'Must be a valid URL or IP address') .required('Host URL is required.'), otherwise: (schema) => schema, }), diff --git a/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx b/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx new file mode 100644 index 00000000000..3cacf12808d --- /dev/null +++ b/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx @@ -0,0 +1,112 @@ +import * as Tooltip from '@radix-ui/react-tooltip' +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { checkPermissions } from 'hooks' +import { IS_PLATFORM } from 'lib/constants' +import { useState } from 'react' +import semver from 'semver' +import { Button, Dropdown, IconChevronDown, IconMail, IconPlus, IconUserPlus } from 'ui' +import CreateUserModal from './CreateUserModal' +import InviteUserModal from './InviteUserModal' + +export type AddUserDropdownProps = { + projectKpsVersion?: string +} + +const AddUserDropdown = ({ projectKpsVersion }: AddUserDropdownProps) => { + const inviteEnabled = IS_PLATFORM + ? semver.gte( + // @ts-ignore + semver.coerce(projectKpsVersion ?? 'kps-v2.5.4'), + semver.coerce('kps-v2.5.3') + ) + : true + + const canInviteUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'invite_user') + const canCreateUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'create_user') + + const [inviteVisible, setInviteVisible] = useState(false) + const [createVisible, setCreateVisible] = useState(false) + + return ( + <> + + {inviteEnabled && ( + + + } + disabled={!canInviteUsers} + onClick={() => setInviteVisible(true)} + > + Send Invitation + + + {!canInviteUsers && ( + + + +
      + + You need additional permissions to invite users + +
      +
      +
      + )} +
      + )} + + + + } + disabled={!canCreateUsers} + onClick={() => setCreateVisible(true)} + > + Create New User + + + + {!canCreateUsers && ( + + + +
      + + You need additional permissions to create users + +
      +
      +
      + )} +
      + + } + > + +
      + + {inviteEnabled && } + + + ) +} + +export default AddUserDropdown diff --git a/studio/components/interfaces/Auth/Users/CreateUserModal.tsx b/studio/components/interfaces/Auth/Users/CreateUserModal.tsx new file mode 100644 index 00000000000..94698aca740 --- /dev/null +++ b/studio/components/interfaces/Auth/Users/CreateUserModal.tsx @@ -0,0 +1,164 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { observer } from 'mobx-react-lite' +import { useContext } from 'react' +import { Button, Checkbox, Form, IconLock, IconMail, Input, Loading, Modal } from 'ui' + +import { useUserCreateMutation } from 'data/auth/user-create-mutation' +import { useProjectApiQuery } from 'data/config/project-api-query' +import { checkPermissions, useStore } from 'hooks' +import { PageContext } from 'pages/project/[ref]/auth/users' + +export type CreateUserModalProps = { + visible: boolean + setVisible: (visible: boolean) => void +} + +const CreateUserModal = ({ visible, setVisible }: CreateUserModalProps) => { + const { ui } = useStore() + const PageState: any = useContext(PageContext) + const projectRef = PageState.projectRef + + const { data, isLoading, isSuccess } = useProjectApiQuery({ projectRef }, { enabled: visible }) + + const handleToggle = () => setVisible(!visible) + const canCreateUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'create_user') + + const validate = (values: any) => { + const errors: any = {} + const emailValidateRegex = + /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/ + + if (values.email.length === 0) { + errors.email = 'Please enter a valid email' + } else if (!emailValidateRegex.test(values.email)) { + errors.email = `${values.email} is an invalid email` + } + + if (!values.password?.trim()) { + errors.password = 'Please enter a password' + } + + return errors + } + + const { mutateAsync: createUser } = useUserCreateMutation({ + async onSuccess() { + await PageState.fetchData(1) + }, + }) + + const onCreateUser = async (values: any, { setSubmitting }: any) => { + if (!isSuccess) { + ui.setNotification({ + category: 'error', + message: `Failed to create user: Error loading project config`, + }) + + return + } + + const { protocol, endpoint, serviceApiKey } = data.autoApiService + + setSubmitting(true) + + try { + await createUser({ + endpoint, + protocol, + serviceApiKey, + user: values, + }) + + ui.setNotification({ + category: 'success', + message: `Created user: ${values.email}`, + }) + + setVisible(false) + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to create user: ${error.message}`, + }) + } + + setSubmitting(false) + } + + return ( + + + {({ isSubmitting }: { isSubmitting: boolean }) => ( + +
      + +
      + } + type="email" + name="email" + placeholder="user@example.com" + disabled={isSubmitting || isLoading} + /> + + } + disabled={isSubmitting || isLoading} + autoComplete="new-password" + /> + + +
      +
      + + + + +
      +
      + )} + +
      + ) +} + +export default observer(CreateUserModal) diff --git a/studio/components/interfaces/Auth/Users/InviteUserModal.tsx b/studio/components/interfaces/Auth/Users/InviteUserModal.tsx index 5f5898714cf..2ec628eb448 100644 --- a/studio/components/interfaces/Auth/Users/InviteUserModal.tsx +++ b/studio/components/interfaces/Auth/Users/InviteUserModal.tsx @@ -1,18 +1,21 @@ -import { useContext, useState } from 'react' -import { observer } from 'mobx-react-lite' -import * as Tooltip from '@radix-ui/react-tooltip' -import { Button, Modal, Input, IconPlus, IconMail, Form } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' +import { observer } from 'mobx-react-lite' +import { useContext } from 'react' +import { Button, Form, IconMail, Input, Modal } from 'ui' -import { useStore, checkPermissions } from 'hooks' +import { checkPermissions, useStore } from 'hooks' import { post } from 'lib/common/fetch' import { API_URL } from 'lib/constants' import { PageContext } from 'pages/project/[ref]/auth/users' -const InviteUserModal = () => { +export type InviteUserModalProps = { + visible: boolean + setVisible: (visible: boolean) => void +} + +const InviteUserModal = ({ visible, setVisible }: InviteUserModalProps) => { const { ui } = useStore() const PageState: any = useContext(PageContext) - const [visible, setVisible] = useState(false) const handleToggle = () => setVisible(!visible) const canInviteUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'invite_user') @@ -31,7 +34,7 @@ const InviteUserModal = () => { return errors } - const onInviteuser = async (values: any, { setSubmitting }: any) => { + const onInviteUser = async (values: any, { setSubmitting }: any) => { setSubmitting(true) const response = await post(`${API_URL}/auth/${PageState.projectRef}/invite`, { @@ -56,28 +59,6 @@ const InviteUserModal = () => { return (
      - - - - - {!canInviteUsers && ( - - -
      - - You need additional permissions to invite users - -
      -
      - )} -
      { onCancel={handleToggle} >
      {({ isSubmitting }: { isSubmitting: boolean }) => (
      { placeholder="User email" /> +
      -
      +
      - {inviteEnabled && } + +
      -
      +
      diff --git a/studio/components/interfaces/Billing/AddOns/ComputeSizeSelection.tsx b/studio/components/interfaces/Billing/AddOns/ComputeSizeSelection.tsx index 3354fc5be98..437e7d152c1 100644 --- a/studio/components/interfaces/Billing/AddOns/ComputeSizeSelection.tsx +++ b/studio/components/interfaces/Billing/AddOns/ComputeSizeSelection.tsx @@ -2,7 +2,8 @@ import { FC } from 'react' import Link from 'next/link' import { Badge, Button, IconExternalLink, Radio } from 'ui' -import { useFlag, useParams } from 'hooks' +import { useFlag } from 'hooks' +import { useParams } from 'common/hooks' import { getProductPrice } from '../Billing.utils' import DisabledWarningDueToIncident from 'components/ui/DisabledWarningDueToIncident' import { SubscriptionAddon } from './AddOns.types' diff --git a/studio/components/interfaces/Billing/AddOns/CustomDomainSelection.tsx b/studio/components/interfaces/Billing/AddOns/CustomDomainSelection.tsx index 8e5515013f9..022940f66cd 100644 --- a/studio/components/interfaces/Billing/AddOns/CustomDomainSelection.tsx +++ b/studio/components/interfaces/Billing/AddOns/CustomDomainSelection.tsx @@ -2,7 +2,8 @@ import Link from 'next/link' import { FC } from 'react' import { Badge, Button, Radio } from 'ui' -import { useFlag, useParams } from 'hooks' +import { useFlag } from 'hooks' +import { useParams } from 'common/hooks' import { getProductPrice } from '../Billing.utils' import DisabledWarningDueToIncident from 'components/ui/DisabledWarningDueToIncident' import { SubscriptionAddon } from './AddOns.types' diff --git a/studio/components/interfaces/Billing/AddOns/PITRDurationSelection.tsx b/studio/components/interfaces/Billing/AddOns/PITRDurationSelection.tsx index 88609d60601..52476a92bd4 100644 --- a/studio/components/interfaces/Billing/AddOns/PITRDurationSelection.tsx +++ b/studio/components/interfaces/Billing/AddOns/PITRDurationSelection.tsx @@ -2,7 +2,8 @@ import Link from 'next/link' import { FC } from 'react' import { Badge, IconAlertCircle, Radio, Button } from 'ui' -import { useFlag, useParams, useStore } from 'hooks' +import { useFlag, useStore } from 'hooks' +import { useParams } from 'common/hooks' import { SubscriptionAddon } from './AddOns.types' import { getProductPrice } from '../Billing.utils' import DisabledWarningDueToIncident from 'components/ui/DisabledWarningDueToIncident' diff --git a/studio/components/interfaces/Billing/AddOns/SupportPlan.tsx b/studio/components/interfaces/Billing/AddOns/SupportPlan.tsx index 56f22afce68..5fa2c3af196 100644 --- a/studio/components/interfaces/Billing/AddOns/SupportPlan.tsx +++ b/studio/components/interfaces/Billing/AddOns/SupportPlan.tsx @@ -1,7 +1,7 @@ import { FC } from 'react' import Link from 'next/link' import { Button } from 'ui' -import { useParams } from 'hooks' +import { useParams } from 'common/hooks' interface Props { currentOption?: any 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/Billing/InvoiceStatusBadge.tsx b/studio/components/interfaces/Billing/InvoiceStatusBadge.tsx new file mode 100644 index 00000000000..ecf6a931eef --- /dev/null +++ b/studio/components/interfaces/Billing/InvoiceStatusBadge.tsx @@ -0,0 +1,92 @@ +import { FC } from 'react' +import * as Tooltip from '@radix-ui/react-tooltip' +import { Badge } from 'ui' +import { InvoiceStatus } from './Invoices.types' + +interface Props { + status: InvoiceStatus +} + +export const invoiceStatusMapping: Record = { + [InvoiceStatus.DRAFT]: { + label: 'Upcoming', + badgeColor: 'yellow', + }, + [InvoiceStatus.PAID]: { + label: 'Paid', + badgeColor: 'green', + }, + [InvoiceStatus.VOID]: { + label: 'Forgiven', + badgeColor: 'green', + }, + + // We do not want to overcomplicate it for the user, so we'll treat uncollectible/open the same from a user perspective + // it's an outstanding invoice + [InvoiceStatus.UNCOLLECTIBLE]: { + label: 'Outstanding', + badgeColor: 'red', + }, + [InvoiceStatus.OPEN]: { + label: 'Outstanding', + badgeColor: 'red', + }, +} + +const InvoiceStatusBadge: FC = ({ status }) => { + const statusMapping = invoiceStatusMapping[status] + + return ( + + + + {statusMapping?.label || status} + + + + + +
      + {[InvoiceStatus.OPEN, InvoiceStatus.UNCOLLECTIBLE].includes(status) && ( +

      + We were not able to collect the money. Make sure you have a valid payment method and + enough funds. Outstanding invoices may cause restrictions. You can manually pay the + using the "Pay Now" button. +

      + )} + + {status === InvoiceStatus.DRAFT && ( +

      + The invoice will soon be finalized and charged for. +

      + )} + + {status === InvoiceStatus.PAID && ( +

      + The invoice has been paid successfully. No action is required on your side. +

      + )} + + {status === InvoiceStatus.VOID && ( +

      + This invoice has been forgiven. No action is required on your side. +

      + )} +
      +
      +
      +
      + ) +} + +export default InvoiceStatusBadge diff --git a/studio/components/interfaces/Billing/Invoices.tsx b/studio/components/interfaces/Billing/Invoices.tsx index 6cde250cd9c..fb255c38353 100644 --- a/studio/components/interfaces/Billing/Invoices.tsx +++ b/studio/components/interfaces/Billing/Invoices.tsx @@ -1,11 +1,15 @@ import { FC, useState, useEffect } from 'react' import { Button, Loading, IconFileText, IconDownload, IconChevronLeft, IconChevronRight } from 'ui' +import Link from 'next/link' import { useStore } from 'hooks' import { API_URL } from 'lib/constants' import { get, head } from 'lib/common/fetch' import Table from 'components/to-be-cleaned/Table' +import InvoiceStatusBadge from './InvoiceStatusBadge' +import { Invoice, InvoiceStatus } from './Invoices.types' + const PAGE_LIMIT = 10 interface Props { @@ -18,7 +22,7 @@ const Invoices: FC = ({ projectRef }) => { const [page, setPage] = useState(1) const [count, setCount] = useState(0) - const [invoices, setInvoices] = useState([]) + const [invoices, setInvoices] = useState([]) const offset = (page - 1) * PAGE_LIMIT @@ -85,6 +89,9 @@ const Invoices: FC = ({ projectRef }) => { Date, Amount due, Invoice number, + + Status + , , ]} body={ @@ -98,7 +105,7 @@ const Invoices: FC = ({ projectRef }) => { ) : ( <> - {invoices.map((x: any) => { + {invoices.map((x) => { return ( @@ -117,11 +124,22 @@ const Invoices: FC = ({ projectRef }) => {

      {x.number}

      + + +
      + {[InvoiceStatus.UNCOLLECTIBLE, InvoiceStatus.OPEN].includes(x.status) && ( + + + + + + )} +
      @@ -130,7 +148,7 @@ const Invoices: FC = ({ projectRef }) => { ) })} - +

      Showing {offset + 1} to {offset + invoices.length} out of {count} invoices diff --git a/studio/components/interfaces/Billing/Invoices.types.ts b/studio/components/interfaces/Billing/Invoices.types.ts new file mode 100644 index 00000000000..97b8916c3e3 --- /dev/null +++ b/studio/components/interfaces/Billing/Invoices.types.ts @@ -0,0 +1,15 @@ +export enum InvoiceStatus { + DRAFT = 'draft', + PAID = 'paid', + VOID = 'void', + UNCOLLECTIBLE = 'uncollectible', + OPEN = 'open', +} + +export type Invoice = { + id: string + number: string + period_end: number + subtotal: number + status: InvoiceStatus +} diff --git a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx index 8deab10c609..bcdd0c7a1a9 100644 --- a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx +++ b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx @@ -1,15 +1,15 @@ -import { FC, useRef, useState } from 'react' +import { FC, useState } from 'react' import * as Tooltip from '@radix-ui/react-tooltip' import { Listbox, IconLoader, Button, IconPlus, IconAlertCircle, IconCreditCard } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' import { checkPermissions, useStore } from 'hooks' -import { PRICING_TIER_PRODUCT_IDS, STRIPE_PRODUCT_IDS } from 'lib/constants' +import { BASE_PATH, PRICING_TIER_PRODUCT_IDS, STRIPE_PRODUCT_IDS } from 'lib/constants' import { SubscriptionPreview } from '../Billing.types' import { getProductPrice, validateSubscriptionUpdatePayload } from '../Billing.utils' import PaymentTotal from './PaymentTotal' import InformationBox from 'components/ui/InformationBox' -import { SubscriptionAddon } from '../AddOns/AddOns.types' +import { AddonPrice, SubscriptionAddon } from '../AddOns/AddOns.types' import { getPITRDays } from './PaymentSummaryPanel.utils' import ConfirmPaymentModal from './ConfirmPaymentModal' import { StripeSubscription } from '../Subscription/Subscription.types' @@ -81,6 +81,9 @@ const PaymentSummaryPanel: FC = ({ const { ui } = useStore() const projectRegion = ui.selectedProject?.region + // [Joshen] Point of refactor: Current plan can just be currentSubscription.tier + // Reduce one unnecessary prop + const canUpdatePaymentMethods = checkPermissions( PermissionAction.BILLING_WRITE, 'stripe.payment_methods' @@ -88,6 +91,24 @@ const PaymentSummaryPanel: FC = ({ const [showConfirmModal, setShowConfirmModal] = useState(false) + const selectedPlanCost = + selectedPlan !== undefined + ? selectedPlan.prices.find( + (price: AddonPrice) => price.id === selectedPlan.metadata.default_price_id + )?.unit_amount ?? 0 + : currentSubscription?.tier.unit_amount ?? 0 + + const totalSelectedAddonCost = Object.keys(selectedAddons) + .map((productName) => { + const product = (selectedAddons as any)[productName] + const price = product.prices.find( + (price: AddonPrice) => price.id === product.metadata.default_price_id + ) + return price + }) + .reduce((a, b) => a + b.unit_amount, 0) + const totalMonthlyCost = selectedPlanCost + totalSelectedAddonCost + const currentPITRDays = currentAddons.pitrDuration !== undefined ? getPITRDays(currentAddons.pitrDuration) : 0 const selectedPITRDays = @@ -105,6 +126,11 @@ const PaymentSummaryPanel: FC = ({ const isChangingCustomDomains = currentAddons.customDomains?.id !== selectedAddons.customDomains?.id + const togglingOnSpendCap = + currentPlan.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.PAYG && + selectedPlan && + selectedPlan.metadata.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.PRO + // If it's enterprise we only only changing of add-ons const hasChangesToPlan = isEnterprise ? isChangingComputeSize || isChangingPITRDuration @@ -301,6 +327,16 @@ const PaymentSummaryPanel: FC = ({ description="It will take up to 2 minutes for changes to take place, and your project will be unavailable during that time" /> )} + + {togglingOnSpendCap && ( + } + title="Enabling spend cap" + description="Exceeding your plan's quota will result in service restrictions. With the spend cap disabled, you'll be charged for usage beyond the quota." + /> + )}

      )} @@ -308,6 +344,7 @@ const PaymentSummaryPanel: FC = ({
      = ({ {!canUpdatePaymentMethods && ( - - -
      - - You need additional permissions to add new payment methods to this - organization - -
      -
      + + + +
      + + You need additional permissions to add new payment methods to this + organization + +
      +
      +
      )}
      @@ -369,7 +408,7 @@ const PaymentSummaryPanel: FC = ({ addOnBefore={() => { return ( = ({ {!hasChangesToPlan ? ( - - -
      - - No changes made to your subscription - -
      -
      + + + +
      + + No changes made to your subscription + +
      +
      +
      ) : !selectedPaymentMethod ? ( - - -
      - Please select a payment method -
      -
      + + + +
      + Please select a payment method +
      +
      +
      ) : ( <> )} diff --git a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentTotal.tsx b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentTotal.tsx index 1966ce0cf4f..7979c53969e 100644 --- a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentTotal.tsx +++ b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentTotal.tsx @@ -5,18 +5,20 @@ import { SubscriptionPreview } from '../Billing.types' import CostBreakdownModal from './CostBreakdownModal' interface Props { + totalMonthlyCost: number subscriptionPreview?: SubscriptionPreview isRefreshingPreview: boolean isSpendCapEnabled: boolean } const PaymentTotal: FC = ({ + totalMonthlyCost, subscriptionPreview, isRefreshingPreview, isSpendCapEnabled, }) => { const hasChanges = subscriptionPreview?.has_changes ?? false - const totalMonthlyCost = (subscriptionPreview?.base_amount_due_next_billing_cycle ?? 0) / 100 + // const totalMonthlyCost = (subscriptionPreview?.base_amount_due_next_billing_cycle ?? 0) / 100 const amountDueImmediately = (subscriptionPreview?.amount_due_immediately ?? 0) / 100 const billingDate = new Date((subscriptionPreview?.bill_on ?? 0) * 1000) const isBillingToday = @@ -52,17 +54,19 @@ const PaymentTotal: FC = ({ onClick={() => setShowCostBreakdown(true)} /> - - -
      - How is this calculated? -
      -
      + + + +
      + How is this calculated? +
      +
      +
      )}
      diff --git a/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCTAButton.tsx b/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCTAButton.tsx index ca1595eaf6c..316265f8e2b 100644 --- a/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCTAButton.tsx +++ b/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCTAButton.tsx @@ -112,19 +112,23 @@ const PlanCTAButton: FC = ({ plan, currentPlan, onSelectPlan }) => { {!disabled && projectUpdateDisabled && ( - - -
      - - Subscription changes are currently disabled, our engineers are working on a fix - -
      -
      + + + + +
      + + Subscription changes are currently disabled, our engineers are working on a fix + +
      +
      +
      +
      )}
      diff --git a/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCard.tsx b/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCard.tsx index 2745c65dbcc..44637d482b9 100644 --- a/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCard.tsx +++ b/studio/components/interfaces/Billing/PlanSelection/Plans/PlanCard.tsx @@ -14,7 +14,8 @@ interface Props { const PlanCard: FC = ({ plan, currentPlan, onSelectPlan }) => { // Team tier is enabled when the flag is turned on OR the user is already on the team tier (manually assigned by us) - const teamTierEnabled = currentPlan?.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM || useFlag('teamTier') + const teamTierEnabled = + currentPlan?.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM || useFlag('teamTier') const planMeta = PRICING_META[plan.id] const isEnterprise = plan.id === 'Enterprise' @@ -118,8 +119,29 @@ const PlanCard: FC = ({ plan, currentPlan, onSelectPlan }) => { {!isEnterprise && (
      -

      {planMeta.additional}

      {planMeta.scale}

      + + {planMeta.name === 'Pro' && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PRO && ( +
      +

      + Need more? Turn off your spend cap to Pay As You Grow +

      +

      + Additional fees apply for spend beyond the limits above +

      {' '} +
      + )} + + {planMeta.name === 'Pro' && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PAYG && ( +
      +

      + Spend cap is currently disabled +

      +

      + You will be charged for usage beyond the plan limits +

      {' '} +
      + )}
      diff --git a/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts b/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts index 025e54ea8a7..c31d72eac83 100644 --- a/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts +++ b/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts @@ -49,8 +49,6 @@ export const PRICING_META = { 'No project pausing', 'Email support', ], - additional: 'Need more? Turn off your spend cap to Pay As You Grow ', - scale: 'Additional fees apply for usage and storage beyond the limits above.', }, [STRIPE_PRODUCT_IDS.TEAM]: { new: true, diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 33919b8a218..897ee2e0005 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -1,8 +1,7 @@ import { FC, useEffect, useRef, useState } from 'react' import { Transition } from '@headlessui/react' import { useRouter } from 'next/router' -import * as Tooltip from '@radix-ui/react-tooltip' -import { Button, IconHelpCircle, Toggle, Modal } from 'ui' +import { IconHelpCircle, Toggle } from 'ui' import { useStore } from 'hooks' import { post, patch } from 'lib/common/fetch' @@ -30,6 +29,7 @@ import { import BackButton from 'components/ui/BackButton' import SupportPlan from './AddOns/SupportPlan' import HCaptcha from '@hcaptcha/react-hcaptcha' +import SpendCapModal from './SpendCapModal' // Do not allow compute size changes for af-south-1 @@ -263,7 +263,7 @@ const ProUpgrade: FC = ({
      -

      Enable spend cap

      +

      Spend cap

      = ({ />

      - If enabled, additional resources will not be charged on a per-usage basis + By default, Pro projects have a spend cap enabled to control costs and prevent + exceeding limits. This restricts usage upon exhausting the quota. To scale + without restrictions, disable the spend cap and be charged for overusage beyond + the quota.

      = ({ /> {/* Spend caps helper modal */} - setShowSpendCapHelperModal(false)} - > -
      - -
      -

      - A spend cap allows you to restrict your project's resource usage within the limits - of the Pro tier. Disabling the spend cap will then remove those limits and any - additional resources consumed beyond the Pro tier limits will be charged on a - per-usage basis -

      -

      - The table below shows an overview of which resources are chargeable, and how they - are charged: -

      - {/* Maybe instead of a table, show something more interactive like a spend cap playground */} - {/* Maybe ideate this in Figma first but this is good enough for now */} -
      -
      -

      Item

      -

      Limit

      -

      Rate

      -
      -
      -
      -

      Database size

      -

      8GB

      -

      $0.125/GB

      -
      -
      -

      Data transfer

      -

      50GB

      -

      $0.09/GB

      -
      -
      -
      -
      -
      -

      Auth MAUs

      - - - - - - -
      - - Monthly Active Users: A user that has made an API request in the last - month - -
      -
      -
      -
      -

      100,000

      -

      $0.00325/user

      -
      -
      -
      -
      -

      Storage size

      -

      100GB

      -

      $0.021/GB

      -
      -
      -

      Data Transfer

      -

      50GB

      -

      $0.09/GB

      -
      -
      -
      -
      -
      - - -
      - -
      -
      -
      -
      + onHide={() => setShowSpendCapHelperModal(false)} + /> ) } diff --git a/studio/components/interfaces/Billing/SpendCapModal.tsx b/studio/components/interfaces/Billing/SpendCapModal.tsx new file mode 100644 index 00000000000..fddf841e0dd --- /dev/null +++ b/studio/components/interfaces/Billing/SpendCapModal.tsx @@ -0,0 +1,153 @@ +import { FC } from 'react' +import * as Tooltip from '@radix-ui/react-tooltip' +import { Button, IconHelpCircle, Modal } from 'ui' +import Link from 'next/link' + +interface Props { + visible: boolean + onHide: () => void +} + +const SpendCapModal: FC = ({ visible, onHide }) => { + return ( + onHide()}> +
      + +
      +

      + Enabling the spend cap sets a limit on your usage to stay within your plan's quota, + which controls costs but can limit service. Disabling the spend cap removes these + limits, but any extra usage beyond the plan's limit will be charged per usage. +

      +

      + Take a look at the following table to see which resources are chargeable and how they + are charged: +

      + {/* Maybe instead of a table, show something more interactive like a spend cap playground */} + {/* Maybe ideate this in Figma first but this is good enough for now */} +
      +
      +

      Item

      +

      Limit

      +

      Rate

      +
      +
      +
      +

      Database size

      +

      8GB

      +

      $0.125/GB

      +
      +
      +

      Database egress

      +

      50GB

      +

      $0.09/GB

      +
      +
      +
      +
      +
      +

      Auth MAUs

      + + + + + + + + +
      + + Monthly Active Users: A user that has made an API request in the + last month + +
      +
      +
      +
      +
      +
      +

      100,000

      +

      $0.00325/user

      +
      +
      +
      +
      +

      Storage size

      +

      100GB

      +

      $0.021/GB

      +
      +
      +

      Storage egress

      +

      200GB

      +

      $0.09/GB

      +
      +
      +

      Asset transformations

      +

      100 origin images

      +

      $5 per 1000 origin images

      +
      +
      + +
      +
      +

      Realtime concurrent peak connections

      +

      500

      +

      $10/1000

      +
      + +
      +

      Realtime messages

      +

      5 Million

      +

      $2.50/Million

      +
      +
      + +
      +
      +

      Function invocations

      +

      2 Million

      +

      $2 per Million

      +
      + +
      +

      Function count

      +

      100 functions

      +

      $10 per 100 functions

      +
      +
      +
      + +

      + See{' '} + + + pricing page + + {' '} + for a full overview. +

      +
      +
      + + +
      + +
      +
      +
      +
      + ) +} + +export default SpendCapModal diff --git a/studio/components/interfaces/Billing/Subscription/Subscription.tsx b/studio/components/interfaces/Billing/Subscription/Subscription.tsx index 4bf3bf8ad04..2203e92885c 100644 --- a/studio/components/interfaces/Billing/Subscription/Subscription.tsx +++ b/studio/components/interfaces/Billing/Subscription/Subscription.tsx @@ -5,7 +5,8 @@ import { Button, Loading } from 'ui' import * as Tooltip from '@radix-ui/react-tooltip' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { checkPermissions, useFlag, useParams } from 'hooks' +import { checkPermissions, useFlag } from 'hooks' +import { useParams } from 'common/hooks' import { STRIPE_PRODUCT_IDS } from 'lib/constants' import { formatBytes } from 'lib/helpers' @@ -97,29 +98,31 @@ const Subscription: FC = ({ {!canUpdateSubscription || projectUpdateDisabled ? ( - - -
      - - {projectUpdateDisabled ? ( - <> - Subscription changes are currently disabled. -
      - Our engineers are working on a fix. - - ) : !canUpdateSubscription ? ( - 'You need additional permissions to amend subscriptions' - ) : ( - '' - )} -
      -
      -
      + + + +
      + + {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/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..00bbace11c8 100644 --- a/studio/components/interfaces/Database/Backups/PITR/PITRNotice.tsx +++ b/studio/components/interfaces/Database/Backups/PITR/PITRNotice.tsx @@ -1,15 +1,23 @@ 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 } from 'hooks' +import { useParams } from 'common/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/PITRSelection.tsx b/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx index 6ed84243dd2..bba2f68b3f4 100644 --- a/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx +++ b/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx @@ -192,19 +192,21 @@ const PITRSelection = ({}) => { {isSelectedOutOfRange && ( - - -
      - - Selected date is out of range where backups are available - -
      -
      + + + +
      + + Selected date is out of range where backups are available + +
      +
      +
      )}
      diff --git a/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx b/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx index 4b47575c1d4..86983a22ba6 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/Backups/PITR/TimeInput.tsx b/studio/components/interfaces/Database/Backups/PITR/TimeInput.tsx index 3b1d4689e74..90b2b7e6032 100644 --- a/studio/components/interfaces/Database/Backups/PITR/TimeInput.tsx +++ b/studio/components/interfaces/Database/Backups/PITR/TimeInput.tsx @@ -110,17 +110,19 @@ const TimeInput: FC = ({ defaultTime, minimumTime, maximumTime, onChange className="w-full text-sm bg-transparent p-0 text-center outline-none border-none focus:ring-0" /> - - -
      - Hours (HH) -
      -
      + + + +
      + Hours (HH) +
      +
      +
      : @@ -138,17 +140,19 @@ const TimeInput: FC = ({ defaultTime, minimumTime, maximumTime, onChange className="w-full text-sm bg-transparent p-0 text-center outline-none border-none focus:ring-0" /> - - -
      - Minutes (MM) -
      -
      + + + +
      + Minutes (MM) +
      +
      +
      : @@ -166,17 +170,19 @@ const TimeInput: FC = ({ defaultTime, minimumTime, maximumTime, onChange className="w-full text-sm bg-transparent p-0 text-center outline-none border-none focus:ring-0" /> - - -
      - Seconds (SS) -
      -
      + + + +
      + Seconds (SS) +
      +
      +
      {error &&

      {error}

      } 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/Extensions/Extensions.tsx b/studio/components/interfaces/Database/Extensions/Extensions.tsx index 17000a747ec..48164cf331f 100644 --- a/studio/components/interfaces/Database/Extensions/Extensions.tsx +++ b/studio/components/interfaces/Database/Extensions/Extensions.tsx @@ -4,10 +4,11 @@ import { partition, isNull } from 'lodash' import { Input, IconSearch, IconAlertCircle, Button, IconBookOpen } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useStore, checkPermissions, useParams } from 'hooks' +import { useStore, checkPermissions } from 'hooks' +import { useParams } from 'common/hooks' import ExtensionCard from './ExtensionCard' import { HIDDEN_EXTENSIONS } from './Extensions.constants' -import NoSearchResults from 'components/to-be-cleaned/NoSearchResults' +import NoSearchResults from 'components/ui/NoSearchResults' import InformationBox from 'components/ui/InformationBox' import Link from 'next/link' @@ -69,7 +70,9 @@ const Extensions: FC = ({}) => {
      - {extensions.length === 0 && } + {extensions.length === 0 && ( + setFilterString('')} /> + )}
      {enabledExtensions.length > 0 && ( diff --git a/studio/components/interfaces/Database/Functions/CreateFunction.tsx b/studio/components/interfaces/Database/Functions/CreateFunction.tsx index d259a962171..80e780b289a 100644 --- a/studio/components/interfaces/Database/Functions/CreateFunction.tsx +++ b/studio/components/interfaces/Database/Functions/CreateFunction.tsx @@ -577,7 +577,7 @@ const InputArgument: FC = observer(({ idx, name, type, error } return ( -
      +
      = observer(({ idx, name, value } return ( -
      +
      = ({
      diff --git a/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx b/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx index 66948295c4c..d9036ab8fa6 100644 --- a/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx +++ b/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx @@ -1,8 +1,9 @@ -import { FC } from 'react' +import { FC, useState } from 'react' import { uniqBy, map as lodashMap, includes } from 'lodash' import { Button, IconSearch, IconLoader, Input } from 'ui' import { observer } from 'mobx-react-lite' import * as Tooltip from '@radix-ui/react-tooltip' +import { PostgresFunction } from '@supabase/postgres-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' import { checkPermissions, useStore } from 'hooks' @@ -10,17 +11,25 @@ import AlphaPreview from 'components/to-be-cleaned/AlphaPreview' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' import SchemaTable from './SchemaTable' -const FunctionsList: FC = ({ - filterString, - setFilterString = () => {}, +interface Props { + createFunction: () => void + editFunction: (fn: PostgresFunction) => void + deleteFunction: (fn: PostgresFunction) => void +} + +const FunctionsList: FC = ({ createFunction = () => {}, editFunction = () => {}, deleteFunction = () => {}, }) => { const { meta } = useStore() - const functions = meta.functions.list((fn: any) => !meta.excludedSchemas.includes(fn.schema)) - const filteredFunctions = functions.filter((x: any) => - includes(x.name.toLowerCase(), filterString.toLowerCase()) + const [filterString, setFilterString] = useState('') + + const functions = meta.functions.list( + (fn: PostgresFunction) => !meta.excludedSchemas.includes(fn.schema) + ) + const filteredFunctions = functions.filter((x: PostgresFunction) => + includes(x.name?.toLowerCase(), filterString.toLowerCase()) ) const filteredFunctionSchemas = lodashMap(uniqBy(filteredFunctions, 'schema'), 'schema') const canCreateFunctions = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'functions') @@ -51,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" >

      @@ -79,19 +90,23 @@ const FunctionsList: FC = ({ {!canCreateFunctions && ( - - -

      - - You need additional permissions to create functions - -
      - + + + + +
      + + You need additional permissions to create functions + +
      +
      +
      +
      )}
      diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/CheckboxEvents.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/CheckboxEvents.tsx deleted file mode 100644 index cf4825e3326..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/CheckboxEvents.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { Checkbox } from 'ui' -import { union, without } from 'lodash' -import { CreateHookContext } from './' - -const CheckboxEvents: FC = observer(({}) => { - const _localState: any = useContext(CreateHookContext) - return ( - // @ts-ignore - { - const temp = _localState.formState.events.value - const value = e.target.checked - ? union(temp, [e.target.value]) - : without(temp, e.target.value) - _localState.onFormChange({ - key: 'events', - value: value, - }) - }} - error={_localState.formState.events.error} - labelOptional="The type of events that will trigger your function hook" - descriptionText="These are the events that are watched by the function hook, only the events selected above will fire the function hook on the table you've selected." - > - - - - - ) -}) - -export default CheckboxEvents diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/InputName.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/InputName.tsx deleted file mode 100644 index ba2f3b9ee61..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/InputName.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { Input } from 'ui' -import { CreateHookContext } from './' - -const InputName: FC = observer(({}) => { - const _localState: any = useContext(CreateHookContext) - return ( - - _localState.onFormChange({ - key: 'name', - value: e.target.value, - }) - } - size="medium" - error={_localState.formState.name.error} - /> - ) -}) - -export default InputName diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/RadioGroupHookService/RadioGroupHookService.constants.ts b/studio/components/interfaces/Database/Hooks/CreateHook/RadioGroupHookService/RadioGroupHookService.constants.ts deleted file mode 100644 index 4b292673fb4..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/RadioGroupHookService/RadioGroupHookService.constants.ts +++ /dev/null @@ -1,41 +0,0 @@ -export const hookServiceOptions: { - id: string - label: string - badge: string - badgeType: 'brand' | 'amber' - description: string - img_url: string -}[] = [ - { - id: 'http_request', - label: 'HTTP Request', - badge: 'Alpha', - badgeType: 'brand', - description: 'Send an HTTP request to any URL.', - img_url: 'http-request.png', - }, - { - id: 'supabase_function', - label: 'Supabase Function', - badge: 'Coming soon', - badgeType: 'amber', - description: 'Choose a Supabase Function to run.', - img_url: 'supabase-severless-function.png', - }, - { - id: 'google_cloud_function', - label: 'Google cloud run', - badge: 'Coming soon', - badgeType: 'amber', - description: 'Choose a google cloud function to run', - img_url: 'google-cloud-run.png', - }, - { - id: 'aws_lambda_function', - label: 'AWS Lambda', - badge: 'Coming soon', - badgeType: 'amber', - description: 'Choose an AWS Lambda function to run.', - img_url: 'aws-lambda-severless-function.png', - }, -] diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/RadioGroupHookService/RadioHookService.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/RadioGroupHookService/RadioHookService.tsx deleted file mode 100644 index 21331ac5ad7..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/RadioGroupHookService/RadioHookService.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import Image from 'next/image' -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { Radio, Badge } from 'ui' -import { CreateHookContext } from '../' - -interface Props { - id: string - description: string - label: string - img_url: string - badgeType: 'brand' | 'amber' - badge: string -} - -const RadioHookService: FC = observer( - ({ id, description, label, img_url, badgeType, badge }) => { - const _localState: any = useContext(CreateHookContext) - return ( - -
      - {/*
      */} - - {/*
      */} -
      -
      - {label} - {badge} -
      - {description} -
      -
      - - } - /> - ) - } -) - -export default RadioHookService diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/RadioGroupHookService/index.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/RadioGroupHookService/index.tsx deleted file mode 100644 index 17afba4014a..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/RadioGroupHookService/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { Radio } from 'ui' - -import { CreateHookContext } from '../' -import RadioHookService from './RadioHookService' -import { hookServiceOptions } from './RadioGroupHookService.constants' - -const RadioGroupHookService: FC = observer(({}) => { - const _localState: any = useContext(CreateHookContext) - return ( - <> -
      - { - _localState.onFormChange({ - key: 'hookService', - value: event.target.value, - }) - }} - value={_localState.formState.hookService.value} - error={_localState.formState.hookService.error} - > - {hookServiceOptions.map((x) => ( - - ))} - -
      - - ) -}) - -export default RadioGroupHookService diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/SelectEnabledMode.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/SelectEnabledMode.tsx deleted file mode 100644 index 7c114d7d735..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/SelectEnabledMode.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { Select } from 'ui' -import { CreateHookContext } from './' - -const SelectEnabledMode: FC = observer(({}) => { - const _localState: any = useContext(CreateHookContext) - return ( - - ) -}) - -export default SelectEnabledMode diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputMultiServiceHeaders.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputMultiServiceHeaders.tsx deleted file mode 100644 index 84be5e4695b..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputMultiServiceHeaders.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { Button, IconPlus } from 'ui' - -import { CreateHookContext } from '../' -import InputServiceHeader from './InputServiceHeader' - -const InputMultiServiceHeaders: FC = observer(({}) => { - const _localState: any = useContext(CreateHookContext) - - function onAddArgument() { - _localState.onFormArrayChange({ - key: 'serviceHeaders', - value: { name: '', value: '' }, - operation: 'add', - }) - } - - return ( -
      -
      -
      HTTP Headers
      -
      -
      - {_localState.formState.serviceHeaders.value.map( - ( - x: { name: string; value: string; error?: { name?: string; value?: string } }, - idx: number - ) => ( - - ) - )} -
      - -
      -
      -
      - ) -}) - -export default InputMultiServiceHeaders diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputMultiServiceParams.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputMultiServiceParams.tsx deleted file mode 100644 index 576a1cfd83e..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputMultiServiceParams.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { Button, IconPlus } from 'ui' - -import { CreateHookContext } from '../' -import InputServiceParam from './InputServiceParam' - -const InputMultiServiceParams: FC = observer(({}) => { - const _localState: any = useContext(CreateHookContext) - - function onAddArgument() { - _localState.onFormArrayChange({ - key: 'serviceParams', - value: { name: '', value: '' }, - operation: 'add', - }) - } - - return ( -
      -
      -
      HTTP Params
      -
      -
      - {_localState.formState.serviceParams.value.map( - ( - x: { name: string; value: string; error?: { name?: string; value?: string } }, - idx: number - ) => ( - - ) - )} - -
      -
      - ) -}) - -export default InputMultiServiceParams diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputServiceHeader.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputServiceHeader.tsx deleted file mode 100644 index d9e587b4a48..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputServiceHeader.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { FC, useContext, FormEvent } from 'react' -import { observer } from 'mobx-react-lite' -import { Input, IconTrash, Button } from 'ui' -import { CreateHookContext } from '../' - -interface Props { - idx: number - name: string - value: string - error?: { name?: string; value?: string } -} - -const InputServiceHeader: FC = observer(({ idx, name, value, error }) => { - const _localState: any = useContext(CreateHookContext) - - function onNameChange(e: FormEvent) { - const _value = e.currentTarget.value - _localState.onFormArrayChange({ - key: 'serviceHeaders', - value: { name: _value, value }, - idx, - operation: 'update', - }) - } - - function onValueChange(e: FormEvent) { - const _value = e.currentTarget.value - _localState.onFormArrayChange({ - key: 'serviceHeaders', - value: { name, value: _value }, - idx, - operation: 'update', - }) - } - - function onDelete() { - _localState.onFormArrayChange({ - key: 'serviceHeaders', - idx, - operation: 'delete', - }) - } - - return ( -
      - - -
      -
      -
      - ) -}) - -export default InputServiceHeader diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputServiceParam.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputServiceParam.tsx deleted file mode 100644 index 4045966d5ba..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputServiceParam.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { FC, useContext, FormEvent } from 'react' -import { observer } from 'mobx-react-lite' -import { Input, IconTrash, Button } from 'ui' -import { CreateHookContext } from '../' - -interface Props { - idx: number - name: string - value: string - error?: { name?: string; value?: string } -} - -const InputServiceParam: FC = observer(({ idx, name, value, error }) => { - const _localState: any = useContext(CreateHookContext) - - function onNameChange(e: FormEvent) { - const _value = e.currentTarget.value - _localState.onFormArrayChange({ - key: 'serviceParams', - value: { name: _value, value }, - idx, - operation: 'update', - }) - } - - function onValueChange(e: FormEvent) { - const _value = e.currentTarget.value - _localState.onFormArrayChange({ - key: 'serviceParams', - value: { name, value: _value }, - idx, - operation: 'update', - }) - } - - function onDelete() { - _localState.onFormArrayChange({ - key: 'serviceParams', - idx, - operation: 'delete', - }) - } - - return ( -
      - - -
      -
      -
      - ) -}) - -export default InputServiceParam diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputServiceTimeout.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputServiceTimeout.tsx deleted file mode 100644 index ae255fb0be6..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputServiceTimeout.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { Input } from 'ui' -import { CreateHookContext } from '../' - -// Not used currently - -const InputServiceTimeout: FC = observer(({}) => { - const _localState: any = useContext(CreateHookContext) - return ( - - _localState.onFormChange({ - key: 'serviceTimeoutMs', - value: e.target.value, - }) - } - size="medium" - error={_localState.formState.serviceTimeoutMs.error} - /> - ) -}) - -export default InputServiceTimeout diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputServiceUrl.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputServiceUrl.tsx deleted file mode 100644 index f65aaa19efe..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/InputServiceUrl.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { Input } from 'ui' -import { CreateHookContext } from '../' - -const InputServiceUrl: FC = observer(({}) => { - const _localState: any = useContext(CreateHookContext) - return ( - - _localState.onFormChange({ - key: 'serviceUrl', - value: e.target.value, - }) - } - size="medium" - error={_localState.formState.serviceUrl.error} - /> - ) -}) - -export default InputServiceUrl diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/SelectServiceMethod.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/SelectServiceMethod.tsx deleted file mode 100644 index fca6069eb39..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/SelectServiceMethod.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { Select } from 'ui' -import { CreateHookContext } from '../' - -const SelectServiceMethod: FC = observer(({}) => { - const _localState: any = useContext(CreateHookContext) - return ( - - ) -}) - -export default SelectServiceMethod diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/ServiceUnavailableBox.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/ServiceUnavailableBox.tsx deleted file mode 100644 index 59e5ae128a8..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/ServiceUnavailableBox.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { Alert, Button } from 'ui' -import { CreateHookContext } from '../' - -const ServiceUnavailableBox: FC = observer(({}) => { - const _localState: any = useContext(CreateHookContext) - return ( - -
      -
      We currently do not support this service.
      - -
      -
      - ) -}) - -export default ServiceUnavailableBox diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/index.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/index.tsx deleted file mode 100644 index 7570f170706..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/ServiceConfigForm/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { SidePanel } from 'ui' - -import { CreateHookContext } from '../' -import SelectServiceMethod from './SelectServiceMethod' -import InputServiceUrl from './InputServiceUrl' -import InputMultiServiceHeaders from './InputMultiServiceHeaders' -import InputMultiServiceParams from './InputMultiServiceParams' -import ServiceUnavailableBox from './ServiceUnavailableBox' - -const ServiceConfigForm: FC = observer(({}) => { - const _localState: any = useContext(CreateHookContext) - return ( - <> - {_localState.formState.hookService.value === 'http_request' ? ( - <> - -
      -
      -
      HTTP Request
      - - -
      - -
      - -
      - -
      - -
      -
      - - ) : ( -
      - -
      - )} - - ) -}) - -export default ServiceConfigForm diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/TableSelection.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/TableSelection.tsx deleted file mode 100644 index e1382da2597..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/TableSelection.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import Link from 'next/link' -import SVG from 'react-inlinesvg' -import { FC, useContext } from 'react' -import { observer } from 'mobx-react-lite' -import { Input, Listbox } from 'ui' - -import { CreateHookContext } from './' -import { useStore } from 'hooks' - -const TableSelection: FC = observer(({}) => { - const { ui } = useStore() - const projectRef = ui.selectedProject?.ref - const _localState: any = useContext(CreateHookContext) - - if (_localState.tables.length === 0) { - return ( - - - This is the table the trigger will watch for changes. There's currently no tables - created - please create one{' '} - - - here - - first. -

      - } - /> - ) - } - - return ( - { - const _table = _localState.tables.find((x: any) => x.id === id) - if (_table) { - _localState.onFormChange({ - key: 'schema', - value: _table.schema, - }) - _localState.onFormChange({ - key: 'table', - value: _table.name, - }) - _localState.onFormChange({ - key: 'tableId', - value: id, - }) - } - }} - size="medium" - error={_localState.formState.table.error} - descriptionText="This is the table the trigger will watch for changes. You can only select 1 table for a trigger." - > - {_localState.tables.map((x: any) => { - return ( - ( -
      - - code.replace(/svg/, 'svg class="m-auto text-color-inherit"') - } - /> -
      - )} - > -
      -

      {x.name}

      -

      {x.schema}

      -
      -
      - ) - })} -
      - ) -}) - -export default TableSelection diff --git a/studio/components/interfaces/Database/Hooks/CreateHook/index.tsx b/studio/components/interfaces/Database/Hooks/CreateHook/index.tsx deleted file mode 100644 index 5de69a46e61..00000000000 --- a/studio/components/interfaces/Database/Hooks/CreateHook/index.tsx +++ /dev/null @@ -1,512 +0,0 @@ -import { makeAutoObservable } from 'mobx' -import { FC, useEffect, createContext } from 'react' -import { observer, useLocalObservable } from 'mobx-react-lite' -import { isEmpty, mapValues, has, filter, keyBy, isUndefined } from 'lodash' -import { Dictionary } from 'components/grid' -import { Badge, Input, SidePanel } from 'ui' -import { useStore } from 'hooks' - -import InputName from './InputName' -import SelectEnabledMode from './SelectEnabledMode' -import CheckboxEvents from './CheckboxEvents' -import TableSelection from './TableSelection' -import RadioGroupHookService from './RadioGroupHookService' -import ServiceConfigForm from './ServiceConfigForm' - -import { formatArguments, convertKeyValue, hasWhitespace, isValidHttpUrl } from '../Hooks.utils' - -// [Joshen] TODO: Refactor to use Form component - -class CreateHookFormState { - id: number | undefined - originalName: string | undefined - // @ts-ignore - enabledMode: { value: string } - // @ts-ignore - events: { value: string[]; error?: string } - // @ts-ignore - name: { value: string; error?: string } - // @ts-ignore - schema: { value: string } - // @ts-ignore - table: { value: string; error?: string } - // @ts-ignore - tableId: { value: number; error?: string } - // @ts-ignore - hookService: { value: string; error?: string } - // @ts-ignore - serviceUrl: { value: string; error?: string } - // @ts-ignore - serviceMethod: { value: string; error?: string } - // @ts-ignore - serviceHeaders: { - value: { name: string; value: string; error?: { name?: string; value?: string } }[] - } - // @ts-ignore - serviceParams: { - value: { name: string; value: string; error?: { name?: string; value?: string } }[] - } - // @ts-ignore - serviceTimeoutMs: { value: string; error?: string } - - constructor() { - makeAutoObservable(this) - this.reset() - } - - get requestBody() { - return { - id: this.id, - activation: 'AFTER', - enabled_mode: this.enabledMode.value, - events: this.events.value, - function_args: [ - this.serviceUrl.value.replaceAll(' ', ''), - this.serviceMethod.value, - JSON.stringify(mapValues(keyBy(this.serviceHeaders.value, 'name'), 'value')), - formatArguments(this.serviceParams.value), - this.serviceTimeoutMs.value, - ], - function_name: 'http_request', - function_schema: 'supabase_functions', - orientation: 'ROW', - name: this.name.value, - schema: this.schema.value, - table: this.table.value, - table_id: this.tableId.value, - } - } - - reset(hook?: Dictionary) { - this.id = hook?.id - this.originalName = hook?.name - this.enabledMode = { value: hook?.enabled_mode ?? 'ORIGIN' } - this.events = { value: hook?.events ?? [] } - this.name = { value: hook?.name ?? '' } - this.schema = { value: hook?.schema ?? '' } - this.table = { value: hook?.table ?? '' } - this.tableId = { value: hook?.table_id ?? '' } - // TODO: hook obj doesn't have service field - this.hookService = { value: 'http_request' } - /** - * hook.function_args contains 5 params in order - * [url, method, headers, params, timeout] - */ - this.serviceUrl = { value: hook?.function_args?.length == 5 ? hook?.function_args[0] : '' } - this.serviceMethod = { - value: hook?.function_args?.length == 5 ? hook?.function_args[1] : 'GET', - } - this.serviceHeaders = - hook?.function_args?.length == 5 - ? convertKeyValue(hook?.function_args[2]) - : { value: [{ name: 'Content-type', value: 'application/json' }] } - - this.serviceParams = - hook?.function_args?.length == 5 ? convertKeyValue(hook?.function_args[3]) : { value: [] } - - this.serviceTimeoutMs = { - value: hook?.function_args?.length == 5 ? hook?.function_args[4] : '1000', - } - } - - update(state: Dictionary) { - this.enabledMode = state.enabledMode - this.events = state.events - this.name = state.name - this.schema = state.schema - this.table = state.table - this.tableId = state.tableId - this.hookService = state.hookService - this.serviceUrl = state.serviceUrl - this.serviceMethod = state.serviceMethod - this.serviceHeaders = state.serviceHeaders - this.serviceParams = state.serviceParams - this.serviceTimeoutMs = state.serviceTimeoutMs - } -} - -interface ICreateHookStore { - loading: boolean - formState: CreateHookFormState - meta: any - project: any - tables: Dictionary[] - onFormChange: (value: { key: string; value: any }) => void - onFormArrayChange: (value: { - operation: 'add' | 'delete' | 'update' - key: string - idx?: number - value?: any - }) => void - setDefaultSelectedTable: () => void - setLoading: (value: boolean) => void - setTables: (value: Dictionary[]) => void - validateForm: () => boolean -} - -class CreateHookStore implements ICreateHookStore { - loading = false - formState = new CreateHookFormState() - meta = null - project = null - tables = [] - - constructor() { - makeAutoObservable(this) - } - - get title() { - return this.formState.id - ? `Edit '${this.formState.originalName}' database webhook` - : 'Add a new database webhook' - } - - get isEditing() { - return this.formState.id != undefined - } - - // set first table as default selection - setDefaultSelectedTable = () => { - if (this.tables?.length != 0) { - this.formState.schema.value = (this.tables[0] as any).schema - this.formState.table.value = (this.tables[0] as any).name - this.formState.tableId.value = (this.tables[0] as any).id - } - } - - setLoading = (value: boolean) => { - this.loading = value - } - - setTables = (value: Dictionary[]) => { - this.tables = value as any - this.setDefaultSelectedTable() - } - - onFormChange = ({ key, value }: { key: string; value: any }) => { - if (has(this.formState, key)) { - const temp = (this.formState as any)[key] as any - ;(this.formState as any)[key] = { ...temp, value, error: undefined } - } else { - ;(this.formState as any)[key] = { value } - } - } - - onFormArrayChange = ({ - operation, - key, - idx, - value, - }: { - operation: 'add' | 'delete' | 'update' - key: string - idx?: number - value?: any - }) => { - switch (operation) { - case 'add': { - if (has(this.formState, key)) { - ;(this.formState as any)[key].value.push(value) - } else { - ;(this.formState as any)[key] = { value: [value] } - } - break - } - case 'delete': { - if (has(this.formState, key)) { - const temp = filter( - (this.formState as any)[key].value, - (_: any, index: number) => index != idx - ) as any - ;(this.formState as any)[key].value = temp - } - break - } - default: { - if (has(this.formState, key) && !isUndefined(idx)) { - ;(this.formState as any)[key].value[idx] = value - } else { - ;(this.formState as any)[key] = { value: [{ value }] } - } - } - } - } - - validateForm = () => { - let isValidated = true - const _state = mapValues(this.formState, (x: { value: any }, key: string) => { - switch (key) { - case 'name': { - if (isEmpty(x.value) || hasWhitespace(x.value)) { - isValidated = false - return { ...x, error: 'Invalid hook name' } - } else { - return x - } - } - case 'events': { - if (isEmpty(x.value)) { - isValidated = false - return { ...x, error: 'Select at least 1 event' } - } else { - return x - } - } - case 'table': { - if (isEmpty(x.value)) { - isValidated = false - return { ...x, error: 'You must choose a table' } - } else { - return x - } - } - case 'hookService': { - if (x.value != 'http_request') { - isValidated = false - return { ...x, error: 'Not supported service' } - } else { - return x - } - } - case 'serviceTimeoutMs': { - if (isEmpty(x.value) || isNaN(x.value)) { - isValidated = false - return { ...x, error: 'Invalid input' } - } else { - return x - } - } - case 'serviceUrl': { - if (isEmpty(x.value) || !isValidHttpUrl(x.value)) { - isValidated = false - return { ...x, error: 'Invalid input' } - } else { - return x - } - } - case 'serviceMethod': { - if (isEmpty(x.value)) { - isValidated = false - return { ...x, error: 'Invalid input' } - } else { - return x - } - } - case 'serviceHeaders': - case 'serviceParams': { - const temp = x.value?.map((i: Dictionary) => { - const error: any = { name: undefined, value: undefined } - if (isEmpty(i.name) || hasWhitespace(i.name)) { - isValidated = false - error.name = 'Invalid name' - } - if (isEmpty(i.value)) { - isValidated = false - error.value = 'Missing value' - } - return { ...i, error } - }) - x.value = temp - return x - } - default: - return x - } - }) - if (!isValidated) { - this.formState.update(_state) - } - return isValidated - } -} - -export const CreateHookContext = createContext(null) - -interface CreateHookProps { - hook?: any - visible: boolean - setVisible: (value: boolean) => void -} - -const CreateHook: FC = ({ hook, visible = true, setVisible }) => { - const { meta, ui } = useStore() - const project = ui.selectedProject - - const _localState: any = useLocalObservable(() => new CreateHookStore()) - _localState.meta = meta - _localState.project = project - - useEffect(() => { - const fetchSchemas = async () => { - await _localState.meta.tables.load() - const tables = _localState!.meta!.tables.list( - (table: any) => !meta.excludedSchemas.includes(table.schema) - ) - _localState.setTables(tables) - } - - fetchSchemas() - }, []) - - useEffect(() => { - if (hook) { - _localState.formState.reset(hook) - } else { - _localState.formState.reset() - _localState.setDefaultSelectedTable() - } - }, [visible, hook]) - - async function handleSubmit() { - try { - if (_localState.validateForm()) { - _localState.setLoading(true) - - const body = _localState.formState.requestBody - - const response: any = _localState.isEditing - ? await _localState.meta.hooks.update(body.id, body) - : await _localState.meta.hooks.create(body) - - if (response.error) { - ui.setNotification({ - category: 'error', - message: `Failed to create hook: ${response.error.message ?? 'Submit request failed'}`, - }) - _localState.setLoading(false) - } else { - ui.setNotification({ - category: 'success', - message: `${_localState.isEditing ? 'Updated' : 'Created new'} hook called ${ - response.name - }`, - }) - _localState.setLoading(false) - setVisible(!visible) - } - } - } catch (error: any) { - ui.setNotification({ category: 'error', message: `Failed to create hook: ${error.message}` }) - _localState.setLoading(false) - } - } - - return ( - setVisible(!visible)} - > - -
      - {_localState.isEditing ? ( - <> -
      - - -
      - - {_localState.formState.serviceHeaders.value.length > 0 && ( - <> - -
      -
      -
      HTTP Headers
      - Read only -
      -
      - {_localState.formState.serviceHeaders.value.map( - (serviceHeader: { name: string; value: string }) => ( -
      - - -
      - ) - )} -
      -
      -

      - Note: You can only edit a webhook's name and mode. To change - other settings, you'll need to delete and recreate the webhook. -

      - - )} - - {_localState.formState.serviceParams.value.length > 0 && ( - <> - -
      -
      -
      HTTP Parameters
      - Read only -
      -
      - {_localState.formState.serviceParams.value.map( - (serviceParam: { name: string; value: string }) => ( -
      - - -
      - ) - )} -
      -
      - - )} - - ) : ( - <> -
      - -
      - -
      -
      Conditions to fire hook
      - - -
      - -
      -
      Type of hook
      - -
      - - - )} -
      -
      -
      - ) -} - -export default observer(CreateHook) diff --git a/studio/components/interfaces/Database/Hooks/DeleteHook.tsx b/studio/components/interfaces/Database/Hooks/DeleteHook.tsx deleted file mode 100644 index 14efa7f8f5b..00000000000 --- a/studio/components/interfaces/Database/Hooks/DeleteHook.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from 'react' -import { observer } from 'mobx-react-lite' -import { useStore } from 'hooks' -import TextConfirmModal from 'components/ui/Modals/TextConfirmModal' - -type DeleteHookProps = { - hook?: any - visible: boolean - setVisible: (value: boolean) => void -} & any - -const DeleteHook: React.FC = ({ hook, visible, setVisible }) => { - const { ui, meta } = useStore() - const [loading, setLoading] = React.useState(false) - const { id, name, schema } = hook ?? {} - - async function handleDelete() { - try { - setLoading(true) - if (!id) { - throw Error('Invalid hook info') - } - const response: any = await meta.hooks.del(id) - if (response.error) { - throw response.error - } else { - ui.setNotification({ category: 'success', message: `Successfully removed ${name}` }) - setVisible(false) - } - } catch (error: any) { - ui.setNotification({ - category: 'error', - message: `Failed to delete ${name}: ${error.message}`, - }) - } finally { - setLoading(false) - } - } - - return ( - setVisible(!visible)} - onConfirm={handleDelete} - title="Delete this hook" - loading={loading} - confirmLabel={`Delete hook ${name}`} - confirmPlaceholder="Type in name of hook" - confirmString={name} - text={`This will delete your hook called ${name} of schema ${schema}.`} - alert="You cannot recover this hook once it is deleted!" - /> - ) -} - -export default observer(DeleteHook) diff --git a/studio/components/interfaces/Database/Hooks/DeleteHookModal.tsx b/studio/components/interfaces/Database/Hooks/DeleteHookModal.tsx new file mode 100644 index 00000000000..36bf3bbd156 --- /dev/null +++ b/studio/components/interfaces/Database/Hooks/DeleteHookModal.tsx @@ -0,0 +1,67 @@ +import { useState } from 'react' +import { observer } from 'mobx-react-lite' +import { useStore } from 'hooks' +import TextConfirmModal from 'components/ui/Modals/TextConfirmModal' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { useDatabaseTriggerDeleteMutation } from 'data/database-triggers/database-trigger-delete-mutation' +import { PostgresTrigger } from '@supabase/postgres-meta' + +interface DeleteHookModalProps { + visible: boolean + selectedHook?: PostgresTrigger + onClose: () => void +} + +const DeleteHookModal = ({ selectedHook, visible, onClose }: DeleteHookModalProps) => { + const { ui } = useStore() + const [loading, setLoading] = useState(false) + const { id, name, schema } = selectedHook ?? {} + + const { project } = useProjectContext() + const { mutateAsync: deleteDatabaseTrigger } = useDatabaseTriggerDeleteMutation() + + async function handleDelete() { + if (!project) { + return console.error('Project ref is required') + } + if (!id) { + return ui.setNotification({ category: 'error', message: 'Unable find selected hook' }) + } + + try { + setLoading(true) + await deleteDatabaseTrigger({ + id, + projectRef: project.ref, + connectionString: project.connectionString, + }) + ui.setNotification({ category: 'success', message: `Successfully deleted ${name}` }) + onClose() + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to delete ${name}: ${error.message}`, + }) + } finally { + setLoading(false) + } + } + + return ( + onClose()} + onConfirm={handleDelete} + title="Delete database webhook" + loading={loading} + confirmLabel={`Delete ${name}`} + confirmPlaceholder="Type in name of webhook" + confirmString={name || ''} + text={`This will delete the webhook "${name}" from the schema "${schema}".`} + alert="You cannot recover this webhook once it is deleted!" + /> + ) +} + +export default observer(DeleteHookModal) diff --git a/studio/components/interfaces/Database/Hooks/EditHookPanel.tsx b/studio/components/interfaces/Database/Hooks/EditHookPanel.tsx new file mode 100644 index 00000000000..1af30acba47 --- /dev/null +++ b/studio/components/interfaces/Database/Hooks/EditHookPanel.tsx @@ -0,0 +1,483 @@ +import Image from 'next/image' +import { useState, useRef, useEffect } from 'react' +import { PostgresTrigger } from '@supabase/postgres-meta' +import { Button, SidePanel, Form, Input, Listbox, Checkbox, Radio, Badge, Modal } from 'ui' + +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' +import { tryParseJson, uuidv4 } from 'lib/helpers' +import HTTPRequestFields from './HTTPRequestFields' +import { isValidHttpUrl } from './Hooks.utils' +import { AVAILABLE_WEBHOOK_TYPES, HOOK_EVENTS } from './Hooks.constants' +import ConfirmationModal from 'components/ui/ConfirmationModal' +import { FormSection, FormSectionLabel, FormSectionContent } from 'components/ui/Forms' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' + +import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query' +import { useDatabaseTriggerCreateMutation } from 'data/database-triggers/database-trigger-create-mutation' +import { useDatabaseTriggerUpdateMutation } from 'data/database-triggers/database-trigger-update-transaction-mutation' + +export interface EditHookPanelProps { + visible: boolean + selectedHook?: PostgresTrigger + onClose: () => void +} + +export type HTTPArgument = { id: string; name: string; value: string } + +const EditHookPanel = ({ visible, selectedHook, onClose }: EditHookPanelProps) => { + // [Joshen] Need to change to use RQ once Alaister's PR goes in + const { ref } = useParams() + const { meta, ui } = useStore() + const submitRef: any = useRef() + const [isEdited, setIsEdited] = useState(false) + const [isSubmitting, setIsSubmitting] = useState(false) + const [isClosingPanel, setIsClosingPanel] = useState(false) + + // [Joshen] There seems to be some bug between Checkbox.Group within the Form component + // hence why this external state as a temporary workaround + const [events, setEvents] = useState([]) + const [eventsError, setEventsError] = useState() + + // For HTTP request + const [httpHeaders, setHttpHeaders] = useState([]) + const [httpParameters, setHttpParameters] = useState([]) + + const { project } = useProjectContext() + const { data: functions } = useEdgeFunctionsQuery({ projectRef: ref }) + const { mutateAsync: createDatabaseTrigger } = useDatabaseTriggerCreateMutation() + const { mutateAsync: updateDatabaseTrigger } = useDatabaseTriggerUpdateMutation() + + const tables = meta.tables.list().sort((a, b) => (a.schema > b.schema ? 0 : -1)) + const restUrl = ui.selectedProject?.restUrl + const restUrlTld = new URL(restUrl as string).hostname.split('.').pop() + + const isEdgeFunction = (url: string) => + url.includes(`https://${ref}.functions.supabase.${restUrlTld}/`) + + const initialValues = { + name: selectedHook?.name ?? '', + table_id: selectedHook?.table_id ?? '', + http_url: selectedHook?.function_args?.[0] ?? '', + http_method: selectedHook?.function_args?.[1] ?? 'POST', + function_type: isEdgeFunction(selectedHook?.function_args?.[0] ?? '') + ? 'supabase_function' + : 'http_request', + } + + useEffect(() => { + if (visible) { + setIsEdited(false) + setIsSubmitting(false) + setIsClosingPanel(false) + + // Reset form fields outside of the Form context + if (selectedHook !== undefined) { + setEvents(selectedHook.events) + + const [url, method, headers, parameters] = selectedHook.function_args + const formattedHeaders = tryParseJson(headers) || {} + setHttpHeaders( + Object.keys(formattedHeaders).map((key) => { + return { id: uuidv4(), name: key, value: formattedHeaders[key] } + }) + ) + const formattedParameters = tryParseJson(parameters) || {} + setHttpParameters( + Object.keys(formattedParameters).map((key) => { + return { id: uuidv4(), name: key, value: formattedParameters[key] } + }) + ) + } else { + setEvents([]) + setHttpHeaders([{ id: uuidv4(), name: 'Content-type', value: 'application/json' }]) + setHttpParameters([{ id: uuidv4(), name: '', value: '' }]) + } + } + }, [visible, selectedHook]) + + const onClosePanel = () => { + if (isEdited) setIsClosingPanel(true) + else onClose() + } + + const onUpdateSelectedEvents = (event: string) => { + if (events.includes(event)) { + setEvents(events.filter((e) => e !== event)) + } else { + setEvents(events.concat(event)) + } + setEventsError(undefined) + } + + const validate = (values: any) => { + const errors: any = {} + + if (!values.name) { + errors['name'] = 'Please provide a name for your webhook' + } + if (!values.table_id) { + errors['table_id'] = 'Please select a table for which your webhook will trigger from' + } + + if (values.function_type === 'http_request') { + // For HTTP requests + if (!values.http_url) { + errors['http_url'] = 'Please provide a URL' + } else if (!values.http_url.startsWith('http')) { + errors['http_url'] = 'Please include HTTP/HTTPs to your URL' + } else if (!isValidHttpUrl(values.http_url)) { + errors['http_url'] = 'Please provide a valid URL' + } + } else if (values.function_type === 'supabase_function') { + // For Supabase Edge Functions + if (values.http_url.includes('undefined')) { + errors['http_url'] = 'No edge functions available for selection' + } + } + + if (JSON.stringify(values) !== JSON.stringify(initialValues)) setIsEdited(true) + return errors + } + + const onSubmit = async (values: any) => { + if (!project?.ref) { + return console.error('Project ref is required') + } + if (events.length === 0) { + return setEventsError('Please select at least one event') + } + + const selectedTable = meta.tables.byId(values.table_id) + if (!selectedTable) { + return ui.setNotification({ category: 'error', message: 'Unable to find selected table' }) + } + + const serviceTimeoutMs = '1000' + const headers = httpHeaders + .filter((header) => header.name && header.value) + .reduce((a: any, b: any) => { + a[b.name] = b.value + return a + }, {}) + const parameters = httpParameters + .filter((param) => param.name && param.value) + .reduce((a: any, b: any) => { + a[b.name] = b.value + return a + }, {}) + + const payload: any = { + events, + activation: 'AFTER', + orientation: 'ROW', + enabled_mode: 'ORIGIN', + name: values.name, + table: selectedTable.name, + schema: selectedTable.schema, + table_id: values.table_id, + function_name: 'http_request', + function_schema: 'supabase_functions', + function_args: [ + values.http_url, + values.http_method, + JSON.stringify(headers), + JSON.stringify(parameters), + serviceTimeoutMs, + ], + } + + if (selectedHook === undefined) { + try { + setIsSubmitting(true) + await createDatabaseTrigger({ + projectRef: project?.ref, + connectionString: project?.connectionString, + payload, + }) + ui.setNotification({ + category: 'success', + message: `Successfully created new webhook "${values.name}"`, + }) + onClose() + } catch (error: any) { + ui.setNotification({ + error, + category: 'error', + message: `Failed to create webhook: ${error.message}`, + }) + } finally { + setIsSubmitting(false) + } + } else { + try { + setIsSubmitting(true) + await updateDatabaseTrigger({ + projectRef: project?.ref, + connectionString: project?.connectionString, + originalTrigger: selectedHook, + updatedTrigger: payload, + }) + ui.setNotification({ + category: 'success', + message: `Successfully updated webhook "${values.name}"`, + }) + onClose() + } catch (error: any) { + ui.setNotification({ + error, + category: 'error', + message: `Failed to update webhook: ${error.message}`, + }) + } finally { + setIsSubmitting(false) + } + } + } + + return ( + <> + + Update webhook {selectedHook.name} + + ) + } + className="hooks-sidepanel mr-0 transform transition-all duration-300 ease-in-out" + onConfirm={() => {}} + onCancel={() => onClosePanel()} + customFooter={ +
      + + +
      + } + > + + {({ values, resetForm, errors }: any) => { + useEffect(() => { + if (values.function_type === 'http_request') { + if (selectedHook !== undefined) { + const [url, method] = selectedHook.function_args + const updatedValues = { ...values, http_url: url, http_method: method } + resetForm({ values: updatedValues, initialValues: updatedValues }) + } else { + const updatedValues = { ...values, http_url: '' } + resetForm({ values: updatedValues, initialValues: updatedValues }) + } + } else if (values.function_type === 'supabase_function') { + const fnSlug = (functions ?? [])[0]?.slug + const defaultFunctionUrl = `https://${ref}.functions.supabase.${restUrlTld}/${fnSlug}` + const updatedValues = { + ...values, + http_url: isEdgeFunction(values.http_url) ? values.http_url : defaultFunctionUrl, + } + resetForm({ values: updatedValues, initialValues: updatedValues }) + } + }, [values.function_type]) + + return ( +
      + General} + > + + + + + + + Select which table and events will trigger your webhook +

      + } + > + Conditions to fire webhook + + } + > + + + + --- + + {tables.map((table) => ( + +
      +

      {table.schema}

      +

      {table.name}

      +
      +
      + ))} +
      + + {HOOK_EVENTS.map((event) => ( + onUpdateSelectedEvents(event.value)} + /> + ))} + +
      +
      + + + Webhook configuration + + } + > + + + {AVAILABLE_WEBHOOK_TYPES.map((webhook) => ( + + +
      +
      +

      {webhook.label}

      +
      +

      {webhook.description}

      +
      +
      + } + /> + ))} + + + + + + { + if (header) setHttpHeaders(httpHeaders.concat(header)) + else setHttpHeaders(httpHeaders.concat({ id: uuidv4(), name: '', value: '' })) + }} + onUpdateHeader={(idx, property, value) => + setHttpHeaders( + httpHeaders.map((header, i) => { + if (idx === i) return { ...header, [property]: value } + else return header + }) + ) + } + onRemoveHeader={(idx) => setHttpHeaders(httpHeaders.filter((_, i) => idx !== i))} + onAddParameter={() => + setHttpParameters(httpParameters.concat({ id: uuidv4(), name: '', value: '' })) + } + onUpdateParameter={(idx, property, value) => + setHttpParameters( + httpParameters.map((param, i) => { + if (idx === i) return { ...param, [property]: value } + else return param + }) + ) + } + onRemoveParameter={(idx) => + setHttpParameters(httpParameters.filter((_, i) => idx !== i)) + } + /> + +
      + ) + }} + + + setIsClosingPanel(false)} + onSelectConfirm={() => { + setIsClosingPanel(false) + setIsEdited(false) + onClose() + }} + children={ + +

      + There are unsaved changes. Are you sure you want to close the panel? Your changes will + be lost. +

      +
      + } + /> + + ) +} + +export default EditHookPanel diff --git a/studio/components/interfaces/Database/Hooks/HTTPRequestFields.tsx b/studio/components/interfaces/Database/Hooks/HTTPRequestFields.tsx new file mode 100644 index 00000000000..c1a9ba41eac --- /dev/null +++ b/studio/components/interfaces/Database/Hooks/HTTPRequestFields.tsx @@ -0,0 +1,254 @@ +import Link from 'next/link' +import { + Button, + Listbox, + Input, + SidePanel, + IconTrash, + IconPlus, + Dropdown, + IconChevronDown, +} from 'ui' +import { FormSection, FormSectionLabel, FormSectionContent } from 'components/ui/Forms' +import { HTTPArgument } from './EditHookPanel' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' +import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query' +import clsx from 'clsx' +import { uuidv4 } from 'lib/helpers' +import { useProjectApiQuery } from 'data/config/project-api-query' + +interface HTTPRequestFieldsProps { + type: 'http_request' | 'supabase_function' + errors: any + httpHeaders: HTTPArgument[] + httpParameters: HTTPArgument[] + onAddHeader: (header?: any) => void + onUpdateHeader: (idx: number, property: string, value: string) => void + onRemoveHeader: (idx: number) => void + onAddParameter: () => void + onUpdateParameter: (idx: number, property: string, value: string) => void + onRemoveParameter: (idx: number) => void +} + +const HTTPRequestFields = ({ + type, + errors, + httpHeaders = [], + httpParameters = [], + onAddHeader, + onUpdateHeader, + onRemoveHeader, + onAddParameter, + onUpdateParameter, + onRemoveParameter, +}: HTTPRequestFieldsProps) => { + const { ui } = useStore() + const { ref } = useParams() + const { data: settings } = useProjectApiQuery({ projectRef: ref }) + const { data: functions } = useEdgeFunctionsQuery({ projectRef: ref }) + + const edgeFunctions = functions ?? [] + const apiService = settings?.autoApiService + const anonKey = apiService?.service_api_keys.find((x) => x.name === 'service_role key') + ? apiService.serviceApiKey + : '[YOUR API KEY]' + + return ( + <> + + {type === 'http_request' + ? 'HTTP Request' + : type === 'supabase_function' + ? 'Edge Function' + : ''} + + } + > + + + + GET + + + POST + + + {type === 'http_request' ? ( + + ) : type === 'supabase_function' && edgeFunctions.length === 0 ? ( +
      +

      Select which edge function to trigger

      +
      +

      No edge functions created yet

      + + + + + +
      + {errors.http_url &&

      {errors.http_url}

      } +
      + ) : type === 'supabase_function' && edgeFunctions.length > 0 ? ( + + {edgeFunctions.map((fn) => { + const restUrl = ui.selectedProject?.restUrl + const restUrlTld = new URL(restUrl as string).hostname.split('.').pop() + const functionUrl = `https://${ref}.functions.supabase.${restUrlTld}/${fn.slug}` + + return ( + + {fn.name} + + ) + })} + + ) : null} +
      +
      + + HTTP Headers} + > + +
      + {httpHeaders.map((header, idx: number) => ( +
      + onUpdateHeader(idx, 'name', event.target.value)} + /> + onUpdateHeader(idx, 'value', event.target.value)} + /> +
      + ))} +
      + + {type === 'supabase_function' && ( + + onAddHeader({ + id: uuidv4(), + name: ' Authorization', + value: `Bearer ${anonKey}`, + }) + } + > +
      +

      Add auth header with service key

      +

      + Required if your edge function enforces JWT verification +

      +
      + , + , + + onAddHeader({ + id: uuidv4(), + name: ' x-supabase-webhook-source', + value: `[Use a secret value]`, + }) + } + > +
      +

      Add custom source header

      +

      + Useful to verify that the edge function was triggered from this webhook +

      +
      +
      , + ]} + > +
      +
      +
      +
      + + HTTP Parameters} + > + +
      + {httpParameters.map((parameter, idx: number) => ( +
      + onUpdateParameter(idx, 'name', event.target.value)} + /> + onUpdateParameter(idx, 'value', event.target.value)} + /> +
      + ))} +
      + +
      +
      +
      +
      + + ) +} + +export default HTTPRequestFields diff --git a/studio/components/interfaces/Database/Hooks/Hooks.constants.ts b/studio/components/interfaces/Database/Hooks/Hooks.constants.ts new file mode 100644 index 00000000000..1fe98a85928 --- /dev/null +++ b/studio/components/interfaces/Database/Hooks/Hooks.constants.ts @@ -0,0 +1,49 @@ +import { BASE_PATH } from 'lib/constants' + +export const ENABLED_MODE_OPTIONS = [ + { label: 'Origin', value: 'ORIGIN', description: 'This is the default behaviour' }, + { + label: 'Replica', + value: 'REPLICA', + description: 'Will only fire if the session is in “replica” mode', + }, + { + label: 'Always', + value: 'ALWAYS', + description: 'Will fire regardless of the current replication role', + }, + { label: 'Disabled', value: 'DISABLED', description: 'Will not fire' }, +] + +export const HOOK_EVENTS = [ + { + label: 'Insert', + value: 'INSERT', + description: 'Any insert operation on the table', + }, + { + label: 'Update', + value: 'UPDATE', + description: 'Any update operation, of any column in the table', + }, + { + label: 'Delete', + value: 'DELETE', + description: 'Any deletion of a record', + }, +] + +export const AVAILABLE_WEBHOOK_TYPES = [ + { + value: 'http_request', + icon: `${BASE_PATH}/img/function-providers/http-request.png`, + label: 'HTTP Request', + description: 'Send an HTTP request to any URL.', + }, + { + value: 'supabase_function', + icon: `${BASE_PATH}/img/function-providers/supabase-severless-function.png`, + label: 'Supabase Edge Functions', + description: 'Choose a Supabase edge function to run.', + }, +] diff --git a/studio/components/interfaces/Database/Hooks/HooksList/HookList.tsx b/studio/components/interfaces/Database/Hooks/HooksList/HookList.tsx index e28a7e373d3..7e434e4cef5 100644 --- a/studio/components/interfaces/Database/Hooks/HooksList/HookList.tsx +++ b/studio/components/interfaces/Database/Hooks/HooksList/HookList.tsx @@ -1,3 +1,4 @@ +import Image from 'next/image' import { FC } from 'react' import { includes } from 'lodash' import { observer } from 'mobx-react-lite' @@ -6,7 +7,11 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { Badge, Button, Dropdown, IconMoreVertical, IconTrash, IconEdit3 } from 'ui' import { checkPermissions, useStore } from 'hooks' +import { useParams } from 'common/hooks' +import { BASE_PATH } from 'lib/constants' import Table from 'components/to-be-cleaned/Table' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { useDatabaseHooks } from 'data/database-triggers/database-triggers-query' interface Props { schema: string @@ -21,89 +26,108 @@ const HookList: FC = ({ editHook = () => {}, deleteHook = () => {}, }) => { - const { meta } = useStore() - const hooks = meta.hooks.list() - const filteredHooks = hooks.filter((x: any) => - includes(x.name.toLowerCase(), filterString.toLowerCase()) + const { ui } = useStore() + const { ref } = useParams() + const { project } = useProjectContext() + const { data: hooks } = useDatabaseHooks({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + + const restUrl = ui.selectedProject?.restUrl + const restUrlTld = new URL(restUrl as string).hostname.split('.').pop() + + const filteredHooks = (hooks ?? []).filter( + (x: any) => includes(x.name.toLowerCase(), filterString.toLowerCase()) && x.schema === schema ) - const _hooks = filteredHooks.filter((x: any) => x.schema == schema) const canUpdateWebhook = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'triggers') - function onEdit(trigger: any) { - editHook(trigger) - } - - function onDelete(trigger: any) { - deleteHook(trigger) - } - return ( <> - {_hooks.map((x: any) => ( - - -

      {x.name}

      -
      - -

      {x.table}

      -
      - -

      + {filteredHooks.map((x: any) => { + const isEdgeFunction = (url: string) => + url.includes(`https://${ref}.functions.supabase.${restUrlTld}/`) + const [url, method] = x.function_args + + return ( + + +

      + +

      {x.name}

      +
      +
      + +

      {x.table}

      +
      + {x.events.map((event: string) => ( {event} ))} -

      -
      - -

      - {x.function_args[0]} -

      -
      - -
      - {canUpdateWebhook ? ( - - } onClick={() => onEdit(x)}> - Edit hook - - } - onClick={() => onDelete(x)} - > - Delete hook - - - } - > -
      -
      -
      - ))} + + +

      + {method}: {url} +

      +
      + +
      + {canUpdateWebhook ? ( + + } onClick={() => editHook(x)}> + Edit hook + + } + onClick={() => deleteHook(x)} + > + Delete hook + + + } + > +
      +
      + + ) + })} ) } diff --git a/studio/components/interfaces/Database/Hooks/HooksList/HooksList.tsx b/studio/components/interfaces/Database/Hooks/HooksList/HooksList.tsx index 87dd212199b..85a9dc5633d 100644 --- a/studio/components/interfaces/Database/Hooks/HooksList/HooksList.tsx +++ b/studio/components/interfaces/Database/Hooks/HooksList/HooksList.tsx @@ -1,81 +1,65 @@ -import { FC } from 'react' +import Link from 'next/link' +import { useState } from 'react' import { includes, uniqBy, map as lodashMap } from 'lodash' import { observer } from 'mobx-react-lite' import * as Tooltip from '@radix-ui/react-tooltip' -import { Button, Input, IconSearch, IconLoader } from 'ui' +import { Button, Input, IconSearch, IconExternalLink } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useStore, checkPermissions } from 'hooks' +import { checkPermissions } from 'hooks' import SchemaTable from './SchemaTable' import AlphaPreview from 'components/to-be-cleaned/AlphaPreview' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' -function isHooksEnabled(schemas: any): boolean { - return schemas.some((schema: any) => schema.name === 'supabase_functions') +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { useDatabaseHooks } from 'data/database-triggers/database-triggers-query' +import { FormHeader } from 'components/ui/Forms' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import ClientLoadingError from 'components/ui/ClientLoadingError' +import NoSearchResults from 'components/ui/NoSearchResults' + +export interface HooksListProps { + createHook: () => void + editHook: (hook: any) => void + deleteHook: (hook: any) => void } -const HooksList: FC = ({ - hooksEnabled, - filterString, - setFilterString = () => {}, +const HooksList = ({ createHook = () => {}, editHook = () => {}, deleteHook = () => {}, - enableHooks = () => {}, -}) => { - const { meta } = useStore() - const hooks = meta.hooks.list() - const schemas = meta.schemas.list() - const filteredHooks = hooks.filter((x: any) => +}: HooksListProps) => { + const { project } = useProjectContext() + const { + data: hooks, + isLoading, + isError, + } = useDatabaseHooks({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + const [filterString, setFilterString] = useState('') + + const filteredHooks = (hooks || []).filter((x: any) => includes(x.name.toLowerCase(), filterString.toLowerCase()) ) const filteredHookSchemas = lodashMap(uniqBy(filteredHooks, 'schema'), 'schema') const canCreateWebhooks = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'triggers') - const isEnabled = hooksEnabled || isHooksEnabled(schemas) - - if (!isEnabled) { - return ( - <> - {schemas.length === 0 ? ( - Checking if hooks are enabled... - ) : ( - enableHooks()} - disabled={!canCreateWebhooks} - disabledMessage="You need additional permissions to enable hooks" - > - -

      Enable hooks on your project.

      -
      - )} - - ) - } - - if (meta.hooks.isLoading) { - return ( -
      - -

      Loading hooks...

      -
      - ) - } - - if (meta.hooks.hasError) { - return ( -

      -

      Error connecting to API

      -

      {`${meta.hooks.error?.message ?? 'Unknown error'}`}

      -

      - ) - } - return ( <> - {hooks.length == 0 ? ( + {isLoading ? ( +
      + + + +
      + ) : isError ? ( + + ) : (hooks || []).length == 0 ? (
      = ({ disabled={!canCreateWebhooks} disabledMessage="You need additional permissions to create webhooks" > -

      Database Webhooks can be used to trigger serverless functions or send requests to an HTTP endpoint. @@ -92,56 +75,73 @@ const HooksList: FC = ({

      ) : ( -
      -
      - } - value={filterString} - onChange={(e) => setFilterString(e.target.value)} + <> +
      + - - - - - {!canCreateWebhooks && ( - - -
      - - You need additional permissions to create webhooks - -
      -
      - )} -
      - {filteredHooks.length <= 0 && ( -
      -

      No results match your filter query

      - +
      +
      + } + value={filterString} + onChange={(e) => setFilterString(e.target.value)} + /> +
      + + + + + + + + + + {!canCreateWebhooks && ( + + + +
      + + You need additional permissions to create webhooks + +
      +
      +
      + )} +
      +
      - )} - {filteredHookSchemas.map((schema: any) => ( - - ))} -
      + {filteredHooks.length <= 0 && ( + setFilterString('')} + /> + )} + {filteredHookSchemas.map((schema: any) => ( + + ))} +
      + )} ) diff --git a/studio/components/interfaces/Database/Hooks/HooksList/SchemaTable.tsx b/studio/components/interfaces/Database/Hooks/HooksList/SchemaTable.tsx index fd8f1927aac..7175735cc79 100644 --- a/studio/components/interfaces/Database/Hooks/HooksList/SchemaTable.tsx +++ b/studio/components/interfaces/Database/Hooks/HooksList/SchemaTable.tsx @@ -20,28 +20,28 @@ const SchemaTable: FC = ({ return (
      -
      +
      schema

      {schema}

      - - Name + +

      Name

      - + Table - + Events Webhook - + } body={ diff --git a/studio/components/interfaces/Database/Publications/PublicationsList.tsx b/studio/components/interfaces/Database/Publications/PublicationsList.tsx index 082a40cf158..ce030e980f3 100644 --- a/studio/components/interfaces/Database/Publications/PublicationsList.tsx +++ b/studio/components/interfaces/Database/Publications/PublicationsList.tsx @@ -6,7 +6,7 @@ import { Button, Input, Toggle, IconSearch, IconAlertCircle } from 'ui' import { checkPermissions, useStore } from 'hooks' import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' import Table from 'components/to-be-cleaned/Table' -import NoSearchResults from 'components/to-be-cleaned/NoSearchResults' +import NoSearchResults from 'components/ui/NoSearchResults' import InformationBox from 'components/ui/InformationBox' interface Props { @@ -83,7 +83,7 @@ const PublicationsList: FC = ({ onSelectPublication = () => {} }) => { {publications.length === 0 ? ( - + setFilterString('')} /> ) : (
      = ({ onSelectPublication = () => {} }) => { System ID , - - Insert - , - - Update - , - - Delete - , - - Truncate - , + Insert, + Update, + Delete, + Truncate, Source , @@ -118,16 +110,12 @@ const PublicationsList: FC = ({ onSelectPublication = () => {} }) => { {publicationEvents.map((event) => ( -
      - - toggleListenEvent(x, event.event.toLowerCase(), x[event.key]) - } - /> -
      + toggleListenEvent(x, event.event.toLowerCase(), x[event.key])} + />
      ))} diff --git a/studio/components/interfaces/Database/Roles/CreateRolePanel.tsx b/studio/components/interfaces/Database/Roles/CreateRolePanel.tsx new file mode 100644 index 00000000000..c313ec041f5 --- /dev/null +++ b/studio/components/interfaces/Database/Roles/CreateRolePanel.tsx @@ -0,0 +1,164 @@ +import { FC, useRef, useState } from 'react' +import * as Tooltip from '@radix-ui/react-tooltip' +import { Form, IconHelpCircle, Input, SidePanel, Button, Toggle } from 'ui' +import { ROLE_PERMISSIONS } from './Roles.constants' +import { FormSection, FormSectionLabel, FormSectionContent } from 'components/ui/Forms' +import { useStore } from 'hooks' +interface Props { + visible: boolean + onClose: () => void +} + +const CreateRolePanel: FC = ({ visible, onClose }) => { + const formId = 'create-new-role' + const submitRef: any = useRef() + const { ui, meta } = useStore() + const [isSubmitting, setIsSubmitting] = useState(false) + + const initialValues = { + name: '', + is_superuser: false, + can_login: false, + can_create_role: false, + can_create_db: false, + is_replication_role: false, + can_bypass_rls: false, + } + + const validate = (values: any) => { + const errors: any = {} + if (values.name.length === 0) errors.name = 'Please provide a name for your role' + return errors + } + + const onSubmit = async (values: any) => { + setIsSubmitting(true) + const res = await meta.roles.create(values) + setIsSubmitting(false) + + if (res.error) { + return ui.setNotification({ + category: 'error', + message: `Failed to create role: ${res.error.message}`, + }) + } else { + ui.setNotification({ + category: 'success', + message: `Successfully created new role: ${res.name}`, + }) + onClose() + } + } + + return ( + + + + + } + > +
      + {({ isSubmitting, handleReset, values, initialValues }: any) => { + return ( +
      + Role Configuration + } + > + + + + + + Role Privileges + } + > + +
      + {Object.keys(ROLE_PERMISSIONS).map((permission) => ( + + + + + + +
      + + This privilege cannot be granted via the dashboard + +
      +
      + + ) + } + /> + ))} +
      +
      +
      +
      + ) + }} + +
      + ) +} + +export default CreateRolePanel diff --git a/studio/components/interfaces/Database/Roles/DeleteRoleModal.tsx b/studio/components/interfaces/Database/Roles/DeleteRoleModal.tsx new file mode 100644 index 00000000000..c4388e74ad0 --- /dev/null +++ b/studio/components/interfaces/Database/Roles/DeleteRoleModal.tsx @@ -0,0 +1,58 @@ +import { FC, useState } from 'react' +import { Modal } from 'ui' +import { PostgresRole } from '@supabase/postgres-meta' +import { useStore } from 'hooks' + +interface Props { + role: PostgresRole + visible: boolean + onClose: () => void +} + +const DeleteRoleModal: FC = ({ role, visible, onClose }) => { + const { ui, meta } = useStore() + const [isDeleting, setIsDeleting] = useState(false) + + const deleteRole = async () => { + if (!role) return console.error('Failed to delete role: role is missing') + + setIsDeleting(true) + const res: any = await meta.roles.del(role.id) + setIsDeleting(false) + if (res.error) { + return ui.setNotification({ + category: 'error', + message: `Failed to delete role: ${res.error.message}`, + }) + } else { + ui.setNotification({ + category: 'success', + message: `Successfully deleted role: "${role.name}"`, + }) + onClose() + } + } + + return ( + Confirm to delete role "{role?.name}"} + loading={isDeleting} + > +
      + +

      + This will automatically revoke any membership of this role in other roles, and this + action cannot be undone. +

      +
      +
      +
      + ) +} + +export default DeleteRoleModal diff --git a/studio/components/interfaces/Database/Roles/RoleRow.tsx b/studio/components/interfaces/Database/Roles/RoleRow.tsx new file mode 100644 index 00000000000..50c7702c4eb --- /dev/null +++ b/studio/components/interfaces/Database/Roles/RoleRow.tsx @@ -0,0 +1,236 @@ +import { FC, useState } from 'react' +import * as Tooltip from '@radix-ui/react-tooltip' +import { PostgresRole } from '@supabase/postgres-meta' +import { + Button, + Form, + Dropdown, + IconChevronUp, + Toggle, + Collapsible, + IconTrash, + IconHelpCircle, + IconMoreVertical, +} from 'ui' + +import { useStore } from 'hooks' +import { ROLE_PERMISSIONS } from './Roles.constants' + +interface Props { + role: PostgresRole + disabled?: boolean + onSelectDelete: (role: PostgresRole) => void +} + +const RoleRow: FC = ({ role, disabled = false, onSelectDelete }) => { + const { ui, meta } = useStore() + const [isExpanded, setIsExpanded] = useState(false) + + const { + is_superuser, + can_login, + can_create_role, + can_create_db, + is_replication_role, + can_bypass_rls, + } = role + + const onSaveChanges = async (values: any, { setSubmitting, resetForm }: any) => { + setSubmitting(true) + const { is_superuser, is_replication_role, ...payload } = values + const res = await meta.roles.update(role.id, payload) + setSubmitting(false) + + if (res.error) { + ui.setNotification({ + category: 'error', + message: `Failed to update role "${role.name}": ${res.error.message}`, + }) + } else { + ui.setNotification({ + category: 'success', + message: `Successfully updated role "${role.name}"`, + }) + resetForm({ values: { ...values }, initialValues: { ...values } }) + } + } + + return ( +
      + {({ values, initialValues, handleReset, isSubmitting }: any) => { + const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues) + + return ( + + + + + +
      +
      + {Object.keys(ROLE_PERMISSIONS).map((permission) => ( + + + + + + + +
      + + This privilege cannot be updated via the dashboard + +
      +
      +
      + + ) + } + /> + ))} +
      + {!disabled && ( +
      + + +
      + )} +
      +
      +
      + ) + }} + + ) +} + +export default RoleRow diff --git a/studio/components/interfaces/Database/Roles/Roles.constants.ts b/studio/components/interfaces/Database/Roles/Roles.constants.ts new file mode 100644 index 00000000000..7e9bcbc3c28 --- /dev/null +++ b/studio/components/interfaces/Database/Roles/Roles.constants.ts @@ -0,0 +1,44 @@ +export const SUPABASE_ROLES = [ + 'anon', + 'service_role', + 'authenticated', + 'authenticator', + 'dashboard_user', + 'supabase_admin', + 'supabase_auth_admin', + 'supabase_replication_admin', + 'supabase_storage_admin', + 'supabase_functions_admin', + 'pgbouncer', + 'pgsodium_keyholder', + 'pgsodium_keyiduser', + 'pgsodium_keymaker', +] + +export const ROLE_PERMISSIONS: any = { + can_login: { + disabled: false, + description: 'User can login', + }, + can_create_role: { + disabled: false, + description: 'User can create roles', + }, + can_create_db: { + disabled: false, + description: 'User can create databases', + }, + can_bypass_rls: { + disabled: true, + description: 'User bypasses every row level security policy', + }, + is_superuser: { + disabled: true, + description: 'User is a Superuser', + }, + is_replication_role: { + disabled: true, + description: + 'User can initiate streaming replication and put the system in and out of backup mode', + }, +} diff --git a/studio/components/interfaces/Database/Roles/RolesList.tsx b/studio/components/interfaces/Database/Roles/RolesList.tsx index fc46dc7b30c..09b4d1d24d2 100644 --- a/studio/components/interfaces/Database/Roles/RolesList.tsx +++ b/studio/components/interfaces/Database/Roles/RolesList.tsx @@ -1,53 +1,228 @@ -import { FC } from 'react' +import { partition } from 'lodash' +import { useState, useEffect } from 'react' import { observer } from 'mobx-react-lite' -import { IconChevronRight } from 'ui' +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 Table from 'components/to-be-cleaned/Table' +import { FormHeader } from 'components/ui/Forms' +import RoleRow from './RoleRow' +import { SUPABASE_ROLES } from './Roles.constants' +import CreateRolePanel from './CreateRolePanel' +import DeleteRoleModal from './DeleteRoleModal' +import NoSearchResults from 'components/ui/NoSearchResults' -interface Props { - onSelectRole: (role: any) => void -} - -const RolesList: FC = ({ onSelectRole = () => {} }) => { +const RolesList = ({}) => { const { meta } = useStore() + + const [maxConnectionLimit, setMaxConnectionLimit] = useState(0) + const [filterString, setFilterString] = useState('') + const [filterType, setFilterType] = useState<'all' | 'active'>('all') + 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') + if (!res.error) { + setMaxConnectionLimit(Number(res[0]?.max_connections ?? 0)) + } + } + getMaxConnectionLimit() + }, []) + const roles = meta.roles.list() + const filteredRoles = ( + filterType === 'active' + ? meta.roles.list((role: PostgresRole) => role.active_connections > 0) + : meta.roles.list() + ).filter((role: PostgresRole) => role.name.includes(filterString)) + const [supabaseRoles, otherRoles] = partition(filteredRoles, (role: PostgresRole) => + SUPABASE_ROLES.includes(role.name) + ) + + const totalActiveConnections = roles + .map((role: PostgresRole) => role.active_connections) + .reduce((a, b) => a + b, 0) + const rolesWithActiveConnections = roles + .filter((role: PostgresRole) => role.active_connections > 0) + .sort((a, b) => b.active_connections - a.active_connections) return ( <> -
      Name, - System ID, - Connections, - , - ]} - body={roles.map((x, i) => ( - onSelectRole(x)}> - -

      {x.name}

      -
      - -

      {x.id}

      -
      - -
      -

      - {x.active_connections}/{x.connection_limit} connections -

      - +
      +
      + +
      + +
      +
      + } + value={filterString} + onChange={(event: any) => setFilterString(event.target.value)} + actions={ + filterString && ( + + ) + } + /> +
      + +
      + +
      +
      +
      + + +
      + 0.9 * maxConnectionLimit + ? 'bg-red-800' + : totalActiveConnections > 0.75 * maxConnectionLimit + ? 'bg-amber-900' + : 'bg-green-800' + } + labelTop={`${totalActiveConnections}/${maxConnectionLimit}`} + labelBottom="Active connections" + /> +
      +
      + + +
      +

      Connections by roles:

      + {rolesWithActiveConnections.map((role: PostgresRole) => ( +
      + {role.name}: {role.active_connections} +
      + ))} +
      +
      +
      + + + + + {!canUpdateRoles && ( + + +
      + You need additional permissions to add a new role +
      +
      + )} +
      +
      +
      + +
      +
      + {supabaseRoles.length > 0 && ( +
      +

      Roles managed by Supabase

      + Protected
      - - - - - - ))} + )} + {supabaseRoles.map((role: PostgresRole, i: number) => ( + + ))} +
      + +
      + {otherRoles.length > 0 && ( +
      +

      Other database roles

      +
      + )} + {otherRoles.map((role: PostgresRole, i: number) => ( + + ))} +
      +
      + + {filterString.length > 0 && filteredRoles.length === 0 && ( + setFilterString('')} /> + )} +
      + + setIsCreatingRole(false)} /> + + setSelectedRoleToDelete(undefined)} /> ) diff --git a/studio/components/interfaces/Database/Roles/RolesSettings.tsx b/studio/components/interfaces/Database/Roles/RolesSettings.tsx deleted file mode 100644 index 2ff4c1da8a6..00000000000 --- a/studio/components/interfaces/Database/Roles/RolesSettings.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { FC } from 'react' -import { observer } from 'mobx-react-lite' -import { Button, IconChevronLeft } from 'ui' -import Divider from 'components/ui/Divider' - -import Panel from 'components/ui/Panel' - -interface Props { - selectedRole: any - onSelectBack: () => void -} - -const RolesSettings: FC = ({ selectedRole, onSelectBack = () => {} }) => { - return ( - <> -
      -
      - -
      -
      - - - -

      Super user

      - {selectedRole.is_superuser ? 'true' : 'false'} -
      - - -

      User can login

      - {selectedRole.can_login ? 'true' : 'false'} -
      - - -

      User can create databases

      - {selectedRole.can_create_db ? 'true' : 'false'} -
      - - -

      - User can initiate streaming replication and put the system in and out of backup mode -

      - {selectedRole.is_replication_role ? 'true' : 'false'} -
      - - -

      User bypasses every row level security policy

      - {selectedRole.can_bypass_rls ? 'true' : 'false'} -
      -
      - - ) -} - -export default observer(RolesSettings) diff --git a/studio/components/interfaces/Database/Tables/ColumnList.tsx b/studio/components/interfaces/Database/Tables/ColumnList.tsx index c5b668777fc..447e4a82750 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,34 @@ const ColumnList: FC = ({
      {!isLocked && (
      - + + + + + {!canUpdateColumns && ( + + + +
      + + You need additional permissions to create columns + +
      +
      +
      + )} +
      )} @@ -103,20 +131,63 @@ const ColumnList: FC = ({
      -
      diff --git a/studio/components/interfaces/Database/Tables/TableList.tsx b/studio/components/interfaces/Database/Tables/TableList.tsx index de856582937..bfb97d7e2ac 100644 --- a/studio/components/interfaces/Database/Tables/TableList.tsx +++ b/studio/components/interfaces/Database/Tables/TableList.tsx @@ -12,13 +12,15 @@ 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' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' interface Props { selectedSchema: string @@ -38,8 +40,9 @@ const TableList: FC = ({ onOpenTable = () => {}, }) => { const { meta } = useStore() - + const { isLoading } = meta.tables 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,13 +123,45 @@ const TableList: FC = ({ {!isLocked && (
      - + + + + + {!canUpdateTables && ( + + + +
      + + You need additional permissions to create tables + +
      +
      +
      + )} +
      )} - {tables.length === 0 ? ( + + {isLoading ? ( +
      + + + +
      + ) : tables.length === 0 ? ( ) : (
      @@ -183,7 +218,66 @@ const TableList: FC = ({ > {x.columns.length} columns -
      diff --git a/studio/components/interfaces/Database/Triggers/CreateTrigger.tsx b/studio/components/interfaces/Database/Triggers/CreateTrigger.tsx index fbe743151c9..335f05f6021 100644 --- a/studio/components/interfaces/Database/Triggers/CreateTrigger.tsx +++ b/studio/components/interfaces/Database/Triggers/CreateTrigger.tsx @@ -21,6 +21,7 @@ import ChooseFunctionForm from './ChooseFunctionForm' import FormEmptyBox from 'components/ui/FormBoxEmpty' import NoTableState from 'components/ui/States/NoTableState' import { useStore } from 'hooks' +import { BASE_PATH } from 'lib/constants' class CreateTriggerFormState { id: number | undefined @@ -545,7 +546,7 @@ const ListboxTable: FC = observer(({}) => { addOnBefore={() => (
      code.replace(/svg/, 'svg class="m-auto text-color-inherit"') diff --git a/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx b/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx index f4411eff581..f459c145aa0 100644 --- a/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx +++ b/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx @@ -82,19 +82,21 @@ const TriggerList: FC = ({ filterString, schema, editTrigger, deleteTrigg } /> - - -
      - - You need additional permissions to update triggers - -
      - + + + +
      + + You need additional permissions to update triggers + +
      +
      +
      )}
      diff --git a/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx b/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx index 2bb4b194b49..75fa2be96dc 100644 --- a/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx +++ b/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx @@ -80,19 +80,21 @@ const TriggersList: FC = ({ {!canCreateTriggers && ( - - -
      - - You need additional permissions to create triggers - -
      -
      + + + +
      + + You need additional permissions to create triggers + +
      +
      +
      )} diff --git a/studio/components/interfaces/Database/Wrappers/CreateWrapper.tsx b/studio/components/interfaces/Database/Wrappers/CreateWrapper.tsx index dbe8fe10c83..558873bb1e3 100644 --- a/studio/components/interfaces/Database/Wrappers/CreateWrapper.tsx +++ b/studio/components/interfaces/Database/Wrappers/CreateWrapper.tsx @@ -6,7 +6,8 @@ import { observer } from 'mobx-react-lite' import { Button, Form, Input, IconArrowLeft, IconExternalLink, IconEdit, IconTrash } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { checkPermissions, useParams, useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' +import { useParams } from 'common/hooks' import { FormPanel, FormActions, @@ -36,7 +37,7 @@ const CreateWrapper = () => { const [selectedTableToEdit, setSelectedTableToEdit] = useState() const [formErrors, setFormErrors] = useState<{ [k: string]: string }>({}) - const canCreateWrapper = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'extensions') + const canCreateWrapper = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'wrappers') const wrapperMeta = WRAPPERS.find((wrapper) => wrapper.name === type) const initialValues = @@ -164,6 +165,7 @@ const CreateWrapper = () => { isSubmitting={isSubmitting} hasChanges={hasChanges} handleReset={handleReset} + disabled={!canCreateWrapper} helper={ !canCreateWrapper ? 'You need additional permissions to create a foreign data wrapper' diff --git a/studio/components/interfaces/Database/Wrappers/EditWrapper.tsx b/studio/components/interfaces/Database/Wrappers/EditWrapper.tsx index 2286ec04c7f..a8661b6f3bf 100644 --- a/studio/components/interfaces/Database/Wrappers/EditWrapper.tsx +++ b/studio/components/interfaces/Database/Wrappers/EditWrapper.tsx @@ -16,7 +16,8 @@ import { import { PermissionAction } from '@supabase/shared-types/out/constants' import { VaultSecret } from 'types' -import { checkPermissions, useImmutableValue, useParams, useStore } from 'hooks' +import { checkPermissions, useImmutableValue, useStore } from 'hooks' +import { useParams } from 'common/hooks' import { useFDWsQuery } from 'data/fdw/fdws-query' import { useFDWUpdateMutation } from 'data/fdw/fdw-update-mutation' @@ -65,7 +66,7 @@ const EditWrapper = () => { const [selectedTableToEdit, setSelectedTableToEdit] = useState() const [formErrors, setFormErrors] = useState<{ [k: string]: string }>({}) - const canCreateWrapper = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'extensions') + const canUpdateWrapper = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'wrappers') const initialValues = wrapperMeta !== undefined @@ -248,7 +249,7 @@ const EditWrapper = () => { return ( { handleReset() setWrapperTables(initialTables) }} + disabled={!canUpdateWrapper} helper={ - !canCreateWrapper - ? 'You need additional permissions to create a foreign data wrapper' + !canUpdateWrapper + ? 'You need additional permissions to edit a foreign data wrapper' : undefined } /> diff --git a/studio/components/interfaces/Database/Wrappers/WrapperRow.tsx b/studio/components/interfaces/Database/Wrappers/WrapperRow.tsx index 35b7efbed34..13798aa8747 100644 --- a/studio/components/interfaces/Database/Wrappers/WrapperRow.tsx +++ b/studio/components/interfaces/Database/Wrappers/WrapperRow.tsx @@ -1,10 +1,13 @@ import Link from 'next/link' import Image from 'next/image' import { FC } from 'react' -import { Collapsible, IconChevronUp, Button, IconExternalLink, IconTrash, IconEdit } from 'ui' import { partition } from 'lodash' +import * as Tooltip from '@radix-ui/react-tooltip' +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { Collapsible, IconChevronUp, Button, IconExternalLink, IconTrash, IconEdit } from 'ui' -import { useParams, useStore } from 'hooks' +import { useStore, checkPermissions } from 'hooks' +import { useParams } from 'common/hooks' import { WrapperMeta } from './Wrappers.types' import { FDW } from 'data/fdw/fdws-query' import { useFDWDeleteMutation } from 'data/fdw/fdw-delete-mutation' @@ -24,6 +27,8 @@ const WrapperRow: FC = ({ wrappers = [], wrapperMeta, isOpen, onOpen }) = const { project } = useProjectContext() const { mutateAsync: deleteFDW } = useFDWDeleteMutation() + const canManageWrappers = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'wrappers') + const onDeleteWrapper = (wrapper: any) => { confirmAlert({ title: `Confirm to disable ${wrapper.name}`, @@ -156,21 +161,73 @@ const WrapperRow: FC = ({ wrappers = [], wrapperMeta, isOpen, onOpen }) =
      - - + {canManageWrappers ? ( + + +
      ) diff --git a/studio/components/interfaces/Database/Wrappers/Wrappers.constants.ts b/studio/components/interfaces/Database/Wrappers/Wrappers.constants.ts index cbb51c09408..fe05d8bb3ab 100644 --- a/studio/components/interfaces/Database/Wrappers/Wrappers.constants.ts +++ b/studio/components/interfaces/Database/Wrappers/Wrappers.constants.ts @@ -1,3 +1,4 @@ +import { BASE_PATH } from 'lib/constants' import { WrapperMeta } from './Wrappers.types' export const WRAPPERS: WrapperMeta[] = [ @@ -5,7 +6,7 @@ export const WRAPPERS: WrapperMeta[] = [ name: 'stripe_wrapper', handlerName: 'stripe_fdw_handler', validatorName: 'stripe_fdw_validator', - icon: '/img/icons/stripe-icon.svg', + icon: `${BASE_PATH}/img/icons/stripe-icon.svg`, extensionName: 'StripeFdw', label: 'Stripe', docsUrl: 'https://supabase.com/docs/guides/database/wrappers/stripe', @@ -409,7 +410,7 @@ export const WRAPPERS: WrapperMeta[] = [ name: 'firebase_wrapper', handlerName: 'firebase_fdw_handler', validatorName: 'firebase_fdw_validator', - icon: '/img/icons/firebase-icon.svg', + icon: `${BASE_PATH}/img/icons/firebase-icon.svg`, extensionName: 'FirebaseFdw', label: 'Firebase', docsUrl: 'https://supabase.com/docs/guides/database/wrappers/firebase', diff --git a/studio/components/interfaces/Database/Wrappers/Wrappers.tsx b/studio/components/interfaces/Database/Wrappers/Wrappers.tsx index 461d9776b17..529d9cd0c16 100644 --- a/studio/components/interfaces/Database/Wrappers/Wrappers.tsx +++ b/studio/components/interfaces/Database/Wrappers/Wrappers.tsx @@ -1,4 +1,5 @@ import Link from 'next/link' +import { groupBy } from 'lodash' import { useState } from 'react' import { Button, IconExternalLink } from 'ui' import { observer } from 'mobx-react-lite' @@ -11,7 +12,6 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import WrappersDropdown from './WrappersDropdown' import WrappersDisabledState from './WrappersDisabledState' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' -import { groupBy } from 'lodash' const Wrappers = () => { const { meta } = useStore() diff --git a/studio/components/interfaces/Database/Wrappers/WrappersDisabledState.tsx b/studio/components/interfaces/Database/Wrappers/WrappersDisabledState.tsx index 54e4fa6a8b1..014c060780f 100644 --- a/studio/components/interfaces/Database/Wrappers/WrappersDisabledState.tsx +++ b/studio/components/interfaces/Database/Wrappers/WrappersDisabledState.tsx @@ -5,7 +5,9 @@ import { observer } from 'mobx-react-lite' import * as Tooltip from '@radix-ui/react-tooltip' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { checkPermissions, useParams, useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' +import { useParams } from 'common/hooks' +import { BASE_PATH } from 'lib/constants' const WrappersDisabledState = () => { const { ui, meta } = useStore() @@ -65,8 +67,8 @@ const WrappersDisabledState = () => { backgroundSize: '45%', backgroundPosition: '105% 40%', backgroundImage: ui.isDarkTheme - ? 'url("/img/wrappers-dark.png")' - : 'url("/img/wrappers-light.png")', + ? `url("${BASE_PATH}/img/wrappers-dark.png")` + : `url("${BASE_PATH}/img/wrappers-light.png")`, }} >
      @@ -130,19 +132,21 @@ const WrappersDisabledState = () => { {!canToggleWrappers && ( - - -
      - - You need additional permissions to enable Wrappers for this project - -
      -
      + + + +
      + + You need additional permissions to enable Wrappers for this project + +
      +
      +
      )}
      diff --git a/studio/components/interfaces/Database/Wrappers/WrappersDropdown.tsx b/studio/components/interfaces/Database/Wrappers/WrappersDropdown.tsx index b74c9a9fffb..33b5cd693ed 100644 --- a/studio/components/interfaces/Database/Wrappers/WrappersDropdown.tsx +++ b/studio/components/interfaces/Database/Wrappers/WrappersDropdown.tsx @@ -3,8 +3,11 @@ import Image from 'next/image' import { FC, Fragment } from 'react' import { observer } from 'mobx-react-lite' import { Button, Dropdown, IconPlus } from 'ui' +import * as Tooltip from '@radix-ui/react-tooltip' +import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useParams } from 'hooks' +import { checkPermissions } from 'hooks' +import { useParams } from 'common/hooks' import { WRAPPERS } from './Wrappers.constants' interface Props { @@ -14,6 +17,34 @@ interface Props { const WrapperDropdown: FC = ({ buttonText = 'Add wrapper', align = 'end' }) => { const { ref } = useParams() + const canManageWrappers = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'wrappers') + + if (!canManageWrappers) { + return ( + + + + + + + +
      + + 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..efe0fec31df 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

      @@ -221,64 +231,51 @@ const ResourceContent = ({ )} - <> -

      Subscribe to changes

      -
      -
      -

      - Supabase provides realtime functionality and broadcasts database changes to authorized - users depending on Row Level Security (RLS) policies. -

      -

      - - Learn more. - -

      -
      -
      - - - - - -
      -
      - - <> -

      Much more

      -
      -
      -

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

      -
      -
      -
      - + {(methods.includes('DELETE') || methods.includes('POST') || methods.includes('PATCH')) && ( + <> +

      Subscribe to changes

      +
      +
      +

      + Supabase provides realtime functionality and broadcasts database changes to + authorized users depending on Row Level Security (RLS) policies. +

      +

      + + Learn more + +

      +
      +
      + + + + + +
      +
      + + )} ) } diff --git a/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx b/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx new file mode 100644 index 00000000000..1095bf70ed0 --- /dev/null +++ b/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx @@ -0,0 +1,324 @@ +import dayjs from 'dayjs' +import Link from 'next/link' +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, + IconTerminal, + IconMinimize2, + IconMaximize2, + Button, + Modal, + Form, + Toggle, + Input, + IconExternalLink, +} from 'ui' + +import { useStore, checkPermissions } from 'hooks' +import { useParams } from 'common/hooks' +import Panel from 'components/ui/Panel' +import CommandRender from '../CommandRender' +import { generateCLICommands } from './EdgeFunctionDetails.utils' +import { useProjectApiQuery } from 'data/config/project-api-query' +import { useEdgeFunctionUpdateMutation } from 'data/edge-functions/edge-functions-update-mutation' +import { useEdgeFunctionDeleteMutation } from 'data/edge-functions/edge-functions-delete-mutation' +import { + FormHeader, + FormPanel, + FormActions, + FormSection, + FormSectionLabel, + FormSectionContent, +} from 'components/ui/Forms' +import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' +import clsx from 'clsx' + +interface Props {} + +const EdgeFunctionDetails: FC = () => { + const router = useRouter() + const { ui } = useStore() + const { ref: projectRef, functionSlug } = useParams() + const [showDeleteModal, setShowDeleteModal] = useState(false) + const [showInstructions, setShowInstructions] = useState(false) + + const { data: settings } = useProjectApiQuery({ projectRef }) + const { data: selectedFunction } = useEdgeFunctionQuery({ projectRef, slug: functionSlug }) + const { mutateAsync: updateEdgeFunction } = useEdgeFunctionUpdateMutation() + const { mutateAsync: deleteEdgeFunction, isLoading: isDeleting } = useEdgeFunctionDeleteMutation() + + const formId = 'edge-function-update-form' + const canUpdateEdgeFunction = checkPermissions(PermissionAction.FUNCTIONS_WRITE, '*') + + // 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 { managementCommands, secretCommands, invokeCommands } = generateCLICommands( + selectedFunction, + functionUrl, + anonKey + ) + + const onUpdateFunction = async (values: any, { setSubmitting, resetForm }: any) => { + if (!projectRef) return console.error('Project ref is required') + if (selectedFunction === undefined) return console.error('No edge function selected') + setSubmitting(true) + + try { + await updateEdgeFunction({ + projectRef, + slug: selectedFunction.slug, + payload: values, + }) + resetForm({ values, initialValues: values }) + ui.setNotification({ category: 'success', message: `Successfully updated edge function` }) + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to update edge function: ${error.message}`, + }) + } finally { + setSubmitting(false) + } + } + + 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}`, + }) + } + } + + return ( + <> +
      +
      + {({ isSubmitting, handleReset, values, initialValues, resetForm }: any) => { + const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues) + + useEffect(() => { + if (selectedFunction !== undefined) { + const formValues = { + name: selectedFunction?.name, + verify_jwt: selectedFunction?.verify_jwt, + } + resetForm({ values: formValues, initialValues: formValues }) + } + }, [selectedFunction]) + return ( + <> + + +
      + } + > + Function Details}> + + + + + + + + + + + Function Configuration}> + + +
      +
      +

      + Import maps are{' '} + + {selectedFunction?.import_map ? 'allowed' : 'disallowed'} + {' '} + for this function +

      +
      +

      + Import maps allow the use of bare specifiers without having to install the + Node.js package locally +

      + +
      +
      +
      +
      + + ) + }} + + +
      +
      +
      +
      + +
      +

      Command line access

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

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

      + + + + + {!canUpdateEdgeFunction && ( + + + +
      + + 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/EdgeFunctionDetails/EdgeFunctionDetails.utils.tsx b/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.utils.tsx new file mode 100644 index 00000000000..bc2ae902432 --- /dev/null +++ b/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.utils.tsx @@ -0,0 +1,93 @@ +export const generateCLICommands = ( + selectedFunction: any, + functionUrl: string, + anonKey: string +) => { + 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 '{functionUrl}' -H + 'Authorization: Bearer [YOUR ANON KEY]' {`--data '{"name":"Functions"}'`} + + ) + }, + comment: 'Invoke your function', + }, + ] + + return { managementCommands, secretCommands, invokeCommands } +} diff --git a/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx b/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx new file mode 100644 index 00000000000..9b90a9c2539 --- /dev/null +++ b/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx @@ -0,0 +1,106 @@ +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 { useStore } from 'hooks' +import { useParams } from 'common/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.slug}/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..d25017cef89 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 @@ -14,12 +13,11 @@ const FunctionsNav = ({ item }) => { size="medium" activeId={!activeRoute ? 'metrics' : activeRoute} onChange={(e: string) => - router.push(`/project/${ref}/functions/${item.id}/${e === 'metrics' ? '' : e}`) + router.push(`/project/${ref}/functions/${item.slug}/${e === 'metrics' ? '' : e}`) } > - - {/* */} + diff --git a/studio/components/interfaces/Functions/TerminalInstructions.tsx b/studio/components/interfaces/Functions/TerminalInstructions.tsx index f8f30b523ab..a4448748dbd 100644 --- a/studio/components/interfaces/Functions/TerminalInstructions.tsx +++ b/studio/components/interfaces/Functions/TerminalInstructions.tsx @@ -1,7 +1,7 @@ import CommandRender from 'components/interfaces/Functions/CommandRender' import { useAccessTokensQuery } from 'data/access-tokens/access-tokens-query' import { useProjectApiQuery } from 'data/config/project-api-query' -import { useParams } from 'hooks' +import { useParams } from 'common/hooks' import Link from 'next/link' import { useRouter } from 'next/router' import { FC, useState } from 'react' @@ -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 }) => {
      )}
      -
      +
      @@ -144,7 +143,7 @@ const TerminalInstructions: FC = ({ closable = false }) => { href="https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions" > - diff --git a/studio/components/interfaces/Functions/index.ts b/studio/components/interfaces/Functions/index.ts index b096ff5e604..992b396e1be 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/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/ClientLibrary.tsx b/studio/components/interfaces/Home/ClientLibrary.tsx index 7e2bd3d8cae..59f1d0b4d5f 100644 --- a/studio/components/interfaces/Home/ClientLibrary.tsx +++ b/studio/components/interfaces/Home/ClientLibrary.tsx @@ -1,5 +1,6 @@ import { FC } from 'react' import { IconBookOpen, IconGitHub, Button, Badge } from 'ui' +import { BASE_PATH } from 'lib/constants' interface Props { language: string @@ -13,7 +14,7 @@ const ClientLibrary: FC = ({ language, releaseState, officialSupport, doc return (
      {`${language} diff --git a/studio/components/interfaces/Home/ExampleProject.tsx b/studio/components/interfaces/Home/ExampleProject.tsx index a5f6e652796..32f76b8bfaf 100644 --- a/studio/components/interfaces/Home/ExampleProject.tsx +++ b/studio/components/interfaces/Home/ExampleProject.tsx @@ -2,6 +2,7 @@ import { FC } from 'react' import { IconChevronRight } from 'ui' import { useStore } from 'hooks' import Link from 'next/link' +import { BASE_PATH } from 'lib/constants' interface Props { framework: string @@ -29,8 +30,12 @@ const ExampleProject: FC = ({ framework, title, description, url }) => {
      {`${framework} = ({}) => { type="default" icon={ code.replace(/svg/, 'svg class="m-auto text-color-inherit"') @@ -65,7 +65,7 @@ const NewProjectPanel: FC = ({}) => { type="default" icon={ code.replace(/svg/, 'svg class="m-auto text-color-inherit"') diff --git a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx index 1199e598a24..c973699deeb 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx @@ -10,6 +10,7 @@ import { makeRandomString } from 'lib/helpers' import ProjectCard from './ProjectCard' import ShimmeringCard from './ShimmeringCard' import { PermissionAction } from '@supabase/shared-types/out/constants' +import { useOverdueInvoicesQuery } from 'data/invoices/invoices-query' interface Props { rewriteHref?: (projectRef: string) => string @@ -20,6 +21,8 @@ const ProjectList: FC = ({ rewriteHref }) => { const { organizations, projects } = app const { isLoading: isLoadingProjects } = projects + const { data: allOverdueInvoices } = useOverdueInvoicesQuery({ enabled: IS_PLATFORM }) + const isLoadingPermissions = IS_PLATFORM ? (ui?.permissions ?? []).length === 0 : false return ( @@ -32,9 +35,24 @@ const ProjectList: FC = ({ rewriteHref }) => { const isEmpty = sortedProjects?.length == 0 const canReadProjects = checkPermissions(PermissionAction.READ, 'projects', undefined, id) + const overdueInvoices = (allOverdueInvoices || []).filter((it) => it.organization_id === id) + return (
      -

      {name}

      +
      +

      {name}

      + + {!!overdueInvoices.length && ( + + )} +
      + {isLoadingPermissions || isLoadingProjects ? (
        diff --git a/studio/components/interfaces/Home/ProjectUsage.tsx b/studio/components/interfaces/Home/ProjectUsage.tsx index d113c2bd5c9..ff7ffc79c9b 100644 --- a/studio/components/interfaces/Home/ProjectUsage.tsx +++ b/studio/components/interfaces/Home/ProjectUsage.tsx @@ -7,7 +7,7 @@ import ChartHandler from 'components/to-be-cleaned/Charts/ChartHandler' import Panel from 'components/ui/Panel' import { DATE_FORMAT, METRICS } from 'lib/constants' import { ChartIntervals } from 'types' -import { useParams } from 'hooks' +import { useParams } from 'common/hooks' import { useProjectLogStatsQuery } from 'data/logs/project-log-stats-query' const CHART_INTERVALS: ChartIntervals[] = [ diff --git a/studio/components/interfaces/Home/ProjectUsageSection.tsx b/studio/components/interfaces/Home/ProjectUsageSection.tsx index 9064717135c..932f22577be 100644 --- a/studio/components/interfaces/Home/ProjectUsageSection.tsx +++ b/studio/components/interfaces/Home/ProjectUsageSection.tsx @@ -2,7 +2,7 @@ import { FC } from 'react' import { observer } from 'mobx-react-lite' import { IconLoader, IconAlertCircle } from 'ui' -import { useParams } from 'hooks' +import { useParams } from 'common/hooks' import { ProjectUsage, NewProjectPanel } from 'components/interfaces/Home' import InformationBox from 'components/ui/InformationBox' import { ProjectUsageResponseUsageKeys, useProjectUsageQuery } from 'data/usage/project-usage-query' @@ -11,6 +11,14 @@ const ProjectUsageSection: FC = observer(({}) => { const { ref: projectRef } = useParams() const { data: usage, error: usageError, isLoading } = useProjectUsageQuery({ projectRef }) + const usageColumns = [ + 'db_egress', + 'storage_egress', + 'monthly_active_users', + 'realtime_message_count', + 'func_invocations', + ] + if (usageError) { return ( { } const hasProjectData = usage - ? Object.keys(usage) + ? usageColumns .map((key) => usage[key as ProjectUsageResponseUsageKeys].usage) .some((usage) => (usage ?? 0) > 0) : false diff --git a/studio/components/interfaces/Organization/BillingSettings/BillingEmail.tsx b/studio/components/interfaces/Organization/BillingSettings/BillingEmail.tsx index b9b25b40ede..db8b8d7b2fa 100644 --- a/studio/components/interfaces/Organization/BillingSettings/BillingEmail.tsx +++ b/studio/components/interfaces/Organization/BillingSettings/BillingEmail.tsx @@ -1,7 +1,8 @@ import { useEffect } from 'react' import { Form, Input } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useStore, checkPermissions, useParams } from 'hooks' +import { useStore, checkPermissions } from 'hooks' +import { useParams } from 'common/hooks' import { API_URL } from 'lib/constants' import { patch } from 'lib/common/fetch' import { FormActions, FormPanel, FormSection, FormSectionContent } from 'components/ui/Forms' diff --git a/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx b/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx index cd7cfb91c0f..ba01a03ef37 100644 --- a/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx +++ b/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx @@ -2,7 +2,8 @@ import { useState, useEffect } from 'react' import { observer } from 'mobx-react-lite' import { Project } from 'types' -import { useParams, useStore } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import { API_URL } from 'lib/constants' import { get } from 'lib/common/fetch' diff --git a/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx b/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx index 799445695b1..9963a537053 100644 --- a/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx +++ b/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx @@ -17,7 +17,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { checkPermissions, useStore } from 'hooks' import { delete_, patch } from 'lib/common/fetch' import { getURL } from 'lib/helpers' -import { API_URL } from 'lib/constants' +import { API_URL, BASE_PATH } from 'lib/constants' import Panel from 'components/ui/Panel' import { AddNewPaymentMethodModal } from 'components/interfaces/Billing' import NoPermission from 'components/ui/NoPermission' @@ -158,7 +158,7 @@ const PaymentMethods: FC = ({
        = ({ + + + )} +
        @@ -137,7 +154,7 @@ const InvoicesSettings = () => { ) })} - +

        Showing {offset + 1} to {offset + invoices.length} out of {count} invoices diff --git a/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx b/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx index 3f73035e896..23225e0ff3b 100644 --- a/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx @@ -1,24 +1,28 @@ import { isNil } from 'lodash' -import { FC, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import { object, string } from 'yup' import * as Tooltip from '@radix-ui/react-tooltip' -import { Button, Form, IconMail, Input, Modal, Select } from 'ui' +import { Button, Form, IconMail, Input, Listbox, Modal } from 'ui' -import { Member, User, Role } from 'types' -import { checkPermissions, useParams, useStore } from 'hooks' -import { post } from 'lib/common/fetch' -import { API_URL } from 'lib/constants' +import { Member, Role } from 'types' +import { checkPermissions, useStore } from 'hooks' +import { useParams } from 'common/hooks' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useOrganizationMemberInviteCreateMutation } from 'data/organizations/organization-member-invite-create-mutation' -interface Props { - user: User +export interface InviteMemberButtonProps { + userId: number members: Member[] roles: Role[] rolesAddable: Number[] } -const InviteMemberButton: FC = ({ user, members = [], roles = [], rolesAddable = [] }) => { +const InviteMemberButton = ({ + userId, + members = [], + roles = [], + rolesAddable = [], +}: InviteMemberButtonProps) => { const { ui } = useStore() const { slug } = useParams() @@ -67,7 +71,7 @@ const InviteMemberButton: FC = ({ user, members = [], roles = [], rolesAd const response = await mutateAsync({ slug, invitedEmail: values.email.toLowerCase(), - ownerId: user.id, + ownerId: userId, roleId, }) @@ -98,19 +102,21 @@ const InviteMemberButton: FC = ({ user, members = [], roles = [], rolesAd {!canInviteMembers && ( - - -

        - - You need additional permissions to invite a member to this organization - -
        - + + + +
        + + You need additional permissions to invite a member to this organization + +
        +
        +
        )} = ({ user, members = [], roles = [], rolesAd
        {roles && ( - + )}
        diff --git a/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx b/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx index a920d38809e..ba2a47bf189 100644 --- a/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx @@ -5,7 +5,8 @@ import { FC } from 'react' import { Button, Dropdown, IconMoreHorizontal, IconTrash } from 'ui' import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' -import { checkPermissions, useParams, useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' +import { useParams } from 'common/hooks' import { Member, Role } from 'types' import { useOrganizationMemberDeleteMutation } from 'data/organizations/organization-member-delete-mutation' @@ -15,12 +16,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) @@ -134,19 +134,21 @@ const MemberActions: FC = ({ members, member, roles }) => {
        ) diff --git a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx index c54088028ed..3dca2403e09 100644 --- a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx @@ -1,17 +1,22 @@ import Image from 'next/image' import * as Tooltip from '@radix-ui/react-tooltip' -import { useState, FC, Fragment } from 'react' +import { useState, Fragment } from 'react' import { observer } from 'mobx-react-lite' -import { Badge, Button, Loading, Listbox, IconUser, Modal, IconAlertCircle } from 'ui' +import { Badge, Button, Loading, Listbox, IconUser, Modal, IconAlertCircle, IconLoader } from 'ui' -import { Member, Role } from 'types' -import { useStore, useParams } from 'hooks' -import { isInviteExpired, getUserDisplayName } from '../Organization.utils' +import { Member } from 'types' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' import Table from 'components/to-be-cleaned/Table' import MemberActions from './MemberActions' import RolesHelperModal from './RolesHelperModal/RolesHelperModal' import { getRolesManagementPermissions } from './TeamSettings.utils' +import { isInviteExpired, getUserDisplayName } from '../Organization.utils' +import { useProfileQuery } from 'data/profile/profile-query' +import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query' +import { useOrganizationDetailQuery } from 'data/organizations/organization-detail-query' import { useOrganizationMemberUpdateMutation } from 'data/organizations/organization-member-update-mutation' interface SelectedMember extends Member { @@ -19,25 +24,39 @@ interface SelectedMember extends Member { newRoleId: number } -interface Props { - roles: Role[] - members: Member[] +export interface MembersViewProps { searchString: string } -const MembersView: FC = ({ searchString, roles, members }) => { +const MembersView = ({ searchString }: MembersViewProps) => { const { ui } = useStore() const { slug } = useParams() - const user = ui.profile + const { data: profile } = useProfileQuery() + const { data: detailData, isLoading: isLoadingOrgDetails } = useOrganizationDetailQuery({ slug }) + const { data: rolesData, isLoading: isLoadingRoles } = useOrganizationRolesQuery({ slug }) + const { mutate: updateOrganizationMember, isLoading } = useOrganizationMemberUpdateMutation({ + onSuccess() { + ui.setNotification({ + category: 'success', + message: `Successfully updated role for ${getUserDisplayName(selectedMember)}`, + }) + }, + onError() { + ui.setNotification({ + category: 'error', + message: `Failed to update role for ${getUserDisplayName(selectedMember)}`, + }) + }, + }) + const roles = rolesData?.roles ?? [] + const members = detailData?.members ?? [] const { rolesAddable, rolesRemovable } = getRolesManagementPermissions(roles) const [selectedMember, setSelectedMember] = useState() const [userRoleChangeModalVisible, setUserRoleChangeModalVisible] = useState(false) - if (!members) return
        - const filteredMembers = ( !searchString ? members @@ -58,21 +77,6 @@ const MembersView: FC = ({ searchString, roles, members }) => { return roles.find((x: any) => x.id === id)?.name } - const { isLoading, mutate } = useOrganizationMemberUpdateMutation({ - onSuccess() { - ui.setNotification({ - category: 'success', - message: `Successfully updated role for ${getUserDisplayName(selectedMember)}`, - }) - }, - onError() { - ui.setNotification({ - category: 'error', - message: `Failed to update role for ${getUserDisplayName(selectedMember)}`, - }) - }, - }) - const handleRoleChange = async () => { if (!selectedMember) return @@ -85,11 +89,20 @@ const MembersView: FC = ({ searchString, roles, members }) => { throw new Error('gotrue_id is required') } - mutate({ slug, gotrueId: gotrue_id, roleId: newRoleId }) - + updateOrganizationMember({ slug, gotrueId: gotrue_id, roleId: newRoleId }) setUserRoleChangeModalVisible(false) } + if (isLoadingOrgDetails) { + return ( +
        + + + +
        + ) + } + return ( <>
        @@ -108,7 +121,7 @@ const MembersView: FC = ({ searchString, roles, members }) => { ...filteredMembers.map((x: Member, i: number) => { const [memberRoleId] = x.role_ids ?? [] const role = (roles || []).find((role) => role.id === memberRoleId) - const memberIsUser = x.primary_email == user?.primary_email + const memberIsUser = x.primary_email == profile?.primary_email const memberIsPendingInvite = !!x.invited_id const canRemoveRole = rolesRemovable.includes(memberRoleId) const disableRoleEdit = !canRemoveRole || memberIsUser || memberIsPendingInvite @@ -175,10 +188,13 @@ const MembersView: FC = ({ searchString, roles, members }) => { - {!role &&

        {x.is_owner ? 'Owner' : 'Developer'}

        } - {role && ( + {isLoadingRoles ? ( +
        + +
        + ) : role !== undefined ? ( - + = ({ searchString, roles, members }) => { value={r.id} label={r.name} disabled={disableRoleEdit} + className="w-36" > {r.name} @@ -198,43 +215,71 @@ const MembersView: FC = ({ searchString, roles, members }) => { {memberIsPendingInvite ? ( - - -
        - - Role can only be changed after the user has accepted the invite - -
        -
        + + + +
        + + Role can only be changed after the user has accepted the + invite + +
        +
        +
        ) : !memberIsUser && !canRemoveRole ? ( - - -
        - - You need additional permissions to manage this team member - -
        -
        + + + +
        + + You need additional permissions to manage this team member + +
        +
        +
        ) : ( <> )}
        + ) : ( +
        +

        Invalid role

        + + + + + + + +
        + + This user has an invalid role, please reach out to us via + support + +
        +
        +
        +
        +
        )}
        - {!memberIsUser && ( - - )} + {!memberIsUser && } diff --git a/studio/components/interfaces/Organization/TeamSettings/RolesHelperModal/RolesHelperModal.tsx b/studio/components/interfaces/Organization/TeamSettings/RolesHelperModal/RolesHelperModal.tsx index 6c322bf60f3..9f08f55e877 100644 --- a/studio/components/interfaces/Organization/TeamSettings/RolesHelperModal/RolesHelperModal.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/RolesHelperModal/RolesHelperModal.tsx @@ -17,17 +17,19 @@ const RolesHelperModal: FC = ({}) => { - - -
        - Only available in Team/Enterprise plan. -
        -
        + + + +
        + Only available in Team/Enterprise plan. +
        +
        +
        ) diff --git a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx index 98212c2153a..bb5f155102d 100644 --- a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx @@ -3,9 +3,11 @@ import { observer } from 'mobx-react-lite' import { Button, Input, IconSearch } from 'ui' import * as Tooltip from '@radix-ui/react-tooltip' -import { useStore, useParams } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import { post } from 'lib/common/fetch' import { API_URL } from 'lib/constants' +import { useProfileQuery } from 'data/profile/profile-query' import InviteMemberButton from './InviteMemberButton' import MembersView from './MembersView' import { getRolesManagementPermissions, hasMultipleOwners } from './TeamSettings.utils' @@ -17,7 +19,7 @@ const TeamSettings = () => { const { ui } = useStore() const { slug } = useParams() - const user = ui.profile + const { data: profile } = useProfileQuery() const isOwner = ui.selectedOrganization?.is_owner const { data: detailData } = useOrganizationDetailQuery({ slug }) @@ -73,10 +75,10 @@ const TeamSettings = () => { placeholder="Filter members" />
        - {canAddMembers && user !== undefined && ( + {canAddMembers && profile !== undefined && (
        { {!canLeave && ( - - -
        - - An organization requires at least 1 owner - -
        -
        + + + +
        + + An organization requires at least 1 owner + +
        +
        +
        )}
        @@ -116,7 +120,7 @@ const TeamSettings = () => {
        - +
        ) diff --git a/studio/components/interfaces/Reports/ReportFilterBar.tsx b/studio/components/interfaces/Reports/ReportFilterBar.tsx new file mode 100644 index 00000000000..15bc75c22ec --- /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)} + 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..def9096eb2e 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 +
        + + +
        - {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.tsx b/studio/components/interfaces/Reports/Reports.tsx index 5fb1630fa29..4b41c46826b 100644 --- a/studio/components/interfaces/Reports/Reports.tsx +++ b/studio/components/interfaces/Reports/Reports.tsx @@ -1,5 +1,4 @@ import { useEffect, useState } from 'react' -import { useRouter } from 'next/router' import * as Tooltip from '@radix-ui/react-tooltip' import { groupBy, isNull } from 'lodash' import { toJS } from 'mobx' @@ -17,10 +16,12 @@ import { } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { checkPermissions, useStore } from 'hooks' +import { checkPermissions } from 'hooks' +import { useParams } from 'common/hooks' import { uuidv4 } from 'lib/helpers' import { METRIC_CATEGORIES, METRICS, TIME_PERIODS_REPORTS } from 'lib/constants' import { useProjectContentStore } from 'stores/projectContentStore' +import { useProfileQuery } from 'data/profile/profile-query' import Loading from 'components/ui/Loading' import DateRangePicker from 'components/to-be-cleaned/DateRangePicker' import NoPermission from 'components/ui/NoPermission' @@ -31,10 +32,8 @@ const DEFAULT_CHART_COLUMN_COUNT = 12 const DEFAULT_CHART_ROW_COUNT = 4 const Reports = () => { - const { ui } = useStore() - - const router = useRouter() - const { id, ref } = router.query + const { id, ref } = useParams() + const { data: profile } = useProfileQuery() const [report, setReport] = useState() @@ -53,7 +52,7 @@ const Reports = () => { visibility: report?.visibility, owner_id: report?.owner_id, }, - subject: { id: ui.profile?.id }, + subject: { id: profile?.id }, }) const canUpdateReport = checkPermissions(PermissionAction.UPDATE, 'user_content', { resource: { @@ -61,7 +60,7 @@ const Reports = () => { visibility: report?.visibility, owner_id: report?.owner_id, }, - subject: { id: ui.profile?.id }, + subject: { id: profile?.id }, }) /* @@ -277,7 +276,7 @@ const Reports = () => { return ( x.attribute === metric.key)} + checked={config.layout?.some((x: any) => x.attribute === metric.key)} onChange={(e) => handleChartSelection({ metric, value: e })} >
        @@ -362,19 +361,21 @@ const Reports = () => { Add / Remove charts - - -
        - - You need additional permissions to update this project's report - -
        -
        + + + +
        + + You need additional permissions to update this project's report + +
        +
        +
        )}
        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/SQLEditor/TabSqlQuery/FavouriteButton.tsx b/studio/components/interfaces/SQLEditor/TabSqlQuery/FavouriteButton.tsx index 19ab8769d49..eb9389602fc 100644 --- a/studio/components/interfaces/SQLEditor/TabSqlQuery/FavouriteButton.tsx +++ b/studio/components/interfaces/SQLEditor/TabSqlQuery/FavouriteButton.tsx @@ -5,11 +5,12 @@ import { Button, IconHeart } from 'ui' import * as Tooltip from '@radix-ui/react-tooltip' import { useStore } from 'hooks' import Telemetry from 'lib/telemetry' +import { useProfileQuery } from 'data/profile/profile-query' import { useSqlStore } from 'localStores/sqlEditor/SqlEditorStore' const FavouriteButton = () => { const { ui, content: contentStore } = useStore() - const { profile: user } = ui + const { data: profile } = useProfileQuery() const sqlEditorStore: any = useSqlStore() @@ -52,7 +53,7 @@ const FavouriteButton = () => { /* * reload sql data in store and re-select tab */ - sqlEditorStore.loadTabs(sqlEditorStore.tabsFromContentStore(contentStore, user?.id), false) + sqlEditorStore.loadTabs(sqlEditorStore.tabsFromContentStore(contentStore, profile?.id), false) sqlEditorStore.selectTab(id) setLoading(false) @@ -93,7 +94,7 @@ const FavouriteButton = () => { /* * reload sql data in store and re-select tab */ - sqlEditorStore.loadTabs(sqlEditorStore.tabsFromContentStore(contentStore, user?.id), false) + sqlEditorStore.loadTabs(sqlEditorStore.tabsFromContentStore(contentStore, profile?.id), false) sqlEditorStore.selectTab(id) setLoading(false) } catch (error: any) { @@ -111,20 +112,22 @@ const FavouriteButton = () => { return ( <> - - -
      - - {isFavorite ? 'Remove' : 'Add'} this query {isFavorite ? 'from' : 'to'} your - Favourites - -
      -
      + + + +
      + + {isFavorite ? 'Remove' : 'Add'} this query {isFavorite ? 'from' : 'to'} your + Favourites + +
      +
      +
      - - -
      - - You need additional permissions to generate a new JWT secret - -
      -
      + + + +
      + + You need additional permissions to generate a new JWT secret + +
      +
      +
      ) : ( = 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', default_pool_size: bouncerInfo.default_pool_size || '', ignore_startup_parameters: bouncerInfo.ignore_startup_parameters || '', @@ -153,11 +158,6 @@ export const PgbouncerConfig: FC = observer( const formSchema = { properties: { - pgbouncer_enabled: { - title: 'Enabled', - type: 'boolean', - help: 'Activates / deactivates Connection Pooling.', - }, pool_mode: { title: 'Pool Mode', type: 'string', @@ -170,16 +170,11 @@ export const PgbouncerConfig: FC = observer( label: 'Session', value: 'session', }, - { - label: 'Statement', - value: 'statement', - }, ], }, ignore_startup_parameters: { title: 'Ignore Startup Parameters', type: 'string', - readOnly: true, help: 'Defaults are either blank or "extra_float_digits"', }, }, @@ -198,18 +193,16 @@ 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" >
      - - - - - {updates.pgbouncer_enabled && ( + {bouncerInfo.pgbouncer_enabled && ( <>

      diff --git a/studio/components/interfaces/Settings/Database/DatabaseSettings/ResetDbPassword.tsx b/studio/components/interfaces/Settings/Database/DatabaseSettings/ResetDbPassword.tsx index 3d77f371349..c3d88b9915f 100644 --- a/studio/components/interfaces/Settings/Database/DatabaseSettings/ResetDbPassword.tsx +++ b/studio/components/interfaces/Settings/Database/DatabaseSettings/ResetDbPassword.tsx @@ -5,7 +5,8 @@ import generator from 'generate-password' import { Input, Button, Modal } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { checkPermissions, useParams, useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' +import { useParams } from 'common/hooks' import { patch } from 'lib/common/fetch' import { passwordStrength } from 'lib/helpers' import { API_URL, DEFAULT_MINIMUM_PASSWORD_STRENGTH } from 'lib/constants' @@ -107,19 +108,21 @@ const ResetDbPassword: FC = ({ disabled = false }) => { {!canResetDbPassword && ( - - -

      - - You need additional permissions to reset the database password - -
      - + + + +
      + + You need additional permissions to reset the database password + +
      +
      +
      )}
      diff --git a/studio/components/interfaces/Settings/Database/NetworkRestrictions/AddRestrictionModal.tsx b/studio/components/interfaces/Settings/Database/NetworkRestrictions/AddRestrictionModal.tsx index c2cfd9755f4..44fcec66a5f 100644 --- a/studio/components/interfaces/Settings/Database/NetworkRestrictions/AddRestrictionModal.tsx +++ b/studio/components/interfaces/Settings/Database/NetworkRestrictions/AddRestrictionModal.tsx @@ -3,7 +3,8 @@ import { Address4 } from 'ip-address' import { Button, Form, IconHelpCircle, Input, Modal } from 'ui' import * as Tooltip from '@radix-ui/react-tooltip' -import { useStore, useParams } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import InformationBox from 'components/ui/InformationBox' import { checkIfPrivate, getAddressEndRange } from './NetworkRestrictions.utils' import { useNetworkRestrictionsApplyMutation } from 'data/network-restrictions/network-retrictions-apply-mutation' @@ -154,22 +155,24 @@ const AddRestrictionModal: FC = ({ - - -
      - - Classless inter-domain routing (CIDR) notation is the notation - used to identify networks and hosts in the networks. The block - size tells us how many bits we need to take for the network - prefix, and is a value between 0 to 32. - -
      -
      + + + +
      + + Classless inter-domain routing (CIDR) notation is the notation + used to identify networks and hosts in the networks. The block + size tells us how many bits we need to take for the network + prefix, and is a value between 0 to 32. + +
      +
      +
      } diff --git a/studio/components/interfaces/Settings/Database/NetworkRestrictions/AllowAllModal.tsx b/studio/components/interfaces/Settings/Database/NetworkRestrictions/AllowAllModal.tsx index 084d12c3d89..880c176f9bb 100644 --- a/studio/components/interfaces/Settings/Database/NetworkRestrictions/AllowAllModal.tsx +++ b/studio/components/interfaces/Settings/Database/NetworkRestrictions/AllowAllModal.tsx @@ -1,7 +1,8 @@ import { FC, useState } from 'react' import { Modal, Button } from 'ui' -import { useStore, useParams } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import { useNetworkRestrictionsApplyMutation } from 'data/network-restrictions/network-retrictions-apply-mutation' interface Props { diff --git a/studio/components/interfaces/Settings/Database/NetworkRestrictions/DisallowAllModal.tsx b/studio/components/interfaces/Settings/Database/NetworkRestrictions/DisallowAllModal.tsx index 537d509515e..c843de5dcbc 100644 --- a/studio/components/interfaces/Settings/Database/NetworkRestrictions/DisallowAllModal.tsx +++ b/studio/components/interfaces/Settings/Database/NetworkRestrictions/DisallowAllModal.tsx @@ -1,7 +1,8 @@ import { FC, useState } from 'react' import { Modal, Button } from 'ui' -import { useStore, useParams } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import { useNetworkRestrictionsApplyMutation } from 'data/network-restrictions/network-retrictions-apply-mutation' import InformationBox from 'components/ui/InformationBox' diff --git a/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx b/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx index 7fa753067e7..31e465530f8 100644 --- a/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx +++ b/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx @@ -1,8 +1,11 @@ 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 } from 'hooks' +import { useParams } from 'common/hooks' import Panel from 'components/ui/Panel' import { FormPanel, FormHeader } from 'components/ui/Forms' import ShimmeringLoader from 'components/ui/ShimmeringLoader' @@ -12,6 +15,66 @@ 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,8 +84,10 @@ 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 restrictedIps = data?.config?.dbAllowedCidrs ?? [] const restrictionStatus = data?.status ?? '' const hasApplyError = restrictedIps.length === 0 && restrictionStatus === 'stored' @@ -48,7 +113,33 @@ const NetworkRestrictions = ({}) => { - + + + + + {!canUpdateNetworkRestrictions && ( + + + +
      + + You need additional permissions to update network restrictions + +
      +
      +
      + )} +
      {isLoading ? ( @@ -75,12 +166,14 @@ const NetworkRestrictions = ({}) => {

      - - + +
      @@ -101,9 +194,10 @@ const NetworkRestrictions = ({}) => {
      - +
      ) : isDisallowedAll ? ( @@ -125,9 +219,10 @@ const NetworkRestrictions = ({}) => {
      - +
      ) : ( @@ -146,12 +241,14 @@ const NetworkRestrictions = ({}) => {

      - - + +
      {restrictedIps.map((ip) => { diff --git a/studio/components/interfaces/Settings/Database/NetworkRestrictions/RemoveRestrictionModal.tsx b/studio/components/interfaces/Settings/Database/NetworkRestrictions/RemoveRestrictionModal.tsx index cd2a4f7f654..c0aa9253ea7 100644 --- a/studio/components/interfaces/Settings/Database/NetworkRestrictions/RemoveRestrictionModal.tsx +++ b/studio/components/interfaces/Settings/Database/NetworkRestrictions/RemoveRestrictionModal.tsx @@ -1,7 +1,8 @@ import { FC, useState } from 'react' import { Alert, Button, Form, Input, Modal } from 'ui' -import { useStore, useParams } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import { useNetworkRestrictionsApplyMutation } from 'data/network-restrictions/network-retrictions-apply-mutation' interface Props { diff --git a/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx b/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx index c276e186320..c764afb0a4a 100644 --- a/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx +++ b/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx @@ -1,8 +1,11 @@ import Link from 'next/link' import { useState, useEffect } from 'react' -import { useParams, useStore } from 'hooks' import * as Tooltip from '@radix-ui/react-tooltip' -import { Button, IconDownload, Toggle, IconExternalLink, IconLoader, Alert } from 'ui' +import { Button, IconDownload, Toggle, IconLoader, Alert } from 'ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { checkPermissions, useStore, useFlag } from 'hooks' +import { useParams } from 'common/hooks' import { FormHeader, FormPanel, @@ -19,12 +22,16 @@ const SSLConfiguration = () => { const { ref } = useParams() const [isEnforced, setIsEnforced] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) + const sslEnforcement = useFlag('sslEnforcement') + const { data: projectSettings } = useProjectSettingsQuery({ projectRef: ref }) const { data: sslEnforcementConfiguration, isLoading } = useSSLEnforcementQuery({ projectRef: ref, }) 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 = @@ -77,7 +84,7 @@ const SSLConfiguration = () => { */} - {hasAccessToSSLEnforcement && ( + {sslEnforcement && hasAccessToSSLEnforcement && ( { )} @@ -146,20 +153,22 @@ const SSLConfiguration = () => { {!hasSSLCertificate && ( - - -
      - - Projects before 15:08 (GMT+08), 29th April 2021 do not have SSL certificates - installed - -
      -
      + + + +
      + + Projects before 15:08 (GMT+08), 29th April 2021 do not have SSL certificates + installed + +
      +
      +
      )} diff --git a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig.tsx b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig.tsx index 261746b733d..9e6192fab4b 100644 --- a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig.tsx +++ b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig.tsx @@ -2,7 +2,8 @@ import Link from 'next/link' import { observer } from 'mobx-react-lite' import { IconAlertCircle } from 'ui' -import { useParams, useStore } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import { useProjectApiQuery } from 'data/config/project-api-query' import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' import Panel from 'components/ui/Panel' @@ -36,46 +37,42 @@ const CustomDomainConfig = () => { return (
      - {data?.status === '0_no_hostname_configured' ? ( + {isLoading ? ( + + + + + + ) : isError ? ( + + +
      + +

      + Failed to retrieve custom domain configuration. Please try again later or{' '} + + contact support + + . +

      +
      +
      +
      + ) : data?.status === '0_no_hostname_configured' ? ( + ) : data?.status === '0_not_allowed' ? ( + } + primaryText="Custom domains are a Pro plan add-on" + projectRef={ref as string} + secondaryText={ + tier === PRICING_TIER_PRODUCT_IDS.FREE + ? 'To configure a custom domain for your project, please upgrade to the Pro plan with the custom domains add-on selected' + : 'To configure a custom domain for your project, please enable the add-on' + } + /> ) : ( - {isLoading && ( - - - - )} - - {data?.status === '0_not_allowed' && ( - - } - primaryText="Custom domains are a Pro plan add-on" - projectRef={ref as string} - secondaryText={ - tier === PRICING_TIER_PRODUCT_IDS.FREE - ? 'To configure a custom domain for your project, please upgrade to the Pro plan with the custom domains add-on selected' - : 'To configure a custom domain for your project, please enable the add-on' - } - /> - - )} - - {isError && ( - -
      - -

      - Failed to retrieve custom domain configuration. Please try again later or{' '} - - contact support - - . -

      -
      -
      - )} - {isSuccess && (
      {(data.status === '2_initiated' || data.status === '3_challenge_verified') && ( diff --git a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainsConfigureHostname.tsx b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainsConfigureHostname.tsx index 398f62b9ec3..c41ae2dd05f 100644 --- a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainsConfigureHostname.tsx +++ b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainsConfigureHostname.tsx @@ -4,7 +4,8 @@ import { observer } from 'mobx-react-lite' import { Button, Form, IconExternalLink, Input } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { checkPermissions, useParams, useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' +import { useParams } from 'common/hooks' import { FormActions, FormPanel, diff --git a/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectButton.tsx b/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectButton.tsx index a316a3b1bf6..7a80c2d4b9f 100644 --- a/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectButton.tsx +++ b/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectButton.tsx @@ -81,9 +81,9 @@ const DeleteProjectButton: FC = ({ type = 'danger' }) => { if (!isFree) { const feedbackRes = await post(`${API_URL}/feedback/downgrade`, { projectRef, - reasons: selectedReasons.reduce((a, b) => `${a}- ${b}\n`, ''), - additionalFeedback: cancellationMessage, - exitAction: 'delete', + reasons: selectedReasons.reduce((a, b) => `${a}- ${b}\n`, ''), + additionalFeedback: cancellationMessage, + exitAction: 'delete', }) if (feedbackRes.error) throw feedbackRes.error } @@ -98,19 +98,21 @@ const DeleteProjectButton: FC = ({ type = 'danger' }) => { {!canDeleteProject && ( - - -
      - - You need additional permissions to delete this project - -
      -
      + + + +
      + + You need additional permissions to delete this project + +
      +
      +
      )} = ({}) => { const { ui } = useStore() + const { ref } = useParams() const project = ui.selectedProject - const isFreeProject = project?.subscription_tier === PRICING_TIER_PRODUCT_IDS.FREE + const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref }) + const isFreeProject = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.FREE + + const { data, isLoading } = useProjectUpgradeEligibilityQuery({ projectRef: ref }) + const { current_app_version, latest_app_version, requires_manual_intervention } = data || {} + const isOnLatestVersion = current_app_version === latest_app_version + const currentPgVersion = (current_app_version ?? '').split('supabase-postgres-')[1] + const latestPgVersion = (latest_app_version ?? '').split('supabase-postgres-')[1] + + const showDbUpgrades = useFlag('databaseUpgrades') + const subject = 'Request%20for%20Postgres%20upgrade%20for%20project' + const message = `Upgrade information:%0A• Manual intervention reason: ${requires_manual_intervention}` return (
      @@ -67,6 +85,65 @@ const Infrastructure: FC = ({}) => { + + Postgres}> + + + + + Latest + + + + + +
      + + Project is on the latest version of Postgres that Supabase supports + +
      +
      +
      + + ), + ]} + /> + {showDbUpgrades && data?.eligible && } + {showDbUpgrades && !data?.eligible && data?.requires_manual_intervention && ( + } + variant="neutral" + title="A new version of Postgres is available for your project" + > +

      + Please reach out to us via our support form if you are keen to upgrade your + Postgres version to the latest available ({latestPgVersion}). +

      + + + + + +
      + )} +
      +
      ) diff --git a/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx b/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx index 9c198671b9e..449c6f1fb13 100644 --- a/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx +++ b/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx @@ -73,31 +73,35 @@ const PauseProjectButton: FC = observer(({ projectRef, projectId }) => { {isPaused ? ( - - -
      - Your project is already paused -
      -
      + + + +
      + Your project is already paused +
      +
      +
      ) : !canPauseProject ? ( - - -
      - - You need additional permissions to pause this project - -
      -
      + + + +
      + + You need additional permissions to pause this project + +
      +
      +
      ) : ( <> )} diff --git a/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.constants.ts b/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.constants.ts new file mode 100644 index 00000000000..e36276103e7 --- /dev/null +++ b/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.constants.ts @@ -0,0 +1,19 @@ +// [Joshen] TODO: Change the keys to enums from shared-types when ready + +export const BREAKING_CHANGES = { + 'postgrest-v10': { + title: 'Postgrest will be upgraded to V10', + url: 'https://postgrest.org/en/stable/releases/v10.0.0.html#breaking-changes', + }, +} + +export const MANUAL_INTERVENTIONS = { + project_unsupported_too_old: { + title: 'Project is too old', + description: '', + }, + project_kps_enabled: { + title: 'Project has KPS enabled', + description: '', + }, +} diff --git a/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.tsx b/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.tsx new file mode 100644 index 00000000000..0af657cae0e --- /dev/null +++ b/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.tsx @@ -0,0 +1,180 @@ +import Link from 'next/link' +import { useRouter } from 'next/router' +import { FC, useState } from 'react' +import { + Alert, + Button, + Form, + Modal, + IconPackage, + Listbox, + IconAlertCircle, + IconExternalLink, +} from 'ui' + +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' +import { post } from 'lib/common/fetch' +import { API_ADMIN_URL, PROJECT_STATUS } from 'lib/constants' +import InformationBox from 'components/ui/InformationBox' +import { BREAKING_CHANGES } from './ProjectUpgradeAlert.constants' +import { useProjectUpgradeEligibilityQuery } from 'data/config/project-upgrade-eligibility-query' + +interface Props {} + +const ProjectUpgradeAlert: FC = ({}) => { + const router = useRouter() + const { app, ui } = useStore() + const { ref } = useParams() + const [showUpgradeModal, setShowUpgradeModal] = useState(false) + + const formId = 'project-upgrade-form' + const { data } = useProjectUpgradeEligibilityQuery({ projectRef: ref }) + const currentPgVersion = (data?.current_app_version ?? '').split('supabase-postgres-')[1] + const latestPgVersion = (data?.latest_app_version ?? '').split('supabase-postgres-')[1] + + const initialValues = { version: data?.target_upgrade_versions?.[0]?.postgres_version ?? 0 } + + const onConfirmUpgrade = async (values: any, { setSubmitting }: any) => { + setSubmitting(true) + + const res = await post(`${API_ADMIN_URL}/projects/${ref}/upgrade`, { + target_version: values.version, + }) + if (res.error) { + ui.setNotification({ + category: 'error', + error: res.error, + message: `Failed to upgrade: ${res.error.message}`, + }) + setSubmitting(false) + } else { + const projectId = ui.selectedProject?.id + if (projectId !== undefined) { + app.onProjectStatusUpdated(projectId, PROJECT_STATUS.UPGRADING) + ui.setNotification({ category: 'success', message: 'Upgrading project' }) + router.push(`/project/${ref}?upgradeInitiated=true`) + } + } + } + + return ( + <> + } + variant="success" + title="Your project can be upgraded to the latest version of Postgres" + > +

      + The latest version of Postgres ({latestPgVersion}) is available for your project. +

      + +
      + setShowUpgradeModal(false)} + header={

      Upgrade Postgres

      } + > +
      + {({ values, isSubmitting }: { values: any; isSubmitting: boolean }) => { + const selectedVersion = (data?.target_upgrade_versions ?? []).find( + (x) => x.postgres_version === values.version + ) + + return ( + <> +
      + +
      +

      + All services, including Auth, Rest, and Extensions will be upgraded. This + action cannot be undone. +

      + {(data?.potential_breaking_changes ?? []).length > 0 && ( + } + title="There will be breaking changes involved with the upgrade" + description={ +
        + {data?.potential_breaking_changes.map((reason) => { + const change = (BREAKING_CHANGES as any)[reason] + if (change !== undefined) + return ( +
      • +

        {change.title}

        +

        + This update has breaking changes. Read more + + + here + + + +

        +
      • + ) + else return null + })} +
      + } + /> + )} + + Upgrades will take a few minutes depending on the size of your database. It + is advised to upgrade at a time when there will be minimal impact for your + application + + + {data?.target_upgrade_versions.map((version) => ( + + {version.postgres_version} ( + {version.app_version.split('supabase-postgres-')[1]}) + + ))} + +
      +
      +
      +
      + + +
      + + ) + }} + +
      + + ) +} + +export default ProjectUpgradeAlert diff --git a/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/index.ts b/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/index.ts new file mode 100644 index 00000000000..74c03a0bc01 --- /dev/null +++ b/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/index.ts @@ -0,0 +1,2 @@ +import ProjectUpgradeAlert from './ProjectUpgradeAlert' +export default ProjectUpgradeAlert diff --git a/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx b/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx index adbc60926ea..8099afc7b63 100644 --- a/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx +++ b/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx @@ -122,19 +122,21 @@ const RestartServerButton: FC = ({ project }) => {
      {!canRestartProject && ( - - -
      - - You need additional permissions to restart this project - -
      -
      + + + +
      + + You need additional permissions to restart this project + +
      +
      +
      )} { const value = received.row?.[v] if (value && typeof value === 'object') { - return `[Object]` + return JSON.stringify(value) } else if (value === null) { return 'NULL' } else { @@ -163,6 +163,15 @@ const LogTable = ({ } }, [stringData]) + const RowRenderer = (props: RowRendererProps) => { + return ( + + ) + } + const LogsExplorerTableHeader = () => (
      @@ -282,6 +291,7 @@ const LogTable = ({ return row.id }} onRowClick={(r) => setFocusedLog(r)} + rowRenderer={RowRenderer} /> {logDataRows.length > 0 ? (
      = ({ 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..c1cb707456d 100644 --- a/studio/components/interfaces/Settings/Logs/Logs.constants.ts +++ b/studio/components/interfaces/Settings/Logs/Logs.constants.ts @@ -1,7 +1,9 @@ import dayjs from 'dayjs' +import { IS_PLATFORM } from 'lib/constants' 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 +202,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 +278,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 +303,7 @@ export const SQL_FILTER_TEMPLATES: any = { }, pgbouncer_logs: { ..._SQL_FILTER_COMMON, - } + }, } export enum LogsTableName { @@ -290,8 +314,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 +327,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 +589,7 @@ export const FILTER_OPTIONS: FilterTableSet = { }, { key: 'recover', - label: "Password Recovery", + label: 'Password Recovery', description: 'Show all password recovery requests', }, { @@ -585,7 +609,7 @@ export const FILTER_OPTIONS: FilterTableSet = { }, ], }, - } + }, } export const LOGS_TAILWIND_CLASSES = { @@ -642,3 +666,5 @@ export const TIER_QUERY_LIMITS: { TEAM: { text: '90 days', value: 90, unit: 'day', promptUpgrade: false }, ENTERPRISE: { text: '90 days', value: 90, unit: 'day', promptUpgrade: false }, } + +export const SHOW_O11Y = IS_PLATFORM ? true : process.env.NEXT_PUBLIC_ENABLE_LOGS == 'true' 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/Logs.utils.ts b/studio/components/interfaces/Settings/Logs/Logs.utils.ts index d039b002740..fa076635e7c 100644 --- a/studio/components/interfaces/Settings/Logs/Logs.utils.ts +++ b/studio/components/interfaces/Settings/Logs/Logs.utils.ts @@ -2,6 +2,11 @@ import { Filters, LogData, LogsEndpointParams, LogsTableName, SQL_FILTER_TEMPLAT import dayjs, { Dayjs } from 'dayjs' import { get } from 'lodash' import { StripeSubscription } from 'components/interfaces/Billing' +import { useMonaco } from '@monaco-editor/react' +import logConstants from 'shared-data/logConstants' +import BackwardIterator from 'components/to-be-cleaned/SqlEditor/BackwardIterator' +import { uniqBy } from 'lodash' +import { useEffect } from 'react' /** * Convert a micro timestamp from number/string to iso timestamp @@ -264,3 +269,83 @@ export const ensureNoTimestampConflict = ( return [nextStart, nextEnd] } } + +/** + * Adds SQL code hints to logs explorer code editor + */ +export const useEditorHints = () => { + const monaco = useMonaco() + + useEffect(() => { + if (monaco) { + const competionProvider = { + triggerCharacters: ['`', ' ', '.'], + provideCompletionItems: function (model: any, position: any, context: any) { + let iterator = new BackwardIterator(model, position.column - 2, position.lineNumber - 1) + if (iterator.isNextDQuote()) return { suggestions: [] } + let suggestions: { label: string; kind: any; insertText: string }[] = [] + + let schemasInUse = logConstants.schemas.filter((schema) => + iterator._text.includes(schema.reference) + ) + if (schemasInUse.length === 0) { + schemasInUse = logConstants.schemas + } + + if (iterator.isNextPeriod()) { + // should be nested key reference, suggest all tail endings of available fields + const fields = schemasInUse.flatMap((schema) => schema.fields) + const trailingKeys = fields.flatMap((field) => { + const [_head, ...rest] = field.path.split('.') + return rest + }) + + const trailingToAdd = trailingKeys.map((key) => ({ + label: key, + kind: monaco.languages.CompletionItemKind.Property, + insertText: key, + })) + suggestions = suggestions.concat(trailingToAdd) + } + + if (context.triggerCharacter === '`' || context.triggerCharacter === ' ') { + // should be reference or start of key + const referencesToAdd = logConstants.schemas.map((schema) => ({ + label: schema.reference, + kind: monaco.languages.CompletionItemKind.Class, + insertText: schema.reference, + })) + + const fields = schemasInUse.flatMap((schema) => schema.fields) + const leadingKeys = fields.flatMap((field) => { + const splitPath = field.path.split('.') + + return splitPath.slice(0, -1) + }) + + const leadingToAdd = leadingKeys.map((key) => ({ + label: key, + kind: monaco.languages.CompletionItemKind.Property, + insertText: key, + })) + suggestions = suggestions.concat(leadingToAdd) + suggestions = suggestions.concat(referencesToAdd) + } + return { + suggestions: uniqBy(suggestions, 'label'), + } + }, + } as any + + // register completion item provider for pgsql + const completeProvider = monaco.languages.registerCompletionItemProvider( + 'pgsql', + competionProvider + ) + + return () => { + completeProvider.dispose() + } + } + }, [monaco]) +} diff --git a/studio/components/interfaces/Settings/Logs/LogsErrorRenderers/ResourcesExceededErrorRenderer.tsx b/studio/components/interfaces/Settings/Logs/LogsErrorRenderers/ResourcesExceededErrorRenderer.tsx index 2473f64aa19..c8b76284adc 100644 --- a/studio/components/interfaces/Settings/Logs/LogsErrorRenderers/ResourcesExceededErrorRenderer.tsx +++ b/studio/components/interfaces/Settings/Logs/LogsErrorRenderers/ResourcesExceededErrorRenderer.tsx @@ -19,7 +19,6 @@ const ResourcesExceededErrorRenderer: React.FC = ({ error, i type="default" chevronAlign="left" size="small" - bordered={false} iconPosition="left" > diff --git a/studio/components/interfaces/Settings/Logs/LogsFilterPopover.tsx b/studio/components/interfaces/Settings/Logs/LogsFilterPopover.tsx index 94297f85347..d830bc030fe 100644 --- a/studio/components/interfaces/Settings/Logs/LogsFilterPopover.tsx +++ b/studio/components/interfaces/Settings/Logs/LogsFilterPopover.tsx @@ -77,7 +77,6 @@ const LogsFilterPopover: React.FC = ({ )} } - portalled showClose side="bottom" > diff --git a/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx b/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx index 543c32821d8..1d718bcfb33 100644 --- a/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx +++ b/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx @@ -22,7 +22,7 @@ import ShimmerLine from 'components/ui/ShimmerLine' import LoadingOpacity from 'components/ui/LoadingOpacity' import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt' import UpgradePrompt from './UpgradePrompt' -import { useParams } from 'hooks' +import { useParams } from 'common/hooks' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' /** diff --git a/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx b/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx index d2b6cfb387c..0168c0234b2 100644 --- a/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx +++ b/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx @@ -6,17 +6,17 @@ import { EXPLORER_DATEPICKER_HELPERS, LogsTableName, LogsWarning, - LOGS_EXPLORER_DOCS_URL, LOGS_SOURCE_DESCRIPTION, LogTemplate, } from '.' import DatePickers from './Logs.DatePickers' import Link from 'next/link' import React from 'react' -import { checkPermissions, useStore } from 'hooks' -import { IconBookOpen } from '@supabase/ui' +import { checkPermissions } from 'hooks' +import { useProfileQuery } from 'data/profile/profile-query' +import { IS_PLATFORM } from 'lib/constants' -interface Props { +export interface LogsQueryPanelProps { templates?: LogTemplate[] onSelectTemplate: (template: LogTemplate) => void onSelectSource: (source: LogsTableName) => void @@ -31,7 +31,7 @@ interface Props { warnings: LogsWarning[] } -const LogsQueryPanel: React.FC = ({ +const LogsQueryPanel = ({ templates = [], onSelectTemplate, hasEditorValue, @@ -44,18 +44,15 @@ const LogsQueryPanel: React.FC = ({ defaultTo, onDateChange, warnings, -}) => { - const { ui } = useStore() +}: LogsQueryPanelProps) => { + const { data: profile } = useProfileQuery() const canCreateLogQuery = checkPermissions(PermissionAction.CREATE, 'user_content', { - resource: { type: 'log_sql', owner_id: ui.profile?.id }, - subject: { id: ui.profile?.id }, + resource: { type: 'log_sql', owner_id: profile?.id }, + subject: { id: profile?.id }, }) return ( -
      +
      @@ -106,7 +103,6 @@ const LogsQueryPanel: React.FC = ({ }`} >
      @@ -132,15 +128,10 @@ const LogsQueryPanel: React.FC = ({
      - - - - {onSave && ( + {IS_PLATFORM && onSave && ( {!canCreateLogQuery && ( - - -
      - - You need additional permissions to save your query - -
      -
      + + + +
      + + You need additional permissions to save your query + +
      +
      +
      )}
      )} diff --git a/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx b/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx index a8025d482ce..b4ab97e9727 100644 --- a/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx +++ b/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx @@ -183,7 +183,7 @@ const PreviewFilterPanel: FC = ({
      -
      diff --git a/studio/components/interfaces/Settings/Logs/UpgradePrompt.tsx b/studio/components/interfaces/Settings/Logs/UpgradePrompt.tsx index f6351377d9d..431cfb6afe9 100644 --- a/studio/components/interfaces/Settings/Logs/UpgradePrompt.tsx +++ b/studio/components/interfaces/Settings/Logs/UpgradePrompt.tsx @@ -62,13 +62,14 @@ const UpgradePrompt: React.FC = ({ show, setShowUpgradePrompt }) => { -
      +
      - - - + + + +
      diff --git a/studio/components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars.tsx b/studio/components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars.tsx index a7d3134706e..a5439c7878b 100644 --- a/studio/components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars.tsx +++ b/studio/components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars.tsx @@ -1,16 +1,30 @@ +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, useFlag, 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' +import { getResourcesExceededLimits } from 'components/ui/OveragesBanner/OveragesBanner.utils' interface Props { projectRef?: string @@ -21,13 +35,27 @@ 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 overusageBadgeEnabled = useFlag('overusageBadge') + + 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 || subscriptionTier === PRICING_TIER_PRODUCT_IDS.ENTERPRISE + const resourcesExceededLimits = getResourcesExceededLimits(usage) + const showUsageExceedMessage = subscriptionTier !== undefined && !projectHasNoLimits const planNames = { @@ -62,13 +90,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 ( {usage && (
      - {USAGE_BASED_PRODUCTS.map((product) => { - const isExceededUsage = - showUsageExceedMessage && - product.features - .map((feature) => { - const featureUsage = usage[feature.key as ProjectUsageResponseUsageKeys] - return (featureUsage.usage ?? 0) / featureUsage.limit > 1 - }) - .some((x) => x === true) + {resourcesExceededLimits.length > 0 && + showUsageExceedMessage && + overusageBadgeEnabled && ( +
      + } + title="You are exceeding your plans quota" + description={ +
      + {subscriptionTier === PRICING_TIER_PRODUCT_IDS.FREE ? ( +
      +

      + Your project is currently on the Free tier - upgrade to the Pro tier for + a greatly increased quota and continue to scale. +

      +

      + See{' '} + + pricing page + {' '} + for a full breakdown of available plans. +

      + + + + + +
      + ) : ( +
      +

      + By default, Pro projects have spend caps to control costs. When enabled, + usage is limited to the plan's quota, with restrictions when limits are + exceeded. To scale beyond Pro limits without restrictions, disable the + spend cap and pay for over-usage beyond the quota. +

      + + + + + +
      + )} +
      + } + /> +
      + )} + {USAGE_BASED_PRODUCTS.map((product) => { return (
      = ({ projectRef }) => {
      {product.title}
      - {/* Plan Limits */} -
      - {/* Usage */} - - {/* Line items */} {usage === undefined ? (
      @@ -148,14 +210,16 @@ const ProjectUsage: FC = ({ projectRef }) => { usageElement = (
      Not included in {planName} tier - + {canUpdateSubscription && ( + + )}
      ) } else if (showUsageExceedMessage) { @@ -203,21 +267,23 @@ const ProjectUsage: FC = ({ projectRef }) => { - - -
      - - {feature.tooltip} - -
      -
      + + + +
      + + {feature.tooltip} + +
      +
      +
      )} @@ -253,6 +319,121 @@ const ProjectUsage: FC = ({ projectRef }) => {
      )}
      - {isExceededUsage && Exceeded usage} -
      + + {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..135381a6600 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,14 @@ 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 { useStore, checkPermissions } from 'hooks' +import { useParams } from 'common/hooks' import Divider from 'components/ui/Divider' const DEFAULT_KEY_NAME = 'No description provided' @@ -34,6 +36,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 +147,34 @@ const EncryptionKeysManagement: FC = ({}) => { - + + + + + {!canManageKeys && ( + + + +
      + + You need additional permissions to add keys + +
      +
      +
      + )} +
      @@ -178,12 +207,34 @@ const EncryptionKeysManagement: FC = ({}) => {

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

      -
      {idx !== keys.length - 1 && } diff --git a/studio/components/interfaces/Settings/Vault/Secrets/SecretRow.tsx b/studio/components/interfaces/Settings/Vault/Secrets/SecretRow.tsx index 412435028c3..1b17adc55e7 100644 --- a/studio/components/interfaces/Settings/Vault/Secrets/SecretRow.tsx +++ b/studio/components/interfaces/Settings/Vault/Secrets/SecretRow.tsx @@ -1,3 +1,4 @@ +import Link from 'next/link' import dayjs from 'dayjs' import { FC, useState } from 'react' import { @@ -12,9 +13,11 @@ import { IconMoreVertical, IconLoader, } from 'ui' +import * as Tooltip from '@radix-ui/react-tooltip' +import { PermissionAction } from '@supabase/shared-types/out/constants' import { VaultSecret } from 'types' -import { useParams, useStore } from 'hooks' -import Link from 'next/link' +import { useStore, checkPermissions } from 'hooks' +import { useParams } from 'common/hooks' interface Props { secret: VaultSecret @@ -30,6 +33,8 @@ const SecretRow: FC = ({ secret, onSelectEdit, onSelectRemove }) => { const [revealedValue, setRevealedValue] = useState() const name = secret?.name ?? 'No name provided' + const canManageSecrets = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') + const revealSecret = async () => { setIsLoading(true) if (revealedValue === undefined) { @@ -106,15 +111,62 @@ const SecretRow: FC = ({ secret, onSelectEdit, onSelectRemove }) => { className="w-[120px]" overlay={ <> - } onClick={() => onSelectEdit(secret)}> - Edit - - } - onClick={() => onSelectRemove(secret)} - > - Delete - + + + } + disabled={!canManageSecrets} + onClick={() => onSelectEdit(secret)} + > + Edit + + + {!canManageSecrets && ( + + + +
      + + You need additional permissions to edit secrets + +
      +
      +
      + )} +
      + + + } + onClick={() => onSelectRemove(secret)} + > + Delete + + + {!canManageSecrets && ( + + + +
      + + You need additional permissions to delete secrets + +
      +
      +
      + )} +
      } > diff --git a/studio/components/interfaces/Settings/Vault/Secrets/SecretsManagement.tsx b/studio/components/interfaces/Settings/Vault/Secrets/SecretsManagement.tsx index 468773ef358..614e6e09b19 100644 --- a/studio/components/interfaces/Settings/Vault/Secrets/SecretsManagement.tsx +++ b/studio/components/interfaces/Settings/Vault/Secrets/SecretsManagement.tsx @@ -1,9 +1,12 @@ import Link from 'next/link' import { observer } from 'mobx-react-lite' +import * as Tooltip from '@radix-ui/react-tooltip' import { FC, Fragment, useState, useEffect } from 'react' import { IconSearch, Input, Button, Listbox, IconLoader, IconExternalLink, IconX } from 'ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useStore, useParams } from 'hooks' +import { useStore, checkPermissions } from 'hooks' +import { useParams } from 'common/hooks' import SecretRow from './SecretRow' import EditSecretModal from './EditSecretModal' import DeleteSecretModal from './DeleteSecretModal' @@ -22,6 +25,8 @@ const SecretsManagement: FC = ({}) => { const [selectedSecretToEdit, setSelectedSecretToEdit] = useState() const [selectedSecretToRemove, setSelectedSecretToRemove] = useState() + const canManageSecrets = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') + useEffect(() => { if (search !== undefined) setSearchValue(search) }, [search]) @@ -105,9 +110,34 @@ const SecretsManagement: FC = ({}) => { - + + + + + {!canManageSecrets && ( + + + +
      + + You need additional permissions to add secrets + +
      +
      +
      + )} +
      diff --git a/studio/components/interfaces/Settings/Vault/VaultToggle.tsx b/studio/components/interfaces/Settings/Vault/VaultToggle.tsx index 1b6647afd05..510e6d66e70 100644 --- a/studio/components/interfaces/Settings/Vault/VaultToggle.tsx +++ b/studio/components/interfaces/Settings/Vault/VaultToggle.tsx @@ -5,7 +5,9 @@ import * as Tooltip from '@radix-ui/react-tooltip' import { Button, IconExternalLink } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { checkPermissions, useParams, useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' +import { useParams } from 'common/hooks' +import { BASE_PATH } from 'lib/constants' interface Props {} @@ -63,8 +65,8 @@ const VaultToggle: FC = () => { backgroundSize: isNotAvailable ? '50%' : '40%', backgroundPosition: '100% 24%', backgroundImage: ui.isDarkTheme - ? 'url("/img/vault-dark.png")' - : 'url("/img/vault-light.png")', + ? `url("${BASE_PATH}/img/vault-dark.png")` + : `url("${BASE_PATH}/img/vault-light.png")`, }} >
      @@ -127,19 +129,21 @@ const VaultToggle: FC = () => { {!canToggleVault && ( - - -
      - - You need additional permissions to enable Vault for this project - -
      -
      + + + +
      + + You need additional permissions to enable Vault for this project + +
      +
      +
      )}
      diff --git a/studio/components/interfaces/SignIn/ForgotPasswordForm.tsx b/studio/components/interfaces/SignIn/ForgotPasswordForm.tsx index 3cdcde52d25..309b739a03e 100644 --- a/studio/components/interfaces/SignIn/ForgotPasswordForm.tsx +++ b/studio/components/interfaces/SignIn/ForgotPasswordForm.tsx @@ -1,7 +1,7 @@ import HCaptcha from '@hcaptcha/react-hcaptcha' import { useStore } from 'hooks' import { post } from 'lib/common/fetch' -import { API_URL } from 'lib/constants' +import { API_URL, BASE_PATH } from 'lib/constants' import { useRouter } from 'next/router' import { useRef, useState } from 'react' import { Button, Form, Input } from 'ui' @@ -35,9 +35,9 @@ const ForgotPasswordForm = () => { hcaptchaToken: token ?? undefined, redirectTo: `${ process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview' - ? process.env.NEXT_PUBLIC_VERCEL_URL + ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}` : process.env.NEXT_PUBLIC_SITE_URL - }/reset-password`, + }${BASE_PATH}/reset-password`, }) const error = response.error @@ -45,7 +45,7 @@ const ForgotPasswordForm = () => { ui.setNotification({ id: toastId, category: 'success', - message: `Password reset email sent successfully! Please check your email`, + message: `If you registered using your email and password, you will receive a password reset email.`, }) await router.push('/sign-in') @@ -71,7 +71,7 @@ const ForgotPasswordForm = () => { > {({ isSubmitting }: { isSubmitting: boolean }) => { return ( -
      +
      { />
      -
      +
      - + + +
      diff --git a/studio/components/interfaces/Support/SupportForm.tsx b/studio/components/interfaces/Support/SupportForm.tsx index 24ec9a1f014..34dff31cf3f 100644 --- a/studio/components/interfaces/Support/SupportForm.tsx +++ b/studio/components/interfaces/Support/SupportForm.tsx @@ -40,7 +40,7 @@ interface Props { const SupportForm: FC = ({ setSentCategory }) => { const { ui, app } = useStore() const router = useRouter() - const { ref, subject, category } = router.query + const { ref, subject, category, message } = router.query const uploadButtonRef = useRef() const enableFreeSupport = useFlag('enableFreeSupport') @@ -108,7 +108,7 @@ const SupportForm: FC = ({ setSentCategory }) => { organizationSlug: initialOrganizationSlug, library: 'no-library', subject: subject ?? '', - message: '', + message: message || '', allowSupportAccess: false, } diff --git a/studio/components/interfaces/TableGridEditor/APIDocumentationPanel.tsx b/studio/components/interfaces/TableGridEditor/APIDocumentationPanel.tsx new file mode 100644 index 00000000000..4565ba6ee28 --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/APIDocumentationPanel.tsx @@ -0,0 +1,143 @@ +import { useState } from 'react' +import { SidePanel, IconBookOpen } from 'ui' + +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' +import { GeneralContent, ResourceContent } from '../Docs' +import LangSelector from '../Docs/LangSelector' +import GeneratingTypes from '../Docs/GeneratingTypes' +import ActionBar from './SidePanelEditor/ActionBar' +import { useProjectApiQuery } from 'data/config/project-api-query' +import { useProjectJsonSchemaQuery } from 'data/docs/project-json-schema-query' +import { snakeToCamel } from 'lib/helpers' + +interface APIDocumentationPanelProps { + visible: boolean + onClose: () => void +} + +const APIDocumentationPanel = ({ visible, onClose }: APIDocumentationPanelProps) => { + const { meta } = useStore() + const { ref, page, id } = useParams() + const { + data: jsonSchema, + error: jsonSchemaError, + refetch, + } = useProjectJsonSchemaQuery({ projectRef: ref }) + const { data: settings } = useProjectApiQuery({ projectRef: ref }) + + const DEFAULT_KEY = { name: 'hide', key: 'SUPABASE_KEY' } + const [selectedLang, setSelectedLang] = useState('js') + const [showApiKey, setShowApiKey] = useState(DEFAULT_KEY) + + const tables = meta.tables.list() + const autoApiService = { + ...settings?.autoApiService, + endpoint: `${settings?.autoApiService.protocol ?? 'https'}://${ + settings?.autoApiService.endpoint ?? '-' + }`, + } + const apiService = settings?.autoApiService + const anonKey = apiService?.service_api_keys.find((x) => x.name === 'anon key') + ? apiService.defaultApiKey + : undefined + const resources = getResourcesFromJsonSchema(jsonSchema) + + function getResourcesFromJsonSchema(value: any) { + const { paths } = value || {} + const functionPath = 'rpc/' + let resources: any = {} + + Object.entries(paths || []).forEach(([name, val]) => { + let trimmed = name.slice(1) + let id = trimmed.replace(functionPath, '') + let displayName = id.replace(/_/g, ' ') + let camelCase = snakeToCamel(id) + let enriched = { id, displayName, camelCase } + if (!trimmed.length) return + else resources[id] = enriched + }) + + return resources + } + + return ( + onClose()} + header={ + + + API + + } + customFooter={ + onClose()} + /> + } + > +
      + + {jsonSchemaError ? ( +
      +
      +

      Error connecting to API

      +

      {`${jsonSchemaError}`}

      +
      +
      + ) : ( + <> + {jsonSchema ? ( + <> +
      + +
      + + + + + {jsonSchema?.definitions && ( + table.id === Number(id))?.name} + resources={resources} + definitions={jsonSchema.definitions} + paths={jsonSchema.paths} + showApiKey={showApiKey.key} + refreshDocs={async () => await refetch()} + /> + )} + + ) : ( +
      +

      Building docs ...

      +
      + )} + + )} +
      +
      +
      + ) +} + +export default APIDocumentationPanel diff --git a/studio/components/interfaces/TableGridEditor/EmptyState.tsx b/studio/components/interfaces/TableGridEditor/EmptyState.tsx index 5b2b3b1d644..3667bf1e4ef 100644 --- a/studio/components/interfaces/TableGridEditor/EmptyState.tsx +++ b/studio/components/interfaces/TableGridEditor/EmptyState.tsx @@ -1,9 +1,10 @@ import { FC } from 'react' import { observer } from 'mobx-react-lite' -import type { PostgresTable } from '@supabase/postgres-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { checkPermissions, useStore } from 'hooks' +import { checkPermissions, useLocalStorage, useStore } from 'hooks' +import { useEntityTypesQuery } from 'data/entity-types/entity-types-infinite-query' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' interface Props { selectedSchema: string @@ -12,14 +13,33 @@ interface Props { const EmptyState: FC = ({ selectedSchema, onAddTable }) => { const { meta } = useStore() - const tables = meta.tables.list((table: PostgresTable) => table.schema === selectedSchema) const isProtectedSchema = meta.excludedSchemas.includes(selectedSchema) const canCreateTables = !isProtectedSchema && checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') + const [sort] = useLocalStorage<'alphabetical' | 'grouped-alphabetical'>( + 'table-editor-sort', + 'alphabetical' + ) + + const { project } = useProjectContext() + const { data } = useEntityTypesQuery( + { + projectRef: project?.ref, + connectionString: project?.connectionString, + schema: selectedSchema, + sort, + }, + { + keepPreviousData: true, + } + ) + + const totalCount = data?.pages?.[0].data.count ?? 0 + return (
      - {tables.length === 0 ? ( + {totalCount === 0 ? ( 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 + +
      +
      +
      +
      + )} +
      + +
      +
      @@ -254,10 +273,10 @@ const ColumnEditor: FC = ({ layout="vertical" enumTypes={enumTypes} error={errors.format} - disabled={!isUndefined(columnFields?.foreignKey)} + disabled={columnFields?.foreignKey !== undefined} onOptionSelect={(format: string) => onUpdateField({ format, defaultValue: null })} /> - {isUndefined(columnFields.foreignKey) && ( + {columnFields.foreignKey === undefined && (
      {columnFields.format.includes('int') && (
      diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts index 2467d36a736..dfc6a08c90b 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts @@ -8,7 +8,14 @@ import type { } from '@supabase/postgres-meta' import { uuidv4 } from 'lib/helpers' -import { ColumnField, CreateColumnPayload, UpdateColumnPayload } from '../SidePanelEditor.types' +import { + ColumnField, + CreateColumnPayload, + ExtendedPostgresRelationship, + UpdateColumnPayload, +} from '../SidePanelEditor.types' +import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants' +import { ForeignKeyConstraint } from 'data/database/foreign-key-constraints-query' const isSQLExpression = (input: string) => { if (['CURRENT_DATE'].includes(input)) return true @@ -49,12 +56,13 @@ export const generateColumnField = (field: any = {}): ColumnField => { export const generateColumnFieldFromPostgresColumn = ( column: PostgresColumn, - table: PostgresTable + table: PostgresTable, + foreignKeys: ForeignKeyConstraint[] ): ColumnField => { const { primary_keys } = table // @ts-ignore const primaryKeyColumns = primary_keys.map((key) => key.name) - const foreignKey = getColumnForeignKey(column, table) + const foreignKey = getColumnForeignKey(column, table, foreignKeys) const isArray = column?.data_type === 'ARRAY' return { @@ -175,8 +183,8 @@ export const validateFields = (field: ColumnField) => { } export const getForeignKeyUIState = ( - originalConfig: PostgresRelationship | undefined, - updatedConfig: PostgresRelationship | undefined + originalConfig: ExtendedPostgresRelationship | undefined, + updatedConfig: ExtendedPostgresRelationship | undefined ): 'Info' | 'Add' | 'Remove' | 'Update' => { if (isUndefined(originalConfig) && !isUndefined(updatedConfig)) { return 'Add' @@ -189,7 +197,8 @@ export const getForeignKeyUIState = ( if ( !isEqual(originalConfig?.target_table_schema, updatedConfig?.target_table_schema) || !isEqual(originalConfig?.target_table_name, updatedConfig?.target_table_name) || - !isEqual(originalConfig?.target_column_name, updatedConfig?.target_column_name) + !isEqual(originalConfig?.target_column_name, updatedConfig?.target_column_name) || + originalConfig?.deletion_action !== updatedConfig?.deletion_action ) { return 'Update' } @@ -197,15 +206,27 @@ export const getForeignKeyUIState = ( return 'Info' } -export const getColumnForeignKey = (column: PostgresColumn, table: PostgresTable) => { +export const getColumnForeignKey = ( + column: PostgresColumn, + table: PostgresTable, + foreignKeys: ForeignKeyConstraint[] +) => { const { relationships } = table - return find(relationships, (relationship) => { + const foreignKey = find(relationships, (relationship) => { return ( relationship.source_schema === column.schema && relationship.source_table_name === column.table && relationship.source_column_name === column.name ) }) + if (foreignKey === undefined) return foreignKey + else { + const foreignKeyMeta = foreignKeys.find((fk) => fk.id === foreignKey.id) + return { + ...foreignKey, + deletion_action: foreignKeyMeta?.deletion_action ?? FOREIGN_KEY_DELETION_ACTION.NO_ACTION, + } + } } // Assumes arrayString is a stringified array (e.g "[1, 2, 3]") @@ -213,3 +234,18 @@ const formatArrayToPostgresArray = (arrayString: string) => { if (!arrayString) return null return arrayString.replaceAll('[', '{').replaceAll(']', '}') } + +export const getForeignKeyDeletionAction = (deletionAction?: string) => { + switch (deletionAction) { + case FOREIGN_KEY_DELETION_ACTION.CASCADE: + return 'Cascade' + case FOREIGN_KEY_DELETION_ACTION.RESTRICT: + return 'Restrict' + case FOREIGN_KEY_DELETION_ACTION.SET_DEFAULT: + return 'Set default' + case FOREIGN_KEY_DELETION_ACTION.SET_NULL: + return 'Set NULL' + default: + return undefined + } +} diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx index 50d0778c246..df57a2cf8f2 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx @@ -1,17 +1,18 @@ import { FC } from 'react' import { isUndefined } from 'lodash' -import { Button, IconArrowRight } from 'ui' +import { Badge, Button, IconArrowRight } from 'ui' import { ColumnField } from '../SidePanelEditor.types' import InformationBox from 'components/ui/InformationBox' -import { getForeignKeyUIState } from './ColumnEditor.utils' -import type { PostgresRelationship } from '@supabase/postgres-meta' +import { getForeignKeyDeletionAction, getForeignKeyUIState } from './ColumnEditor.utils' +import type { ExtendedPostgresRelationship } from '../SidePanelEditor.types' interface Props { column: ColumnField - originalForeignKey: PostgresRelationship | undefined + originalForeignKey: ExtendedPostgresRelationship | undefined onSelectEditRelation: () => void onSelectRemoveRelation: () => void + onSelectCancelRemoveRelation: () => void } const ColumnForeignKey: FC = ({ @@ -19,6 +20,7 @@ const ColumnForeignKey: FC = ({ originalForeignKey, onSelectEditRelation = () => {}, onSelectRemoveRelation = () => {}, + onSelectCancelRemoveRelation, }) => { const hasNoForeignKey = isUndefined(originalForeignKey) && isUndefined(column?.foreignKey) if (hasNoForeignKey) { @@ -47,6 +49,7 @@ const ColumnForeignKey: FC = ({ columnName={column.name} originalForeignKey={originalForeignKey} onSelectEditRelation={onSelectEditRelation} + onSelectCancelRemoveRelation={onSelectCancelRemoveRelation} /> ) case 'Update': @@ -79,24 +82,28 @@ export default ColumnForeignKey const ColumnForeignKeyInformation: FC<{ columnName: string - foreignKey?: PostgresRelationship + foreignKey?: ExtendedPostgresRelationship onSelectEditRelation: () => void onSelectRemoveRelation: () => void }> = ({ columnName, foreignKey, onSelectEditRelation, onSelectRemoveRelation }) => { + const deletionAction = getForeignKeyDeletionAction(foreignKey?.deletion_action) return ( +
      - This column has the following foreign key relation: -
      - {columnName} +

      This column has the following foreign key relation:

      +
      + {columnName} - + {foreignKey?.target_table_schema}.{foreignKey?.target_table_name}. {foreignKey?.target_column_name} + {deletionAction !== undefined && ( + On delete: {deletionAction} + )}
      @@ -115,10 +122,11 @@ const ColumnForeignKeyInformation: FC<{ const ColumnForeignKeyAdded: FC<{ columnName: string - foreignKey?: PostgresRelationship + foreignKey?: ExtendedPostgresRelationship onSelectEditRelation: () => void onSelectRemoveRelation: () => void }> = ({ columnName, foreignKey, onSelectEditRelation, onSelectRemoveRelation }) => { + const deletionAction = getForeignKeyDeletionAction(foreignKey?.deletion_action) return ( + {deletionAction !== undefined && ( + On delete: {deletionAction} + )}
      @@ -160,23 +171,24 @@ const ColumnForeignKeyAdded: FC<{ const ColumnForeignKeyRemoved: FC<{ columnName: string - originalForeignKey?: PostgresRelationship + originalForeignKey?: ExtendedPostgresRelationship onSelectEditRelation: () => void -}> = ({ columnName, originalForeignKey, onSelectEditRelation }) => { + onSelectCancelRemoveRelation: () => void +}> = ({ columnName, originalForeignKey, onSelectEditRelation, onSelectCancelRemoveRelation }) => { return (
      -

      +

      The following foreign key relation will be{' '} - removed from this column: + removed:

      - {columnName} + {columnName} - + {originalForeignKey?.target_table_schema}.{originalForeignKey?.target_table_name}. {originalForeignKey?.target_column_name} @@ -186,6 +198,9 @@ const ColumnForeignKeyRemoved: FC<{ +
      } @@ -195,8 +210,8 @@ const ColumnForeignKeyRemoved: FC<{ const ColumnForeignKeyUpdated: FC<{ columnName: string - originalForeignKey?: PostgresRelationship - updatedForeignKey?: PostgresRelationship + originalForeignKey?: ExtendedPostgresRelationship + updatedForeignKey?: ExtendedPostgresRelationship onSelectEditRelation: () => void onSelectRemoveRelation: () => void }> = ({ @@ -206,6 +221,12 @@ const ColumnForeignKeyUpdated: FC<{ onSelectEditRelation, onSelectRemoveRelation, }) => { + const originalKey = `${originalForeignKey?.target_table_schema}.${originalForeignKey?.target_table_name}.${originalForeignKey?.target_column_name}` + const updatedKey = `${updatedForeignKey?.target_table_schema}.${updatedForeignKey?.target_table_name}.${updatedForeignKey?.target_column_name}` + + const originalDeletionAction = getForeignKeyDeletionAction(originalForeignKey?.deletion_action) + const updatedDeletionAction = getForeignKeyDeletionAction(updatedForeignKey?.deletion_action) + return (
      - {columnName} + {columnName}
      -

      - - {originalForeignKey?.target_table_schema}. - {originalForeignKey?.target_table_name}.{originalForeignKey?.target_column_name} - -

      - - {updatedForeignKey?.target_table_schema}.{updatedForeignKey?.target_table_name}. - {updatedForeignKey?.target_column_name} - + {originalKey !== updatedKey && ( +

      + {originalKey} +

      + )} + {updatedKey} +
      +
      + {originalDeletionAction !== undefined && + originalDeletionAction !== updatedDeletionAction && ( + + On delete: {originalDeletionAction} + + )} + {updatedDeletionAction !== undefined && ( +
      + On delete: {updatedDeletionAction} +
      + )}
      diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx index 59da15bb67b..af4232ea154 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx @@ -112,19 +112,21 @@ const InputWithSuggestions: FC = ({ icon={} /> - - -
      - - {suggestionsTooltip || 'Suggestions'} - -
      -
      + + + +
      + + {suggestionsTooltip || 'Suggestions'} + +
      +
      +
      ) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.constants.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.constants.ts new file mode 100644 index 00000000000..411938bbc4e --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.constants.ts @@ -0,0 +1,9 @@ +import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants' + +export const FOREIGN_KEY_DELETION_OPTIONS = [ + { key: 'no-action', label: 'No action', value: FOREIGN_KEY_DELETION_ACTION.NO_ACTION }, + { key: 'cascade', label: 'Cascade', value: FOREIGN_KEY_DELETION_ACTION.CASCADE }, + { key: 'restrict', label: 'Restrict', value: FOREIGN_KEY_DELETION_ACTION.RESTRICT }, + { key: 'set-default', label: 'Set default', value: FOREIGN_KEY_DELETION_ACTION.SET_DEFAULT }, + { key: 'set-null', label: 'Set NULL', value: FOREIGN_KEY_DELETION_ACTION.SET_NULL }, +] diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx index 823ad2382d4..bcf7dac9cd0 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx @@ -1,7 +1,7 @@ import React, { FC, useEffect, useState } from 'react' import { get, find, isEmpty, sortBy } from 'lodash' import { Dictionary } from 'components/grid' -import { SidePanel, Input, Listbox, IconHelpCircle, IconDatabase } from 'ui' +import { SidePanel, Input, Listbox, IconHelpCircle, IconDatabase, Toggle } from 'ui' import type { PostgresTable, PostgresColumn, PostgresSchema } from '@supabase/postgres-meta' import { useStore } from 'hooks' @@ -9,13 +9,18 @@ import ActionBar from '../ActionBar' import { ForeignKey } from './ForeignKeySelector.types' import { ColumnField } from '../SidePanelEditor.types' import InformationBox from 'components/ui/InformationBox' +import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants' +import { FOREIGN_KEY_DELETION_OPTIONS } from './ForeignKeySelector.constants' +import { generateDeletionActionDescription } from './ForeignKeySelector.utils' interface Props { column: ColumnField metadata?: any visible: boolean closePanel: () => void - saveChanges: (value: { table: PostgresTable; column: PostgresColumn } | undefined) => void + saveChanges: ( + value: { table: PostgresTable; column: PostgresColumn; deletionAction: string } | undefined + ) => void } const ForeignKeySelector: FC = ({ column, visible = false, closePanel, saveChanges }) => { @@ -25,6 +30,7 @@ const ForeignKeySelector: FC = ({ column, visible = false, closePanel, sa schema: 'public', table: '', column: '', + deletionAction: FOREIGN_KEY_DELETION_ACTION.NO_ACTION, }) const schemas = meta.schemas.list() @@ -51,12 +57,14 @@ const ForeignKeySelector: FC = ({ column, visible = false, closePanel, sa schema: foreignKey.target_table_schema, table: foreignKey.target_table_name, column: foreignKey.target_column_name, + deletionAction: foreignKey.deletion_action, }) } else { setSelectedForeignKey({ schema: 'public', table: '', column: '', + deletionAction: FOREIGN_KEY_DELETION_ACTION.NO_ACTION, }) } } @@ -64,7 +72,12 @@ const ForeignKeySelector: FC = ({ column, visible = false, closePanel, sa const updateSelectedSchema = (schema: string) => { meta.tables.loadBySchema(schema) - const updatedForeignKey = { ...selectedForeignKey, schema, table: '', column: '' } as ForeignKey + const updatedForeignKey = { + schema, + table: '', + column: '', + deletionAction: FOREIGN_KEY_DELETION_ACTION.NO_ACTION, + } setSelectedForeignKey(updatedForeignKey) } @@ -75,6 +88,7 @@ const ForeignKeySelector: FC = ({ column, visible = false, closePanel, sa schema: '', table: '', column: '', + deletionAction: FOREIGN_KEY_DELETION_ACTION.NO_ACTION, }) } const table = find(tables, { id: tableId }) @@ -83,6 +97,7 @@ const ForeignKeySelector: FC = ({ column, visible = false, closePanel, sa schema: table.schema, table: table.name, column: table.columns?.length ? table.columns[0].name : undefined, + deletionAction: FOREIGN_KEY_DELETION_ACTION.NO_ACTION, }) } } @@ -96,6 +111,11 @@ const ForeignKeySelector: FC = ({ column, visible = false, closePanel, sa } } + const updateDeletionAction = (value: string) => { + setErrors({}) + setSelectedForeignKey({ ...selectedForeignKey, deletionAction: value }) + } + const onSaveChanges = (resolve: () => void) => { const errors = {} as Dictionary if (!selectedForeignKey?.table) { @@ -110,7 +130,11 @@ const ForeignKeySelector: FC = ({ column, visible = false, closePanel, sa // Remove foreign key since no table selected saveChanges(undefined) } else { - saveChanges({ table: selectedTable, column: selectedColumn }) + saveChanges({ + table: selectedTable, + column: selectedColumn, + deletionAction: selectedForeignKey.deletionAction, + }) } } resolve() @@ -211,7 +235,11 @@ const ForeignKeySelector: FC = ({ column, visible = false, closePanel, sa disabled label={
      - Select a column from {selectedForeignKey?.table} to reference to + Select a column from{' '} + + {selectedForeignKey?.schema}.{selectedForeignKey?.table} + {' '} + to reference to
      } error={errors.column} @@ -224,7 +252,11 @@ const ForeignKeySelector: FC = ({ column, visible = false, closePanel, sa // @ts-ignore label={
      - Select a column from {selectedForeignKey?.table} to reference to + Select a column from{' '} + + {selectedForeignKey?.schema}.{selectedForeignKey?.table} + {' '} + to reference to
      } error={errors.column} @@ -240,6 +272,60 @@ const ForeignKeySelector: FC = ({ column, visible = false, closePanel, sa ))} )} + + } + title="Which action is most appropriate?" + description={ + <> +

      + The choice of the action depends on what kinds of objects the related tables + represent: +

      +
        +
      • + Cascade: if the referencing table + represents something that is a component of what is represented by the + referenced table and cannot exist independently +
      • +
      • + Restrict or{' '} + No action: if the two tables represent + independent objects +
      • +
      • + Set NULL or{' '} + Set default: if a foreign-key relationship + represents optional information +
      • +
      +

      + Typically, restricting and cascading deletes are the most common options, but + the default behavior is no action +

      + + } + url="https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-FK" + urlLabel="More information" + /> + updateDeletionAction(value)} + > + {FOREIGN_KEY_DELETION_OPTIONS.map((option) => ( + +

      {option.label}

      +
      + ))} +
      )}
      diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.types.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.types.ts index a5a73bf717f..3451d63707f 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.types.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.types.ts @@ -2,4 +2,5 @@ export interface ForeignKey { schema: string table: string column?: string + deletionAction: string } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.utils.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.utils.tsx new file mode 100644 index 00000000000..1e5a32085d2 --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.utils.tsx @@ -0,0 +1,77 @@ +import * as Tooltip from '@radix-ui/react-tooltip' +import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants' +import { IconHelpCircle } from 'ui' +import { getForeignKeyDeletionAction } from '../ColumnEditor/ColumnEditor.utils' + +export const generateDeletionActionDescription = (deletionAction: string, reference: string) => { + const actionName = getForeignKeyDeletionAction(deletionAction) ?? 'No action' + + switch (deletionAction) { + case FOREIGN_KEY_DELETION_ACTION.NO_ACTION: + return ( + <> + {actionName}: Deleting a record from{' '} + {reference} will{' '} + raise an error if there are records + existing in this table that reference it + + ) + case FOREIGN_KEY_DELETION_ACTION.CASCADE: + return ( + <> + {actionName}: Deleting a record from{' '} + {reference} will{' '} + also delete any records that reference + it in this table + + ) + case FOREIGN_KEY_DELETION_ACTION.RESTRICT: + return ( + <> + {actionName} + + + + + + + +
      +

      + This is similar to no action, but the restrict check cannot be deferred till + later in the transaction +

      +
      +
      +
      +
      + : Deleting a record from {reference} will{' '} + prevent deletion of existing + referencing rows from this table. + + ) + case FOREIGN_KEY_DELETION_ACTION.SET_DEFAULT: + return ( + <> + {actionName}: Deleting a record from{' '} + {reference} will set the value of any + existing records in this table referencing it to their{' '} + default value + + ) + case FOREIGN_KEY_DELETION_ACTION.SET_NULL: + return ( + <> + {actionName}: Deleting a record from{' '} + {reference} will set the value of any + existing records in this table referencing it{' '} + to NULL + + ) + } +} diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx new file mode 100644 index 00000000000..add2ac309c1 --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx @@ -0,0 +1,180 @@ +import { PostgresTable } from '@supabase/postgres-meta' +import { DndProvider } from 'react-dnd' +import { HTML5Backend } from 'react-dnd-html5-backend' +import { parseSupaTable } from 'components/grid' +import RefreshButton from 'components/grid/components/header/RefreshButton' +import FilterPopover from 'components/grid/components/header/filter' +import SortPopover from 'components/grid/components/header/sort' +import { formatFilterURLParams, formatSortURLParams } from 'components/grid/SupabaseGrid.utils' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { useTableRowsQuery } from 'data/table-rows/table-rows-query' +import { ForeignKeyConstraint } from 'data/database/foreign-key-constraints-query' +import { useStore } from 'hooks' +import { IconLoader, SidePanel } from 'ui' + +import ActionBar from '../../ActionBar' +import { useEncryptedColumns } from './ForeignRowSelector.utils' +import SelectorGrid from './SelectorGrid' +import { useEffect, useState } from 'react' +import Pagination from './Pagination' + +export interface ForeignRowSelectorProps { + visible: boolean + foreignKey?: ForeignKeyConstraint + onSelect: (value: any) => void + closePanel: () => void +} + +const ForeignRowSelector = ({ + visible, + foreignKey, + onSelect, + closePanel, +}: ForeignRowSelectorProps) => { + const { meta } = useStore() + const { project } = useProjectContext() + + const { + target_id: tableId, + target_schema: schemaName, + target_table: tableName, + target_columns: columnName, + } = foreignKey ?? {} + + const table = tableId ? meta.tables.byId(tableId) : undefined + + useEffect(() => { + if (!table && tableId) { + meta.tables.loadById(tableId) + } + }, [table, tableId]) + + const encryptedColumns = useEncryptedColumns({ schemaName, tableName }) + + const supaTable = + table && + parseSupaTable( + { + table: table as PostgresTable, + columns: (table as PostgresTable).columns ?? [], + primaryKeys: (table as PostgresTable).primary_keys, + relationships: (table as PostgresTable).relationships, + }, + encryptedColumns + ) + + const [params, setParams] = useState({ filter: [], sort: [] }) + + const sorts = formatSortURLParams(params.sort ?? []) + const filters = formatFilterURLParams(params.filter ?? []) + + const rowsPerPage = 100 + const [page, setPage] = useState(1) + + const { data, isLoading, isSuccess, isError, isRefetching } = useTableRowsQuery( + { + queryKey: [schemaName, tableName], + projectRef: project?.ref, + connectionString: project?.connectionString, + table: supaTable, + sorts, + filters, + page, + limit: rowsPerPage, + }, + { + keepPreviousData: true, + } + ) + + return ( + + Select a record to reference from{' '} + + {schemaName}.{tableName} + +
      + } + onCancel={closePanel} + customFooter={} + > + +
      + {isLoading && ( +
      + +

      Loading rows

      +
      + )} + + {isError && ( +
      +

      + Unable to load rows from{' '} + + {schemaName}.{tableName} + + . Please try again or contact support. +

      +
      + )} + + {isSuccess && supaTable && ( +
      +
      +
      + + { + // Reset page to 1 when filters change + if (page > 1) { + setPage(1) + } + + setParams(...args) + }} + /> + + + +
      + + +
      + + {data.rows.length > 0 ? ( + onSelect(row[columnName ?? ''])} + /> + ) : ( +
      + No Rows Found +
      + )} +
      + )} +
      +
      + + ) +} + +export default ForeignRowSelector diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.utils.ts new file mode 100644 index 00000000000..7bb0a05c154 --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.utils.ts @@ -0,0 +1,36 @@ +import { useFlag, useStore } from 'hooks' +import { useEffect, useState } from 'react' + +export interface UseEncryptedColumnsArgs { + schemaName?: string + tableName?: string +} + +export function useEncryptedColumns({ schemaName, tableName }: UseEncryptedColumnsArgs) { + const isVaultEnabled = useFlag('vaultExtension') + const { vault } = useStore() + + const [encryptedColumns, setEncryptedColumns] = useState([]) + + useEffect(() => { + let isMounted = true + + const getEncryptedColumns = async () => { + if (schemaName !== undefined && tableName !== undefined && isVaultEnabled) { + const columns = await vault.listEncryptedColumns(schemaName, tableName) + + if (isMounted) { + setEncryptedColumns(columns) + } + } + } + + getEncryptedColumns() + + return () => { + isMounted = false + } + }, [schemaName, tableName, isVaultEnabled]) + + return encryptedColumns +} diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/Pagination.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/Pagination.tsx new file mode 100644 index 00000000000..4e01b3063d4 --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/Pagination.tsx @@ -0,0 +1,53 @@ +import { Button, IconArrowLeft, IconArrowRight, IconLoader } from 'ui' + +export interface PaginationProps { + page: number + setPage: (setter: (prev: number) => number) => void + rowsPerPage: number + currentPageRowsCount?: number + isLoading?: boolean +} + +const Pagination = ({ + page, + setPage, + rowsPerPage, + currentPageRowsCount = 0, + isLoading = false, +}: PaginationProps) => { + const onPreviousPage = () => { + setPage((prev) => prev - 1) + } + + const onNextPage = () => { + setPage((prev) => prev + 1) + } + + const hasRunOutOfRows = currentPageRowsCount < rowsPerPage + + return ( +
      + {isLoading && } + +
      + ) +} + +export default Pagination diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/SelectorGrid.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/SelectorGrid.tsx new file mode 100644 index 00000000000..a77e38a9039 --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/SelectorGrid.tsx @@ -0,0 +1,82 @@ +import DataGrid, { Column } from '@supabase/react-data-grid' +import * as Tooltip from '@radix-ui/react-tooltip' +import { SupaRow, SupaTable } from 'components/grid' +import { IconKey } from 'ui' +import { + ESTIMATED_CHARACTER_PIXEL_WIDTH, + getColumnDefaultWidth, +} from 'components/grid/utils/gridColumns' +import { COLUMN_MIN_WIDTH } from 'components/grid/constants' + +export interface SelectorGridProps { + table: SupaTable + rows: SupaRow[] + onRowSelect: (row: SupaRow) => void +} + +const columnRender = (name: string, isPrimaryKey = false) => { + return ( +
      + {isPrimaryKey && ( + + +
      + +
      +
      + + + +
      + Primary key +
      +
      +
      +
      + )} + + {name} +
      + ) +} + +const formatter = (column: string, row: any) => { + const formattedValue = typeof row[column] === 'object' ? JSON.stringify(row[column]) : row[column] + return ( +
      + {formattedValue} +
      + ) +} + +const SelectorGrid = ({ table, rows, onRowSelect }: SelectorGridProps) => { + const columns: Column[] = table.columns.map((column) => { + const columnDefaultWidth = getColumnDefaultWidth(column) + const columnWidthBasedOnName = + (column.name.length + column.format.length) * ESTIMATED_CHARACTER_PIXEL_WIDTH + const columnWidth = + columnDefaultWidth < columnWidthBasedOnName ? columnWidthBasedOnName : columnDefaultWidth + + return { + key: column.name, + name: column.name, + formatter: ({ row }: any) => formatter(column.name, row), + headerRenderer: () => columnRender(column.name, column.isPrimaryKey), + resizable: true, + width: columnWidth, + minWidth: COLUMN_MIN_WIDTH, + } + }) + + return ( + 'cursor-pointer'} + /> + ) +} + +export default SelectorGrid diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/HeaderTitle.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/HeaderTitle.tsx index b9b2823c7c9..38d4e20d5f3 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/HeaderTitle.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/HeaderTitle.tsx @@ -11,7 +11,7 @@ const HeaderTitle: FC = ({ isNewRecord, tableName }) => { return ( <> {header} - {tableName && {tableName}} + {tableName && {tableName}} ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx index 5c3b2e6fead..cabdefd6fea 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx @@ -1,28 +1,28 @@ import { FC } from 'react' -import { isUndefined, includes } from 'lodash' -import { Button, Select, Input, IconLink, IconArrowRight, IconEdit2 } from 'ui' +import { isUndefined, includes, noop } from 'lodash' +import { Button, Select, Input, IconLink, IconArrowRight, IconEdit2, Listbox } from 'ui' import { RowField } from './RowEditor.types' import DateTimeInput from './DateTimeInput' import { TEXT_TYPES, JSON_TYPES, DATETIME_TYPES } from '../SidePanelEditor.constants' -interface Props { +export interface InputFieldProps { field: RowField errors: any isEditable?: boolean onUpdateField?: (changes: object) => void onEditJson?: (data: any) => void - onViewForeignKey?: () => void + onSelectForeignKey?: () => void } -const InputField: FC = ({ +const InputField = ({ field, errors, isEditable = true, - onUpdateField = () => {}, - onEditJson = () => {}, - onViewForeignKey = () => {}, -}) => { + onUpdateField = noop, + onEditJson = noop, + onSelectForeignKey = noop, +}: InputFieldProps) => { if (field.enums.length > 0) { const isArray = field.format[0] === '_' if (isArray) { @@ -42,7 +42,7 @@ const InputField: FC = ({ field.defaultValue === null ? '' : typeof field.defaultValue === 'string' && field.defaultValue.length === 0 - ? 'Default: Empty string' + ? 'EMPTY' : `Default: ${field.defaultValue}` } onChange={(event: any) => onUpdateField({ [field.name]: event.target.value })} @@ -73,26 +73,24 @@ const InputField: FC = ({ } } - if (!isUndefined(field.foreignKey)) { + if (field.foreignKey !== undefined) { return ( element as a parent descriptionText={ -
      - {field.comment &&

      {field.comment}

      } -

      ({field.name}

      -

      - -

      -

      + <> + {field.comment && {field.comment} } + + {field.comment && '('}Has a foreign key relation to + + {field.foreignKey.target_table_schema}.{field.foreignKey.target_table_name}. - {field.foreignKey.target_column_name}) -

      -
      + {field.foreignKey.target_column_name} + + {field.comment && {`)`}} + } labelOptional={field.format} disabled={!isEditable} @@ -100,13 +98,13 @@ const InputField: FC = ({ onChange={(event: any) => onUpdateField({ [field.name]: event.target.value })} actions={ } /> @@ -127,11 +125,26 @@ const InputField: FC = ({ rows={5} value={field.value ?? ''} placeholder={ - field.defaultValue === null - ? '' + field.value === null && field.defaultValue === null + ? 'NULL' + : field.value === '' + ? 'EMPTY' : typeof field.defaultValue === 'string' && field.defaultValue.length === 0 - ? 'Default: Empty string' - : `Default: ${field.defaultValue}` + ? 'EMPTY' + : `NULL (Default: ${field.defaultValue})` + } + actions={ +
      + {(field.isNullable || (!field.isNullable && field.defaultValue)) && ( + + )} +
      } onChange={(event: any) => onUpdateField({ [field.name]: event.target.value })} /> @@ -148,7 +161,7 @@ const InputField: FC = ({ descriptionText={field.comment} labelOptional={field.format} disabled={!isEditable} - placeholder={field?.defaultValue ?? ''} + placeholder={field?.defaultValue ?? 'NULL'} error={errors[field.name]} onChange={(event: any) => onUpdateField({ [field.name]: event.target.value })} actions={ @@ -182,6 +195,40 @@ const InputField: FC = ({ ) } + if (field.format === 'bool') { + const options = [ + { value: 'true', label: 'TRUE' }, + { value: 'false', label: 'FALSE' }, + ...(field.isNullable ? [{ value: 'null', label: 'NULL' }] : []), + ] + return ( + { + if (value === 'null') onUpdateField({ [field.name]: null }) + else onUpdateField({ [field.name]: value }) + }} + > + {options.map((option) => ( + + {option.label} + + ))} + + ) + } + return ( = ({ ? 'Automatically generated as identity' : field.defaultValue !== null ? `Default: ${field.defaultValue}` - : '' + : 'NULL' } disabled={!isEditable} onChange={(event: any) => onUpdateField({ [field.name]: event.target.value })} diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx index ee2af239228..9e0329af84d 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx @@ -3,7 +3,7 @@ import { SidePanel } from 'ui' import { useStore } from 'hooks' import JsonEditor from './JsonCodeEditor' -import TwoOptionToggle from './TwoOptionToggle' +import TwoOptionToggle from 'components/ui/TwoOptionToggle' import DrilldownViewer from './DrilldownViewer' import ActionBar from '../../ActionBar' import { minifyJSON, prettifyJSON, tryParseJson } from 'lib/helpers' diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ReferenceRowViewer.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ReferenceRowViewer.tsx deleted file mode 100644 index 8760f7b0385..00000000000 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ReferenceRowViewer.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { FC, useEffect, useState } from 'react' -import { SidePanel, IconLoader, IconXCircle } from 'ui' -import type { PostgresRelationship } from '@supabase/postgres-meta' - -import ActionBar from '../ActionBar' -import InputField from './InputField' -import { RowField } from './RowEditor.types' -import { generateRowFieldsWithoutColumnMeta } from './RowEditor.utils' - -interface Props { - visible: boolean - referenceRow?: { loading: boolean; foreignKey: any; row: any } - closePanel: () => void -} - -const ReferenceRowViewer: FC = ({ visible, referenceRow, closePanel }) => { - const loading = referenceRow?.loading ?? true - const foreignKey: PostgresRelationship = referenceRow?.foreignKey ?? undefined - const row = referenceRow?.row ?? undefined - const [rowFields, setRowFields] = useState([]) - - useEffect(() => { - if (visible) { - if (!foreignKey) { - setRowFields([]) - } else if (row) { - const rowFields = generateRowFieldsWithoutColumnMeta(row) - setRowFields(rowFields) - } - } - }, [visible, referenceRow]) - - return ( - - Viewing reference row from{' '} - - {foreignKey?.target_table_schema ?? ''}.{foreignKey?.target_table_name ?? ''} - -
      - } - hideFooter={false} - onCancel={closePanel} - customFooter={} - > - -
      - {loading ? ( -
      - -

      Loading reference row

      -
      - ) : !row ? ( -
      - -

      - Unable to find the corresponding row in {foreignKey?.target_table_schema}. - {foreignKey?.target_table_name} -

      -
      - ) : ( -
      - {rowFields.map((field: RowField) => { - return - })} -
      - )} -
      -
      - - ) -} - -export default ReferenceRowViewer diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx index 4ebd6a4f6bc..5866545dff8 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx @@ -1,25 +1,25 @@ -import { FC, useEffect, useState } from 'react' -import { isUndefined, partition, isEmpty } from 'lodash' +import { useEffect, useMemo, useState } from 'react' +import { isUndefined, partition, isEmpty, noop } from 'lodash' import { SidePanel } from 'ui' import { Dictionary } from 'components/grid' -import { Query } from 'components/grid/query/Query' import type { PostgresTable } from '@supabase/postgres-meta' -import { useStore } from 'hooks' +import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import ActionBar from '../ActionBar' import HeaderTitle from './HeaderTitle' import InputField from './InputField' import JsonEdit from './JsonEditor' -import ReferenceRowViewer from './ReferenceRowViewer' +import ForeignRowSelector from './ForeignRowSelector/ForeignRowSelector' import { generateRowFields, validateFields, generateRowObjectFromFields, generateUpdateRowPayload, } from './RowEditor.utils' -import { JsonEditValue, ReferenceRow, RowField } from './RowEditor.types' +import { JsonEditValue, RowField } from './RowEditor.types' -interface Props { +export interface RowEditorProps { row?: Dictionary selectedTable: PostgresTable visible: boolean @@ -28,21 +28,20 @@ interface Props { updateEditorDirty: () => void } -const RowEditor: FC = ({ +const RowEditor = ({ row, selectedTable, visible = false, - closePanel = () => {}, - saveChanges = () => {}, - updateEditorDirty = () => {}, -}) => { - const { meta, ui } = useStore() + closePanel = noop, + saveChanges = noop, + updateEditorDirty = noop, +}: RowEditorProps) => { const [errors, setErrors] = useState>({}) const [rowFields, setRowFields] = useState([]) const [selectedValueForJsonEdit, setSelectedValueForJsonEdit] = useState() - const [isViewingReferenceRow, setIsViewingReferenceRow] = useState(false) - const [referenceRow, setReferenceRow] = useState() + const [isSelectingForeignKey, setIsSelectingForeignKey] = useState(false) + const [referenceRow, setReferenceRow] = useState() const isNewRecord = isUndefined(row) const isEditingJson = !isUndefined(selectedValueForJsonEdit) @@ -54,6 +53,21 @@ const RowEditor: FC = ({ (rowField: any) => !rowField.isNullable ) + const { project } = useProjectContext() + const { data } = useForeignKeyConstraintsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + schema: selectedTable.schema, + }) + + const foreignKey = useMemo( + () => + data && referenceRow?.foreignKey?.id + ? data.find((key) => key.id === referenceRow.foreignKey?.id) + : undefined, + [data, referenceRow?.foreignKey?.id] + ) + useEffect(() => { if (visible) { setErrors({}) @@ -75,45 +89,18 @@ const RowEditor: FC = ({ updateEditorDirty() } - const onViewForeignKey = async (row: RowField) => { - // Possible low prio refactor: Shift fetching reference row retrieval to ReferenceRowViewer - // in a useEffect, rather than trying to manage a loading state in this method - if (!row.value) { - ui.setNotification({ - category: 'error', - message: `Please enter a value in the ${row.name} field first`, - duration: 4000, - }) - } - const foreignKey = row.foreignKey - setReferenceRow({ loading: true, foreignKey, row: undefined }) - setIsViewingReferenceRow(true) + const onOpenForeignRowSelector = async (row: RowField) => { + setIsSelectingForeignKey(true) + setReferenceRow(row) + } - if (foreignKey) { - const schema = foreignKey.target_table_schema - const table = foreignKey.target_table_name - const column = foreignKey.target_column_name + const onSelectForeignRowValue = (value: any) => { + if (!referenceRow) return - const query = new Query() - .from(table, schema) - .select() - .match({ [column]: row.value }) - .toSql() - const res = await meta.query(query) - if (res.error) { - setReferenceRow({ loading: false, foreignKey, row: undefined }) - return ui.setNotification({ category: 'error', message: res.error.message }) - } - if (res.length === 0) { - setReferenceRow({ loading: false, foreignKey, row: undefined }) - return ui.setNotification({ - category: 'error', - message: `Unable to find the corresponding row in ${foreignKey.target_table_schema}.${foreignKey.target_table_name} where ${foreignKey.target_column_name} equals ${row.value}`, - duration: 4000, - }) - } - setReferenceRow({ loading: false, foreignKey, row: res[0] }) - } + onUpdateField({ [referenceRow.name]: value }) + + setIsSelectingForeignKey(false) + setReferenceRow(undefined) } const onSaveChanges = (e: React.FormEvent) => { @@ -153,14 +140,12 @@ const RowEditor: FC = ({ visible={visible} header={} className={`transition-all duration-100 ease-in ${ - isEditingJson || isViewingReferenceRow ? ' mr-32' : '' + isEditingJson || isSelectingForeignKey ? ' mr-32' : '' }`} onCancel={closePanel} onInteractOutside={(event) => { const isToast = (event.target as Element)?.closest('#toast') - if (isToast) { - event.preventDefault() - } + if (isToast) event.preventDefault() }} >
      onSaveChanges(e)} className="h-full"> @@ -176,7 +161,7 @@ const RowEditor: FC = ({ errors={errors} onUpdateField={onUpdateField} onEditJson={setSelectedValueForJsonEdit} - onViewForeignKey={() => onViewForeignKey(field)} + onSelectForeignKey={() => onOpenForeignRowSelector(field)} /> ) })} @@ -201,7 +186,7 @@ const RowEditor: FC = ({ errors={errors} onUpdateField={onUpdateField} onEditJson={setSelectedValueForJsonEdit} - onViewForeignKey={() => onViewForeignKey(field)} + onSelectForeignKey={() => onOpenForeignRowSelector(field)} /> ) })} @@ -220,15 +205,6 @@ const RowEditor: FC = ({ setSelectedValueForJsonEdit(undefined) }} /> - - { - setIsViewingReferenceRow(false) - setReferenceRow(undefined) - }} - />
      = ({
      + + { + setIsSelectingForeignKey(false) + setReferenceRow(undefined) + }} + /> ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types.ts index fe7eade0c1f..b9093798754 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types.ts @@ -6,12 +6,6 @@ export interface JsonEditValue { jsonString: string } -export interface ReferenceRow { - loading: boolean - foreignKey: any // ForeignKey - row: any // RowField -} - export interface RowField { id: string name: string diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils.ts index 7bbe183f5f7..ac195962610 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils.ts @@ -3,9 +3,14 @@ import { find, isUndefined, compact, isEqual, omitBy, isNull, isString } from 'l import { Dictionary } from 'components/grid' import type { PostgresTable } from '@supabase/postgres-meta' -import { uuidv4, minifyJSON, tryParseJson } from 'lib/helpers' +import { minifyJSON, tryParseJson } from 'lib/helpers' import { RowField } from './RowEditor.types' -import { DATETIME_TYPES, TIME_TYPES, TIMESTAMP_TYPES } from '../SidePanelEditor.constants' +import { + DATETIME_TYPES, + TIME_TYPES, + TIMESTAMP_TYPES, + TEXT_TYPES, +} from '../SidePanelEditor.constants' export const generateRowFields = ( row: Dictionary | undefined, @@ -16,11 +21,18 @@ export const generateRowFields = ( const primaryKeyColumns = primary_keys.map((key) => key.name) return table.columns!.map((column) => { - const value = isUndefined(row) - ? '' - : DATETIME_TYPES.includes(column.format) - ? convertPostgresDatetimeToInputDatetime(column.format, row[column.name]) - : parseValue(row[column.name], column.format) + const value = + isUndefined(row) && TEXT_TYPES.includes(column.format) + ? null + : isUndefined(row) && column.format === 'bool' && !column.is_nullable + ? 'true' + : isUndefined(row) && column.format === 'bool' && column.is_nullable + ? 'null' + : isUndefined(row) + ? '' + : DATETIME_TYPES.includes(column.format) + ? convertPostgresDatetimeToInputDatetime(column.format, row[column.name]) + : parseValue(row[column.name], column.format) const foreignKey = find(relationships, (relationship) => { return ( @@ -70,27 +82,7 @@ export const validateFields = (fields: RowField[]) => { return errors } -// Currently only used in ReferenceRowViewer for rows outside of public schema -export const generateRowFieldsWithoutColumnMeta = (row: any): RowField[] => { - const properties = Object.keys(row) - return properties.map((property) => { - return { - id: uuidv4(), - value: row[property] || '', - name: property, - comment: '', - format: '', - enums: [], - defaultValue: '', - foreignKey: undefined, - isNullable: false, - isIdentity: false, - isPrimaryKey: false, - } - }) -} - -const parseValue = (originalValue: string, format: string) => { +const parseValue = (originalValue: any, format: string) => { try { if (originalValue === null || originalValue.length === 0) { return originalValue @@ -99,7 +91,7 @@ const parseValue = (originalValue: string, format: string) => { } else if (typeof originalValue === 'object') { return JSON.stringify(originalValue) } else if (typeof originalValue === 'boolean') { - return (originalValue as any).toString() + return originalValue.toString() } else { return originalValue } @@ -162,7 +154,14 @@ export const generateRowObjectFromFields = ( const rowObject = {} as any fields.forEach((field) => { const isArray = field.format.startsWith('_') - const value = (field?.value ?? '').length === 0 ? null : field.value + + // Do not convert empty field inputs to NULL for text types + // so that we discern NULL and EMPTY + const value = TEXT_TYPES.includes(field.format) + ? field.value + : (field?.value ?? '').length === 0 + ? null + : field.value if (isArray && value !== null) { rowObject[field.name] = tryParseJson(value) @@ -173,7 +172,8 @@ export const generateRowObjectFromFields = ( rowObject[field.name] = tryParseJson(value) } } else if (field.format === 'bool' && value) { - rowObject[field.name] = value === 'true' + if (value === 'null') rowObject[field.name] = null + else rowObject[field.name] = value === 'true' } else if (DATETIME_TYPES.includes(field.format)) { // Seconds are missing if they are set to 0 in the HTML input // Need to format that first, before passing to dayjs diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx index 25626fd10ee..425ed9b90cd 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx @@ -1,25 +1,44 @@ -import { FC, useState } from 'react' -import { find, isEmpty, isUndefined } from 'lodash' -import { Query, Dictionary } from 'components/grid' +import { useState } from 'react' +import { QueryKey, useQueryClient } from '@tanstack/react-query' +import { find, isEmpty, isUndefined, noop } from 'lodash' +import { Dictionary } from 'components/grid' import { Modal } from 'ui' -import type { PostgresRelationship, PostgresTable, PostgresColumn } from '@supabase/postgres-meta' +import type { PostgresTable, PostgresColumn } from '@supabase/postgres-meta' -import { useStore } from 'hooks' -import { RowEditor, ColumnEditor, TableEditor } from '.' +import { useStore, useUrlState } from 'hooks' +import { entityTypeKeys } from 'data/entity-types/keys' +import { useTableRowCreateMutation } from 'data/table-rows/table-row-create-mutation' +import { useTableRowUpdateMutation } from 'data/table-rows/table-row-update-mutation' +import { RowEditor, ColumnEditor, TableEditor, SpreadsheetImport } from '.' import { ImportContent } from './TableEditor/TableEditor.types' -import { ColumnField, CreateColumnPayload, UpdateColumnPayload } from './SidePanelEditor.types' +import { + ColumnField, + CreateColumnPayload, + ExtendedPostgresRelationship, + UpdateColumnPayload, +} from './SidePanelEditor.types' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import ConfirmationModal from 'components/ui/ConfirmationModal' import JsonEdit from './RowEditor/JsonEditor/JsonEditor' import { JsonEditValue } from './RowEditor/RowEditor.types' +import { sqlKeys } from 'data/sql/keys' +import ForeignRowSelector, { + ForeignRowSelectorProps, +} from './RowEditor/ForeignRowSelector/ForeignRowSelector' -interface Props { +export interface SidePanelEditorProps { selectedSchema: string selectedTable?: PostgresTable selectedRowToEdit?: Dictionary selectedColumnToEdit?: PostgresColumn selectedTableToEdit?: PostgresTable selectedValueForJsonEdit?: JsonEditValue - sidePanelKey?: 'row' | 'column' | 'table' | 'json' + selectedForeignKeyToEdit?: { + foreignKey: NonNullable + row: any + column: any + } + sidePanelKey?: 'row' | 'column' | 'table' | 'json' | 'foreign-row-selector' | 'csv-import' isDuplicating?: boolean closePanel: () => void onRowCreated?: (row: Dictionary) => void @@ -31,27 +50,95 @@ interface Props { onColumnSaved?: (hasEncryptedColumns?: boolean) => void } -const SidePanelEditor: FC = ({ +const SidePanelEditor = ({ selectedSchema, selectedTable, selectedRowToEdit, selectedColumnToEdit, selectedTableToEdit, selectedValueForJsonEdit, + selectedForeignKeyToEdit, sidePanelKey, isDuplicating = false, closePanel, - onRowCreated = () => {}, - onRowUpdated = () => {}, - onTableCreated = () => {}, - onColumnSaved = () => {}, -}) => { + onRowCreated = noop, + onRowUpdated = noop, + onTableCreated = noop, + onColumnSaved = noop, +}: SidePanelEditorProps) => { + const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] }) const { meta, ui } = useStore() + const queryClient = useQueryClient() const [isEdited, setIsEdited] = useState(false) const [isClosingPanel, setIsClosingPanel] = useState(false) const tables = meta.tables.list() + const enumArrayColumns = (selectedTable?.columns ?? []) + .filter((column) => { + return (column?.enums ?? []).length > 0 && column.data_type.toLowerCase() === 'array' + }) + .map((column) => column.name) + + const { project } = useProjectContext() + const { mutateAsync: createTableRows } = useTableRowCreateMutation() + const { mutateAsync: updateTableRow } = useTableRowUpdateMutation({ + async onMutate({ projectRef, table, configuration, payload }) { + closePanel() + + const primaryKeyColumns = new Set(Object.keys(configuration.identifiers)) + + const queryKey = sqlKeys.query(projectRef, [ + table.schema, + table.name, + { table: { name: table.name, schema: table.schema } }, + ]) + + await queryClient.cancelQueries(queryKey) + + const previousRowsQueries = queryClient.getQueriesData<{ result: any[] }>(queryKey) + + queryClient.setQueriesData<{ result: any[] }>(queryKey, (old) => { + return { + result: + old?.result.map((row) => { + // match primary keys + if ( + Object.entries(row) + .filter(([key]) => primaryKeyColumns.has(key)) + .every(([key, value]) => value === configuration.identifiers[key]) + ) { + return { ...row, ...payload } + } + + return row + }) ?? [], + } + }) + + return { previousRowsQueries } + }, + onError(error, _variables, context) { + const { previousRowsQueries } = context as { + previousRowsQueries: [ + QueryKey, + ( + | { + result: any[] + } + | undefined + ) + ][] + } + + previousRowsQueries.forEach(([queryKey, previousRows]) => { + if (previousRows) { + queryClient.setQueriesData(queryKey, previousRows) + } + queryClient.invalidateQueries(queryKey) + }) + }, + }) const saveRow = async ( payload: any, @@ -59,45 +146,46 @@ const SidePanelEditor: FC = ({ configuration: { identifiers: any; rowIdx: number }, onComplete: Function ) => { - if (selectedTable === undefined) return + if (!project || selectedTable === undefined) { + return console.error('no project or table selected') + } let saveRowError = false - // @ts-ignore - const enumArrayColumns = selectedTable.columns - .filter((column) => { - return (column?.enums ?? []).length > 0 && column.data_type.toLowerCase() === 'array' - }) - .map((column) => column.name) - if (isNewRecord) { - const insertQuery = new Query() - .from(selectedTable.name, selectedTable.schema) - .insert([payload], { returning: true, enumArrayColumns }) - .toSql() + try { + const result = await createTableRows({ + projectRef: project.ref, + connectionString: project.connectionString, + table: selectedTable as any, + payload, + enumArrayColumns, + }) - const res: any = await meta.query(insertQuery) - if (res.error) { + onRowCreated(result[0]) + } catch (error: any) { saveRowError = true - ui.setNotification({ category: 'error', message: res.error?.message }) - } else { - onRowCreated(res[0]) + ui.setNotification({ category: 'error', message: error?.message }) } } else { const hasChanges = !isEmpty(payload) if (hasChanges) { if (selectedTable.primary_keys.length > 0) { - const updateQuery = new Query() - .from(selectedTable.name, selectedTable.schema) - .update(payload, { returning: true, enumArrayColumns }) - .match(configuration.identifiers) - .toSql() + if (selectedTable!.primary_keys.length > 0) { + try { + const result = await updateTableRow({ + projectRef: project.ref, + connectionString: project.connectionString, + table: selectedTable as any, + configuration, + payload, + enumArrayColumns, + }) - const res: any = await meta.query(updateQuery) - if (res.error) { - saveRowError = true - ui.setNotification({ category: 'error', message: res.error?.message }) - } else { - onRowUpdated(res[0], configuration.rowIdx) + onRowUpdated(result[0], configuration.rowIdx) + } catch (error: any) { + saveRowError = true + ui.setNotification({ category: 'error', message: error?.message }) + } } } else { saveRowError = true @@ -133,9 +221,25 @@ const SidePanelEditor: FC = ({ } catch (error: any) {} } + const onSaveForeignRow = async (value: any) => { + if (selectedTable === undefined || selectedForeignKeyToEdit === undefined) return + + try { + const { row, column } = selectedForeignKeyToEdit + const payload = { [column.name]: value } + const identifiers = {} as Dictionary + selectedTable.primary_keys.forEach((column) => (identifiers[column.name] = row![column.name])) + + const isNewRecord = false + const configuration = { identifiers, rowIdx: row.idx } + + saveRow(payload, isNewRecord, configuration, () => {}) + } catch (error) {} + } + const saveColumn = async ( payload: CreateColumnPayload | UpdateColumnPayload, - foreignKey: Partial | undefined, + foreignKey: ExtendedPostgresRelationship | undefined, isNewRecord: boolean, configuration: { columnId?: string; isEncrypted: boolean; keyId?: string; keyName?: string }, resolve: any @@ -158,7 +262,29 @@ const SidePanelEditor: FC = ({ if (response?.error) { ui.setNotification({ category: 'error', message: response.error.message }) } else { - await meta.tables.loadById(selectedTable!.id) + if ( + !isNewRecord && + payload.name && + selectedColumnToEdit && + selectedColumnToEdit.name !== payload.name + ) { + reAddRenamedColumnSortAndFilter(selectedColumnToEdit.name, payload.name) + } + queryClient.invalidateQueries(sqlKeys.query(project?.ref, ['foreign-key-constraints'])) + await Promise.all([ + meta.tables.loadById(selectedTable!.id), + queryClient.invalidateQueries( + sqlKeys.query(project?.ref, [selectedTable!.schema, selectedTable!.name]) + ), + queryClient.invalidateQueries( + sqlKeys.query(project?.ref, [ + 'table-definition', + selectedTable!.schema, + selectedTable!.name, + ]) + ), + queryClient.invalidateQueries(entityTypeKeys.list(project?.ref)), + ]) onColumnSaved(configuration.isEncrypted) setIsEdited(false) closePanel() @@ -171,6 +297,28 @@ const SidePanelEditor: FC = ({ resolve() } + /** + * Adds the renamed column's filter and/or sort rules. + */ + const reAddRenamedColumnSortAndFilter = (oldColumnName: string, newColumnName: string) => { + setParams((prevParams) => { + const existingFilters = (prevParams?.filter ?? []) as string[] + const existingSorts = (prevParams?.sort ?? []) as string[] + + return { + ...prevParams, + filter: existingFilters.map((filter: string) => { + const [column] = filter.split(':') + return column === oldColumnName ? filter.replace(column, newColumnName) : filter + }), + sort: existingSorts.map((sort: string) => { + const [column] = sort.split(':') + return column === oldColumnName ? sort.replace(column, newColumnName) : sort + }), + } + }) + } + const saveTable = async ( payload: any, columns: ColumnField[], @@ -192,21 +340,27 @@ const SidePanelEditor: FC = ({ try { if (isDuplicating) { const duplicateTable = find(tables, { id: tableId }) as PostgresTable + toastId = ui.setNotification({ category: 'loading', message: `Duplicating table: ${duplicateTable.name}...`, }) + const table: any = await meta.duplicateTable(payload, { isRLSEnabled, isRealtimeEnabled, isDuplicateRows, duplicateTable, }) + + await queryClient.invalidateQueries(entityTypeKeys.list(project?.ref)) + ui.setNotification({ id: toastId, category: 'success', message: `Table ${duplicateTable.name} has been successfully duplicated into ${table.name}!`, }) + onTableCreated(table) } else if (isNewRecord) { toastId = ui.setNotification({ @@ -222,17 +376,22 @@ const SidePanelEditor: FC = ({ isRealtimeEnabled, importContent ) + + await queryClient.invalidateQueries(entityTypeKeys.list(project?.ref)) + ui.setNotification({ id: toastId, category: 'success', message: `Table ${table.name} is good to go!`, }) + onTableCreated(table) } else if (selectedTableToEdit) { toastId = ui.setNotification({ category: 'loading', message: `Updating table: ${selectedTableToEdit?.name}...`, }) + const { table, hasError }: any = await meta.updateTable( toastId, selectedTableToEdit, @@ -240,6 +399,7 @@ const SidePanelEditor: FC = ({ columns, isRealtimeEnabled ) + if (hasError) { ui.setNotification({ id: toastId, @@ -247,6 +407,21 @@ const SidePanelEditor: FC = ({ message: `Table ${table.name} has been updated, but there were some errors`, }) } else { + queryClient.invalidateQueries(sqlKeys.query(project?.ref, ['foreign-key-constraints'])) + await Promise.all([ + queryClient.invalidateQueries( + sqlKeys.query(project?.ref, [selectedTable!.schema, selectedTable!.name]) + ), + queryClient.invalidateQueries( + sqlKeys.query(project?.ref, [ + 'table-definition', + selectedTable!.schema, + selectedTable!.name, + ]) + ), + queryClient.invalidateQueries(entityTypeKeys.list(project?.ref)), + ]) + ui.setNotification({ id: toastId, category: 'success', @@ -267,6 +442,54 @@ const SidePanelEditor: FC = ({ resolve() } + const onImportData = async (importContent: ImportContent) => { + if (!project || selectedTable === undefined) { + return console.error('no project or table selected') + } + + const { file, rowCount, selectedHeaders, resolve } = importContent + const toastId = ui.setNotification({ + category: 'loading', + message: `Adding ${rowCount.toLocaleString()} rows to ${selectedTable.name}`, + }) + const { error }: any = await meta.insertRowsViaSpreadsheet( + file, + selectedTable, + selectedHeaders, + (progress: number) => { + ui.setNotification({ + id: toastId, + progress, + category: 'loading', + message: `Adding ${rowCount.toLocaleString()} rows to ${selectedTable.name}`, + }) + } + ) + + if (error) { + ui.setNotification({ + error, + id: toastId, + category: 'error', + message: `Failed to import data: ${error.message}`, + }) + resolve() + } else { + await Promise.all([ + queryClient.invalidateQueries( + sqlKeys.query(project?.ref, [selectedTable!.schema, selectedTable!.name]) + ), + ]) + ui.setNotification({ + id: toastId, + category: 'success', + message: `Successfully imported ${rowCount} rows of data into ${selectedTable.name}`, + }) + resolve() + closePanel() + } + } + const onClosePanel = () => { if (isEdited) { setIsClosingPanel(true) @@ -289,7 +512,6 @@ const SidePanelEditor: FC = ({ )} {!isUndefined(selectedTable) && ( = ({ closePanel={onClosePanel} onSaveJSON={onSaveJSON} /> + + = ({ } return ( -
      +

      Upload a CSV or TSV file. The first row should be the headers of the table, and your @@ -42,13 +42,13 @@ const SpreadSheetFileUpload: FC = ({ -) or underscores (_ ).

      -

      +

      Tip: Datetime columns should be formatted as YYYY-MM-DD HH:mm:ss

      {!uploadedFile ? (
      = ({ onDrop={onDrop} onClick={() => (uploadButtonRef.current as any)?.click()} > -

      - Drag and drop, or browse your files +

      + Drag and drop, or browse your files

      ) : ( -
      +
      -

      {uploadedFile.name}

      +

      {uploadedFile.name}

      {parseProgress === 100 ? (
      diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetImportConfiguration.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetImportConfiguration.tsx new file mode 100644 index 00000000000..f77a9d7194d --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetImportConfiguration.tsx @@ -0,0 +1,71 @@ +import clsx from 'clsx' +import { useState } from 'react' +import { SidePanel, Button, IconChevronDown, Collapsible } from 'ui' +import { SpreadsheetData } from './SpreadsheetImport.types' + +interface SpreadSheetImportConfigurationProps { + spreadsheetData: SpreadsheetData + selectedHeaders: string[] + onToggleHeader: (header: string) => void +} + +const SpreadsheetImportConfiguration = ({ + spreadsheetData, + selectedHeaders, + onToggleHeader, +}: SpreadSheetImportConfigurationProps) => { + const [expandConfiguration, setExpandConfiguration] = useState(false) + + return ( + + + +
      +

      Configure import data

      +
      +
      +
      + + +
      +
      +

      Select which columns to import

      +

      + By default, all columns are selected to be imported from your CSV +

      +
      +
      + {spreadsheetData.headers.map((header) => { + const isSelected = selectedHeaders.includes(header) + return ( + + ) + })} +
      +
      +
      +
      +
      + ) +} + +export default SpreadsheetImportConfiguration diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadSheetTextInput.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetTextInput.tsx similarity index 95% rename from studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadSheetTextInput.tsx rename to studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetTextInput.tsx index a5641296883..54b30ce6c94 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadSheetTextInput.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetTextInput.tsx @@ -15,7 +15,7 @@ const SpreadSheetTextInput: FC = ({ input, onInputChange }) => ( include any special characters other than hyphens (-) or underscores ( _).

      -

      +

      Tip: Datetime columns should be formatted as YYYY-MM-DD HH:mm:ss

      diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.constants.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.constants.ts similarity index 85% rename from studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.constants.ts rename to studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.constants.ts index ac5a636ebcc..7c29186ce9a 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.constants.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.constants.ts @@ -7,10 +7,7 @@ export const UPLOAD_FILE_TYPES = [ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ] -export const UPLOAD_FILE_EXTENSIONS = [ - 'csv', - 'tsv', -] +export const UPLOAD_FILE_EXTENSIONS = ['csv', 'tsv'] export const EMPTY_SPREADSHEET_DATA: SpreadsheetData = { headers: [], diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx similarity index 53% rename from studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.tsx rename to studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx index 5720920afbb..4a2c7954a3c 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx @@ -1,12 +1,12 @@ import { useCallback, useState, FC, useEffect } from 'react' -import { debounce, includes } from 'lodash' -import { SidePanel, Tabs, IconArrowRight, IconChevronRight } from 'ui' +import { debounce, includes, noop } from 'lodash' +import { SidePanel, Tabs } from 'ui' +import { PostgresTable } from '@supabase/postgres-meta' import { useStore } from 'hooks' -import ActionBar from '../../ActionBar' +import ActionBar from '../ActionBar' import SpreadSheetTextInput from './SpreadSheetTextInput' import SpreadSheetFileUpload from './SpreadSheetFileUpload' -import SpreadsheetPreview from './SpreadsheetPreview' import { SpreadsheetData } from './SpreadsheetImport.types' import { acceptedFileExtension, @@ -14,31 +14,36 @@ import { parseSpreadsheetText, } from './SpreadsheetImport.utils' import { UPLOAD_FILE_TYPES, EMPTY_SPREADSHEET_DATA } from './SpreadsheetImport.constants' +import { ImportContent } from '../TableEditor/TableEditor.types' +import SpreadsheetImportConfiguration from './SpreadSheetImportConfiguration' +import SpreadsheetImportPreview from './SpreadsheetImportPreview' interface Props { debounceDuration?: number headers?: string[] rows?: any[] visible: boolean - saveContent: (prefillData: any) => void + selectedTable?: PostgresTable + saveContent: (prefillData: ImportContent) => void closePanel: () => void + updateEditorDirty?: (value: boolean) => void } const SpreadsheetImport: FC = ({ + visible = false, debounceDuration = 250, headers = [], rows = [], + selectedTable, saveContent, closePanel, - visible = false, + updateEditorDirty = noop, }) => { const { ui } = useStore() useEffect(() => { - if (visible) { - if (headers.length === 0) { - resetSpreadsheetImport() - } + if (visible && headers.length === 0) { + resetSpreadsheetImport() } }, [visible]) @@ -52,7 +57,13 @@ const SpreadsheetImport: FC = ({ columnTypeMap: {}, }) const [errors, setErrors] = useState([]) - const [expandedErrors, setExpandedErrors] = useState([]) + const [selectedHeaders, setSelectedHeaders] = useState([]) + + const selectedTableColumns = (selectedTable?.columns ?? []).map((column) => column.name) + const incompatibleHeaders = selectedHeaders.filter( + (header) => !selectedTableColumns.includes(header) + ) + const isCompatible = selectedTable !== undefined ? incompatibleHeaders.length === 0 : true const onProgressUpdate = (progress: number) => { setParseProgress(progress) @@ -68,6 +79,7 @@ const SpreadsheetImport: FC = ({ message: 'Sorry! We only accept CSV or TSV file types, please upload another file.', }) } else { + updateEditorDirty(true) setUploadedFile(file) const { headers, rowCount, columnTypeMap, errors, previewRows } = await parseSpreadsheet( file, @@ -83,6 +95,7 @@ const SpreadsheetImport: FC = ({ } setErrors(errors) + setSelectedHeaders(headers) setSpreadsheetData({ headers, rows: previewRows, rowCount, columnTypeMap }) } event.target.value = '' @@ -93,7 +106,7 @@ const SpreadsheetImport: FC = ({ setSpreadsheetData(EMPTY_SPREADSHEET_DATA) setUploadedFile(null) setErrors([]) - setExpandedErrors([]) + updateEditorDirty(false) } const readSpreadsheetText = async (text: string) => { @@ -108,6 +121,7 @@ const SpreadsheetImport: FC = ({ }) } setErrors(errors) + setSelectedHeaders(headers) setSpreadsheetData({ headers, rows, rowCount: rows.length, columnTypeMap }) } else { setSpreadsheetData(EMPTY_SPREADSHEET_DATA) @@ -120,11 +134,28 @@ const SpreadsheetImport: FC = ({ handler(event.target.value) } - const onSelectExpandError = (key: string) => { - if (expandedErrors.includes(key)) { - setExpandedErrors(expandedErrors.filter((error) => error !== key)) + const onToggleHeader = (header: string) => { + const updatedSelectedHeaders = selectedHeaders.includes(header) + ? selectedHeaders.filter((h) => h !== header) + : selectedHeaders.concat([header]) + setSelectedHeaders(updatedSelectedHeaders) + } + + const onConfirm = (resolve: () => void) => { + if (selectedHeaders.length === 0) { + ui.setNotification({ + category: 'error', + message: 'Please select at least one header from your CSV', + }) + resolve() + } else if (!isCompatible) { + ui.setNotification({ + category: 'error', + message: 'The data that you are trying to import is incompatible with your table structure', + }) + resolve() } else { - setExpandedErrors(expandedErrors.concat([key])) + saveContent({ file: uploadedFile, ...spreadsheetData, selectedHeaders, resolve }) } } @@ -133,24 +164,34 @@ const SpreadsheetImport: FC = ({ size="large" visible={visible} align="right" - header="Add content to new table" + header={ + selectedTable !== undefined ? ( + <> + Add data to{' '} + + {selectedTable.schema}.{selectedTable.name} + + + ) : ( + 'Add content to new table' + ) + } onCancel={() => closePanel()} customFooter={ { - saveContent({ - file: uploadedFile, - ...spreadsheetData, - }) - }} + applyFunction={onConfirm} /> } + onInteractOutside={(event) => { + const isToast = (event.target as Element)?.closest('#toast') + if (isToast) event.preventDefault() + }} > -
      +
      = ({
      - - {spreadsheetData.headers.length > 0 && ( -
      -
      -
      -

      Content Preview

      -

      - Your table will have {spreadsheetData.rowCount.toLocaleString()} rows and the - following {spreadsheetData.headers.length} columns. -

      -

      - Here is a preview of your table (up to the first 20 columns and first 20 rows). -

      -
      - -
      - {errors.length > 0 && ( -
      -
      -

      Issues found in spreadsheet

      -

      - Your table can still be created nonetheless despite issues in the following - rows. -

      -
      -
      - {errors.map((error: any, idx: number) => { - const key = `import-error-${idx}` - const isExpanded = expandedErrors.includes(key) - return ( -
      -
      - onSelectExpandError(key)} - /> -

      Row: {error.row}

      -

      {error.message}

      - {error.data?.__parsed_extra && ( - <> - -

      Extra field(s):

      - {error.data?.__parsed_extra.map((value: any, i: number) => ( - {value} - ))} - - )} -
      - {isExpanded && ( - - )} -
      - ) - })} -
      -
      - )} -
      - )} + {spreadsheetData.headers.length > 0 && ( + <> +
      + +
      + + + + + + )} ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.types.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.types.ts similarity index 100% rename from studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.types.ts rename to studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.types.ts diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.utils.ts similarity index 100% rename from studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.utils.ts rename to studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.utils.ts diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx new file mode 100644 index 00000000000..5e967af0f64 --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx @@ -0,0 +1,200 @@ +import clsx from 'clsx' +import { useEffect, useState } from 'react' +import { + SidePanel, + Badge, + Button, + Collapsible, + IconChevronDown, + IconChevronRight, + IconArrowRight, + IconAlertCircle, +} from 'ui' + +import { SpreadsheetData } from './SpreadsheetImport.types' +import SpreadsheetPreviewGrid from './SpreadsheetPreviewGrid' +import { PostgresTable } from '@supabase/postgres-meta' + +const MAX_ROWS = 20 +const MAX_HEADERS = 20 + +interface SpreadsheetImportPreviewProps { + selectedTable?: PostgresTable + spreadsheetData: SpreadsheetData + errors?: any[] + selectedHeaders: string[] + incompatibleHeaders: string[] +} + +const SpreadsheetImportPreview = ({ + selectedTable, + spreadsheetData, + errors = [], + selectedHeaders, + incompatibleHeaders, +}: SpreadsheetImportPreviewProps) => { + const [expandPreview, setExpandPreview] = useState(false) + const [expandedErrors, setExpandedErrors] = useState([]) + + const { headers, rows } = spreadsheetData + const previewHeaders = headers + .filter((header) => selectedHeaders.includes(header)) + .slice(0, MAX_HEADERS) + const previewRows = rows.slice(0, MAX_ROWS) + + const isCompatible = selectedTable !== undefined ? incompatibleHeaders.length === 0 : true + + useEffect(() => { + setExpandPreview(true) + }, [spreadsheetData]) + + const onSelectExpandError = (key: string) => { + if (expandedErrors.includes(key)) { + setExpandedErrors(expandedErrors.filter((error) => error !== key)) + } else { + setExpandedErrors(expandedErrors.concat([key])) + } + } + + return ( + + + +
      +
      +

      Preview data to be imported

      + {!isCompatible && Data incompatible} + {errors.length > 0 && {errors.length} issues found} +
      +
      +
      +
      + + +
      +

      + {selectedTable === undefined + ? `Your table will have ${spreadsheetData.rowCount.toLocaleString()} rows and the + following ${spreadsheetData.headers.length} columns.` + : `A total of ${spreadsheetData.rowCount.toLocaleString()} rows will be added to the table "${ + selectedTable.name + }"`} +

      +

      + Here is a preview of the data that will be added (up to the first 20 columns and first + 20 rows). +

      +
      +
      + {previewHeaders.length > 0 && previewRows.length > 0 ? ( + + ) : ( +
      + +

      + {previewHeaders.length === 0 + ? 'No headers have been selected' + : previewRows.length === 0 + ? 'Your CSV contains no data' + : ''} +

      +
      + )} +
      + {(!isCompatible || errors.length > 0) && ( +
      +
      +

      Issues found in spreadsheet

      + {isCompatible && ( +

      + {selectedTable !== undefined + ? 'This CSV can still be imported into your table despite issues in the following rows.' + : 'Your table can still be created nonetheless despite issues in the following rows.'} +

      + )} +
      +
      + {!isCompatible && ( +
      +
      +
      +
      +
      +

      + This CSV cannot be imported into your + table due to incompatible headers: +
      + The column{incompatibleHeaders.length > 1 ? 's' : ''}{' '} + {incompatibleHeaders.map((x) => `"${x}"`).join(', ')}{' '} + {incompatibleHeaders.length > 1 ? 'are' : 'is'} not present in your table +

      +
      +
      + )} + {errors.map((error: any, idx: number) => { + const key = `import-error-${idx}` + const isExpanded = expandedErrors.includes(key) + + return ( +
      +
      onSelectExpandError(key)} + > + {error.data !== undefined ? ( + + ) : ( +
      +
      +
      + )} + {error.data !== undefined && ( +

      Row: {error.row}

      + )} +

      {error.message}

      + {error.data?.__parsed_extra && ( + <> + +

      Extra field(s):

      + {error.data?.__parsed_extra.map((value: any, i: number) => ( + + {value} + + ))} + + )} +
      + {error.data !== undefined && isExpanded && ( + + )} +
      + ) + })} +
      +
      + )} + + + + ) +} + +export default SpreadsheetImportPreview diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreviewGrid.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreviewGrid.tsx new file mode 100644 index 00000000000..671ace28386 --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreviewGrid.tsx @@ -0,0 +1,55 @@ +import clsx from 'clsx' +import DataGrid from '@supabase/react-data-grid' + +const MAX_ROWS = 20 +const MAX_HEADERS = 20 + +interface SpreadsheetPreviewGridProps { + headers: string[] + rows?: any[] + height?: number +} + +const SpreadsheetPreviewGrid = ({ + headers = [], + rows = [], + height, +}: SpreadsheetPreviewGridProps) => { + const previewHeaders = headers.slice(0, MAX_HEADERS) + const previewRows = rows.slice(0, MAX_ROWS) + + return ( + { + const columnValues = previewRows.map((row) => row[header]?.toString() ?? '') + const maxLength = Math.max(...columnValues.map((el) => el.length)) + const maxWidth = maxLength > 20 ? 200 : maxLength * 10 + + return { + key: header, + name: header, + width: maxWidth, + resizable: true, + headerRenderer: () => ( +
      +

      {header}

      +
      + ), + formatter: ({ row }: { row: any }) => { + const isEmpty = !row[header] + return ( + + {isEmpty ? 'NULL' : row[header]} + + ) + }, + } + })} + rows={previewRows} + className="!border-l !border-r" + style={{ height: height || `${34 + 34 * (previewRows.length || 1)}px` }} + /> + ) +} + +export default SpreadsheetPreviewGrid diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx index 5970f19f8d8..cca74f59704 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx @@ -1,6 +1,16 @@ import { FC } from 'react' -import { isUndefined } from 'lodash' -import { Checkbox, Input, IconX, IconMenu, Popover, IconLink, IconSettings, Button } from 'ui' +import * as Tooltip from '@radix-ui/react-tooltip' +import { + Checkbox, + Input, + IconX, + IconMenu, + Popover, + IconLink, + IconSettings, + Button, + IconArrowRight, +} from 'ui' import type { PostgresType } from '@supabase/postgres-meta' import { ColumnField } from '../SidePanelEditor.types' @@ -8,6 +18,8 @@ import ColumnType from '../ColumnEditor/ColumnType' import InputWithSuggestions from '../ColumnEditor/InputWithSuggestions' import { Suggestion } from '../ColumnEditor/ColumnEditor.types' import { typeExpressionSuggestions } from '../ColumnEditor/ColumnEditor.constants' +import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants' +import { getForeignKeyDeletionAction } from '../ColumnEditor/ColumnEditor.utils' /** * [Joshen] For context: @@ -50,12 +62,12 @@ const Column: FC = ({ }) => { const suggestions: Suggestion[] = typeExpressionSuggestions?.[column.format] ?? [] - let settingsCount = 0 - - column.isNullable ? (settingsCount += 1) : null - column.isIdentity ? (settingsCount += 1) : null - column.isUnique ? (settingsCount += 1) : null - column.isArray ? (settingsCount += 1) : null + const settingsCount = [ + column.isNullable ? 1 : 0, + column.isIdentity ? 1 : 0, + column.isUnique ? 1 : 0, + column.isArray ? 1 : 0, + ].reduce((a, b) => a + b, 0) return (
      @@ -79,14 +91,54 @@ const Column: FC = ({ />
      -
      - +
      + + + + + + + +
      + {column.foreignKey === undefined ? ( + Edit foreign key relation + ) : ( +
      +

      Foreign key relation:

      +
      +

      + {column.foreignKey.source_schema}.{column.foreignKey.source_table_name}. + {column.foreignKey.source_column_name} +

      + +

      + {column.foreignKey.target_table_schema}. + {column.foreignKey.target_table_name}.{column.foreignKey.target_column_name} +

      +
      + {column.foreignKey.deletion_action !== + FOREIGN_KEY_DELETION_ACTION.NO_ACTION && ( +

      + On delete: {getForeignKeyDeletionAction(column.foreignKey.deletion_action)} +

      + )} +
      + )} +
      +
      +
      +
      @@ -96,7 +148,7 @@ const Column: FC = ({ size="small" showLabel={false} className="table-editor-column-type lg:gap-0 " - disabled={!isUndefined(column.foreignKey)} + disabled={column.foreignKey !== undefined} onOptionSelect={(format: string) => { onUpdateColumn({ format, defaultValue: null }) }} @@ -104,11 +156,11 @@ const Column: FC = ({
      -
      +
      = ({
      Extra options
      diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ColumnManagement.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ColumnManagement.tsx index a834c258255..5c55f64b17e 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ColumnManagement.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ColumnManagement.tsx @@ -1,14 +1,9 @@ import Link from 'next/link' -import React, { FC, useState } from 'react' -import { partition, isEmpty, isUndefined } from 'lodash' +import { FC, useState } from 'react' +import { partition, isEmpty } from 'lodash' import { Alert, Button, IconEdit, IconHelpCircle, IconKey, IconTrash, IconExternalLink } from 'ui' -import type { - PostgresTable, - PostgresColumn, - PostgresRelationship, - PostgresType, -} from '@supabase/postgres-meta' +import type { PostgresTable, PostgresColumn, PostgresType } from '@supabase/postgres-meta' import { DragDropContext, Droppable, @@ -23,12 +18,11 @@ import InformationBox from 'components/ui/InformationBox' import ForeignKeySelector from '../ForeignKeySelector/ForeignKeySelector' import { ImportContent } from './TableEditor.types' import { generateColumnField } from '../ColumnEditor/ColumnEditor.utils' -import { ColumnField } from '../SidePanelEditor.types' +import { ColumnField, ExtendedPostgresRelationship } from '../SidePanelEditor.types' import { TEXT_TYPES } from '../SidePanelEditor.constants' interface Props { table?: Partial - tables: PostgresTable[] columns?: ColumnField[] enumTypes: PostgresType[] importContent?: ImportContent @@ -40,7 +34,6 @@ interface Props { const ColumnManagement: FC = ({ table, - tables = [], columns = [], enumTypes = [], importContent, @@ -57,24 +50,28 @@ const ColumnManagement: FC = ({ (column: ColumnField) => column.isPrimaryKey ) - const saveColumnForeignKey = ( - foreignKeyConfiguration: { table: PostgresTable; column: PostgresColumn } | undefined - ) => { - if (!isUndefined(selectedColumnToEditRelation)) { + const saveColumnForeignKey = (foreignKeyConfiguration?: { + table: PostgresTable + column: PostgresColumn + deletionAction: string + }) => { + if (selectedColumnToEditRelation !== undefined) { onUpdateColumn(selectedColumnToEditRelation, { - foreignKey: !isUndefined(foreignKeyConfiguration) - ? { - id: 0, - constraint_name: '', - source_schema: table?.schema ?? '', - source_table_name: table?.name ?? '', - source_column_name: selectedColumnToEditRelation?.name, - target_table_schema: foreignKeyConfiguration.table.schema, - target_table_name: foreignKeyConfiguration.table.name, - target_column_name: foreignKeyConfiguration.column.name, - } - : undefined, - ...(!isUndefined(foreignKeyConfiguration) && { + foreignKey: + foreignKeyConfiguration !== undefined + ? { + id: 0, + constraint_name: '', + source_schema: table?.schema ?? '', + source_table_name: table?.name ?? '', + source_column_name: selectedColumnToEditRelation?.name, + target_table_schema: foreignKeyConfiguration.table.schema, + target_table_name: foreignKeyConfiguration.table.name, + target_column_name: foreignKeyConfiguration.column.name, + deletion_action: foreignKeyConfiguration.deletionAction, + } + : undefined, + ...(foreignKeyConfiguration !== undefined && { format: foreignKeyConfiguration.column.format, defaultValue: null, }), @@ -91,8 +88,8 @@ const ColumnManagement: FC = ({ changes.defaultValue = null } - if ('name' in changes && !isUndefined(column.foreignKey)) { - const foreignKey: PostgresRelationship = { + if ('name' in changes && column.foreignKey !== undefined) { + const foreignKey: ExtendedPostgresRelationship = { ...column.foreignKey, source_column_name: changes?.name ?? '', } @@ -166,12 +163,10 @@ const ColumnManagement: FC = ({
      {hasImportContent && ( -
      -

      - Your table will be created with {importContent?.rowCount?.toLocaleString()} rows and - the following {columns.length} columns. -

      -
      +

      + Your table will be created with {importContent?.rowCount?.toLocaleString()} rows and the + following {columns.length} columns. +

      )} {primaryKeyColumns.length === 0 && ( @@ -205,20 +200,22 @@ const ColumnManagement: FC = ({ - - -
      - - Recommended to use lowercase and use an underscore to separate words e.g. - column_name - -
      -
      + + + +
      + + Recommended to use lowercase and use an underscore to separate words e.g. + column_name + +
      +
      +
      @@ -233,19 +230,21 @@ const ColumnManagement: FC = ({ - - -
      - - Can be either a value or a SQL expression - -
      -
      + + + +
      + + Can be either a value or a SQL expression + +
      +
      +
      @@ -354,7 +353,7 @@ const ColumnManagement: FC = ({
      setSelectedColumnToEditRelation(undefined)} saveChanges={saveColumnForeignKey} /> diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetPreview.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetPreview.tsx deleted file mode 100644 index dc8a98ead37..00000000000 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetPreview.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { FC } from 'react' -import { isUndefined } from 'lodash' -import DataGrid from '@supabase/react-data-grid' - -interface Props { - headers: string[] - rows?: any[] -} - -const MAX_ROWS = 20 -const MAX_HEADERS = 20 - -const SpreadsheetPreview: FC = ({ headers = [], rows = [] }) => { - const previewHeaders = headers.slice(0, MAX_HEADERS) - const previewRows = rows.slice(0, MAX_ROWS) - return ( - { - return { - key: header, - name: header, - width: header.length * 10, - resizable: true, - headerRenderer: () => ( -
      -

      {header}

      -
      - ), - formatter: ({ row }: { row: any }) => ( - - {isUndefined(row[header]) ? 'NULL' : row[header]} - - ), - } - })} - rows={previewRows} - className="!border-l !border-r" - style={{ height: `${34 + 34 * (previewRows.length || 1)}px` }} - /> - ) -} - -export default SpreadsheetPreview diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx index 0d20cee9e41..7f0c9ad1464 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx @@ -1,13 +1,14 @@ +import Link from 'next/link' import { FC, useEffect, useState } from 'react' import { isUndefined, isEmpty } from 'lodash' -import { Badge, Checkbox, SidePanel, Input, Alert } from 'ui' +import { Badge, Checkbox, SidePanel, Input, Alert, IconBookOpen, Button } from 'ui' import type { PostgresTable, PostgresType } from '@supabase/postgres-meta' import { useStore } from 'hooks' import ActionBar from '../ActionBar' import HeaderTitle from './HeaderTitle' import ColumnManagement from './ColumnManagement' -import SpreadsheetImport from './SpreadsheetImport/SpreadsheetImport' +import { SpreadsheetImport } from '../' import { ColumnField, CreateTablePayload, UpdateTablePayload } from '../SidePanelEditor.types' import { DEFAULT_COLUMNS } from './TableEditor.constants' import { TableField, ImportContent } from './TableEditor.types' @@ -17,7 +18,8 @@ import { generateTableFieldFromPostgresTable, formatImportedContentToColumnFields, } from './TableEditor.utils' -import InformationBox from 'components/ui/InformationBox' +import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' interface Props { table?: PostgresTable @@ -51,9 +53,9 @@ const TableEditor: FC = ({ updateEditorDirty = () => {}, }) => { const { ui, meta } = useStore() + const { project } = useProjectContext() const isNewRecord = isUndefined(table) - const tables = meta.tables.list() const enumTypes = meta.types.list( (type: PostgresType) => !meta.excludedSchemas.includes(type.schema) ) @@ -73,6 +75,13 @@ const TableEditor: FC = ({ const [importContent, setImportContent] = useState() const [isImportingSpreadsheet, setIsImportingSpreadsheet] = useState(false) + const { data } = useForeignKeyConstraintsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + schema: table?.schema, + }) + const foreignKeyMeta = data || [] + useEffect(() => { if (visible) { setErrors({}) @@ -84,6 +93,7 @@ const TableEditor: FC = ({ } else { const tableFields = generateTableFieldFromPostgresTable( table!, + foreignKeyMeta, isDuplicating, isRealtimeEnabled ) @@ -216,18 +226,46 @@ const TableEditor: FC = ({

      Restrict access to your table by enabling RLS and writing Postgres policies.

      -

      - RLS is secure by default - all normal access to this table must be allowed by a - policy. -

      - {!tableFields.isRLSEnabled && ( + + {tableFields.isRLSEnabled ? ( +

      + You need to write a policy before you can query data from this table. + Without a policy, querying this table will result in an empty array{' '} + of results. +

      +

      + + + + + +

      + + ) : ( + - As such, anyone with the anonymous key can modify or delete your data. +

      As such, anyone with the anonymous key can modify or delete your data.

      +

      + + + + + +

      )} @@ -253,7 +291,6 @@ const TableEditor: FC = ({ {!isDuplicating && ( + selectedHeaders: string[] + resolve: () => void } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.utils.ts index 2f0d60f983a..c79570968ce 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.utils.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.utils.ts @@ -8,6 +8,7 @@ import { generateColumnField, generateColumnFieldFromPostgresColumn, } from '../ColumnEditor/ColumnEditor.utils' +import { ForeignKeyConstraint } from 'data/database/foreign-key-constraints-query' export const validateFields = (field: TableField) => { const errors = {} as any @@ -36,6 +37,7 @@ export const generateTableField = (): TableField => { export const generateTableFieldFromPostgresTable = ( table: PostgresTable, + foreignKeys: ForeignKeyConstraint[], isDuplicating = false, isRealtimeEnabled = false ): TableField => { @@ -45,7 +47,7 @@ export const generateTableFieldFromPostgresTable = ( comment: isDuplicating ? `This is a duplicate of ${table.name}` : table?.comment ?? '', // @ts-ignore columns: table.columns.map((column: PostgresColumn) => { - return generateColumnFieldFromPostgresColumn(column, table) + return generateColumnFieldFromPostgresColumn(column, table, foreignKeys) }), isRLSEnabled: table.rls_enabled, isRealtimeEnabled, @@ -53,9 +55,12 @@ export const generateTableFieldFromPostgresTable = ( } export const formatImportedContentToColumnFields = (importContent: ImportContent) => { - const columnFields = importContent.headers.map((header: string) => { - const columnType = importContent.columnTypeMap[header] - return generateColumnField({ name: header, format: columnType }) - }) + const { headers, selectedHeaders, columnTypeMap } = importContent + const columnFields = headers + .filter((header) => selectedHeaders.includes(header)) + .map((header) => { + const columnType = columnTypeMap[header] + return generateColumnField({ name: header, format: columnType }) + }) return columnFields } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/index.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/index.ts index 373f44921dd..6b0602c7648 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/index.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/index.ts @@ -2,7 +2,8 @@ import SidePanelEditor from './SidePanelEditor' import RowEditor from './RowEditor/RowEditor' import ColumnEditor from './ColumnEditor/ColumnEditor' import TableEditor from './TableEditor/TableEditor' +import SpreadsheetImport from './SpreadsheetImport/SpreadsheetImport' import ForeignKeySelector from './ForeignKeySelector/ForeignKeySelector' -export { RowEditor, ColumnEditor, TableEditor, ForeignKeySelector } +export { RowEditor, ColumnEditor, TableEditor, ForeignKeySelector, SpreadsheetImport } export default SidePanelEditor diff --git a/studio/components/interfaces/TableGridEditor/TableDefinition.tsx b/studio/components/interfaces/TableGridEditor/TableDefinition.tsx new file mode 100644 index 00000000000..d0d8b28dbe0 --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/TableDefinition.tsx @@ -0,0 +1,127 @@ +import Editor from '@monaco-editor/react' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { useTableDefinitionQuery } from 'data/database/table-definition-query' +import { useViewDefinitionQuery } from 'data/database/view-definition-query' +import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants' +import { useStore } from 'hooks' +import useEntityType from 'hooks/misc/useEntityType' +import { timeout } from 'lib/helpers' +import { observer } from 'mobx-react-lite' +import { useMemo, useRef } from 'react' +import { format } from 'sql-formatter' + +export interface TableDefinitionProps { + id?: number +} + +const TableDefinition = ({ id }: TableDefinitionProps) => { + const editorRef = useRef(null) + const monacoRef = useRef(null) + + const { ui } = useStore() + const { isDarkTheme } = ui + + const entityType = useEntityType(id) + + const { project } = useProjectContext() + const viewResult = useViewDefinitionQuery( + { + schema: entityType?.schema, + name: entityType?.name, + projectRef: project?.ref, + connectionString: project?.connectionString, + }, + { + enabled: + entityType?.type === ENTITY_TYPE.VIEW || entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW, + } + ) + + const tableResult = useTableDefinitionQuery( + { + schema: entityType?.schema, + name: entityType?.name, + projectRef: project?.ref, + connectionString: project?.connectionString, + }, + { + enabled: entityType?.type === ENTITY_TYPE.TABLE, + } + ) + + const { data: definition, isLoading } = + entityType?.type === ENTITY_TYPE.VIEW || entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW + ? viewResult + : tableResult + + const prepend = + entityType?.type === ENTITY_TYPE.VIEW + ? `create view ${entityType.schema}.${entityType.name} as\n` + : entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW + ? `create materialized view ${entityType.schema}.${entityType.name} as\n` + : '' + + const formattedDefinition = useMemo( + () => + definition + ? format(prepend + definition, { + language: 'postgresql', + keywordCase: 'lower', + }) + : undefined, + [definition] + ) + + const handleEditorOnMount = async (editor: any, monaco: any) => { + editorRef.current = editor + monacoRef.current = monaco + + // add margin above first line + editor.changeViewZones((accessor: any) => { + accessor.addZone({ + afterLineNumber: 0, + heightInPx: 4, + domNode: document.createElement('div'), + }) + }) + + // when editor did mount, it will need a delay before focus() works properly + await timeout(500) + editor?.focus() + } + + if (isLoading) { + return ( +
      + + + +
      + ) + } + + return ( +
      + +
      + ) +} + +export default observer(TableDefinition) diff --git a/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx b/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx index 442723952cc..c4909d2264e 100644 --- a/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx @@ -1,19 +1,42 @@ -import { FC, useRef, useEffect, useState } from 'react' +import { useRef, useEffect, useState } from 'react' import { observer } from 'mobx-react-lite' import { useRouter } from 'next/router' -import { find, isUndefined } from 'lodash' -import type { PostgresColumn, PostgresTable } from '@supabase/postgres-meta' +import { find, isUndefined, noop } from 'lodash' +import type { PostgresColumn, PostgresRelationship, PostgresTable } from '@supabase/postgres-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' +import { QueryKey, useQueryClient } from '@tanstack/react-query' import { SchemaView } from 'types' -import { checkPermissions, useFlag, useStore } from 'hooks' +import { checkPermissions, useFlag, useStore, useUrlState } from 'hooks' +import useEntityType from 'hooks/misc/useEntityType' +import { useParams } from 'common/hooks' import GridHeaderActions from './GridHeaderActions' import NotFoundState from './NotFoundState' import SidePanelEditor from './SidePanelEditor' -import { Dictionary, parseSupaTable, SupabaseGrid, SupabaseGridRef } from 'components/grid' +import { + Dictionary, + parseSupaTable, + SupabaseGrid, + SupabaseGridRef, + SupaTable, +} from 'components/grid' +import { sqlKeys } from 'data/sql/keys' +import { useProjectJsonSchemaQuery } from 'data/docs/project-json-schema-query' +import { useTableRowUpdateMutation } from 'data/table-rows/table-row-update-mutation' import { JsonEditValue } from './SidePanelEditor/RowEditor/RowEditor.types' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants' +import { + ForeignKeyConstraint, + useForeignKeyConstraintsQuery, +} from 'data/database/foreign-key-constraints-query' +import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants' +import { ForeignRowSelectorProps } from './SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector' +import TwoOptionToggle from 'components/ui/TwoOptionToggle' +import TableDefinition from './TableDefinition' +import APIDocumentationPanel from './APIDocumentationPanel' -interface Props { +export interface TableGridEditorProps { /** Theme for the editor */ theme?: 'dark' | 'light' @@ -21,7 +44,7 @@ interface Props { selectedTable: any // PostgresTable | SchemaView /** Determines what side panel editor to show */ - sidePanelKey?: 'row' | 'column' | 'table' | 'json' + sidePanelKey?: 'row' | 'column' | 'table' | 'json' | 'foreign-row-selector' | 'csv-import' /** Toggles if we're duplicating a table */ isDuplicating: boolean /** Selected entities if we're editing a row, column or table */ @@ -29,6 +52,11 @@ interface Props { selectedColumnToEdit?: PostgresColumn selectedTableToEdit?: PostgresTable selectedValueForJsonEdit?: JsonEditValue + selectedForeignKeyToEdit?: { + foreignKey: NonNullable + row: any + column: any + } onAddRow: () => void onEditRow: (row: Dictionary) => void @@ -36,10 +64,16 @@ interface Props { onEditColumn: (column: PostgresColumn) => void onDeleteColumn: (column: PostgresColumn) => void onExpandJSONEditor: (column: string, row: any) => void + onEditForeignKeyColumnValue: (args: { + foreignKey: NonNullable + row: any + column: any + }) => void onClosePanel: () => void + onImportData: () => void } -const TableGridEditor: FC = ({ +const TableGridEditor = ({ theme = 'dark', selectedSchema, @@ -50,47 +84,151 @@ const TableGridEditor: FC = ({ selectedColumnToEdit, selectedTableToEdit, selectedValueForJsonEdit, + selectedForeignKeyToEdit, - onAddRow = () => {}, - onEditRow = () => {}, - onAddColumn = () => {}, - onEditColumn = () => {}, - onDeleteColumn = () => {}, - onExpandJSONEditor = () => {}, - onClosePanel = () => {}, -}) => { + onAddRow = noop, + onEditRow = noop, + onAddColumn = noop, + onEditColumn = noop, + onDeleteColumn = noop, + onExpandJSONEditor = noop, + onEditForeignKeyColumnValue = noop, + onClosePanel = noop, + onImportData = noop, +}: TableGridEditorProps) => { const { meta, ui, vault } = useStore() const router = useRouter() + const { ref: projectRef, id } = useParams() const gridRef = useRef(null) - const projectRef = ui.selectedProject?.ref + const { project } = useProjectContext() const isVaultEnabled = useFlag('vaultExtension') const [encryptedColumns, setEncryptedColumns] = useState([]) + const [apiPreviewPanelOpen, setApiPreviewPanelOpen] = useState(false) + + const [{ view: selectedView = 'data' }, setUrlState] = useUrlState() + const setSelectedView = (view: string) => { + if (view === 'data') { + setUrlState({ view: undefined }) + } else { + setUrlState({ view }) + } + } + + const isReadOnly = + !checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') && + !checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns') const getEncryptedColumns = async (table: any) => { const columns = await vault.listEncryptedColumns(table.schema, table.name) setEncryptedColumns(columns) } + const queryClient = useQueryClient() + const { mutate: mutateUpdateTableRow } = useTableRowUpdateMutation({ + async onMutate({ projectRef, table, configuration, payload }) { + const primaryKeyColumns = new Set(Object.keys(configuration.identifiers)) + + const queryKey = sqlKeys.query(projectRef, [ + table.schema, + table.name, + { table: { name: table.name, schema: table.schema } }, + ]) + + await queryClient.cancelQueries(queryKey) + + const previousRowsQueries = queryClient.getQueriesData<{ result: any[] }>(queryKey) + + queryClient.setQueriesData<{ result: any[] }>(queryKey, (old) => { + return { + result: + old?.result.map((row) => { + // match primary keys + if ( + Object.entries(row) + .filter(([key]) => primaryKeyColumns.has(key)) + .every(([key, value]) => value === configuration.identifiers[key]) + ) { + return { ...row, ...payload } + } + + return row + }) ?? [], + } + }) + + return { previousRowsQueries } + }, + onError(error, _variables, context) { + const { previousRowsQueries } = context as { + previousRowsQueries: [ + QueryKey, + ( + | { + result: any[] + } + | undefined + ) + ][] + } + + previousRowsQueries.forEach(([queryKey, previousRows]) => { + if (previousRows) { + queryClient.setQueriesData(queryKey, previousRows) + } + queryClient.invalidateQueries(queryKey) + }) + + onError(error) + }, + }) + + const { refetch } = useProjectJsonSchemaQuery({ projectRef }) + const refreshDocs = async () => await refetch() + + const { data } = useForeignKeyConstraintsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + schema: selectedTable?.schema, + }) + const foreignKeyMeta = data || [] + useEffect(() => { if (selectedTable !== undefined && selectedTable.id !== undefined && isVaultEnabled) { getEncryptedColumns(selectedTable) } }, [selectedTable?.id]) + const entityType = useEntityType(selectedTable?.id) + + // NOTE: DO NOT PUT HOOKS AFTER THIS LINE if (isUndefined(selectedTable)) { - return + return } const tableId = selectedTable?.id - // @ts-ignore - const schema = meta.schemas.list().find((schema) => schema.name === selectedSchema) - const isViewSelected = !Object.keys(selectedTable).includes('rls_enabled') - const isForeignTableSelected = meta.foreignTables.byId(selectedTable.id) !== undefined - const isLocked = meta.excludedSchemas.includes(schema?.name ?? '') + const isViewSelected = + entityType?.type === ENTITY_TYPE.VIEW || entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW + const isTableSelected = entityType?.type === ENTITY_TYPE.TABLE + const isForeignTableSelected = entityType?.type === ENTITY_TYPE.FOREIGN_TABLE + const isLocked = meta.excludedSchemas.includes(entityType?.schema ?? '') const canUpdateTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') - const canEditViaTableEditor = !isViewSelected && !isForeignTableSelected && !isLocked + const canEditViaTableEditor = isTableSelected && !isLocked + + // [Joshen] We can tweak below to eventually support composite keys as the data + // returned from foreignKeyMeta should be easy to deal with, rather than pg-meta + const formattedRelationships = (selectedTable?.relationships ?? []).map( + (relationship: PostgresRelationship) => { + const relationshipMeta = foreignKeyMeta.find( + (fk: ForeignKeyConstraint) => fk.id === relationship.id + ) + return { + ...relationship, + deletion_action: relationshipMeta?.deletion_action ?? FOREIGN_KEY_DELETION_ACTION.NO_ACTION, + } + } + ) const gridTable = !isViewSelected && !isForeignTableSelected @@ -99,11 +237,16 @@ const TableGridEditor: FC = ({ table: selectedTable as PostgresTable, columns: (selectedTable as PostgresTable).columns ?? [], primaryKeys: (selectedTable as PostgresTable).primary_keys, - relationships: (selectedTable as PostgresTable).relationships, + relationships: formattedRelationships, }, encryptedColumns ) - : (selectedTable as SchemaView).name + : parseSupaTable({ + table: selectedTable as SchemaView, + columns: (selectedTable as SchemaView).columns ?? [], + primaryKeys: [], + relationships: [], + }) const gridKey = `${selectedTable.schema}_${selectedTable.name}` @@ -162,6 +305,37 @@ const TableGridEditor: FC = ({ }) } + const updateTableRow = (previousRow: any, updatedData: any) => { + if (!project) return + + const enumArrayColumns = selectedTable.columns + .filter((column: any) => { + return (column?.enums ?? []).length > 0 && column.data_type.toLowerCase() === 'array' + }) + .map((column: any) => column.name) + + const identifiers = {} as Dictionary + ;(selectedTable as PostgresTable).primary_keys.forEach( + (column) => (identifiers[column.name] = previousRow[column.name]) + ) + + const configuration = { identifiers } + + mutateUpdateTableRow({ + projectRef: project.ref, + connectionString: project.connectionString, + table: gridTable as SupaTable, + configuration, + payload: updatedData, + enumArrayColumns, + }) + } + + /** [Joshen] We're going to need to refactor SupabaseGrid eventually to make the code here more readable + * For context we previously built the SupabaseGrid as a reusable npm component, but eventually decided + * to just integrate it directly into the dashboard. The header, and body (+footer) should be decoupled. + */ + return ( <> = ({ theme={theme} gridProps={{ height: '100%' }} storageRef={projectRef} - editable={canUpdateTables && canEditViaTableEditor} + editable={!isReadOnly && canUpdateTables && canEditViaTableEditor} schema={selectedTable.schema} table={gridTable} + refreshDocs={refreshDocs} headerActions={ - canEditViaTableEditor && + isTableSelected || isViewSelected || canEditViaTableEditor ? ( + <> + {canEditViaTableEditor && ( + + )} + {(isTableSelected || isViewSelected) && ( + <> + {canEditViaTableEditor && ( +
      + )} +
      + +
      + + )} + + ) : null } onAddColumn={onAddColumn} onEditColumn={onSelectEditColumn} onDeleteColumn={onSelectDeleteColumn} onAddRow={onAddRow} + updateTableRow={updateTableRow} onEditRow={onEditRow} + onImportData={onImportData} onError={onError} onSqlQuery={onSqlQuery} onExpandJSONEditor={onExpandJSONEditor} - /> + onEditForeignKeyColumnValue={onEditForeignKeyColumnValue} + showCustomChildren={(isViewSelected || isTableSelected) && selectedView === 'definition'} + customHeader={ + (isViewSelected || isTableSelected) && selectedView === 'definition' ? ( +
      +

      + SQL Definition of {selectedTable.name}{' '} +

      +

      (Read only)

      +
      + ) : null + } + > + {(isViewSelected || isTableSelected) && } +
      + {!isUndefined(selectedSchema) && ( = ({ selectedColumnToEdit={selectedColumnToEdit} selectedTableToEdit={selectedTableToEdit} selectedValueForJsonEdit={selectedValueForJsonEdit} + selectedForeignKeyToEdit={selectedForeignKeyToEdit} sidePanelKey={sidePanelKey} onRowCreated={onRowCreated} onRowUpdated={onRowUpdated} @@ -202,6 +422,11 @@ const TableGridEditor: FC = ({ closePanel={onClosePanel} /> )} + + setApiPreviewPanelOpen(false)} + /> ) } diff --git a/studio/components/layouts/AccountLayout/AccountLayout.tsx b/studio/components/layouts/AccountLayout/AccountLayout.tsx index d070b44eff8..59b733ba78e 100644 --- a/studio/components/layouts/AccountLayout/AccountLayout.tsx +++ b/studio/components/layouts/AccountLayout/AccountLayout.tsx @@ -75,14 +75,14 @@ const AccountLayout: FC = ({ children, title, breadcrumbs }) => { links: [ { isActive: router.pathname === `/account/me`, - icon: '/img/user.svg', + icon: `${router.basePath}/img/user.svg`, label: 'Preferences', href: `/account/me`, key: `/account/me`, }, { isActive: router.pathname === `/account/tokens`, - icon: '/img/user.svg', + icon: `${router.basePath}/img/user.svg`, label: 'Access Tokens', href: `/account/tokens`, key: `/account/tokens`, @@ -97,16 +97,16 @@ const AccountLayout: FC = ({ children, title, breadcrumbs }) => { links: [ { key: 'ext-guides', - icon: '/img/book.svg', + icon: `${router.basePath}/img/book.svg`, label: 'Guides', href: 'https://supabase.com/docs', isExternal: true, }, { key: 'ext-guides', - icon: '/img/book-open.svg', + icon: `${router.basePath}/img/book-open.svg`, label: 'API Reference', - href: 'https://supabase.com/docs/guides/database/api', + href: 'https://supabase.com/docs/guides/api', isExternal: true, }, ], diff --git a/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx b/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx index f7fa11e4234..5b45d0b9064 100644 --- a/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx +++ b/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx @@ -16,7 +16,8 @@ interface Props { const DatabaseLayout: FC = ({ title, children }) => { const { meta, ui, vault, backups } = useStore() - const { isInitialized, isLoading, error } = meta.tables + const { isLoading } = meta.schemas + const { isInitialized, error } = meta.tables const project = ui.selectedProject const router = useRouter() diff --git a/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts b/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts index 774ad0fb2a7..48961f0397a 100644 --- a/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts +++ b/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts @@ -26,7 +26,6 @@ export const generateDatabaseMenu = (project?: Project): ProductMenuGroup[] => { name: 'Functions', key: 'functions', url: `/project/${ref}/database/functions`, - label: 'ALPHA', items: [], }, { @@ -49,7 +48,6 @@ export const generateDatabaseMenu = (project?: Project): ProductMenuGroup[] => { key: 'hooks', url: `/project/${ref}/database/hooks`, items: [], - label: 'ALPHA', }, ] : []), diff --git a/studio/components/layouts/DocsLayout/DocsLayout.tsx b/studio/components/layouts/DocsLayout/DocsLayout.tsx index c172f2c7a88..f4d677f2669 100644 --- a/studio/components/layouts/DocsLayout/DocsLayout.tsx +++ b/studio/components/layouts/DocsLayout/DocsLayout.tsx @@ -14,6 +14,8 @@ function DocsLayout({ title, children }: { title: string; children: ReactElement const { data, isLoading, error } = meta.openApi const getPage = () => { + if (router.pathname.endsWith('graphiql')) return 'graphiql' + const { page, resource } = router.query if (!page && !resource) return 'introduction' return (page || resource || '') as string diff --git a/studio/components/layouts/DocsLayout/DocsLayout.utils.tsx b/studio/components/layouts/DocsLayout/DocsLayout.utils.tsx index c97284cf3e1..99d4ae82d85 100644 --- a/studio/components/layouts/DocsLayout/DocsLayout.utils.tsx +++ b/studio/components/layouts/DocsLayout/DocsLayout.utils.tsx @@ -1,5 +1,7 @@ +import SVG from 'react-inlinesvg' import { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types' import { IconBook, IconBookOpen } from 'ui' +import { BASE_PATH } from 'lib/constants' export const generateDocsMenu = ( ref: string, @@ -58,6 +60,25 @@ export const generateDocsMenu = ( }), ], }, + { + title: 'GraphQL', + items: [ + { + name: 'GraphiQL', + key: 'graphiql', + url: `/project/${ref}/api/graphiql`, + icon: ( + code.replace(/svg/, 'svg class="m-auto text-color-inherit"')} + /> + ), + items: [], + }, + ], + }, { title: 'More Resources', items: [ @@ -72,7 +93,7 @@ export const generateDocsMenu = ( { name: 'API Reference', key: 'api-reference', - url: `https://supabase.com/docs/guides/database/api`, + url: `https://supabase.com/docs/guides/api`, icon: , items: [], isExternal: true, diff --git a/studio/components/layouts/FunctionsLayout.tsx b/studio/components/layouts/FunctionsLayout.tsx index 511d401da95..c7271b1d877 100644 --- a/studio/components/layouts/FunctionsLayout.tsx +++ b/studio/components/layouts/FunctionsLayout.tsx @@ -1,14 +1,17 @@ import Link from 'next/link' -import { useRouter } from 'next/router' -import { FC, useEffect, ReactNode } from 'react' +import { FC, ReactNode, useState } from 'react' import { observer } from 'mobx-react-lite' -import { IconCode } from 'ui' +import { Button, IconExternalLink, IconCode, Modal, IconTerminal } from 'ui' -import { checkPermissions, useStore, withAuth } from 'hooks' +import { checkPermissions, withAuth } from 'hooks' +import { useParams } from 'common/hooks' import FunctionsNav from '../interfaces/Functions/FunctionsNav' import BaseLayout from 'components/layouts' import NoPermission from 'components/ui/NoPermission' import { PermissionAction } from '@supabase/shared-types/out/constants' +import { TerminalInstructions } from 'components/interfaces/Functions' +import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' +import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query' interface Props { title?: string @@ -16,14 +19,10 @@ interface Props { } const FunctionsLayout: FC = ({ title, children }) => { - const { functions, ui } = useStore() - const router = useRouter() - - const { id, ref } = router.query - - useEffect(() => { - if (ui.selectedProjectRef) functions.load() - }, [ui.selectedProjectRef]) + const { functionSlug, ref } = useParams() + const [showTerminalInstructions, setShowTerminalInstructions] = useState(false) + const { data: functions, isLoading } = useEdgeFunctionsQuery({ projectRef: ref }) + const { data: selectedFunction } = useEdgeFunctionQuery({ projectRef: ref, slug: functionSlug }) const canReadFunctions = checkPermissions(PermissionAction.FUNCTIONS_READ, '*') if (!canReadFunctions) { @@ -36,27 +35,21 @@ const FunctionsLayout: FC = ({ title, children }) => { ) } - const item = id ? functions.byId(id) : null - const name = item?.name || '' - - const hasFunctions = functions.list().length > 0 + const name = selectedFunction?.name || '' + const hasFunctions = (functions ?? []).length > 0 const centered = !hasFunctions return ( - + {centered ? ( <>
      @@ -87,7 +80,7 @@ const FunctionsLayout: FC = ({ title, children }) => { ].join(' ')} >
      -
      +
      = ({ title, children }) => {
      -
      - -

      - Edge Functions -

      - -

      Beta

      +
      +
      + +

      + Edge Functions +

      + +

      Beta

      + {name && ( +
      + + + + + +
      {name}
      +
      + )} +
      +
      + + + + + + +
      - - {name && ( - <> - - - - - -
      {name}
      - - )}
      - {item && } + {selectedFunction !== undefined && }
      = ({ title, children }) => {
      )} + + setShowTerminalInstructions(false)} + header={

      Deploying an edge function to your project

      } + customFooter={ +
      + +
      + } + > +
      + +
      +
      ) } diff --git a/studio/components/layouts/LogsLayout/LogsMenu.utils.ts b/studio/components/layouts/LogsLayout/LogsMenu.utils.ts index 26a7eaf0748..64920f230fb 100644 --- a/studio/components/layouts/LogsLayout/LogsMenu.utils.ts +++ b/studio/components/layouts/LogsLayout/LogsMenu.utils.ts @@ -1,75 +1,75 @@ import { Project } from 'types' -import { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types' +import { ProductMenuGroup, ProductMenuGroupItem } from 'components/ui/ProductMenu/ProductMenu.types' import { IS_PLATFORM } from 'lib/constants' export const generateLogsMenu = (project?: Project): ProductMenuGroup[] => { const ref = project?.ref ?? 'default' return [ - ...(IS_PLATFORM - ? [ - { - title: 'Logs Explorer', - items: [ - { key: 'explorer', name: 'Query', root: true }, - { key: 'saved', name: 'Saved Queries' }, - { key: 'recent', name: 'Recent Queries' }, - { key: 'templates', name: 'Templates' }, - ].map(({ key, name, root }) => ({ - name, - key, - url: `/project/${ref}/logs/explorer${root ? '' : '/' + key}`, + { + title: 'Logs Explorer', + items: ( + [ + { key: 'explorer', name: 'Query', root: true }, + IS_PLATFORM ? { key: 'saved', name: 'Saved Queries' } : null, + { key: 'recent', name: 'Recent Queries' }, + { key: 'templates', name: 'Templates' }, + ].filter((item) => item) as { name: string; key: string; root: boolean }[] + ).map(({ key, name, root }) => ({ + name, + key, + url: `/project/${ref}/logs/explorer${root ? '' : '/' + key}`, + items: [], + })), + }, + { + items: [ + { + name: IS_PLATFORM ? 'API Edge Network' : 'Kong API', + key: 'edge-logs', + url: `/project/${ref}/logs/edge-logs`, + items: [], + }, + { + name: 'Postgres', + key: 'postgres-logs', + url: `/project/${ref}/logs/postgres-logs`, + items: [], + }, + { + name: 'PostgREST', + key: 'postgrest-logs', + url: `/project/${ref}/logs/postgrest-logs`, + items: [], + }, + IS_PLATFORM + ? { + name: 'PgBouncer', + key: 'pgbouncer-logs', + url: `/project/${ref}/logs/pgbouncer-logs`, items: [], - })), - }, - { - items: [ - { - name: 'API Edge Network', - key: 'edge-logs', - url: `/project/${ref}/logs/edge-logs`, - items: [], - }, - { - name: 'Postgres', - key: 'postgres-logs', - url: `/project/${ref}/logs/postgres-logs`, - items: [], - }, - { - name: 'PostgREST', - key: 'postgrest-logs', - url: `/project/${ref}/logs/postgrest-logs`, - items: [], - }, - { - name: 'PgBouncer', - key: 'pgbouncer-logs', - url: `/project/${ref}/logs/pgbouncer-logs`, - items: [], - }, - { - name: 'Auth', - key: 'auth-logs', - url: `/project/${ref}/logs/auth-logs`, - items: [], - }, - { - name: 'Storage', - key: 'storage-logs', - url: `/project/${ref}/logs/storage-logs`, - items: [], - }, - - { - name: 'Realtime', - key: 'realtime-logs', - url: `/project/${ref}/logs/realtime-logs`, - items: [], - }, - ], - }, - ] - : []), + } + : null, + , + { + name: 'Auth', + key: 'auth-logs', + url: `/project/${ref}/logs/auth-logs`, + items: [], + }, + { + name: 'Storage', + key: 'storage-logs', + url: `/project/${ref}/logs/storage-logs`, + items: [], + }, + { + name: 'Realtime', + key: 'realtime-logs', + url: `/project/${ref}/logs/realtime-logs`, + items: [], + }, + ].filter((item) => item) as ProductMenuGroupItem[], + }, ] } diff --git a/studio/components/layouts/ProjectLayout/BuildingState.tsx b/studio/components/layouts/ProjectLayout/BuildingState.tsx index 01b4af13c10..06c7c221db9 100644 --- a/studio/components/layouts/ProjectLayout/BuildingState.tsx +++ b/studio/components/layouts/ProjectLayout/BuildingState.tsx @@ -111,7 +111,9 @@ const BuildingState: FC = ({ project }) => { support ticket.

      - + + + } 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/HelpPopover.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPopover.tsx index fe837159d1c..7f8bdc90e39 100644 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPopover.tsx +++ b/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPopover.tsx @@ -37,9 +37,11 @@ const HelpPopover: FC = () => {

      - + + + @@ -83,14 +85,16 @@ const HelpPopover: FC = () => { > discord illustration header @@ -106,7 +110,7 @@ const HelpPopover: FC = () => { > discord illustration header { 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 && - resourcesExceededLimits.length > 0 + resourcesExceededLimits.length > 0 && + useFlag('overusageBadge') return (
      - {/* [Joshen TODO] Temporarily hidden until usage endpoint is sorted out */} - {/* {showOverUsageBadge && ( + {/* [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 && ( + )} + + {showOverUsageBadge && ( + - )} */} + )} )} diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopover/NotificationRow.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopover/NotificationRow.tsx index 450722f5a8c..0b3fe098097 100644 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopover/NotificationRow.tsx +++ b/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopover/NotificationRow.tsx @@ -97,17 +97,19 @@ const NotificationRow: FC = ({ onClick={() => dismissNotification(notification.id)} /> - - -
      - Dismiss -
      -
      + + + +
      + Dismiss +
      +
      +
      diff --git a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx index 913d8d9010d..3d534b80502 100644 --- a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx +++ b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx @@ -13,7 +13,7 @@ import { generateToolRoutes, } from './NavigationBar.utils' import NavigationIconButton from './NavigationIconButton' -import { useParams } from 'hooks/misc/useParams' +import { useParams } from 'common/hooks' interface Props {} @@ -42,7 +42,7 @@ const NavigationBar: FC = ({}) => { Supabase diff --git a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx index 5a2c1a7bac5..783e7b4aeb9 100644 --- a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx +++ b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx @@ -12,7 +12,8 @@ import SVG from 'react-inlinesvg' import { ProjectBase } from 'types' import { Route } from 'components/ui/ui.types' -import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' +import { BASE_PATH, IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' +import { SHOW_O11Y } from 'components/interfaces/Settings/Logs' export const generateToolRoutes = (ref?: string, project?: ProjectBase): Route[] => { const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP @@ -27,7 +28,7 @@ export const generateToolRoutes = (ref?: string, project?: ProjectBase): Route[] label: 'Table Editor', icon: ( code.replace(/svg/, 'svg class="m-auto text-color-inherit"')} /> @@ -41,7 +42,7 @@ export const generateToolRoutes = (ref?: string, project?: ProjectBase): Route[] label: 'SQL Editor', icon: ( code.replace(/svg/, 'svg class="m-auto text-color-inherit"')} /> @@ -139,7 +140,7 @@ export const generateOtherRoutes = (ref?: string, project?: ProjectBase): Route[ }, ] : []), - ...(IS_PLATFORM + ...(SHOW_O11Y ? [ { key: 'logs', diff --git a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconButton.tsx b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconButton.tsx index e1d751dd6b4..078addfffa2 100644 --- a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconButton.tsx +++ b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconButton.tsx @@ -31,17 +31,19 @@ const NavigationIconButton: FC = ({ route, isActive = false }) => { - - -
      - {route.label} -
      -
      + + + +
      + {route.label} +
      +
      +
      ) } diff --git a/studio/components/layouts/ProjectLayout/ProjectContext.tsx b/studio/components/layouts/ProjectLayout/ProjectContext.tsx index 7be2501528c..aa78d0ac3ad 100644 --- a/studio/components/layouts/ProjectLayout/ProjectContext.tsx +++ b/studio/components/layouts/ProjectLayout/ProjectContext.tsx @@ -1,4 +1,5 @@ import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import { observer } from 'mobx-react-lite' import { createContext, PropsWithChildren, useContext, useMemo } from 'react' import { Project } from 'types' @@ -44,3 +45,9 @@ export const ProjectContextProvider = observer( return {children} } ) + +export const ProjectContextFromParamsProvider = ({ children }: PropsWithChildren<{}>) => { + const { ref: projectRef } = useParams() + + return {children} +} diff --git a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx index 3def6eaba71..71a37032b0f 100644 --- a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx +++ b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx @@ -2,7 +2,8 @@ import Head from 'next/head' import { FC, ReactNode, PropsWithChildren, Fragment } from 'react' import { observer } from 'mobx-react-lite' import { useRouter } from 'next/router' -import { useStore, withAuth, useFlag, useParams } from 'hooks' +import { useStore, withAuth, useFlag } from 'hooks' +import { useParams } from 'common/hooks' import { PROJECT_STATUS } from 'lib/constants' import Connecting from 'components/ui/Loading' @@ -14,6 +15,7 @@ import PausingState from './PausingState' import BuildingState from './BuildingState' import { ProjectContextProvider } from './ProjectContext' import RestoringState from './RestoringState' +import UpgradingState from './UpgradingState' interface Props { title?: string @@ -115,10 +117,9 @@ const ContentWrapper: FC = observer(({ isLoading, children const requiresDbConnection: boolean = router.pathname !== '/project/[ref]/settings/general' const requiresPostgrestConnection = !routesToIgnorePostgrestConnection.includes(router.pathname) + const isProjectUpgrading = ui.selectedProject?.status === PROJECT_STATUS.UPGRADING const isProjectRestoring = ui.selectedProject?.status === PROJECT_STATUS.RESTORING - const isProjectBuilding = [PROJECT_STATUS.COMING_UP, PROJECT_STATUS.RESTORING].includes( - ui.selectedProject?.status ?? '' - ) + const isProjectBuilding = ui.selectedProject?.status === PROJECT_STATUS.COMING_UP const isProjectPausing = ui.selectedProject?.status === PROJECT_STATUS.GOING_DOWN const isProjectOffline = ui.selectedProject?.postgrestStatus === 'OFFLINE' @@ -126,6 +127,8 @@ const ContentWrapper: FC = observer(({ isLoading, children <> {isLoading || ui.selectedProject === undefined ? ( + ) : isProjectUpgrading ? ( + ) : isProjectPausing ? ( ) : requiresPostgrestConnection && isProjectOffline ? ( diff --git a/studio/components/layouts/ProjectLayout/ProjectPausedState.tsx b/studio/components/layouts/ProjectLayout/ProjectPausedState.tsx index 8181045826d..0733a2fbc81 100644 --- a/studio/components/layouts/ProjectLayout/ProjectPausedState.tsx +++ b/studio/components/layouts/ProjectLayout/ProjectPausedState.tsx @@ -72,19 +72,21 @@ const ProjectPausedState: FC = ({ project }) => { {!canResumeProject && ( - - -
      - - You need additional permissions to resume this project - -
      -
      + + + +
      + + You need additional permissions to resume this project + +
      +
      +
      )} diff --git a/studio/components/layouts/ProjectLayout/UpgradingState/UpgradingState.constants.ts b/studio/components/layouts/ProjectLayout/UpgradingState/UpgradingState.constants.ts new file mode 100644 index 00000000000..e0423da8708 --- /dev/null +++ b/studio/components/layouts/ProjectLayout/UpgradingState/UpgradingState.constants.ts @@ -0,0 +1,52 @@ +import { DatabaseUpgradeProgress } from '@supabase/shared-types/out/events' + +export const DATABASE_UPGRADE_MESSAGES = [ + { + key: DatabaseUpgradeProgress.Started, + initial: 'Prepare new server', + progress: 'Preparing new server', + completed: 'Prepared new server', + }, + { + key: DatabaseUpgradeProgress.LaunchedUpgradedInstance, + initial: 'Prepare new server for migration', + progress: 'Preparing new server for migration', + completed: 'New server ready for migration', + }, + { + key: DatabaseUpgradeProgress.DetachedVolumeFromUpgradedInstance, + initial: 'Execute pre-upgrade checks', + progress: 'Executing pre-upgrade checks', + completed: 'Completed pre-upgrade checks', + }, + { + key: DatabaseUpgradeProgress.AttachedVolumeToOriginalInstance, + initial: 'Shut down API services', + progress: 'Shutting down API services', + completed: 'Completed shutting down API services', + }, + { + key: DatabaseUpgradeProgress.InitiatedDataUpgrade, + initial: 'Migrate to new database', + progress: 'Migrating to new database', + completed: 'Completed migration to new database', + }, + { + key: DatabaseUpgradeProgress.CompletedDataUpgrade, + initial: 'Execute database migration', + progress: 'Executing database migration', + completed: 'Completed database migration', + }, + { + key: DatabaseUpgradeProgress.DetachedVolumeFromOriginalInstance, + initial: 'Update database extensions', + progress: 'Updating database extensions', + completed: 'Updated database extensions', + }, + { + key: DatabaseUpgradeProgress.AttachedVolumeToUpgradedInstance, + initial: 'Optimize database', + progress: 'Optimizing database', + completed: 'Completed optimization of database', + }, +] diff --git a/studio/components/layouts/ProjectLayout/UpgradingState/UpgradingState.tsx b/studio/components/layouts/ProjectLayout/UpgradingState/UpgradingState.tsx new file mode 100644 index 00000000000..fbfacac365a --- /dev/null +++ b/studio/components/layouts/ProjectLayout/UpgradingState/UpgradingState.tsx @@ -0,0 +1,232 @@ +import dayjs from 'dayjs' +import Link from 'next/link' +import { useState } from 'react' +import * as Tooltip from '@radix-ui/react-tooltip' +import { + Button, + IconCircle, + IconCheckCircle, + IconSettings, + IconAlertCircle, + IconLoader, + IconCheck, + IconMaximize2, + IconMinimize2, +} from 'ui' +import { DatabaseUpgradeStatus } from '@supabase/shared-types/out/events' + +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' +import { IS_PLATFORM } from 'lib/constants' +import { DATABASE_UPGRADE_MESSAGES } from './UpgradingState.constants' +import { useProjectUpgradingStatusQuery } from 'data/config/project-upgrade-status-query' + +const UpgradingState = () => { + const { ref } = useParams() + const { app, ui, meta } = useStore() + const [loading, setLoading] = useState(false) + const [isExpanded, setIsExpanded] = useState(false) + const { data } = useProjectUpgradingStatusQuery( + { + projectRef: ref, + projectStatus: ui.selectedProject?.status, + }, + { + enabled: IS_PLATFORM, + } + ) + + const project = ui.selectedProject + const { initiated_at, status, progress, target_version, error } = + data?.databaseUpgradeStatus ?? {} + const progressStage = Number((progress || '').split('_')[0]) + + const isFailed = status === DatabaseUpgradeStatus.Failed + const isCompleted = status === DatabaseUpgradeStatus.Upgraded + + const initiatedAt = dayjs(initiated_at ?? 0).format('DD MMM YYYY HH:mm:ss (ZZ)') + const initiatedAtUTC = dayjs(initiated_at ?? 0) + .utc() + .format('DD MMM YYYY HH:mm:ss') + + const refetchProjectDetails = async () => { + setLoading(true) + await app.projects.fetchDetail(project?.ref ?? '', (project) => meta.setProjectDetails(project)) + } + + const subject = 'Upgrade%20failed%20for%20project' + const message = `Upgrade information:%0A• Initiated at: ${initiated_at}%0A• Target Version: ${target_version}%0A• Error: ${error}` + + return ( +
      +
      +
      +

      {project?.name}

      +
      +
      +
      + {isCompleted ? ( +
      +
      + +
      +
      +

      Upgrade completed!

      +

      + Your project has been successfully upgraded to Postgres {target_version} and is + now back online. +

      +
      +
      + +
      +
      + ) : isFailed ? ( +
      +
      + +
      +
      +

      We ran into an issue while upgrading your project

      +

      + Your project is back online and its data is not affected. Please reach out to us + via our support form for assistance with the upgrade. +

      +
      +
      + + + + + + +
      +
      + ) : ( +
      +
      +
      + +
      + +
      +
      +

      Upgrading in progress

      +

      + Upgrades can take from a few minutes up to several hours depending on the size + of your database. Your project will be offline while it is being upgraded. +

      + +
      + {isExpanded ? ( + setIsExpanded(false)} + /> + ) : ( + setIsExpanded(true)} + /> + )} +
      6 + ? 5 + : progressStage - 2) * -28 + }px`, + }} + > + {DATABASE_UPGRADE_MESSAGES.map((message, idx: number) => { + const isCurrent = message.key === progress + const isCompleted = progressStage > idx + return ( +
      + {isCurrent ? ( +
      + +
      + ) : isCompleted ? ( +
      + +
      + ) : ( +
      + )} +

      + {isCurrent + ? message.progress + : isCompleted + ? message.completed + : message.initial} +

      +
      + ) + })} +
      +
      + + {initiated_at !== undefined && ( + + +

      + Started on: {initiatedAtUTC} (UTC) +

      +
      + + + +
      + {initiatedAt} +
      +
      +
      +
      + )} +
      +
      + )} +
      +
      +
      +
      + ) +} + +export default UpgradingState diff --git a/studio/components/layouts/ProjectLayout/UpgradingState/index.ts b/studio/components/layouts/ProjectLayout/UpgradingState/index.ts new file mode 100644 index 00000000000..eb515bf7666 --- /dev/null +++ b/studio/components/layouts/ProjectLayout/UpgradingState/index.ts @@ -0,0 +1,2 @@ +import UpgradingState from './UpgradingState' +export default UpgradingState diff --git a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts index 8e6142aaef8..4e088fd0ae6 100644 --- a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts +++ b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts @@ -1,10 +1,8 @@ import { Project } from 'types' import { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types' -import { useFlag } from 'hooks' export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { const ref = project?.ref ?? 'default' - const productReports = useFlag('productReports') return [ { @@ -20,28 +18,26 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { }, { items: [ - ...(productReports - ? [ - { - name: 'API', - key: 'api', - url: `/project/${ref}/reports/api`, - items: [], - }, - { - name: 'Auth', - key: 'auth', - url: `/project/${ref}/reports/auth`, - items: [], - }, - ] - : []), + { + name: 'API', + key: 'api-overview', + url: `/project/${ref}/reports/api-overview`, + items: [], + label: 'NEW', + }, { name: 'Database', key: 'database', 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/SQLEditorLayout/SQLEditorLayout.tsx b/studio/components/layouts/SQLEditorLayout/SQLEditorLayout.tsx index 57dac2a1607..bee4b202877 100644 --- a/studio/components/layouts/SQLEditorLayout/SQLEditorLayout.tsx +++ b/studio/components/layouts/SQLEditorLayout/SQLEditorLayout.tsx @@ -1,27 +1,28 @@ -import { FC, ReactNode, useEffect } from 'react' +import { ReactNode, useEffect } from 'react' import { observer } from 'mobx-react-lite' import { useStore, withAuth } from 'hooks' +import { useProfileQuery } from 'data/profile/profile-query' import Error from 'components/ui/Error' import { useSqlEditorStore, SqlEditorContext } from 'localStores/sqlEditor/SqlEditorStore' import ProjectLayout from '../ProjectLayout/ProjectLayout' import SQLEditorMenu from './SQLEditorMenu' -interface Props { +export interface SQLEditorLayoutProps { title: string children: ReactNode } -const SQLEditorLayout: FC = ({ title, children }) => { +const SQLEditorLayout = ({ title, children }: SQLEditorLayoutProps) => { const { ui, content, meta } = useStore() - const { profile: user } = ui + const { data: profile } = useProfileQuery() const sqlEditorStore = useSqlEditorStore(ui.selectedProject?.ref, meta) useEffect(() => { if (sqlEditorStore) { // this calls content.load() for us, as well as loading tabs - sqlEditorStore.loadRemotePersistentData(content, user?.id) + sqlEditorStore.loadRemotePersistentData(content, profile?.id) } }, [sqlEditorStore]) diff --git a/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx b/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx index 262e106b899..699839ab3df 100644 --- a/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx +++ b/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx @@ -20,6 +20,7 @@ import { useOptimisticSqlSnippetCreate, useStore, checkPermissions } from 'hooks import { IS_PLATFORM } from 'lib/constants' import QueryTab from 'localStores/sqlEditor/QueryTab' import { useSqlStore, TAB_TYPES } from 'localStores/sqlEditor/SqlEditorStore' +import { useProfileQuery } from 'data/profile/profile-query' import RenameQuery from 'components/to-be-cleaned/SqlEditor/RenameQuery' import ConfirmationModal from 'components/ui/ConfirmationModal' @@ -45,10 +46,8 @@ const OpenQueryItem = observer( ) const DropdownMenu = observer(({ tabInfo }: { tabInfo: QueryTab }) => { - const { - ui: { profile: user }, - content: contentStore, - } = useStore() + const { data: profile } = useProfileQuery() + const { content: contentStore } = useStore() const sqlEditorStore: any = useSqlStore() @@ -114,7 +113,7 @@ const DropdownMenu = observer(({ tabInfo }: { tabInfo: QueryTab }) => { await contentStore.del(id) sqlEditorStore.loadTabs( - sqlEditorStore.tabsFromContentStore(contentStore, user?.id), + sqlEditorStore.tabsFromContentStore(contentStore, profile?.id), false ) }} @@ -129,13 +128,13 @@ const DropdownMenu = observer(({ tabInfo }: { tabInfo: QueryTab }) => { }) const SideBarContent = observer(() => { - const { ui } = useStore() + const { data: profile } = useProfileQuery() const sqlEditorStore: any = useSqlStore() const [filterString, setFilterString] = useState('') const canCreateSQLSnippet = checkPermissions(PermissionAction.CREATE, 'user_content', { - resource: { type: 'sql', owner_id: ui.profile?.id }, - subject: { id: ui.profile?.id }, + resource: { type: 'sql', owner_id: profile?.id }, + subject: { id: profile?.id }, }) const handleNewQuery = useOptimisticSqlSnippetCreate(canCreateSQLSnippet) diff --git a/studio/components/layouts/SignInLayout/ForgotPasswordLayout.tsx b/studio/components/layouts/SignInLayout/ForgotPasswordLayout.tsx index 6e6f4ef2eb5..7de8e7cfaf3 100644 --- a/studio/components/layouts/SignInLayout/ForgotPasswordLayout.tsx +++ b/studio/components/layouts/SignInLayout/ForgotPasswordLayout.tsx @@ -1,8 +1,9 @@ -import { useStore } from 'hooks' import { observer } from 'mobx-react-lite' import Image from 'next/image' import Link from 'next/link' import { PropsWithChildren } from 'react' +import { useStore } from 'hooks' +import { BASE_PATH } from 'lib/constants' type ForgotPasswordLayoutProps = { heading: string @@ -31,7 +32,11 @@ const ForgotPasswordLayout = ({ + `} + - - - {getLayout()} - - + + + + {getLayout()} + + + - - - - + + + + + ) } -export default MyApp + +export default CustomApp diff --git a/studio/pages/_document.tsx b/studio/pages/_document.tsx index 877f4fac274..564b3755aea 100644 --- a/studio/pages/_document.tsx +++ b/studio/pages/_document.tsx @@ -16,7 +16,7 @@ class MyDocument extends Document { rel="stylesheet" type="text/css" data-name="vs/editor/editor.main" - href="https://cdn.jsdelivr.net/npm/monaco-editor@0.28.1/min/vs/editor/editor.main.css" + href="https://cdn.jsdelivr.net/npm/monaco-editor@0.37.0/min/vs/editor/editor.main.css" /> diff --git a/studio/pages/account/me.tsx b/studio/pages/account/me.tsx index e5cdeeeb8bd..6c00e76aa4e 100644 --- a/studio/pages/account/me.tsx +++ b/studio/pages/account/me.tsx @@ -1,14 +1,13 @@ import { observer } from 'mobx-react-lite' -import { useEffect, useState } from 'react' import { Button, IconMoon, IconSun, Input, Listbox } from 'ui' -import { Session } from '@supabase/supabase-js' import { AccountLayout } from 'components/layouts' import SchemaFormPanel from 'components/to-be-cleaned/forms/SchemaFormPanel' import Panel from 'components/ui/Panel' +import { Profile as ProfileType, useProfileQuery } from 'data/profile/profile-query' import { useProfileUpdateMutation } from 'data/profile/profile-update-mutation' import { useStore } from 'hooks' -import { auth } from 'lib/gotrue' +import { useSession } from 'lib/auth' import Link from 'next/link' import { NextPageWithLayout } from 'types' @@ -38,12 +37,14 @@ export default User const ProfileCard = observer(() => { const { ui } = useStore() - const { mutate } = useProfileUpdateMutation() - const user = ui.profile + const { mutateAsync } = useProfileUpdateMutation() + + const { data: profile } = useProfileQuery() + // TODO: ^ handle loading state const updateUser = async (model: any) => { try { - mutate({ + await mutateAsync({ firstName: model.first_name, lastName: model.last_name, }) @@ -58,26 +59,10 @@ const ProfileCard = observer(() => { } } - const [session, setSession] = useState(null) - - useEffect(() => { - let cancel = false - ;(async () => { - const { - data: { session }, - } = await auth.getSession() - if (session && !cancel) setSession(session) - })() - - return () => { - cancel = true - } - }, []) - return (
      - +
      @@ -93,8 +78,8 @@ const ProfileCard = observer(() => { }, }} model={{ - first_name: user?.first_name ?? '', - last_name: user?.last_name ?? '', + first_name: profile?.first_name ?? '', + last_name: profile?.last_name ?? '', }} onSubmit={updateUser} /> @@ -107,8 +92,8 @@ const ProfileCard = observer(() => { ) }) -const Profile = observer(({ session }: any) => { - const { ui } = useStore() +const Profile = ({ profile }: { profile?: ProfileType }) => { + const session = useSession() return ( { disabled label="Username" layout="horizontal" - value={ui.profile?.username ?? ''} + value={profile?.username ?? ''} /> {session?.user.app_metadata.provider === 'email' && (
      @@ -154,7 +139,7 @@ const Profile = observer(({ session }: any) => { ) -}) +} const ThemeSettings = observer(() => { const { ui } = useStore() diff --git a/studio/pages/account/tokens.tsx b/studio/pages/account/tokens.tsx index 1ccd1c420f8..a7f85064b93 100644 --- a/studio/pages/account/tokens.tsx +++ b/studio/pages/account/tokens.tsx @@ -1,13 +1,51 @@ -import { AccountLayout } from 'components/layouts' -import { AccessTokenList, NewAccessTokenButton } from 'components/interfaces/Account' +import { useState } from 'react' import { NextPageWithLayout } from 'types' +import { FormHeader } from 'components/ui/Forms' +import { AccountLayout } from 'components/layouts' +import { + AccessTokenList, + NewTokenBanner, + NewAccessTokenButton, +} from 'components/interfaces/Account' +import { NewAccessToken } from 'data/access-tokens/access-tokens-create-mutation' + +import Link from 'next/link' +import { Button, IconExternalLink } from 'ui' const UserAccessTokens: NextPageWithLayout = () => { + const [newToken, setNewToken] = useState() + return ( -
      -

      Access Tokens

      - - +
      ) } @@ -15,12 +53,7 @@ const UserAccessTokens: NextPageWithLayout = () => { UserAccessTokens.getLayout = (page) => ( {page} diff --git a/studio/pages/api/constants.ts b/studio/pages/api/constants.ts index 7d40e62c010..e798f1e5693 100644 --- a/studio/pages/api/constants.ts +++ b/studio/pages/api/constants.ts @@ -1,5 +1,8 @@ const PUBLIC_URL = new URL(process.env.SUPABASE_PUBLIC_URL || 'http://localhost:8000') +// Use LOGFLARE_URL until analytics/v1/ routing is supported +export const PROJECT_ANALYTICS_URL = `${process.env.LOGFLARE_URL}/api/` + export const PROJECT_REST_URL = `${PUBLIC_URL.origin}/rest/v1/` export const PROJECT_ENDPOINT = PUBLIC_URL.host export const PROJECT_ENDPOINT_PROTOCOL = PUBLIC_URL.protocol.replace(':', '') diff --git a/studio/pages/api/pg-meta/[ref]/materialized-views.ts b/studio/pages/api/pg-meta/[ref]/materialized-views.ts new file mode 100644 index 00000000000..5bd5550dbfa --- /dev/null +++ b/studio/pages/api/pg-meta/[ref]/materialized-views.ts @@ -0,0 +1,32 @@ +import { NextApiRequest, NextApiResponse } from 'next' + +import apiWrapper from 'lib/api/apiWrapper' +import { get } from 'lib/common/fetch' +import { constructHeaders } from 'lib/api/apiHelpers' +import { PG_META_URL } from 'lib/constants' + +export default (req: NextApiRequest, res: NextApiResponse) => + apiWrapper(req, res, handler, { withAuth: true }) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'GET': + return handleGetAll(req, res) + default: + res.setHeader('Allow', ['GET']) + res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { + const headers = constructHeaders(req.headers) + let response = await get(`${PG_META_URL}/materialized-views`, { + headers, + }) + if (response.error) { + return res.status(400).json({ error: response.error }) + } + return res.status(200).json(response) +} diff --git a/studio/pages/api/projects/[ref]/analytics/endpoints/[name].ts b/studio/pages/api/projects/[ref]/analytics/endpoints/[name].ts new file mode 100644 index 00000000000..2b7ab5ec113 --- /dev/null +++ b/studio/pages/api/projects/[ref]/analytics/endpoints/[name].ts @@ -0,0 +1,46 @@ +import { NextApiRequest, NextApiResponse } from 'next' +import apiWrapper from 'lib/api/apiWrapper' +import { PROJECT_ANALYTICS_URL } from 'pages/api/constants' +import { get } from 'lib/common/fetch' + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'GET': + const missingEnvVars = [ + process.env.LOGFLARE_API_KEY ? null : 'LOGFLARE_API_KEY', + process.env.LOGFLARE_URL ? null : 'LOGFLARE_URL', + ].filter((v) => v) + if (missingEnvVars.length == 0) { + const result = await proxyRequest(req) + return res.status(200).json(result) + } else { + return res + .status(500) + .json({ error: { message: `${missingEnvVars.join(', ')} env variables are not set` } }) + } + + default: + res.setHeader('Allow', ['GET']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const proxyRequest = async (req: NextApiRequest) => { + const { name, ...toForward } = req.query + const payload = { ...toForward, project_tier: 'ENTERPRISE' } + const search = '?' + new URLSearchParams(payload as any).toString() + const apiKey = process.env.LOGFLARE_API_KEY + const url = `${PROJECT_ANALYTICS_URL}endpoints/query/name/${name}${search}` + const result = await get(url, { + headers: { + 'x-api-key': apiKey, + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + }) + return result +} diff --git a/studio/pages/api/projects/[ref]/api/graphql.ts b/studio/pages/api/projects/[ref]/api/graphql.ts new file mode 100644 index 00000000000..73f206a218c --- /dev/null +++ b/studio/pages/api/projects/[ref]/api/graphql.ts @@ -0,0 +1,40 @@ +import { NextApiRequest, NextApiResponse } from 'next' +import apiWrapper from 'lib/api/apiWrapper' + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'POST': + return handleGet(req, res) + + default: + res.setHeader('Allow', ['POST']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handleGet = async (req: NextApiRequest, res: NextApiResponse) => { + const authorizationHeader = req.headers['x-graphql-authorization'] + + const response = await fetch(`${process.env.SUPABASE_URL}/graphql/v1`, { + method: 'POST', + headers: { + apikey: process.env.SUPABASE_SERVICE_KEY!, + Authorization: + (Array.isArray(authorizationHeader) ? authorizationHeader[0] : authorizationHeader) ?? + `Bearer ${process.env.SUPABASE_ANON_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(req.body), + }) + if (response.ok) { + const data = await response.json() + + return res.status(200).json(data) + } + + return res.status(500).json({ error: { message: 'Internal Server Error' } }) +} diff --git a/studio/pages/api/projects/[ref]/api/rest.ts b/studio/pages/api/projects/[ref]/api/rest.ts new file mode 100644 index 00000000000..d4c2a519ac3 --- /dev/null +++ b/studio/pages/api/projects/[ref]/api/rest.ts @@ -0,0 +1,38 @@ +import { NextApiRequest, NextApiResponse } from 'next' +import apiWrapper from 'lib/api/apiWrapper' + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'GET': + return handleGet(req, res) + case 'HEAD': + return handleHead(req, res) + default: + res.setHeader('Allow', ['GET', 'HEAD']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handleGet = async (req: NextApiRequest, res: NextApiResponse) => { + const response = await fetch(`${process.env.SUPABASE_URL}/rest/v1/`, { + method: 'GET', + headers: { + apikey: process.env.SUPABASE_SERVICE_KEY!, + }, + }) + if (response.ok) { + const data = await response.json() + + return res.status(200).json(data) + } + + return res.status(500).json({ error: { message: 'Internal Server Error' } }) +} + +const handleHead = async (req: NextApiRequest, res: NextApiResponse) => { + res.status(200).end() +} diff --git a/studio/pages/api/storage/[ref]/buckets/[id]/empty.ts b/studio/pages/api/storage/[ref]/buckets/[id]/empty.ts new file mode 100644 index 00000000000..9b162f6a637 --- /dev/null +++ b/studio/pages/api/storage/[ref]/buckets/[id]/empty.ts @@ -0,0 +1,30 @@ +import { createClient } from '@supabase/supabase-js' +import apiWrapper from 'lib/api/apiWrapper' +import { NextApiRequest, NextApiResponse } from 'next' + +const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'POST': + return handlePost(req, res) + default: + res.setHeader('Allow', ['POST']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { + const { id } = req.query + + const { data, error } = await supabase.storage.emptyBucket(id as string) + if (error) { + return res.status(400).json({ error: { message: error.message } }) + } + + return res.status(200).json(data) +} diff --git a/studio/pages/api/storage/[ref]/buckets/[id]/index.ts b/studio/pages/api/storage/[ref]/buckets/[id]/index.ts new file mode 100644 index 00000000000..72af258757b --- /dev/null +++ b/studio/pages/api/storage/[ref]/buckets/[id]/index.ts @@ -0,0 +1,46 @@ +import { createClient } from '@supabase/supabase-js' +import apiWrapper from 'lib/api/apiWrapper' +import { NextApiRequest, NextApiResponse } from 'next' + +const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'PATCH': + return handlePatch(req, res) + case 'DELETE': + return handleDelete(req, res) + default: + res.setHeader('Allow', ['PATCH', 'DELETE']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handlePatch = async (req: NextApiRequest, res: NextApiResponse) => { + const { id } = req.query + const { public: isPublicBucket } = req.body + + const { data, error } = await supabase.storage.updateBucket(id as string, { + public: isPublicBucket, + }) + if (error) { + return res.status(400).json({ error: { message: error.message } }) + } + + return res.status(200).json(data) +} + +const handleDelete = async (req: NextApiRequest, res: NextApiResponse) => { + const { id } = req.query + + const { data, error } = await supabase.storage.deleteBucket(id as string) + if (error) { + return res.status(400).json({ error: { message: error.message } }) + } + + return res.status(200).json(data) +} diff --git a/studio/pages/api/storage/[ref]/buckets/[id]/objects/download.ts b/studio/pages/api/storage/[ref]/buckets/[id]/objects/download.ts new file mode 100644 index 00000000000..fa51a404ed0 --- /dev/null +++ b/studio/pages/api/storage/[ref]/buckets/[id]/objects/download.ts @@ -0,0 +1,34 @@ +import { createClient } from '@supabase/supabase-js' +import apiWrapper from 'lib/api/apiWrapper' +import { NextApiRequest, NextApiResponse } from 'next' + +const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'POST': + return handlePost(req, res) + default: + res.setHeader('Allow', ['POST']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { + const { id } = req.query + const { path } = req.body + + const { data, error } = await supabase.storage.from(id as string).download(path) + if (error) { + return res.status(400).json({ error: { message: error.message } }) + } + + return res + .status(200) + .setHeader('Content-Type', 'application/octet-stream') + .send(Buffer.from(await data.arrayBuffer())) +} diff --git a/studio/pages/api/storage/[ref]/buckets/[id]/objects/index.ts b/studio/pages/api/storage/[ref]/buckets/[id]/objects/index.ts new file mode 100644 index 00000000000..d80425dd173 --- /dev/null +++ b/studio/pages/api/storage/[ref]/buckets/[id]/objects/index.ts @@ -0,0 +1,31 @@ +import { createClient } from '@supabase/supabase-js' +import apiWrapper from 'lib/api/apiWrapper' +import { NextApiRequest, NextApiResponse } from 'next' + +const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'DELETE': + return handleDelete(req, res) + default: + res.setHeader('Allow', ['DELETE']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handleDelete = async (req: NextApiRequest, res: NextApiResponse) => { + const { id } = req.query + const { paths } = req.body + + const { data, error } = await supabase.storage.from(id as string).remove(paths as string[]) + if (error) { + return res.status(400).json({ error: { message: error.message } }) + } + + return res.status(200).json(data) +} diff --git a/studio/pages/api/storage/[ref]/buckets/[id]/objects/list.ts b/studio/pages/api/storage/[ref]/buckets/[id]/objects/list.ts new file mode 100644 index 00000000000..d4dc551668b --- /dev/null +++ b/studio/pages/api/storage/[ref]/buckets/[id]/objects/list.ts @@ -0,0 +1,32 @@ +import { createClient } from '@supabase/supabase-js' +import apiWrapper from 'lib/api/apiWrapper' +import { NextApiRequest, NextApiResponse } from 'next' + +const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'POST': + return handlePost(req, res) + + default: + res.setHeader('Allow', ['POST']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { + const { id } = req.query + const { path, ...params } = req.body + + const { data, error } = await supabase.storage.from(id as string).list(path, params) + if (error) { + return res.status(500).json({ error: error.message }) + } + + return res.status(200).json(data) +} diff --git a/studio/pages/api/storage/[ref]/buckets/[id]/objects/move.ts b/studio/pages/api/storage/[ref]/buckets/[id]/objects/move.ts new file mode 100644 index 00000000000..d4c8b5485f0 --- /dev/null +++ b/studio/pages/api/storage/[ref]/buckets/[id]/objects/move.ts @@ -0,0 +1,31 @@ +import { createClient } from '@supabase/supabase-js' +import apiWrapper from 'lib/api/apiWrapper' +import { NextApiRequest, NextApiResponse } from 'next' + +const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'POST': + return handlePost(req, res) + default: + res.setHeader('Allow', ['POST']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { + const { id } = req.query + const { from, to } = req.body + + const { data, error } = await supabase.storage.from(id as string).move(from, to) + if (error) { + return res.status(400).json({ error: { message: error.message } }) + } + + return res.status(200).json(data) +} diff --git a/studio/pages/api/storage/[ref]/buckets/[id]/objects/public-url.ts b/studio/pages/api/storage/[ref]/buckets/[id]/objects/public-url.ts new file mode 100644 index 00000000000..41efb4e8107 --- /dev/null +++ b/studio/pages/api/storage/[ref]/buckets/[id]/objects/public-url.ts @@ -0,0 +1,28 @@ +import { createClient } from '@supabase/supabase-js' +import apiWrapper from 'lib/api/apiWrapper' +import { NextApiRequest, NextApiResponse } from 'next' + +const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'POST': + return handlePost(req, res) + default: + res.setHeader('Allow', ['POST']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { + const { id } = req.query + const { path } = req.body + + const { data } = supabase.storage.from(id as string).getPublicUrl(path) + + return res.status(200).json(data) +} diff --git a/studio/pages/api/storage/[ref]/buckets/[id]/objects/sign.ts b/studio/pages/api/storage/[ref]/buckets/[id]/objects/sign.ts new file mode 100644 index 00000000000..8677334f21b --- /dev/null +++ b/studio/pages/api/storage/[ref]/buckets/[id]/objects/sign.ts @@ -0,0 +1,31 @@ +import { createClient } from '@supabase/supabase-js' +import apiWrapper from 'lib/api/apiWrapper' +import { NextApiRequest, NextApiResponse } from 'next' + +const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'POST': + return handlePost(req, res) + default: + res.setHeader('Allow', ['POST']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { + const { id } = req.query + const { path, expiresIn = 60 * 60 * 24 } = req.body + + const { data, error } = await supabase.storage.from(id as string).createSignedUrl(path, expiresIn) + if (error) { + return res.status(400).json({ error: { message: error.message } }) + } + + return res.status(200).json(data) +} diff --git a/studio/pages/api/storage/[ref]/buckets/index.ts b/studio/pages/api/storage/[ref]/buckets/index.ts new file mode 100644 index 00000000000..e426db1810c --- /dev/null +++ b/studio/pages/api/storage/[ref]/buckets/index.ts @@ -0,0 +1,42 @@ +import { createClient } from '@supabase/supabase-js' +import apiWrapper from 'lib/api/apiWrapper' +import { NextApiRequest, NextApiResponse } from 'next' + +const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!) + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'GET': + return handleGet(req, res) + case 'POST': + return handlePost(req, res) + + default: + res.setHeader('Allow', ['GET', 'POST']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handleGet = async (req: NextApiRequest, res: NextApiResponse) => { + const { data, error } = await supabase.storage.listBuckets() + if (error) { + return res.status(500).json({ error: { message: 'Internal Server Error' } }) + } + + return res.status(200).json(data) +} + +const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { + const { id, public: isPublicBucket } = req.body + + const { data, error } = await supabase.storage.createBucket(id, { public: isPublicBucket }) + if (error) { + return res.status(400).json({ error: { message: error.message } }) + } + + return res.status(200).json(data) +} diff --git a/studio/pages/join/index.tsx b/studio/pages/join/index.tsx index 120db3cb51b..2f462f2f535 100644 --- a/studio/pages/join/index.tsx +++ b/studio/pages/join/index.tsx @@ -212,14 +212,14 @@ const JoinOrganizationPage = () => {

      - + + + - + + +
      @@ -240,7 +240,7 @@ const JoinOrganizationPage = () => { Supabase diff --git a/studio/pages/new/[slug].tsx b/studio/pages/new/[slug].tsx index 9702dca4aac..67305391615 100644 --- a/studio/pages/new/[slug].tsx +++ b/studio/pages/new/[slug].tsx @@ -4,7 +4,7 @@ import { debounce, isUndefined, values } from 'lodash' import { toJS } from 'mobx' import { observer } from 'mobx-react-lite' import generator from 'generate-password' -import { Button, Listbox, IconUsers, Input, Alert } from 'ui' +import { Button, Listbox, IconUsers, Input, Alert, IconHelpCircle, Toggle } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' import { NextPageWithLayout } from 'types' @@ -21,7 +21,8 @@ import { PRICING_TIER_FREE_KEY, PRICING_TIER_PRODUCT_IDS, } from 'lib/constants' -import { useStore, useFlag, withAuth, checkPermissions, useParams } from 'hooks' +import { useStore, useFlag, withAuth, checkPermissions } from 'hooks' +import { useParams } from 'common/hooks' import { useFreeProjectLimitCheckQuery } from 'data/organizations/free-project-limit-check-query' import { WizardLayoutWithoutAuth } from 'components/layouts' @@ -33,6 +34,7 @@ import { NotOrganizationOwnerWarning, EmptyPaymentMethodWarning, } from 'components/interfaces/Organization/NewProject' +import SpendCapModal from 'components/interfaces/Billing/SpendCapModal' const Wizard: NextPageWithLayout = () => { const router = useRouter() @@ -45,6 +47,7 @@ const Wizard: NextPageWithLayout = () => { useFreeProjectLimitCheckQuery({ slug }) const [projectName, setProjectName] = useState('') + const [postgresVersion, setPostgresVersion] = useState('') const [dbPass, setDbPass] = useState('') const [dbRegion, setDbRegion] = useState(REGIONS_DEFAULT) const [dbPricingTierKey, setDbPricingTierKey] = useState(PRICING_TIER_DEFAULT_KEY) @@ -54,6 +57,10 @@ const Wizard: NextPageWithLayout = () => { const [passwordStrengthScore, setPasswordStrengthScore] = useState(0) const [paymentMethods, setPaymentMethods] = useState(undefined) + const [showSpendCapHelperModal, setShowSpendCapHelperModal] = useState(false) + + const [isSpendCapEnabled, setIsSpendCapEnabled] = useState(true) + const organizations = values(toJS(app.organizations.list())) const currentOrg = organizations.find((o: any) => o.slug === slug) const stripeCustomerId = currentOrg?.stripe_customer_id @@ -66,6 +73,8 @@ const Wizard: NextPageWithLayout = () => { const isSelectFreeTier = dbPricingTierKey === PRICING_TIER_FREE_KEY const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0 + const showCustomVersionInput = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' + const canCreateProject = isAdmin && (!isSelectFreeTier || (isSelectFreeTier && !hasMembersExceedingFreeTierLimit)) @@ -147,15 +156,23 @@ const Wizard: NextPageWithLayout = () => { const onClickNext = async () => { setNewProjectLoading(true) - const data = { + + const dbTier = dbPricingTierKey === 'PRO' && !isSpendCapEnabled ? 'PAYG' : dbPricingTierKey + + const data: Record = { cloud_provider: PROVIDERS.AWS.id, // hardcoded for DB instances to be under AWS org_id: currentOrg?.id, name: projectName, db_pass: dbPass, db_region: dbRegion, - db_pricing_tier_id: (PRICING_TIER_PRODUCT_IDS as any)[dbPricingTierKey], + db_pricing_tier_id: (PRICING_TIER_PRODUCT_IDS as any)[dbTier], kps_enabled: kpsEnabled, } + if (postgresVersion) { + data['custom_supabase_internal_requests'] = { + ami: { search_tags: { 'tag:postgresVersion': postgresVersion } }, + } + } const response = await post(`${API_URL}/projects`, data) if (response.error) { setNewProjectLoading(false) @@ -235,7 +252,7 @@ const Wizard: NextPageWithLayout = () => { <> @@ -266,7 +283,7 @@ const Wizard: NextPageWithLayout = () => { <> @@ -282,7 +299,33 @@ const Wizard: NextPageWithLayout = () => { /> - + {showCustomVersionInput && ( + + + Specify a custom version of Postgres (Defaults to the latest) +
      + This is only applicable for local/staging projects +

      + } + type="text" + placeholder="Postgres Version" + value={postgresVersion} + onChange={(event: any) => setPostgresVersion(event.target.value)} + /> +
      + )} + + 0} @@ -304,7 +347,7 @@ const Wizard: NextPageWithLayout = () => { /> - + { addOnBefore={({ active, selected }: any) => ( )} > @@ -338,7 +383,7 @@ const Wizard: NextPageWithLayout = () => { )} {isAdmin && ( - + { )} )} + + {!isSelectFreeTier && ( + <> + + + Spend Cap + setShowSpendCapHelperModal(true)} + /> +
      + } + placeholder="Project name" + checked={isSpendCapEnabled} + onChange={() => setIsSpendCapEnabled(!isSpendCapEnabled)} + descriptionText={ +
      +

      + By default, Pro projects have spend caps to control costs. When enabled, + usage is limited to the plan's quota, with restrictions when limits are + exceeded. To scale beyond Pro limits without restrictions, disable the + spend cap and pay for over-usage beyond the quota. +

      +
      + } + /> + + + setShowSpendCapHelperModal(false)} + /> + + )} )} diff --git a/studio/pages/org/[slug]/billing.tsx b/studio/pages/org/[slug]/billing.tsx index b8510f913f3..83c44735b40 100644 --- a/studio/pages/org/[slug]/billing.tsx +++ b/studio/pages/org/[slug]/billing.tsx @@ -3,7 +3,8 @@ import { observer } from 'mobx-react-lite' import { Tabs } from 'ui' import { NextPageWithLayout } from 'types' -import { useParams, useStore } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import Loading from 'components/ui/Loading' import { OrganizationLayout } from 'components/layouts' import { BillingSettings } from 'components/interfaces/Organization' diff --git a/studio/pages/org/[slug]/general.tsx b/studio/pages/org/[slug]/general.tsx index 43cd730e8a2..f07415ba47e 100644 --- a/studio/pages/org/[slug]/general.tsx +++ b/studio/pages/org/[slug]/general.tsx @@ -3,7 +3,8 @@ import { observer } from 'mobx-react-lite' import { Tabs } from 'ui' import { NextPageWithLayout } from 'types' -import { useParams, useStore } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import Loading from 'components/ui/Loading' import { OrganizationLayout } from 'components/layouts' import { GeneralSettings } from 'components/interfaces/Organization' diff --git a/studio/pages/org/[slug]/invoices.tsx b/studio/pages/org/[slug]/invoices.tsx index f6ee9ee7eae..5ccf1ebc825 100644 --- a/studio/pages/org/[slug]/invoices.tsx +++ b/studio/pages/org/[slug]/invoices.tsx @@ -3,7 +3,8 @@ import { observer } from 'mobx-react-lite' import { Tabs } from 'ui' import { NextPageWithLayout } from 'types' -import { useParams, useStore } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import Loading from 'components/ui/Loading' import { OrganizationLayout } from 'components/layouts' import { InvoicesSettings } from 'components/interfaces/Organization' diff --git a/studio/pages/org/[slug]/team.tsx b/studio/pages/org/[slug]/team.tsx index f90d16a6486..7f96623cd61 100644 --- a/studio/pages/org/[slug]/team.tsx +++ b/studio/pages/org/[slug]/team.tsx @@ -3,7 +3,8 @@ import { observer } from 'mobx-react-lite' import { Tabs } from 'ui' import { NextPageWithLayout } from 'types' -import { useParams, useStore } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import Loading from 'components/ui/Loading' import { OrganizationLayout } from 'components/layouts' import { TeamSettings } from 'components/interfaces/Organization' diff --git a/studio/pages/org/_/[[...routeSlug]].tsx b/studio/pages/org/_/[[...routeSlug]].tsx index 69c379f35b3..1e53059a44d 100644 --- a/studio/pages/org/_/[[...routeSlug]].tsx +++ b/studio/pages/org/_/[[...routeSlug]].tsx @@ -5,6 +5,7 @@ import { observer } from 'mobx-react-lite' import { Button, IconPlus } from 'ui' import { withAuth, useStore } from 'hooks' +import { BASE_PATH } from 'lib/constants' import CardButton from 'components/ui/CardButton' import ShimmeringCard from 'components/interfaces/Home/ProjectList/ShimmeringCard' @@ -15,7 +16,7 @@ const Header = () => {
      Supabase { + const { ref: projectRef } = useParams() + const { ui, meta } = useStore() + + const isExtensionsLoading = meta.extensions.isLoading + const pgGraphqlExtension = meta.extensions.byId('pg_graphql') + + const { data: accessToken } = useSessionAccessTokenQuery({ enabled: IS_PLATFORM }) + const { data: settings, isFetched } = useProjectApiQuery({ projectRef }) + + const apiService = settings?.autoApiService + const anonKey = apiService?.service_api_keys.find((x) => x.name === 'anon key') + ? apiService.defaultApiKey + : undefined + + 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(() => { + const fetcherFn = createGraphiQLFetcher({ url: graphqlUrl, fetch }) + const customFetcher: Fetcher = (graphqlParams, opts) => { + return fetcherFn(graphqlParams, { + ...opts, + headers: { + ...opts?.headers, + Authorization: `Bearer ${accessToken}`, + 'x-graphql-authorization': + opts?.headers?.['Authorization'] ?? opts?.headers?.['authorization'], + }, + }) + } + + return customFetcher + }, [graphqlUrl, accessToken]) + + if ((IS_PLATFORM && !accessToken) || !isFetched || (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..a890cfd8de3 100644 --- a/studio/pages/project/[ref]/api/index.tsx +++ b/studio/pages/project/[ref]/api/index.tsx @@ -1,16 +1,17 @@ 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' import { NextPageWithLayout } from 'types' -import { checkPermissions, useParams, useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' +import { useParams } from 'common/hooks' import { snakeToCamel } from 'lib/helpers' import { useProjectApiQuery } from 'data/config/project-api-query' 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 +78,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 +95,7 @@ const DocView: FC = observer(({}) => { if (error || jsonSchemaError) return ( -
      +

      Error connecting to API

      {`${error || jsonSchemaError}`}

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

      Building docs ...

      ) @@ -129,81 +120,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]/auth/policies.tsx b/studio/pages/project/[ref]/auth/policies.tsx index 3d4b440d580..5fe4fc3171b 100644 --- a/studio/pages/project/[ref]/auth/policies.tsx +++ b/studio/pages/project/[ref]/auth/policies.tsx @@ -6,7 +6,8 @@ import { PostgresTable, PostgresPolicy } from '@supabase/postgres-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' import { NextPageWithLayout } from 'types' -import { checkPermissions, useParams, useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' +import { useParams } from 'common/hooks' import { AuthLayout } from 'components/layouts' import { Policies } from 'components/interfaces/Auth/Policies' import NoPermission from 'components/ui/NoPermission' diff --git a/studio/pages/project/[ref]/auth/url-configuration.tsx b/studio/pages/project/[ref]/auth/url-configuration.tsx index fd7ced97616..b0415afb0c9 100644 --- a/studio/pages/project/[ref]/auth/url-configuration.tsx +++ b/studio/pages/project/[ref]/auth/url-configuration.tsx @@ -7,7 +7,7 @@ import { useStore, checkPermissions } from 'hooks' import { AuthLayout } from 'components/layouts' import NoPermission from 'components/ui/NoPermission' -import RedirectDomains from 'components/interfaces/Auth/RedirectDomains/RedirectDomains' +import RedirectUrls from 'components/interfaces/Auth/RedirectUrls/RedirectUrls' import { FormsContainer } from 'components/ui/Forms' import SiteUrl from 'components/interfaces/Auth/SiteUrl/SiteUrl' @@ -26,7 +26,7 @@ const URLConfiguration: NextPageWithLayout = () => { return ( - + ) } else { 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/backups/scheduled.tsx b/studio/pages/project/[ref]/database/backups/scheduled.tsx index 18cd5658a7b..71694a0c630 100644 --- a/studio/pages/project/[ref]/database/backups/scheduled.tsx +++ b/studio/pages/project/[ref]/database/backups/scheduled.tsx @@ -8,6 +8,7 @@ import { checkPermissions, useStore } from 'hooks' import { DatabaseLayout } from 'components/layouts' import { BackupsList } from 'components/interfaces/Database' import NoPermission from 'components/ui/NoPermission' +import { FormsContainer } from 'components/ui/Forms' const DatabaseScheduledBackups: NextPageWithLayout = () => { const { ui } = useStore() @@ -17,33 +18,35 @@ const DatabaseScheduledBackups: NextPageWithLayout = () => { const canReadScheduledBackups = checkPermissions(PermissionAction.READ, 'back_ups') return ( -
      -

      Backups

      + +
      +

      Backups

      - { - if (id === 'pitr') router.push(`/project/${ref}/database/backups/pitr`) - }} - > - - - + { + if (id === 'pitr') router.push(`/project/${ref}/database/backups/pitr`) + }} + > + + + -
      -

      - Projects are backed up daily around midnight of your project's region and can be restored - at any time. -

      - {canReadScheduledBackups ? ( - - ) : ( - - )} +
      +

      + Projects are backed up daily around midnight of your project's region and can be + restored at any time. +

      + {canReadScheduledBackups ? ( + + ) : ( + + )} +
      -
      +
      ) } diff --git a/studio/pages/project/[ref]/database/functions.tsx b/studio/pages/project/[ref]/database/functions.tsx index 86e19b62999..5717e14bbcf 100644 --- a/studio/pages/project/[ref]/database/functions.tsx +++ b/studio/pages/project/[ref]/database/functions.tsx @@ -11,7 +11,6 @@ import FunctionsList from 'components/interfaces/Database/Functions/FunctionsLis const FunctionsPage: NextPageWithLayout = () => { const { meta, ui } = useStore() - const [filterString, setFilterString] = useState('') const [selectedFunction, setSelectedFunction] = useState() const [showCreateFunctionForm, setShowCreateFunctionForm] = useState(false) const [showDeleteFunctionForm, setShowDeleteFunctionForm] = useState(false) @@ -50,8 +49,6 @@ const FunctionsPage: NextPageWithLayout = () => { return ( <> { const { meta, ui } = useStore() const router = useRouter() const { ref } = router.query + const schemas = meta.schemas.list() + const { isLoading: isLoadingSchemas } = meta.schemas - const [hooksEnabled, setHooksEnabled] = useState(false) - const [filterString, setFilterString] = useState('') const [selectedHook, setSelectedHook] = useState() const [showCreateHookForm, setShowCreateHookForm] = useState(false) const [showDeleteHookForm, setShowDeleteHookForm] = useState(false) + const isHooksEnabled = schemas.some((schema: any) => schema.name === 'supabase_functions') const canReadWebhooks = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'triggers') + const canCreateWebhooks = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'triggers') useEffect(() => { - if (ui.selectedProject?.ref) { - fetchHooks() - } + if (ui.selectedProject?.ref) meta.hooks.load() }, [ui.selectedProject?.ref]) - const fetchHooks = async () => { - meta.hooks.load() - } - const enableHooksForProject = async () => { - const headers: any = {} - const connectionString = ui.selectedProject?.connectionString - if (connectionString) headers['x-connection-encrypted'] = connectionString - try { - await post(`${API_URL}/database/${ref}/hook-enable`, {}) - setHooksEnabled(true) - } catch (error) { - console.error(error) + const res = await post(`${API_URL}/database/${ref}/hook-enable`, {}) + if (res.error) { + ui.setNotification({ + category: 'error', + message: `Failed to enable webhooks: ${res.error.message}`, + }) + } else { + meta.schemas.load() + ui.setNotification({ + category: 'success', + message: `Successfully enabled webhooks`, + }) } } @@ -68,28 +72,55 @@ const HooksPage: NextPageWithLayout = () => { return } + if (isLoadingSchemas) { + return ( +
      + +

      Checking if hooks are enabled

      +
      + ) + } + + if (!isHooksEnabled) { + return ( +
      + enableHooksForProject()} + disabled={!canCreateWebhooks} + disabledMessage="You need additional permissions to enable webhooks" + > +

      + Database Webhooks can be used to trigger serverless functions or send requests to an + HTTP endpoint. +

      +

      Enable database webhooks on your project.

      +
      +
      + ) + } + return ( - <> - - + + setShowCreateHookForm(false)} /> - setShowDeleteHookForm(false)} /> - +
      ) } diff --git a/studio/pages/project/[ref]/database/roles.tsx b/studio/pages/project/[ref]/database/roles.tsx deleted file mode 100644 index 1cd86534d1c..00000000000 --- a/studio/pages/project/[ref]/database/roles.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useState } from 'react' -import { observer } from 'mobx-react-lite' -import { isUndefined } from 'lodash' - -import { DatabaseLayout } from 'components/layouts' -import { RolesList, RolesSettings } from 'components/interfaces/Database' -import { NextPageWithLayout } from 'types' - -const DatabaseRoles: NextPageWithLayout = () => { - const [selectedRole, setSelectedRole] = useState() - - return ( -
      - {isUndefined(selectedRole) ? ( - - ) : ( - setSelectedRole(undefined)} - /> - )} -
      - ) -} - -DatabaseRoles.getLayout = (page) => {page} - -export default observer(DatabaseRoles) diff --git a/studio/pages/project/[ref]/database/roles/index.tsx b/studio/pages/project/[ref]/database/roles/index.tsx new file mode 100644 index 00000000000..ef584cc1668 --- /dev/null +++ b/studio/pages/project/[ref]/database/roles/index.tsx @@ -0,0 +1,22 @@ +import { observer } from 'mobx-react-lite' +import { DatabaseLayout } from 'components/layouts' +import { RolesList } from 'components/interfaces/Database' +import { NextPageWithLayout } from 'types' +import clsx from 'clsx' + +const DatabaseRoles: NextPageWithLayout = () => { + return ( +
      + +
      + ) +} + +DatabaseRoles.getLayout = (page) => {page} + +export default observer(DatabaseRoles) diff --git a/studio/pages/project/[ref]/database/tables.tsx b/studio/pages/project/[ref]/database/tables.tsx index fa709fc8e06..5ce9684374e 100644 --- a/studio/pages/project/[ref]/database/tables.tsx +++ b/studio/pages/project/[ref]/database/tables.tsx @@ -1,3 +1,4 @@ +import { useQueryClient } from '@tanstack/react-query' import { useState, useEffect } from 'react' import { observer } from 'mobx-react-lite' import { isUndefined } from 'lodash' @@ -5,16 +6,22 @@ import type { PostgresColumn, PostgresTable } from '@supabase/postgres-meta' import { Modal } from 'ui' import { useStore } from 'hooks' +import { sqlKeys } from 'data/sql/keys' import { DatabaseLayout } from 'components/layouts' import ConfirmationModal from 'components/ui/ConfirmationModal' import { TableList, ColumnList } from 'components/interfaces/Database' import { SidePanelEditor } from 'components/interfaces/TableGridEditor' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { NextPageWithLayout } from 'types' const DatabaseTables: NextPageWithLayout = () => { const { meta, ui } = useStore() + const { project } = useProjectContext() + + const queryClient = useQueryClient() + const [isDeleting, setIsDeleting] = useState(false) const [selectedSchema, setSelectedSchema] = useState('public') const [selectedTable, setSelectedTable] = useState() @@ -108,6 +115,21 @@ const DatabaseTables: NextPageWithLayout = () => { category: 'success', message: `Successfully removed ${selectedColumnToDelete.name}.`, }) + + queryClient.invalidateQueries(sqlKeys.query(project?.ref, ['foreign-key-constraints'])) + await Promise.all([ + meta.tables.loadById(selectedColumnToDelete!.table_id), + queryClient.invalidateQueries( + sqlKeys.query(project?.ref, [selectedTable!.schema, selectedTable!.name]) + ), + queryClient.invalidateQueries( + sqlKeys.query(project?.ref, [ + 'table-definition', + selectedTable!.schema, + selectedTable!.name, + ]) + ), + ]) } } catch (error: any) { ui.setNotification({ 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..c5454dff1ff 100644 --- a/studio/pages/project/[ref]/database/wrappers/index.tsx +++ b/studio/pages/project/[ref]/database/wrappers/index.tsx @@ -1,3 +1,4 @@ +import clsx from 'clsx' import { observer } from 'mobx-react-lite' import { PermissionAction } from '@supabase/shared-types/out/constants' @@ -9,7 +10,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..721f01a1f04 100644 --- a/studio/pages/project/[ref]/editor/[id].tsx +++ b/studio/pages/project/[ref]/editor/[id].tsx @@ -1,5 +1,5 @@ +import { useQueryClient } from '@tanstack/react-query' 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' @@ -7,20 +7,32 @@ import { isUndefined, isNaN } from 'lodash' import { Alert, Button, Checkbox, IconExternalLink, Modal } from 'ui' import type { PostgresTable, PostgresColumn } from '@supabase/postgres-meta' -import { useStore, withAuth, useUrlState, useParams } from 'hooks' +import { useStore, withAuth, useUrlState } from 'hooks' +import { useParams } from 'common/hooks' +import { entityTypeKeys } from 'data/entity-types/keys' +import { Entity } from 'data/entity-types/entity-type-query' +import { sqlKeys } from 'data/sql/keys' 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, + useProjectContext, +} from 'components/layouts/ProjectLayout/ProjectContext' +import { ForeignRowSelectorProps } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector' -const TableEditorPage: NextPage = () => { +const TableEditorPage: NextPageWithLayout = () => { const router = useRouter() - const { id } = useParams() + const { id, ref: projectRef } = useParams() const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] }) + const { project } = useProjectContext() + + const queryClient = useQueryClient() + const { meta, ui } = useStore() const [selectedSchema, setSelectedSchema] = useState() @@ -31,15 +43,22 @@ const TableEditorPage: NextPage = () => { const [selectedColumnToDelete, setSelectedColumnToDelete] = useState() const [selectedTableToDelete, setSelectedTableToDelete] = useState() - const [sidePanelKey, setSidePanelKey] = useState<'row' | 'column' | 'table' | 'json'>() + const [sidePanelKey, setSidePanelKey] = useState< + 'row' | 'column' | 'table' | 'json' | 'foreign-row-selector' | 'csv-import' + >() const [selectedRowToEdit, setSelectedRowToEdit] = useState>() const [selectedColumnToEdit, setSelectedColumnToEdit] = useState() const [selectedTableToEdit, setSelectedTableToEdit] = useState() const [selectedValueForJsonEdit, setSelectedValueForJsonEdit] = useState() + const [selectedForeignKeyToEdit, setSelectedForeignKeyToEdit] = useState<{ + foreignKey: NonNullable + row: any + column: any + }>() - const projectRef = ui.selectedProject?.ref const tables: PostgresTable[] = meta.tables.list() const views: SchemaView[] = meta.views.list() + const materializedViews = meta.materializedViews.list() const foreignTables: Partial[] = meta.foreignTables.list() const selectedTable = !isNaN(Number(id)) @@ -48,6 +67,8 @@ const TableEditorPage: NextPage = () => { // @ts-ignore .concat(views) // @ts-ignore + .concat(materializedViews) + // @ts-ignore .concat(foreignTables) .find((table) => table.id === Number(id)) : undefined @@ -90,21 +111,27 @@ const TableEditorPage: NextPage = () => { setSelectedTableToEdit(undefined) } - const onEditTable = (table: PostgresTable) => { + const onEditTable = (entity: Entity) => { setSidePanelKey('table') setIsDuplicating(false) + + const table = meta.tables.byId(entity.id) setSelectedTableToEdit(table) } - const onDeleteTable = (table: PostgresTable) => { + const onDeleteTable = (entity: Entity) => { setIsDeleting(true) + + const table = meta.tables.byId(entity.id) setSelectedTableToDelete(table) setIsDeleteWithCascade(false) } - const onDuplicateTable = (table: PostgresTable) => { + const onDuplicateTable = (entity: Entity) => { setSidePanelKey('table') setIsDuplicating(true) + + const table = meta.tables.byId(entity.id) setSelectedTableToEdit(table) } @@ -113,6 +140,33 @@ const TableEditorPage: NextPage = () => { setSelectedValueForJsonEdit({ column, row, jsonString: JSON.stringify(row[column]) || '' }) } + const onImportData = () => { + if (id) { + setSidePanelKey('csv-import') + const table = meta.tables.byId(id) + setSelectedTableToEdit(table) + } else { + console.error('Table ID not found') + } + } + + const onEditForeignKeyColumnValue = ({ + foreignKey, + row, + column, + }: { + foreignKey: NonNullable + row: any + column: any + }) => { + setSidePanelKey('foreign-row-selector') + setSelectedForeignKeyToEdit({ + foreignKey, + row, + column, + }) + } + const onClosePanel = () => { setSidePanelKey(undefined) } @@ -150,7 +204,21 @@ const TableEditorPage: NextPage = () => { message: `Successfully deleted column "${selectedColumnToDelete.name}"`, }) - await meta.tables.loadById(selectedColumnToDelete!.table_id) + queryClient.invalidateQueries(sqlKeys.query(project?.ref, ['foreign-key-constraints'])) + await Promise.all([ + meta.tables.loadById(selectedColumnToDelete!.table_id), + queryClient.invalidateQueries( + sqlKeys.query(project?.ref, [selectedTable!.schema, selectedTable!.name]) + ), + queryClient.invalidateQueries( + sqlKeys.query(project?.ref, [ + 'table-definition', + selectedTable!.schema, + selectedTable!.name, + ]) + ), + ]) + if (selectedSchema) await meta.views.loadBySchema(selectedSchema) } catch (error: any) { ui.setNotification({ @@ -172,6 +240,8 @@ const TableEditorPage: NextPage = () => { const tables = meta.tables.list((table: PostgresTable) => table.schema === selectedSchema) + await queryClient.invalidateQueries(entityTypeKeys.list(projectRef)) + // For simplicity for now, we just open the first table within the same schema if (tables.length > 0) { router.push(`/project/${projectRef}/editor/${tables[0].id}`) @@ -213,14 +283,17 @@ const TableEditorPage: NextPage = () => { selectedColumnToEdit={selectedColumnToEdit} selectedTableToEdit={selectedTableToEdit} selectedValueForJsonEdit={selectedValueForJsonEdit} + selectedForeignKeyToEdit={selectedForeignKeyToEdit} onAddRow={onAddRow} onEditRow={onEditRow} onAddColumn={onAddColumn} onEditColumn={onEditColumn} onDeleteColumn={onDeleteColumn} onExpandJSONEditor={onExpandJSONEditor} + onEditForeignKeyColumnValue={onEditForeignKeyColumnValue} onClosePanel={onClosePanel} theme={ui.themeOption == 'dark' ? 'dark' : 'light'} + onImportData={onImportData} /> { ) } +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..23384fd610d 100644 --- a/studio/pages/project/[ref]/editor/index.tsx +++ b/studio/pages/project/[ref]/editor/index.tsx @@ -1,16 +1,18 @@ 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 { Entity } from 'data/entity-types/entity-type-query' 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'>() @@ -26,20 +28,26 @@ const Editor: NextPage = () => { setSelectedTableToEdit(undefined) } - const onEditTable = (table: PostgresTable) => { + const onEditTable = (entity: Entity) => { setSidePanelKey('table') setIsDuplicating(false) + + const table = meta.tables.byId(entity.id) setSelectedTableToEdit(table) } - const onDeleteTable = (table: PostgresTable) => { + const onDeleteTable = (entity: Entity) => { setIsDeleting(true) + + const table = meta.tables.byId(entity.id) setSelectedTableToDelete(table) } - const onDuplicateTable = (table: PostgresTable) => { + const onDuplicateTable = (entity: Entity) => { setSidePanelKey('table') setIsDuplicating(true) + + const table = meta.tables.byId(entity.id) setSelectedTableToEdit(table) } @@ -99,4 +107,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/[functionSlug]/details.tsx b/studio/pages/project/[ref]/functions/[functionSlug]/details.tsx new file mode 100644 index 00000000000..5ffb3c78c33 --- /dev/null +++ b/studio/pages/project/[ref]/functions/[functionSlug]/details.tsx @@ -0,0 +1,9 @@ +import { observer } from 'mobx-react-lite' +import { NextPageWithLayout } from 'types' +import FunctionsLayout from 'components/layouts/FunctionsLayout' +import { EdgeFunctionDetails } from 'components/interfaces/Functions' + +const PageLayout: NextPageWithLayout = () => +PageLayout.getLayout = (page) => {page} + +export default observer(PageLayout) diff --git a/studio/pages/project/[ref]/functions/[id]/index.tsx b/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx similarity index 94% rename from studio/pages/project/[ref]/functions/[id]/index.tsx rename to studio/pages/project/[ref]/functions/[functionSlug]/index.tsx index ed3b891d2a2..403f83ceec8 100644 --- a/studio/pages/project/[ref]/functions/[id]/index.tsx +++ b/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx @@ -5,14 +5,16 @@ import { Button } from 'ui' import { observer } from 'mobx-react-lite' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { checkPermissions, useParams } from 'hooks' -import { useFunctionsInvStatsQuery } from 'data/analytics/functions-inv-stats-query' +import { checkPermissions } from 'hooks' +import { useParams } from 'common/hooks' import { ChartIntervals, NextPageWithLayout } from 'types' import { DATE_FORMAT } from 'lib/constants' import Panel from 'components/ui/Panel' import NoPermission from 'components/ui/NoPermission' import ChartHandler from 'components/to-be-cleaned/Charts/ChartHandler' import FunctionsLayout from 'components/layouts/FunctionsLayout' +import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' +import { useFunctionsInvStatsQuery } from 'data/analytics/functions-inv-stats-query' const CHART_INTERVALS: ChartIntervals[] = [ { @@ -49,12 +51,14 @@ function calculateHighlightedValue(array: any, attribute: string, options?: { su const PageLayout: NextPageWithLayout = () => { const router = useRouter() - const { ref: projectRef, id } = useParams() + const { ref: projectRef, functionSlug } = useParams() const [interval, setInterval] = useState('15min') - const selectedInterval = CHART_INTERVALS.find((i) => i.key === interval) || CHART_INTERVALS[1] + const { data: selectedFunction } = useEdgeFunctionQuery({ projectRef, slug: functionSlug }) + const id = selectedFunction?.id + const { data, error } = useFunctionsInvStatsQuery({ projectRef, functionId: id, @@ -86,7 +90,7 @@ const PageLayout: NextPageWithLayout = () => { } } - const canReadFunction = checkPermissions(PermissionAction.FUNCTIONS_READ, id as string) + const canReadFunction = checkPermissions(PermissionAction.FUNCTIONS_READ, functionSlug as string) if (!canReadFunction) { return } diff --git a/studio/pages/project/[ref]/functions/[id]/invocations.tsx b/studio/pages/project/[ref]/functions/[functionSlug]/invocations.tsx similarity index 51% rename from studio/pages/project/[ref]/functions/[id]/invocations.tsx rename to studio/pages/project/[ref]/functions/[functionSlug]/invocations.tsx index 8e45b17b1de..16e179787e9 100644 --- a/studio/pages/project/[ref]/functions/[id]/invocations.tsx +++ b/studio/pages/project/[ref]/functions/[functionSlug]/invocations.tsx @@ -1,31 +1,28 @@ -import React from 'react' -import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' -import { PermissionAction } from '@supabase/shared-types/out/constants' import { NextPageWithLayout } from 'types' -import { checkPermissions } from 'hooks' -import NoPermission from 'components/ui/NoPermission' +import { useParams } from 'common/hooks' import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer' import FunctionsLayout from 'components/layouts/FunctionsLayout' +import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' /** * Placeholder page for logs previewers until we figure out where to slot them */ export const LogPage: NextPageWithLayout = () => { - const router = useRouter() - const { ref, id } = router.query + const { ref, functionSlug } = useParams() + const { data: selectedFunction, isLoading } = useEdgeFunctionQuery({ + projectRef: ref, + slug: functionSlug, + }) - const canReadFunction = checkPermissions(PermissionAction.FUNCTIONS_READ, id as string) - if (!canReadFunction) { - return - } + if (selectedFunction === undefined || isLoading) return null return ( ) } diff --git a/studio/pages/project/[ref]/functions/[functionSlug]/logs.tsx b/studio/pages/project/[ref]/functions/[functionSlug]/logs.tsx new file mode 100644 index 00000000000..404985c654a --- /dev/null +++ b/studio/pages/project/[ref]/functions/[functionSlug]/logs.tsx @@ -0,0 +1,29 @@ +import { observer } from 'mobx-react-lite' +import { NextPageWithLayout } from 'types' +import { useParams } from 'common/hooks' +import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer' +import FunctionsLayout from 'components/layouts/FunctionsLayout' +import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' + +export const LogPage: NextPageWithLayout = () => { + const { ref, functionSlug } = useParams() + + const { data: selectedFunction, isLoading } = useEdgeFunctionQuery({ + projectRef: ref, + slug: functionSlug, + }) + + if (selectedFunction === undefined || isLoading) return null + + return ( + + ) +} + +LogPage.getLayout = (page) => {page} + +export default observer(LogPage) diff --git a/studio/pages/project/[ref]/functions/[id]/details.tsx b/studio/pages/project/[ref]/functions/[id]/details.tsx deleted file mode 100644 index 613833e4f31..00000000000 --- a/studio/pages/project/[ref]/functions/[id]/details.tsx +++ /dev/null @@ -1,246 +0,0 @@ -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 FunctionsLayout from 'components/layouts/FunctionsLayout' -import CommandRender from 'components/interfaces/Functions/CommandRender' -import NoPermission from 'components/ui/NoPermission' - -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 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
      - -
      -
      -
      - ) -} - -PageLayout.getLayout = (page) => {page} - -export default observer(PageLayout) diff --git a/studio/pages/project/[ref]/functions/[id]/logs.tsx b/studio/pages/project/[ref]/functions/[id]/logs.tsx deleted file mode 100644 index 2913da2c2d2..00000000000 --- a/studio/pages/project/[ref]/functions/[id]/logs.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useRouter } from 'next/router' -import { observer } from 'mobx-react-lite' -import { PermissionAction } from '@supabase/shared-types/out/constants' - -import { NextPageWithLayout } from 'types' -import { checkPermissions } from 'hooks' -import NoPermission from 'components/ui/NoPermission' -import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer' -import FunctionsLayout from 'components/layouts/FunctionsLayout' - -export const LogPage: NextPageWithLayout = () => { - const router = useRouter() - const { ref, id } = router.query - - const canReadFunction = checkPermissions(PermissionAction.FUNCTIONS_READ, id as string) - if (!canReadFunction) { - return - } - - return ( - - ) -} - -LogPage.getLayout = (page) => {page} - -export default observer(LogPage) diff --git a/studio/pages/project/[ref]/functions/index.tsx b/studio/pages/project/[ref]/functions/index.tsx index f8778eb09f5..0673931084f 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 'common/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 FunctionsList = ({ 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]/index.tsx b/studio/pages/project/[ref]/index.tsx index 1c334d1e856..44249b8cb22 100644 --- a/studio/pages/project/[ref]/index.tsx +++ b/studio/pages/project/[ref]/index.tsx @@ -1,6 +1,7 @@ import { observer } from 'mobx-react-lite' -import { useParams, useStore } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import { NextPageWithLayout } from 'types' import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' import { ProjectLayoutWithAuth } from 'components/layouts' @@ -9,6 +10,7 @@ import { CLIENT_LIBRARIES, EXAMPLE_PROJECTS } from 'components/interfaces/Home/H import ProjectUsageSection from 'components/interfaces/Home/ProjectUsageSection' import ProjectPausedState from 'components/layouts/ProjectLayout/ProjectPausedState' import OveragesBanner from 'components/ui/OveragesBanner/OveragesBanner' +import ProjectUpgradeFailedBanner from 'components/ui/ProjectUpgradeFailedBanner' const Home: NextPageWithLayout = () => { const { ui } = useStore() @@ -31,6 +33,7 @@ const Home: NextPageWithLayout = () => {
      {/* [Joshen TODO] Temporarily hidden until usage endpoint is sorted out */} {/* {projectTier !== undefined && } */} +
      {project?.status === PROJECT_STATUS.INACTIVE && } @@ -39,26 +42,30 @@ const Home: NextPageWithLayout = () => { {IS_PLATFORM && project?.status !== PROJECT_STATUS.INACTIVE && } -
      -
      -

      Client libraries

      -
      -
      - {CLIENT_LIBRARIES.map((library) => ( - - ))} -
      -
      -
      -
      -

      Example projects

      -
      -
      - {EXAMPLE_PROJECTS.sort((a, b) => a.title.localeCompare(b.title)).map((project) => ( - - ))} -
      -
      + {project?.status !== PROJECT_STATUS.INACTIVE && ( + <> +
      +
      +

      Client libraries

      +
      +
      + {CLIENT_LIBRARIES.map((library) => ( + + ))} +
      +
      +
      +
      +

      Example projects

      +
      +
      + {EXAMPLE_PROJECTS.sort((a, b) => a.title.localeCompare(b.title)).map((project) => ( + + ))} +
      +
      + + )} ) } diff --git a/studio/pages/project/[ref]/logs/explorer/index.tsx b/studio/pages/project/[ref]/logs/explorer/index.tsx index 2a93f527629..a45ba94cdc3 100644 --- a/studio/pages/project/[ref]/logs/explorer/index.tsx +++ b/studio/pages/project/[ref]/logs/explorer/index.tsx @@ -4,7 +4,8 @@ import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' import { Input, Modal, Form, Button } from 'ui' -import { useParams, useStore } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import useLogsQuery from 'hooks/analytics/useLogsQuery' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' import { NextPageWithLayout, UserContent } from 'types' @@ -23,22 +24,25 @@ import { LogTemplate, maybeShowUpgradePrompt, TEMPLATES, + useEditorHints, } from 'components/interfaces/Settings/Logs' import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt' import UpgradePrompt from 'components/interfaces/Settings/Logs/UpgradePrompt' import LogsExplorerHeader from 'components/ui/Logs/LogsExplorerHeader' +const PLACEHOLDER_QUERY = + 'select\n cast(timestamp as datetime) as timestamp,\n event_message, metadata \nfrom edge_logs \nlimit 5' export const LogsExplorerPage: NextPageWithLayout = () => { + useEditorHints() const { ui, content } = useStore() const router = useRouter() const { ref: projectRef, q, ite, its } = useParams() const [editorId, setEditorId] = useState(uuidv4()) - const [editorValue, setEditorValue] = useState('') + const [editorValue, setEditorValue] = useState(PLACEHOLDER_QUERY) const [saveModalOpen, setSaveModalOpen] = useState(false) const [warnings, setWarnings] = useState([]) const { data: subscription } = useProjectSubscriptionQuery({ projectRef }) const tier = subscription?.tier - const [{ params, logData, error, isLoading }, { changeQuery, runQuery, setParams }] = useLogsQuery(projectRef as string, { iso_timestamp_start: its ? (its as string) : undefined, 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..9a2a9f8a60c --- /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 'common/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 253fcc54f6d..ae7c16394cb 100644 --- a/studio/pages/project/[ref]/reports/database.tsx +++ b/studio/pages/project/[ref]/reports/database.tsx @@ -1,10 +1,15 @@ import dayjs from 'dayjs' import { observer } from 'mobx-react-lite' import { FC, useEffect, useState } from 'react' -import { IconArrowRight } from 'ui' +import { Badge, Button, IconArrowRight, IconExternalLink } from 'ui' -import { useParams, useStore } from 'hooks' -import { TIME_PERIODS_INFRA, USAGE_APPROACHING_THRESHOLD } from 'lib/constants' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' +import { + PRICING_TIER_PRODUCT_IDS, + TIME_PERIODS_INFRA, + USAGE_APPROACHING_THRESHOLD, +} from 'lib/constants' import { formatBytes } from 'lib/helpers' import { NextPageWithLayout } from 'types' @@ -71,6 +76,10 @@ const DatabaseUsage: FC = () => { const egressIsApproaching = databaseSizeUsageRatio >= USAGE_APPROACHING_THRESHOLD const egressIsExceeded = databaseSizeUsageRatio >= 1 + const subscriptionTier = ui.selectedProject?.subscription_tier + + const isPaidTier = subscriptionTier !== PRICING_TIER_PRODUCT_IDS.FREE + return ( <>
      @@ -94,6 +103,26 @@ const DatabaseUsage: FC = () => { labelTop={databaseSizeLimit > 0 ? formatBytes(databaseSizeLimit) : ''} />
      + + {isPaidTier && ( +
      +
      + {usage?.disk_volume_size_gb && ( + Disk Size: {usage.disk_volume_size_gb} GB + )} + Auto-Scaling +
      + + +
      + )}
      diff --git a/studio/pages/project/[ref]/reports/index.tsx b/studio/pages/project/[ref]/reports/index.tsx index f4ae714fbba..dfda77f3db6 100644 --- a/studio/pages/project/[ref]/reports/index.tsx +++ b/studio/pages/project/[ref]/reports/index.tsx @@ -5,9 +5,11 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { NextPageWithLayout } from 'types' import { checkPermissions, useFlag, useStore } from 'hooks' +import { useParams } from 'common/hooks' import { post } from 'lib/common/fetch' import { API_URL, PROJECT_STATUS } from 'lib/constants' import { useProjectContentStore } from 'stores/projectContentStore' +import { useProfileQuery } from 'data/profile/profile-query' import Loading from 'components/ui/Loading' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' import { createReport } from 'components/to-be-cleaned/Reports/Reports.utils' @@ -17,15 +19,16 @@ export const UserReportPage: NextPageWithLayout = () => { const [loading, setLoading] = useState(true) const router = useRouter() - const { ref } = router.query + const { ref } = useParams() + const { data: profile } = useProfileQuery() const { ui } = useStore() const project = ui.selectedProject const contentStore = useProjectContentStore(ref) const canCreateReport = checkPermissions(PermissionAction.CREATE, 'user_content', { - resource: { type: 'report', owner_id: ui.profile?.id }, - subject: { id: ui.profile?.id }, + resource: { type: 'report', owner_id: profile?.id }, + subject: { id: profile?.id }, }) const kpsEnabled = useFlag('initWithKps') 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..f475188813c --- /dev/null +++ b/studio/pages/project/[ref]/reports/query-performance.tsx @@ -0,0 +1,395 @@ +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' +import Link from 'next/link' + +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. + {'\n\n'}It relies on the pg_stat_statements table. Read more about{' '} + + examining query performance + + .{'\n\n'}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%. +

      +
      +
      + )} + + + )} + + {headerText} + +
      + +
      + + 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/api.tsx b/studio/pages/project/[ref]/settings/api.tsx index 288127c97a6..0446e3d361a 100644 --- a/studio/pages/project/[ref]/settings/api.tsx +++ b/studio/pages/project/[ref]/settings/api.tsx @@ -2,7 +2,8 @@ import { createContext } from 'react' import { useRouter } from 'next/router' import { observer, useLocalObservable } from 'mobx-react-lite' -import { useParams, useStore } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import { NextPageWithLayout } from 'types' import { SettingsLayout } from 'components/layouts' import ServiceList from 'components/interfaces/Settings/API/ServiceList' 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]/settings/database.tsx b/studio/pages/project/[ref]/settings/database.tsx index bac5fc6755c..d9b90a1cecb 100644 --- a/studio/pages/project/[ref]/settings/database.tsx +++ b/studio/pages/project/[ref]/settings/database.tsx @@ -1,6 +1,7 @@ import { observer } from 'mobx-react-lite' import { NextPageWithLayout } from 'types' -import { useFlag, useParams } from 'hooks' +import { useFlag } from 'hooks' +import { useParams } from 'common/hooks' import { SettingsLayout } from 'components/layouts' import { ConnectionPooling, @@ -12,16 +13,14 @@ import SSLConfiguration from 'components/interfaces/Settings/Database/SSLConfigu const ProjectSettings: NextPageWithLayout = () => { const { ref: projectRef } = useParams() - const networkRestrictions = useFlag('networkRestrictions') - const sslEnforcement = useFlag('sslEnforcement') return (
      - {sslEnforcement && } + {networkRestrictions && }
      diff --git a/studio/pages/project/[ref]/settings/storage.tsx b/studio/pages/project/[ref]/settings/storage.tsx index 7f2f2c15180..0ac1df2ea45 100644 --- a/studio/pages/project/[ref]/settings/storage.tsx +++ b/studio/pages/project/[ref]/settings/storage.tsx @@ -2,7 +2,8 @@ import React, { useEffect } from 'react' import { observer } from 'mobx-react-lite' import { API_URL, PROJECT_STATUS } from 'lib/constants' -import { useFlag, useParams, useStore } from 'hooks' +import { useFlag, useStore } from 'hooks' +import { useParams } from 'common/hooks' import { post } from 'lib/common/fetch' import { SettingsLayout } from 'components/layouts' import { StorageSettings } from 'components/to-be-cleaned/Storage' diff --git a/studio/pages/project/[ref]/settings/vault/keys.tsx b/studio/pages/project/[ref]/settings/vault/keys.tsx index 530c5b2fb3d..1b14ead4e9a 100644 --- a/studio/pages/project/[ref]/settings/vault/keys.tsx +++ b/studio/pages/project/[ref]/settings/vault/keys.tsx @@ -3,7 +3,8 @@ import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' import { Tabs } from 'ui' -import { useParams, useStore } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import { NextPageWithLayout } from 'types' import { SettingsLayout } from 'components/layouts' import { VaultToggle, EncryptionKeysManagement } from 'components/interfaces/Settings/Vault' diff --git a/studio/pages/project/[ref]/settings/vault/secrets.tsx b/studio/pages/project/[ref]/settings/vault/secrets.tsx index 4a7a92ebb33..8b50c447ac0 100644 --- a/studio/pages/project/[ref]/settings/vault/secrets.tsx +++ b/studio/pages/project/[ref]/settings/vault/secrets.tsx @@ -3,7 +3,8 @@ import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' import { Tabs } from 'ui' -import { useParams, useStore } from 'hooks' +import { useStore } from 'hooks' +import { useParams } from 'common/hooks' import { NextPageWithLayout } from 'types' import { SettingsLayout } from 'components/layouts' import { VaultToggle, SecretsManagement } from 'components/interfaces/Settings/Vault' diff --git a/studio/pages/project/[ref]/storage/buckets/index.tsx b/studio/pages/project/[ref]/storage/buckets/index.tsx index 80ca79f9f03..8f4ad3bba21 100644 --- a/studio/pages/project/[ref]/storage/buckets/index.tsx +++ b/studio/pages/project/[ref]/storage/buckets/index.tsx @@ -4,20 +4,21 @@ import { observer } from 'mobx-react-lite' import { API_URL } from 'lib/constants' import { useFlag, useStore } from 'hooks' +import { useParams } from 'common/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 +27,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 +48,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/pages/project/_/[[...routeSlug]].tsx b/studio/pages/project/_/[[...routeSlug]].tsx index dacc4ed5bdb..5dd5959f40a 100644 --- a/studio/pages/project/_/[[...routeSlug]].tsx +++ b/studio/pages/project/_/[[...routeSlug]].tsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' import { withAuth } from 'hooks' +import { BASE_PATH } from 'lib/constants' import ProjectList from 'components/interfaces/Home/ProjectList' interface Props {} @@ -16,7 +17,7 @@ const Header: FC = () => { Supabase { return ( <> - {app.organizations.isLoading ? ( + {!app.organizations.isInitialized ? (

      diff --git a/studio/pages/sign-in.tsx b/studio/pages/sign-in.tsx index c9fbefdb8cc..a98b3b31dcc 100644 --- a/studio/pages/sign-in.tsx +++ b/studio/pages/sign-in.tsx @@ -8,6 +8,7 @@ import { SignInLayout } from 'components/layouts' import { useEffect } from 'react' import { IS_PLATFORM } from 'lib/constants' import { useRouter } from 'next/router' +import { auth } from 'lib/gotrue' const SignInPage: NextPageWithLayout = () => { const router = useRouter() @@ -15,6 +16,20 @@ const SignInPage: NextPageWithLayout = () => { // if the dashboard is running locally, redirect straight to the projects page if (!IS_PLATFORM) { router.replace('/projects') + return + } + + const { + data: { subscription }, + } = auth.onAuthStateChange((event) => { + if (event === 'SIGNED_IN') { + // if user has signed in on another tab, take them to the home page + router.replace('/projects') + } + }) + + return () => { + subscription.unsubscribe() } }, []) diff --git a/studio/pages/support/new.tsx b/studio/pages/support/new.tsx index 0197ba4c9ce..a3e256afafa 100644 --- a/studio/pages/support/new.tsx +++ b/studio/pages/support/new.tsx @@ -6,6 +6,7 @@ import { Button, IconLoader } from 'ui' import * as Tooltip from '@radix-ui/react-tooltip' import { withAuth, useFlag } from 'hooks' +import { BASE_PATH } from 'lib/constants' import { usePlatformStatusQuery } from 'data/platform/platform-status-query' import Success from 'components/interfaces/Support/Success' import SupportForm from 'components/interfaces/Support/SupportForm' @@ -28,7 +29,7 @@ const SupportPage = () => {
      - +

      Supabase support

      @@ -57,17 +58,19 @@ const SupportPage = () => { - - -
      - Check Supabase status page -
      -
      + + + +
      + Check Supabase status page +
      +
      +
      diff --git a/studio/pages/vercel/setupProject.tsx b/studio/pages/vercel/setupProject.tsx index 6cb30e758a8..52452be8d20 100644 --- a/studio/pages/vercel/setupProject.tsx +++ b/studio/pages/vercel/setupProject.tsx @@ -317,7 +317,7 @@ const CreateProject = observer(() => { addOnBefore={({ active, selected }: any) => ( )} > diff --git a/studio/public/css/fonts.css b/studio/public/css/fonts.css index 47a1664edf1..11fc167fc7c 100644 --- a/studio/public/css/fonts.css +++ b/studio/public/css/fonts.css @@ -2,57 +2,57 @@ @font-face { font-family: 'circular'; - src: url(/fonts/custom-font/CustomFont-Book.woff2) format('woff2'), - url(/fonts/custom-font/CustomFont-Book.woff) format('woff'); + src: url(/fonts/custom/CustomFont-Book.woff2) format('woff2'), + url(/fonts/custom/CustomFont-Book.woff) format('woff'); font-weight: 400; font-style: normal; } @font-face { font-family: 'circular'; - src: url(/fonts/custom-font/CustomFont-BookItalic.woff2) format('woff2'), - url(/fonts/custom-font/CustomFont-BookItalic.woff) format('woff'); + src: url(/fonts/custom/CustomFont-BookItalic.woff2) format('woff2'), + url(/fonts/custom/CustomFont-BookItalic.woff) format('woff'); font-weight: 400; font-style: italic; } @font-face { font-family: 'circular'; - src: url(/fonts/custom-font/CustomFont-Medium.woff2) format('woff2'), - url(/fonts/custom-font/CustomFont-Medium.woff) format('woff'); + src: url(/fonts/custom/CustomFont-Medium.woff2) format('woff2'), + url(/fonts/custom/CustomFont-Medium.woff) format('woff'); font-weight: 500; font-style: normal; } @font-face { font-family: 'circular'; - src: url(/fonts/custom-font/CustomFont-MediumItalic.woff2) format('woff2'), - url(/fonts/custom-font/CustomFont-MediumItalic.woff) format('woff'); + src: url(/fonts/custom/CustomFont-MediumItalic.woff2) format('woff2'), + url(/fonts/custom/CustomFont-MediumItalic.woff) format('woff'); font-weight: 500; font-style: italic; } @font-face { font-family: 'circular'; - src: url(/fonts/custom-font/CustomFont-Bold.woff2) format('woff2'), - url(/fonts/custom-font/CustomFont-Bold.woff) format('woff'); + src: url(/fonts/custom/CustomFont-Bold.woff2) format('woff2'), + url(/fonts/custom/CustomFont-Bold.woff) format('woff'); font-weight: 700; font-style: 600; } @font-face { font-family: 'circular'; - src: url(/fonts/custom-font/CustomFont-BoldItalic.woff2) format('woff2'), - url(/fonts/custom-font/CustomFont-BoldItalic.woff) format('woff'); + src: url(/fonts/custom/CustomFont-BoldItalic.woff2) format('woff2'), + url(/fonts/custom/CustomFont-BoldItalic.woff) format('woff'); font-style: 600; font-style: italic; } @font-face { font-family: 'circular'; - src: url(/fonts/custom-font/CustomFont-Black.woff2) format('woff2'), - url(/fonts/custom-font/CustomFont-Black.woff) format('woff'); + src: url(/fonts/custom/CustomFont-Black.woff2) format('woff2'), + url(/fonts/custom/CustomFont-Black.woff) format('woff'); font-weight: 800; font-style: normal; } @font-face { font-family: 'circular'; - src: url(/fonts/custom-font/CustomFont-BlackItalic.woff2) format('woff2'), - url(/fonts/custom-font/CustomFont-BlackItalic.woff) format('woff'); + src: url(/fonts/custom/CustomFont-BlackItalic.woff2) format('woff2'), + url(/fonts/custom/CustomFont-BlackItalic.woff) format('woff'); font-weight: 800; font-style: italic; } diff --git a/studio/public/fonts/custom-font/CustomFont-Black.woff b/studio/public/fonts/custom/CustomFont-Black.woff similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-Black.woff rename to studio/public/fonts/custom/CustomFont-Black.woff diff --git a/studio/public/fonts/custom-font/CustomFont-Black.woff2 b/studio/public/fonts/custom/CustomFont-Black.woff2 similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-Black.woff2 rename to studio/public/fonts/custom/CustomFont-Black.woff2 diff --git a/studio/public/fonts/custom-font/CustomFont-BlackItalic.woff b/studio/public/fonts/custom/CustomFont-BlackItalic.woff similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-BlackItalic.woff rename to studio/public/fonts/custom/CustomFont-BlackItalic.woff diff --git a/studio/public/fonts/custom-font/CustomFont-BlackItalic.woff2 b/studio/public/fonts/custom/CustomFont-BlackItalic.woff2 similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-BlackItalic.woff2 rename to studio/public/fonts/custom/CustomFont-BlackItalic.woff2 diff --git a/studio/public/fonts/custom-font/CustomFont-Bold.woff b/studio/public/fonts/custom/CustomFont-Bold.woff similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-Bold.woff rename to studio/public/fonts/custom/CustomFont-Bold.woff diff --git a/studio/public/fonts/custom-font/CustomFont-Bold.woff2 b/studio/public/fonts/custom/CustomFont-Bold.woff2 similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-Bold.woff2 rename to studio/public/fonts/custom/CustomFont-Bold.woff2 diff --git a/studio/public/fonts/custom-font/CustomFont-BoldItalic.woff b/studio/public/fonts/custom/CustomFont-BoldItalic.woff similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-BoldItalic.woff rename to studio/public/fonts/custom/CustomFont-BoldItalic.woff diff --git a/studio/public/fonts/custom-font/CustomFont-BoldItalic.woff2 b/studio/public/fonts/custom/CustomFont-BoldItalic.woff2 similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-BoldItalic.woff2 rename to studio/public/fonts/custom/CustomFont-BoldItalic.woff2 diff --git a/studio/public/fonts/custom-font/CustomFont-Book.woff b/studio/public/fonts/custom/CustomFont-Book.woff similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-Book.woff rename to studio/public/fonts/custom/CustomFont-Book.woff diff --git a/studio/public/fonts/custom-font/CustomFont-Book.woff2 b/studio/public/fonts/custom/CustomFont-Book.woff2 similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-Book.woff2 rename to studio/public/fonts/custom/CustomFont-Book.woff2 diff --git a/studio/public/fonts/custom-font/CustomFont-BookItalic.woff b/studio/public/fonts/custom/CustomFont-BookItalic.woff similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-BookItalic.woff rename to studio/public/fonts/custom/CustomFont-BookItalic.woff diff --git a/studio/public/fonts/custom-font/CustomFont-BookItalic.woff2 b/studio/public/fonts/custom/CustomFont-BookItalic.woff2 similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-BookItalic.woff2 rename to studio/public/fonts/custom/CustomFont-BookItalic.woff2 diff --git a/studio/public/fonts/custom-font/CustomFont-Medium.woff b/studio/public/fonts/custom/CustomFont-Medium.woff similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-Medium.woff rename to studio/public/fonts/custom/CustomFont-Medium.woff diff --git a/studio/public/fonts/custom-font/CustomFont-Medium.woff2 b/studio/public/fonts/custom/CustomFont-Medium.woff2 similarity index 100% rename from studio/public/fonts/custom-font/CustomFont-Medium.woff2 rename to studio/public/fonts/custom/CustomFont-Medium.woff2 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/public/img/icons/saml-icon.svg b/studio/public/img/icons/saml-icon.svg new file mode 100644 index 00000000000..7348e7b2e96 --- /dev/null +++ b/studio/public/img/icons/saml-icon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/studio/public/img/icons/table-icon.svg b/studio/public/img/icons/table-icon.svg new file mode 100644 index 00000000000..eab407087bd --- /dev/null +++ b/studio/public/img/icons/table-icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/studio/public/img/icons/view-icon.svg b/studio/public/img/icons/view-icon.svg new file mode 100644 index 00000000000..be2dd326b89 --- /dev/null +++ b/studio/public/img/icons/view-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file 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..b568fcc85ab 100644 --- a/studio/stores/UiStore.ts +++ b/studio/stores/UiStore.ts @@ -15,7 +15,6 @@ export interface IUiStore { selectedProjectBaseInfo?: ProjectBase selectedOrganization?: Organization notification?: Notification - profile?: User permissions?: Permission[] googleAnalyticsProps?: GoogleAnalyticsProps @@ -26,7 +25,7 @@ export interface IUiStore { setProjectRef: (ref?: string) => void setOrganizationSlug: (slug?: string) => void setNotification: (notification: Notification) => string - setProfile: (value?: User) => void + setProfile: (value: User) => void setPermissions: (permissions?: Permission[]) => void setGaClientId: (clientId?: string) => void } @@ -39,7 +38,6 @@ export default class UiStore implements IUiStore { selectedProjectRef?: string selectedOrganizationSlug?: string notification?: Notification - profile?: User permissions?: Permission[] = [] constructor(rootStore: IRootStore) { @@ -128,8 +126,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') { @@ -158,12 +156,8 @@ export default class UiStore implements IUiStore { return id } - setProfile(value?: User) { - if (value && value?.id !== this.profile?.id) { - Telemetry.sendIdentify(value, this.googleAnalyticsProps) - } - - this.profile = value + setProfile(value: User) { + Telemetry.sendIdentify(value, this.googleAnalyticsProps) } setPermissions(permissions?: any) { 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..a5d6d7118be 100644 --- a/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx +++ b/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx @@ -1,5 +1,5 @@ import { boolean, number, object, string } from 'yup' -import { domainRegex } from 'components/interfaces/Auth/Auth.constants' +import { urlRegex } from 'components/interfaces/Auth/Auth.constants' const JSON_SCHEMA_VERSION = 'http://json-schema.org/draft-07/schema#' @@ -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({ @@ -305,7 +305,15 @@ const EXTERNAL_PROVIDER_APPLE = { title: 'Services ID', description: ` Client identifier when authenticating or validating users. -[learn more](https://developer.apple.com/documentation/sign_in_with_apple/configuring_your_environment_for_sign_in_with_apple)`, +[Learn more](https://developer.apple.com/documentation/sign_in_with_apple/configuring_your_environment_for_sign_in_with_apple)`, + type: 'string', + }, + EXTERNAL_IOS_BUNDLE_ID: { + /** + * to do: change docs + */ + title: 'IOS Bundle ID', + description: `The iOS app's unique identifier. [Learn more](https://developer.apple.com/documentation/appstoreconnectapi/bundle_ids)`, type: 'string', }, EXTERNAL_APPLE_SECRET: { @@ -320,19 +328,38 @@ The secret key is a JWT token that must be generated. isSecret: true, }, }, - validationSchema: object().shape({ - EXTERNAL_APPLE_ENABLED: boolean().required(), - EXTERNAL_APPLE_CLIENT_ID: string().when('EXTERNAL_APPLE_ENABLED', { - is: true, - then: (schema) => schema.required('Services ID is required'), - otherwise: (schema) => schema, - }), - EXTERNAL_APPLE_SECRET: string().when('EXTERNAL_APPLE_ENABLED', { - is: true, - then: (schema) => schema.required('Secret key is required'), - otherwise: (schema) => schema, - }), - }), + validationSchema: object().shape( + { + EXTERNAL_APPLE_ENABLED: boolean().required(), + EXTERNAL_APPLE_SECRET: string().when(['EXTERNAL_APPLE_ENABLED'], { + is: true, + then: (schema) => schema.required('Secret key is required'), + otherwise: (schema) => schema, + }), + EXTERNAL_APPLE_CLIENT_ID: string().when( + ['EXTERNAL_APPLE_ENABLED', 'EXTERNAL_IOS_BUNDLE_ID'], + { + is: (EXTERNAL_APPLE_ENABLED: boolean, EXTERNAL_IOS_BUNDLE_ID: string) => { + return EXTERNAL_APPLE_ENABLED && !EXTERNAL_IOS_BUNDLE_ID + }, + then: (schema) => schema.required('Either the Services ID or iOS Bundle ID is required'), + otherwise: (schema) => schema, + } + ), + EXTERNAL_IOS_BUNDLE_ID: string().when( + ['EXTERNAL_APPLE_ENABLED', 'EXTERNAL_APPLE_CLIENT_ID'], + { + is: (EXTERNAL_APPLE_ENABLED: boolean, EXTERNAL_APPLE_CLIENT_ID: string) => { + return EXTERNAL_APPLE_ENABLED && !EXTERNAL_APPLE_CLIENT_ID + }, + then: (schema) => schema.required('Either the Services ID or iOS Bundle ID is required'), + otherwise: (schema) => schema, + } + ), + }, + // this is necessary for the "either or" validation on EXTERNAL_APPLE_CLIENT_ID and EXTERNAL_IOS_BUNDLE_ID + [['EXTERNAL_APPLE_CLIENT_ID', 'EXTERNAL_IOS_BUNDLE_ID']] + ), misc: { iconKey: 'apple-icon', requiresRedirect: true, @@ -385,7 +412,7 @@ const EXTERNAL_PROVIDER_AZURE = { then: (schema) => schema.required('Secret ID is required'), otherwise: (schema) => schema, }), - EXTERNAL_AZURE_URL: string().matches(domainRegex, 'Must be a valid URL').optional(), + EXTERNAL_AZURE_URL: string().matches(urlRegex, 'Must be a valid URL').optional(), }), misc: { iconKey: 'microsoft-icon', @@ -583,7 +610,7 @@ const EXTERNAL_PROVIDER_GITLAB = { then: (schema) => schema.required('Client Secret is required'), otherwise: (schema) => schema, }), - EXTERNAL_GITLAB_URL: string().matches(domainRegex, 'Must be a valid URL').optional(), + EXTERNAL_GITLAB_URL: string().matches(urlRegex, 'Must be a valid URL').optional(), }), misc: { iconKey: 'gitlab-icon', @@ -671,8 +698,8 @@ const EXTERNAL_PROVIDER_KEYCLOAK = { EXTERNAL_KEYCLOAK_URL: string().when('EXTERNAL_KEYCLOAK_ENABLED', { is: true, then: (schema) => - schema.matches(domainRegex, 'Must be a valid URL').required('Realm URL is required'), - otherwise: (schema) => schema.matches(domainRegex, 'Must be a valid URL'), + schema.matches(urlRegex, 'Must be a valid URL').required('Realm URL is required'), + otherwise: (schema) => schema.matches(urlRegex, 'Must be a valid URL'), }), }), misc: { @@ -937,7 +964,7 @@ const EXTERNAL_PROVIDER_WORKOS = { validationSchema: object().shape({ EXTERNAL_WORKOS_ENABLED: boolean().required(), EXTERNAL_WORKOS_URL: string() - .matches(domainRegex, 'Must be a valid URL') + .matches(urlRegex, 'Must be a valid URL') .when('EXTERNAL_WORKOS_ENABLED', { is: true, then: (schema) => schema.required('WorkOS URL is required'), @@ -998,534 +1025,30 @@ const EXTERNAL_PROVIDER_ZOOM = { }, } -export const OLD = { +export const PROVIDER_SAML = { + $schema: JSON_SCHEMA_VERSION, type: 'object', - required: [ - 'SITE_URL', - 'DISABLE_SIGNUP', - 'JWT_EXP', - 'JWT_AUD', - 'JWT_DEFAULT_GROUP_NAME', - 'SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION', - 'EXTERNAL_EMAIL_ENABLED', - 'EXTERNAL_PHONE_ENABLED', - 'EXTERNAL_APPLE_ENABLED', - 'EXTERNAL_AZURE_ENABLED', - 'EXTERNAL_BITBUCKET_ENABLED', - 'EXTERNAL_DISCORD_ENABLED', - 'EXTERNAL_FACEBOOK_ENABLED', - 'EXTERNAL_GITHUB_ENABLED', - 'EXTERNAL_GITLAB_ENABLED', - 'EXTERNAL_GOOGLE_ENABLED', - 'EXTERNAL_KEYCLOAK_ENABLED', - 'EXTERNAL_LINKEDIN_ENABLED', - 'EXTERNAL_NOTION_ENABLED', - 'EXTERNAL_TWITCH_ENABLED', - 'EXTERNAL_TWITTER_ENABLED', - 'EXTERNAL_SLACK_ENABLED', - 'EXTERNAL_SPOTIFY_ENABLED', - 'EXTERNAL_WORKOS_ENABLED', - 'EXTERNAL_ZOOM_ENABLED', - 'SMS_AUTOCONFIRM', - 'SMS_MAX_FREQUENCY', - 'SMS_OTP_EXP', - 'SMS_OTP_LENGTH', - 'SMS_PROVIDER', - 'SMS_TEMPLATE', - 'MAILER_AUTOCONFIRM', - 'MAILER_URLPATHS_INVITE', - 'MAILER_URLPATHS_CONFIRMATION', - 'MAILER_URLPATHS_RECOVERY', - 'MAILER_URLPATHS_EMAIL_CHANGE', - 'MAILER_SUBJECTS_INVITE', - 'MAILER_SUBJECTS_CONFIRMATION', - 'MAILER_SUBJECTS_RECOVERY', - 'MAILER_SUBJECTS_MAGIC_LINK', - 'MAILER_SUBJECTS_EMAIL_CHANGE', - 'MAILER_TEMPLATES_INVITE_CONTENT', - 'MAILER_TEMPLATES_CONFIRMATION_CONTENT', - 'MAILER_TEMPLATES_RECOVERY_CONTENT', - 'MAILER_TEMPLATES_MAGIC_LINK_CONTENT', - 'MAILER_TEMPLATES_EMAIL_CHANGE_CONTENT', - ], + title: 'SAML 2.0', properties: { - SITE_URL: { - title: 'Site URL', - type: 'string', - help: 'The base URL of your website. Used as an allow-list for redirects and for constructing URLs used in emails.', - }, - URI_ALLOW_LIST: { - title: 'Additional redirect URLs', - type: 'string', - help: 'A comma separated list of *exact* URLs that auth providers are permitted to redirect to post authentication.', - }, - DISABLE_SIGNUP: { - title: 'Disable signup', - type: 'boolean', - help: 'Allow/disallow new user signups to your project.', - }, - EXTERNAL_EMAIL_ENABLED: { - title: 'Enable email signup', - type: 'boolean', - help: 'Allow/disallow new user signups via email to your project.', - }, - EXTERNAL_PHONE_ENABLED: { - title: 'Enable phone signup', - type: 'boolean', - help: 'Allow/disallow new user signups via phone to your project.', - }, - JWT_EXP: { - title: 'JWT expiry', - type: 'integer', - help: 'How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one week).', - minimum: 1, - maximum: 604800, - multipleof: 1, - }, - JWT_AUD: { - title: 'JWT audience', - type: 'string', - }, - JWT_DEFAULT_GROUP_NAME: { - title: 'Default user group', - type: 'string', - }, - EXTERNAL_APPLE_ENABLED: { - title: 'Apple enabled', + SAML_ENABLED: { + title: 'Enable SAML 2.0 Single Sign-on', + description: + 'You will need to use the [Supabase CLI](https://supabase.com/docs/guides/auth/sso/auth-sso-saml#managing-saml-20-connections) to set up SAML after enabling it', type: 'boolean', }, - EXTERNAL_APPLE_CLIENT_ID: { - title: 'Apple client ID', - type: 'string', - }, - EXTERNAL_APPLE_SECRET: { - title: 'Apple secret', - type: 'string', - }, - EXTERNAL_AZURE_ENABLED: { - title: 'Azure enabled', - type: 'boolean', - }, - EXTERNAL_AZURE_CLIENT_ID: { - title: 'Azure client ID', - type: 'string', - }, - EXTERNAL_AZURE_SECRET: { - title: 'Azure secret', - type: 'string', - }, - EXTERNAL_AZURE_URL: { - title: 'Azure Tenant URL', - type: 'string', - }, - EXTERNAL_BITBUCKET_ENABLED: { - title: 'Bitbucket enabled', - type: 'boolean', - }, - EXTERNAL_BITBUCKET_CLIENT_ID: { - title: 'Bitbucket client ID', - type: 'string', - }, - EXTERNAL_BITBUCKET_SECRET: { - title: 'Bitbucket secret', - type: 'string', - }, - EXTERNAL_DISCORD_ENABLED: { - title: 'Discord enabled', - type: 'boolean', - }, - EXTERNAL_DISCORD_CLIENT_ID: { - title: 'Discord client ID', - type: 'string', - }, - EXTERNAL_DISCORD_SECRET: { - title: 'Discord secret', - type: 'string', - }, - EXTERNAL_FACEBOOK_ENABLED: { - title: 'Facebook enabled', - type: 'boolean', - }, - EXTERNAL_FACEBOOK_CLIENT_ID: { - title: 'Facebook client ID', - type: 'string', - }, - EXTERNAL_FACEBOOK_SECRET: { - title: 'Facebook secret', - type: 'string', - }, - EXTERNAL_GITHUB_ENABLED: { - title: 'GitHub enabled', - type: 'boolean', - }, - EXTERNAL_GITHUB_CLIENT_ID: { - title: 'GitHub client ID', - type: 'string', - }, - EXTERNAL_GITHUB_SECRET: { - title: 'GitHub secret', - type: 'string', - }, - EXTERNAL_GITLAB_ENABLED: { - title: 'GitLab enabled', - type: 'boolean', - }, - EXTERNAL_GITLAB_CLIENT_ID: { - title: 'GitLab client ID', - type: 'string', - }, - EXTERNAL_GITLAB_SECRET: { - title: 'Gitlab secret', - type: 'string', - }, - EXTERNAL_GITLAB_URL: { - title: - 'The base URL used for constructing the URLs to request authorization and access tokens.', - type: 'string', - }, - EXTERNAL_GOOGLE_ENABLED: { - title: 'Google enabled', - type: 'boolean', - }, - EXTERNAL_GOOGLE_CLIENT_ID: { - title: 'Google client ID', - type: 'string', - }, - EXTERNAL_GOOGLE_SECRET: { - title: 'Google secret', - type: 'string', - }, - EXTERNAL_KEYCLOAK_ENABLED: { - title: 'Keycloak enabled', - type: 'boolean', - }, - EXTERNAL_KEYCLOAK_CLIENT_ID: { - title: 'Keycloak client ID', - type: 'string', - }, - EXTERNAL_KEYCLOAK_SECRET: { - title: 'Keycloak secret', - type: 'string', - }, - EXTERNAL_KEYCLOAK_URL: { - title: 'Keycloak URL', - type: 'string', - }, - EXTERNAL_LINKEDIN_ENABLED: { - title: 'Linkedin enabled', - type: 'boolean', - }, - EXTERNAL_LINKEDIN_CLIENT_ID: { - title: 'Linkedin client ID', - type: 'string', - }, - EXTERNAL_LINKEDIN_SECRET: { - title: 'Linkedin secret', - type: 'string', - }, - EXTERNAL_NOTION_ENABLED: { - title: 'Notion enabled', - type: 'boolean', - }, - EXTERNAL_NOTION_CLIENT_ID: { - title: 'Notion client ID', - type: 'string', - }, - EXTERNAL_NOTION_SECRET: { - title: 'Notion secret', - type: 'string', - }, - EXTERNAL_TWITCH_ENABLED: { - title: 'Twitch enabled', - type: 'boolean', - }, - EXTERNAL_TWITCH_CLIENT_ID: { - title: 'Twitch client ID', - type: 'string', - }, - EXTERNAL_TWITCH_SECRET: { - title: 'Twitch secret', - type: 'string', - }, - EXTERNAL_TWITTER_ENABLED: { - title: 'Twitter enabled', - type: 'boolean', - }, - EXTERNAL_TWITTER_CLIENT_ID: { - title: 'Twitter client ID', - type: 'string', - }, - EXTERNAL_TWITTER_SECRET: { - title: 'Twitter secret', - type: 'string', - }, - EXTERNAL_SLACK_ENABLED: { - title: 'Slack enabled', - type: 'boolean', - }, - EXTERNAL_SLACK_CLIENT_ID: { - title: 'Slack client ID', - type: 'string', - }, - EXTERNAL_SLACK_SECRET: { - title: 'Slack secret', - type: 'string', - }, - EXTERNAL_SPOTIFY_ENABLED: { - title: 'Spotify enabled', - type: 'boolean', - }, - EXTERNAL_SPOTIFY_CLIENT_ID: { - title: 'Spotify client ID', - type: 'string', - }, - EXTERNAL_SPOTIFY_SECRET: { - title: 'Spotify secret', - type: 'string', - }, - EXTERNAL_WORKOS_ENABLED: { - title: 'WorkOS enabled', - type: 'boolean', - }, - EXTERNAL_WORKOS_CLIENT_ID: { - title: 'WorkOS client ID', - type: 'string', - }, - EXTERNAL_WORKOS_SECRET: { - title: 'WorkOS secret', - type: 'string', - }, - EXTERNAL_WORKOS_URL: { - title: 'WorkOS URL', - type: 'string', - }, - EXTERNAL_ZOOM_ENABLED: { - title: 'Zoom enabled', - type: 'boolean', - }, - EXTERNAL_ZOOM_CLIENT_ID: { - title: 'Zoom client ID', - type: 'string', - }, - EXTERNAL_ZOOM_SECRET: { - title: 'Zoom secret', - type: 'string', - }, - SMTP_ADMIN_EMAIL: { - title: 'SMTP admin email', - type: 'string', - }, - SMTP_HOST: { - title: 'SMTP host', - type: 'string', - }, - SMTP_PORT: { - title: 'SMTP port', - type: 'string', - }, - SMTP_USER: { - title: 'SMTP user', - type: 'string', - }, - SMTP_PASS: { - title: 'SMTP password', - type: 'string', - }, - SMTP_PASS_ENCRYPTED: { - title: 'SMTP password', - type: 'string', - }, - SMTP_SENDER_NAME: { - title: 'SMTP sender name', - type: 'string', - }, - RATE_LIMIT_EMAIL_SENT: { - title: 'Rate limit', - type: 'number', - help: 'Maximum number of emails sent per hour (Default: 30, Max: 32,767)', - minimum: 1, - maximum: 32767, - multipleof: 1, - }, - RATE_LIMIT_SMS_SENT: { - title: 'Rate limit', - type: 'number', - help: 'Maximum number of SMS-es sent per hour (Default: 30, Max: 32,767)', - minimum: 1, - maximum: 32767, - multipleof: 1, - }, - MAILER_SECURE_EMAIL_CHANGE_ENABLED: { - title: 'Double confirm email changes', - type: 'boolean', - help: 'If enabled, a user will be required to confirm any email change on both the old, and new email addresses. If disabled, only the new email is required to confirm', - }, - MAILER_AUTOCONFIRM: { - title: 'Enable email confirmations', - type: 'boolean', - help: 'If enabled, users need to confirm their email address before signing in.', - }, - MAILER_URLPATHS_INVITE: { - title: 'Confirmation URL', - type: 'string', - }, - MAILER_URLPATHS_CONFIRMATION: { - title: 'Path', - type: 'string', - }, - MAILER_URLPATHS_RECOVERY: { - title: 'Confirmation URL', - type: 'string', - }, - MAILER_URLPATHS_EMAIL_CHANGE: { - title: 'Confirmation URL', - type: 'string', - }, - MAILER_SUBJECTS_INVITE: { - title: 'Subject', - type: 'string', - }, - MAILER_SUBJECTS_CONFIRMATION: { - title: 'Subject', - type: 'string', - }, - MAILER_SUBJECTS_MAGIC_LINK: { - title: 'Subject', - type: 'string', - }, - MAILER_SUBJECTS_RECOVERY: { - title: 'Subject', - type: 'string', - }, - MAILER_SUBJECTS_EMAIL_CHANGE: { - title: 'Subject', - type: 'string', - }, - MAILER_TEMPLATES_INVITE: { - type: 'string', - }, - MAILER_TEMPLATES_INVITE_CONTENT: { - title: 'Body', - type: 'string', - }, - MAILER_TEMPLATES_CONFIRMATION: { - type: 'string', - }, - MAILER_TEMPLATES_CONFIRMATION_CONTENT: { - title: 'Body', - type: 'string', - }, - MAILER_TEMPLATES_RECOVERY: { - type: 'string', - }, - MAILER_TEMPLATES_MAGIC_LINK: { - type: 'string', - }, - MAILER_TEMPLATES_RECOVERY_CONTENT: { - title: 'Body', - type: 'string', - }, - MAILER_TEMPLATES_MAGIC_LINK_CONTENT: { - title: 'Body', - type: 'string', - }, - MAILER_TEMPLATES_EMAIL_CHANGE: { - type: 'string', - }, - MAILER_TEMPLATES_EMAIL_CHANGE_CONTENT: { - title: 'Body', - type: 'string', - }, - PASSWORD_MIN_LENGTH: { - title: 'Minimum password length', - type: 'integer', - }, - SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION: { - title: 'Enable strict password updates', - type: 'boolean', - help: 'If enabled, users need to reauthenticate via email or phone before updating their passwords.', - }, - SMS_AUTOCONFIRM: { - title: 'Enable phone confirmations', - type: 'boolean', - help: 'If enabled, users need to confirm their phone number before signing in.', - }, - SMS_PROVIDER: { - title: 'Sms provider', - type: 'string', - options: [ - { - value: 'twilio', - label: 'Twilio', - }, - { - value: 'messagebird', - label: 'Messagebird', - }, - { - value: 'vonage', - label: 'Vonage', - }, - { - value: 'textlocal', - label: 'Textlocal', - }, - ], - }, - SMS_TEXTLOCAL_API_KEY: { - title: 'Textlocal API key', - type: 'string', - }, - SMS_TEXTLOCAL_SENDER: { - title: 'Textlocal sender', - type: 'string', - help: 'Textlocal sender phone number', - }, - SMS_TWILIO_ACCOUNT_SID: { - title: 'Twilio account SID', - type: 'string', - }, - SMS_TWILIO_AUTH_TOKEN: { - title: 'Twilio auth token', - type: 'string', - }, - SMS_TWILIO_MESSAGE_SERVICE_SID: { - title: 'Twilio message service SID', - type: 'string', - help: 'Twilio message service SID or twilio phone number', - }, - SMS_MESSAGEBIRD_ACCESS_KEY: { - title: 'Messagebird access key', - type: 'string', - }, - SMS_MESSAGEBIRD_ORIGINATOR: { - title: 'Messagebird originator', - type: 'string', - help: 'Messagebird sender name or phone number', - }, - SMS_VONAGE_API_KEY: { - title: 'Vonage API key', - type: 'string', - }, - SMS_VONAGE_API_SECRET: { - title: 'Vonage API secret', - type: 'string', - }, - SMS_VONAGE_FROM: { - title: 'Vonage sender', - type: 'string', - help: 'Vonage sender phone number', - }, - SMS_TEMPLATE: { - title: 'Body', - type: 'string', - }, + }, + validationSchema: object().shape({ + SAML_ENABLED: boolean().required(), + }), + misc: { + iconKey: 'saml-icon', }, } export const PROVIDERS_SCHEMAS = [ PROVIDER_EMAIL, PROVIDER_PHONE, + PROVIDER_SAML, EXTERNAL_PROVIDER_APPLE, EXTERNAL_PROVIDER_AZURE, EXTERNAL_PROVIDER_BITBUCKET, diff --git a/studio/stores/authConfig/schema/AuthProviders/AuthTemplatesValidation.tsx b/studio/stores/authConfig/schema/AuthProviders/AuthTemplatesValidation.tsx index 526e91022a9..b9d00348ed2 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/authConfig/schema/index.ts b/studio/stores/authConfig/schema/index.ts index 05aa9d8f6c5..43266b2cd49 100644 --- a/studio/stores/authConfig/schema/index.ts +++ b/studio/stores/authConfig/schema/index.ts @@ -1,2 +1,2 @@ -export { PROVIDERS_SCHEMAS } from './AuthProviders/AuthProvidersFormValidation' +export { PROVIDERS_SCHEMAS, PROVIDER_SAML } from './AuthProviders/AuthProvidersFormValidation' export { TEMPLATES_SCHEMAS } from './AuthProviders/AuthTemplatesValidation' diff --git a/studio/stores/common/PostgresMetaInterface.ts b/studio/stores/common/PostgresMetaInterface.ts index 07445b75e46..2d03eb347b7 100644 --- a/studio/stores/common/PostgresMetaInterface.ts +++ b/studio/stores/common/PostgresMetaInterface.ts @@ -47,6 +47,7 @@ export default class PostgresMetaInterface implements IPostgresMetaInterface< state = this.STATES.INITIAL data: { [key in DataKeys]: T } = {} headers: any = {} + isInitialized: boolean = false constructor( rootStore: IRootStore, @@ -68,6 +69,7 @@ export default class PostgresMetaInterface implements IPostgresMetaInterface< count: computed, hasError: computed, isLoading: computed, + isInitialized: observable, load: action, create: action, update: action, @@ -90,10 +92,6 @@ export default class PostgresMetaInterface implements IPostgresMetaInterface< return this.state === this.STATES.INITIAL || this.state === this.STATES.LOADING } - get isInitialized() { - return this.state === this.STATES.LOADED || this.state === this.STATES.ERROR - } - async fetchData() { const headers = { 'Content-Type': 'application/json', ...this.headers } const response = await get(this.url, { headers }) @@ -114,9 +112,12 @@ export default class PostgresMetaInterface implements IPostgresMetaInterface< console.error('Load error message', e.message) this.setError(e) this.setState(ERROR) + } finally { + if (!this.isInitialized) this.isInitialized = true } } + // [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 } as any this.setState(LOADED) return data @@ -146,7 +154,7 @@ export default class PostgresMetaInterface implements IPostgresMetaInterface< initialDataArray(value: T[]) { if (this.state === this.STATES.INITIAL) { - this.data = keyBy(value, this.identifier) + this.data = keyBy(value, this.identifier) as any this.state = this.STATES.LOADED } } @@ -156,7 +164,7 @@ export default class PostgresMetaInterface implements IPostgresMetaInterface< } setDataArray(value: T[]) { - this.data = keyBy(value, this.identifier) + this.data = keyBy(value, this.identifier) as any } setError(value: any) { @@ -200,10 +208,9 @@ export default class PostgresMetaInterface implements IPostgresMetaInterface< } } - async update(id: number | string, updates: any) { + async update(id: number | string, payload: any) { try { const headers = { 'Content-Type': 'application/json', ...this.headers } - let payload = { ...updates, id } const url = `${this.url}?id=${id}` const response = await patch(url, payload, { headers }) if (response.error) throw response.error diff --git a/studio/stores/pgmeta/ForeignTableStore.ts b/studio/stores/pgmeta/ForeignTableStore.ts index 19c0deb4aea..70282372e01 100644 --- a/studio/stores/pgmeta/ForeignTableStore.ts +++ b/studio/stores/pgmeta/ForeignTableStore.ts @@ -1,7 +1,13 @@ import type { PostgresTable } from '@supabase/postgres-meta' -import PostgresMetaInterface from '../common/PostgresMetaInterface' +import { get } from 'lib/common/fetch' +import { ResponseError } from 'types' +import PostgresMetaInterface, { IPostgresMetaInterface } from '../common/PostgresMetaInterface' import { IRootStore } from '../RootStore' +export interface IForeignTableStore extends IPostgresMetaInterface> { + loadById: (id: number | string) => Promise | { error: ResponseError }> +} + export default class ForeignTableStore extends PostgresMetaInterface> { constructor( rootStore: IRootStore, @@ -18,4 +24,19 @@ export default class ForeignTableStore extends PostgresMetaInterface + // @ts-ignore + this.data[id] = data + return data + } catch (error: any) { + return { error } + } + } } diff --git a/studio/stores/pgmeta/MaterializedViewStore.ts b/studio/stores/pgmeta/MaterializedViewStore.ts new file mode 100644 index 00000000000..96d81b3ceb4 --- /dev/null +++ b/studio/stores/pgmeta/MaterializedViewStore.ts @@ -0,0 +1,39 @@ +import { PostgresMaterializedView } from '@supabase/postgres-meta' +import { ResponseError } from 'types' +import PostgresMetaInterface, { IPostgresMetaInterface } from '../common/PostgresMetaInterface' +import { IRootStore } from '../RootStore' +import { get } from 'lib/common/fetch' + +export interface IMaterializedViewStore extends IPostgresMetaInterface { + loadById: ( + id: number | string + ) => Promise | { error: ResponseError }> +} + +export default class MaterializedViewStore extends PostgresMetaInterface { + constructor( + rootStore: IRootStore, + dataUrl: string, + headers?: { + [prop: string]: any + }, + options?: { identifier: string } + ) { + super(rootStore, dataUrl, headers, options) + } + + async loadById(id: number | string) { + try { + const url = `${this.url}?id=${id}` + const response = await get(url, { headers: this.headers }) + if (response.error) throw response.error + + const data = response as Partial + // @ts-ignore + this.data[id] = data + return data + } catch (error: any) { + return { error } + } + } +} diff --git a/studio/stores/pgmeta/MetaStore.ts b/studio/stores/pgmeta/MetaStore.ts index 3d0a73e40d0..88fe5e0c8da 100644 --- a/studio/stores/pgmeta/MetaStore.ts +++ b/studio/stores/pgmeta/MetaStore.ts @@ -14,7 +14,7 @@ import type { import { IS_PLATFORM, API_URL } from 'lib/constants' import { post } from 'lib/common/fetch' import { timeout } from 'lib/helpers' -import { ResponseError, SchemaView } from 'types' +import { ResponseError } from 'types' import { IRootStore } from '../RootStore' import ColumnStore from './ColumnStore' @@ -26,6 +26,7 @@ import { IPostgresMetaInterface } from '../common/PostgresMetaInterface' import { ColumnField, CreateColumnPayload, + ExtendedPostgresRelationship, UpdateColumnPayload, } from 'components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.types' import { @@ -41,8 +42,10 @@ import FunctionsStore from './FunctionsStore' import HooksStore from './HooksStore' import ExtensionsStore from './ExtensionsStore' import TypesStore from './TypesStore' -import ForeignTableStore from './ForeignTableStore' -import ViewStore from './ViewStore' +import ForeignTableStore, { IForeignTableStore } from './ForeignTableStore' +import ViewStore, { IViewStore } from './ViewStore' +import MaterializedViewStore, { IMaterializedViewStore } from './MaterializedViewStore' +import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants' const BATCH_SIZE = 1000 const CHUNK_SIZE = 1024 * 1024 * 0.1 // 0.1MB @@ -54,8 +57,9 @@ export interface IMetaStore { tables: ITableStore columns: IPostgresMetaInterface schemas: IPostgresMetaInterface - views: IPostgresMetaInterface - foreignTables: IPostgresMetaInterface> + views: IViewStore + materializedViews: IMaterializedViewStore + foreignTables: IForeignTableStore hooks: IPostgresMetaInterface roles: IRolesStore @@ -81,7 +85,7 @@ export interface IMetaStore { ) => Promise removePrimaryKey: (schema: string, table: string) => Promise addForeignKey: ( - relationship: Partial + relationship: ExtendedPostgresRelationship ) => Promise removeForeignKey: ( relationship: Partial @@ -92,15 +96,16 @@ export interface IMetaStore { createColumn: ( payload: CreateColumnPayload, selectedTable: PostgresTable, - foreignKey?: Partial, + foreignKey?: ExtendedPostgresRelationship, securityConfiguration?: { isEncrypted: boolean; keyId?: string; keyName?: string } ) => any updateColumn: ( id: string, payload: UpdateColumnPayload, selectedTable: PostgresTable, - foreignKey?: Partial, - skipPKCreation?: boolean + foreignKey?: ExtendedPostgresRelationship, + skipPKCreation?: boolean, + skipSuccessMessage?: boolean ) => any duplicateTable: ( payload: any, @@ -126,7 +131,12 @@ export interface IMetaStore { columns: ColumnField[], isRealtimeEnabled: boolean ) => any - + insertRowsViaSpreadsheet: ( + file: any, + table: PostgresTable, + selectedHeaders: string[], + onProgressUpdate: (progress: number) => void + ) => void setProjectDetails: (details: { ref: string; connectionString?: string }) => void } export default class MetaStore implements IMetaStore { @@ -136,6 +146,7 @@ export default class MetaStore implements IMetaStore { columns: ColumnStore schemas: SchemaStore views: ViewStore + materializedViews: MaterializedViewStore foreignTables: ForeignTableStore hooks: HooksStore @@ -183,12 +194,17 @@ 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) this.schemas = new SchemaStore(this.rootStore, `${this.baseUrl}/schemas`, this.headers) this.views = new ViewStore(this.rootStore, `${this.baseUrl}/views`, this.headers) + this.materializedViews = new MaterializedViewStore( + this.rootStore, + `${this.baseUrl}/materialized-views`, + this.headers + ) this.foreignTables = new ForeignTableStore( this.rootStore, `${this.baseUrl}/foreign-tables`, @@ -276,12 +292,25 @@ export default class MetaStore implements IMetaStore { return await this.query(query) } - async addForeignKey(relationship: Partial) { + // [Joshen TODO] Eventually need to extend this to composite foreign keys + async addForeignKey(relationship: ExtendedPostgresRelationship) { + const { deletion_action } = relationship + const deletionAction = + deletion_action === FOREIGN_KEY_DELETION_ACTION.CASCADE + ? 'ON DELETE CASCADE' + : deletion_action === FOREIGN_KEY_DELETION_ACTION.RESTRICT + ? 'ON DELETE RESTRICT' + : deletion_action === FOREIGN_KEY_DELETION_ACTION.SET_DEFAULT + ? 'ON DELETE SET DEFAULT' + : deletion_action === FOREIGN_KEY_DELETION_ACTION.SET_NULL + ? 'ON DELETE SET NULL' + : '' + const query = ` ALTER TABLE "${relationship.source_schema}"."${relationship.source_table_name}" ADD CONSTRAINT "${relationship.source_table_name}_${relationship.source_column_name}_fkey" FOREIGN KEY ("${relationship.source_column_name}") - REFERENCES "${relationship.target_table_schema}"."${relationship.target_table_name}" ("${relationship.target_column_name}"); + REFERENCES "${relationship.target_table_schema}"."${relationship.target_table_name}" ("${relationship.target_column_name}") ${deletionAction}; ` .replace(/\s+/g, ' ') .trim() @@ -360,7 +389,7 @@ export default class MetaStore implements IMetaStore { async createColumn( payload: CreateColumnPayload, selectedTable: PostgresTable, - foreignKey?: Partial, + foreignKey?: ExtendedPostgresRelationship, securityConfiguration?: { isEncrypted: boolean; keyId?: string; keyName?: string } ) { const toastId = this.rootStore.ui.setNotification({ @@ -444,8 +473,9 @@ export default class MetaStore implements IMetaStore { id: string, payload: UpdateColumnPayload, selectedTable: PostgresTable, - foreignKey?: Partial, - skipPKCreation?: boolean + foreignKey?: ExtendedPostgresRelationship, + skipPKCreation?: boolean, + skipSuccessMessage: boolean = false ) { try { const { isPrimaryKey, ...formattedPayload } = payload @@ -474,15 +504,22 @@ export default class MetaStore implements IMetaStore { // For updating of foreign key relationship, we remove the original one by default // Then just add whatever was in foreignKey - simplicity over trying to derive whether to update or not - if (!isUndefined(existingForeignKey)) { + if (existingForeignKey !== undefined) { const relation: any = await this.removeForeignKey(existingForeignKey) if (relation.error) throw relation.error } - if (!isUndefined(foreignKey)) { + if (foreignKey !== undefined) { const relation: any = await this.addForeignKey(foreignKey) if (relation.error) throw relation.error } + + if (!skipSuccessMessage) { + this.rootStore.ui.setNotification({ + category: 'success', + message: `Successfully updated column "${column.name}"`, + }) + } } catch (error: any) { return { error } } @@ -516,6 +553,7 @@ export default class MetaStore implements IMetaStore { const relation = await this.rootStore.meta.addForeignKey({ ...relationship, source_table_name: duplicatedTableName, + deletion_action: FOREIGN_KEY_DELETION_ACTION.NO_ACTION, }) if (relation.error) throw relation.error }) @@ -627,6 +665,7 @@ export default class MetaStore implements IMetaStore { const { error }: any = await this.insertRowsViaSpreadsheet( importContent.file, table, + importContent.selectedHeaders, (progress: number) => { this.rootStore.ui.setNotification({ id: toastId, @@ -769,12 +808,14 @@ export default class MetaStore implements IMetaStore { message: `Updating column ${column.name} from ${updatedTable.name}`, }) const skipPKCreation = true + const skipSuccessMessage = true const res: any = await this.updateColumn( column.id, columnPayload, updatedTable, column.foreignKey, - skipPKCreation + skipPKCreation, + skipSuccessMessage ) if (res?.error) { hasError = true @@ -807,6 +848,7 @@ export default class MetaStore implements IMetaStore { async insertRowsViaSpreadsheet( file: any, table: PostgresTable, + selectedHeaders: string[], onProgressUpdate: (progress: number) => void ) { let chunkNumber = 0 @@ -822,9 +864,15 @@ export default class MetaStore implements IMetaStore { chunk: async (results: any, parser: any) => { parser.pause() + const formattedData = results.data.map((row: any) => { + const formattedRow: any = {} + selectedHeaders.forEach((header) => (formattedRow[header] = row[header])) + return formattedRow + }) + const insertQuery = new Query() .from(table.name, table.schema) - .insert(results.data) + .insert(formattedData) .toSql() const res = await this.query(insertQuery) @@ -895,7 +943,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`) @@ -910,6 +958,9 @@ export default class MetaStore implements IMetaStore { this.views.setUrl(`${this.baseUrl}/views`) this.views.setHeaders(this.headers) + this.materializedViews.setUrl(`${this.baseUrl}/materialized-views`) + this.materializedViews.setHeaders(this.headers) + this.foreignTables.setUrl(`${this.baseUrl}/foreign-tables`) this.foreignTables.setHeaders(this.headers) 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/stores/pgmeta/ViewStore.ts b/studio/stores/pgmeta/ViewStore.ts index 63b74701e42..56067240562 100644 --- a/studio/stores/pgmeta/ViewStore.ts +++ b/studio/stores/pgmeta/ViewStore.ts @@ -1,6 +1,11 @@ -import { SchemaView } from 'types' -import PostgresMetaInterface from '../common/PostgresMetaInterface' +import { ResponseError, SchemaView } from 'types' +import PostgresMetaInterface, { IPostgresMetaInterface } from '../common/PostgresMetaInterface' import { IRootStore } from '../RootStore' +import { get } from 'lib/common/fetch' + +export interface IViewStore extends IPostgresMetaInterface { + loadById: (id: number | string) => Promise | { error: ResponseError }> +} export default class ViewStore extends PostgresMetaInterface { constructor( @@ -13,4 +18,19 @@ export default class ViewStore extends PostgresMetaInterface { ) { super(rootStore, dataUrl, headers, options) } + + async loadById(id: number | string) { + try { + const url = `${this.url}?id=${id}` + const response = await get(url, { headers: this.headers }) + if (response.error) throw response.error + + const data = response as Partial + // @ts-ignore + this.data[id] = data + return data + } catch (error: any) { + return { error } + } + } } 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/grid.scss b/studio/styles/grid.scss index 0ae4ae6517e..cfcb2d862ad 100644 --- a/studio/styles/grid.scss +++ b/studio/styles/grid.scss @@ -571,62 +571,6 @@ @apply block; } -/* - ForeignTableModal -*/ - -.foreign-table-modal__content-container { - @apply w-full; -} - -/* .foreign-table-modal__content-container__inner { -@apply px-6; -} */ - -.foreign-table-modal__content-container__inner__overflow { - @apply w-full overflow-auto; -} - -.foreign-table-modal__filter { - @apply mt-2 flex items-center space-x-2; -} - -.foreign-table-modal__filter__trigger-content { - @apply space-x-2; -} - -.foreign-table-modal__filter__trigger-content__label { - @apply text-typography-body-light dark:text-typography-body-dark; -} - -.foreign-table-modal__filter__trigger-content__name { - @apply text-typography-body-strong-light dark:text-typography-body-strong-dark font-bold; -} - -.foreign-table-modal__filter__search-input { - @apply flex-grow; -} - -.foreign-table-modal__menu { - @apply flex flex-col space-y-2; -} - -.foreign-table-modal__row-item { - @apply border-scale-400 dark:border-scale-500 overflow-auto rounded border border-solid px-5 py-2 shadow-sm first:mt-2; -} - -.foreign-table-modal__row-item__inner { - @apply flex space-x-4; -} - -.foreign-table-modal__row-item__inner__key-item { - @apply flex flex-initial flex-col; -} - -.foreign-table-modal__row-item__inner__key-item__key { - @apply font-mono; -} - /* header/filter/FilterDropdown */ diff --git a/studio/styles/main.scss b/studio/styles/main.scss index e3d187c24c4..b25a92f920c 100644 --- a/studio/styles/main.scss +++ b/studio/styles/main.scss @@ -184,10 +184,6 @@ div[data-radix-portal]:not(.portal--toast) { } } -div.fixed.inset-0 { - z-index: 11 !important; -} - .recharts-tooltip-cursor { @apply fill-transparent; } @@ -236,4 +232,9 @@ div.fixed.inset-0 { input { @apply pr-7; } -} \ No newline at end of file +} + +// to do: remove this CSS and do the same thing in a reusable tooltip component +[data-radix-popper-content-wrapper] { + @apply z-50 #{!important}; +} diff --git a/studio/styles/react-data-grid-logs.scss b/studio/styles/react-data-grid-logs.scss index 9a394ae26bd..ede173ea6af 100644 --- a/studio/styles/react-data-grid-logs.scss +++ b/studio/styles/react-data-grid-logs.scss @@ -49,7 +49,7 @@ &.rdg-row--focused .rdg-cell, &.rdg-row--focused .rdg-cell span { - @apply text-scale-1200 font-semibold #{!important}; + @apply text-scale-1100 font-semibold #{!important}; } } } diff --git a/studio/styles/ui.scss b/studio/styles/ui.scss index 16a1284fc6b..12e09fa2017 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 { @@ -333,4 +366,75 @@ .pitr-listbox { @apply md:gap-x-2; -} \ No newline at end of file +} + +.roles-toggle { + label { + @apply mt-1; + } +} + +.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); + } + } +} + +.form-field-markdown { + a { + @apply text-brand-900 transition opacity-75 hover:opacity-100 underline; + } +} + +.view-icon { + rect { + @apply fill-blue-500 stroke-blue-500; + } + path, + circle { + @apply stroke-blue-900 fill-blue-500; + } +} + +.table-icon { + rect { + @apply fill-green-1000 stroke-green-500; + } + line { + @apply stroke-green-500; + } +} 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..6cf20d5b59a 100644 --- a/studio/tailwind.config.js +++ b/studio/tailwind.config.js @@ -37,6 +37,7 @@ module.exports = ui({ './lib/**/*.{js,ts,jsx,tsx}', './lib/**/**/*.{js,ts,jsx,tsx}', './../packages/ui/src/lib/theme/defaultTheme.ts', + './../packages/ui/src/components/**/*.{ts,tsx}', // purge styles from supabase ui theme // './../node_modules/ui/dist/config/default-theme.js', // purge styles from grid library @@ -73,6 +74,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/Auth/Auth.constants.test.js b/studio/tests/components/Auth/Auth.constants.test.js index 4c8a3fe62f3..ab7504014dc 100644 --- a/studio/tests/components/Auth/Auth.constants.test.js +++ b/studio/tests/components/Auth/Auth.constants.test.js @@ -1,87 +1,87 @@ -import { domainRegex } from 'components/interfaces/Auth/Auth.constants' +import { urlRegex } from 'components/interfaces/Auth/Auth.constants' describe('Auth.constants: domainRegex', () => { test('should validate typical URLs', () => { const mockInput1 = 'http://domain.com' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(true) const mockInput2 = 'https://supabase.io' - const output2 = domainRegex.test(mockInput2) + const output2 = urlRegex.test(mockInput2) expect(output2).toBe(true) const mockInput3 = 'https://new-domain-vercel.com' - const output3 = domainRegex.test(mockInput3) + const output3 = urlRegex.test(mockInput3) expect(output3).toBe(true) const mockInput4 = 'www.test-domain.com' - const output4 = domainRegex.test(mockInput4) + const output4 = urlRegex.test(mockInput4) expect(output4).toBe(true) }) test('should validate app-based domains', () => { const mockInput1 = 'exp://exp.host/some-app' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(true) const mockInput2 = 'exp://exp.host/some-app?release-channel=default' - const output2 = domainRegex.test(mockInput2) + const output2 = urlRegex.test(mockInput2) expect(output2).toBe(true) }) test('should validate subdomains', () => { const mockInput1 = 'https://app.supabase.com' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(true) }) test('should validate localhost URLs', () => { const mockInput1 = 'http://localhost:3000' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(true) }) test('should validate URLs with query params', () => { const mockInput1 = 'https://supabase.com?name=test' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(true) const mockInput2 = 'https://supabase.com?name=test&description=hello&page=2' - const output2 = domainRegex.test(mockInput2) + const output2 = urlRegex.test(mockInput2) expect(output2).toBe(true) }) test('should validate URLs with wildcards', () => { const mockInput1 = 'https://supabase*.com' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(true) const mockInput2 = 'https://supabase.com/*' - const output2 = domainRegex.test(mockInput2) + const output2 = urlRegex.test(mockInput2) expect(output2).toBe(true) const mockInput3 = 'https://new-*-domain.com/*' - const output3 = domainRegex.test(mockInput3) + const output3 = urlRegex.test(mockInput3) expect(output3).toBe(true) const mockInput4 = 'https://new-*-domain.com/*/*/*' - const output4 = domainRegex.test(mockInput4) + const output4 = urlRegex.test(mockInput4) expect(output4).toBe(true) const mockInput5 = 'https://sub-*-domain.new-*-domain.com/*/*/*' - const output5 = domainRegex.test(mockInput5) + const output5 = urlRegex.test(mockInput5) expect(output5).toBe(true) }) test('should invalidate invalid URLs', () => { const mockInput1 = 'supabase' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(false) const mockInput2 = 'mailto:test@gmail.com' - const output2 = domainRegex.test(mockInput2) + const output2 = urlRegex.test(mockInput2) expect(output2).toBe(false) const mockInput4 = 'hello world.com' - const output4 = domainRegex.test(mockInput4) + const output4 = urlRegex.test(mockInput4) expect(output4).toBe(false) const mockInput5 = 'email@domain.com' - const output5 = domainRegex.test(mockInput5) + const output5 = urlRegex.test(mockInput5) expect(output5).toBe(false) }) }) diff --git a/studio/tests/components/Editor/SpreadsheetImport.utils.test.js b/studio/tests/components/Editor/SpreadsheetImport.utils.test.js index 061d2944b93..373da64a4e6 100644 --- a/studio/tests/components/Editor/SpreadsheetImport.utils.test.js +++ b/studio/tests/components/Editor/SpreadsheetImport.utils.test.js @@ -1,4 +1,4 @@ -import { inferColumnType } from 'components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.utils' +import { inferColumnType } from 'components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.utils' describe('SpreadsheedImport.utils: inferColumnType', () => { test('should default column type to text if no rows to infer from', () => { 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/helpers.tsx b/studio/tests/helpers.tsx index 6aacb05b4de..8dbd99290f2 100644 --- a/studio/tests/helpers.tsx +++ b/studio/tests/helpers.tsx @@ -5,6 +5,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render as originalRender } from '@testing-library/react' import { RootStore } from 'stores' import { StoreProvider } from 'hooks' +import { TooltipProvider } from '@radix-ui/react-tooltip' interface SelectorOptions { container?: HTMLElement } @@ -68,7 +69,9 @@ const ReactQueryTestConfig: React.FC = ({ children }) => { return ( - {children} + + {children} + ) } diff --git a/studio/tests/pages/projects/LogsPreviewer.test.js b/studio/tests/pages/projects/LogsPreviewer.test.js index 15774d0b66c..780a82f9d4a 100644 --- a/studio/tests/pages/projects/LogsPreviewer.test.js +++ b/studio/tests/pages/projects/LogsPreviewer.test.js @@ -23,7 +23,8 @@ import userEvent from '@testing-library/user-event' import { wait } from '@testing-library/user-event/dist/utils' import { logDataFixture } from '../../fixtures' import { LogsTableName } from 'components/interfaces/Settings/Logs' -import { useParams } from 'hooks' +jest.mock('common/hooks') +import { useParams } from 'common/hooks' beforeEach(() => { // reset mocks between tests diff --git a/studio/tests/pages/projects/logs-query.test.js b/studio/tests/pages/projects/logs-query.test.js index 979e257cff7..eb1ce4d5c50 100644 --- a/studio/tests/pages/projects/logs-query.test.js +++ b/studio/tests/pages/projects/logs-query.test.js @@ -8,7 +8,8 @@ import { logDataFixture } from '../../fixtures' import { clickDropdown } from 'tests/helpers' import dayjs from 'dayjs' import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query' -import { useParams } from 'hooks' +jest.mock('common/hooks') +import { useParams } from 'common/hooks' const defaultRouterMock = () => { const router = jest.fn() @@ -172,6 +173,12 @@ test('query warnings', async () => { await screen.findByText('1 warning') }) +test('field reference', async () => { + render() + userEvent.click(await screen.findByText('Field Reference')) + await screen.findByText('metadata.request.cf.asOrganization') +}) + describe.each(['FREE', 'PRO', 'TEAM', 'ENTERPRISE'])('upgrade modal for %s', (key) => { beforeEach(() => { useProjectSubscriptionQuery.mockReturnValue({ 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/studio/types/next.ts b/studio/types/next.ts index 04bd6659eb4..440e552e411 100644 --- a/studio/types/next.ts +++ b/studio/types/next.ts @@ -1,11 +1,17 @@ import { NextPage } from 'next' import { AppProps } from 'next/app' -import { ReactElement, ReactNode } from 'react' +import { ComponentType, ReactElement, ReactNode } from 'react' -export type AppPropsWithLayout = AppProps & { +export type AppPropsWithLayout = AppProps<{ dehydratedState: any }> & { Component: NextPageWithLayout } -export type NextPageWithLayout

      = NextPage & { +export type NextPageWithLayout

      = NextPage & { getLayout?: (page: ReactElement) => ReactNode } + +export function isNextPageWithLayout( + Component: ComponentType | NextPageWithLayout +): Component is NextPageWithLayout { + return 'getLayout' in Component && typeof Component.getLayout === 'function' +} diff --git a/studio/types/sse.d.ts b/studio/types/sse.d.ts new file mode 100644 index 00000000000..dbf97ac0836 --- /dev/null +++ b/studio/types/sse.d.ts @@ -0,0 +1,12 @@ +declare module 'sse.js' { + export type SSEOptions = EventSourceInit & { + headers?: Record + payload?: string + method?: string + } + + export class SSE extends EventSource { + constructor(url: string | URL, sseOptions?: SSEOptions) + stream(): void + } +} diff --git a/supabase/functions/clippy-search/index.ts b/supabase/functions/clippy-search/index.ts index 8a80354e429..de86010809b 100644 --- a/supabase/functions/clippy-search/index.ts +++ b/supabase/functions/clippy-search/index.ts @@ -1,10 +1,16 @@ import { serve } from 'https://deno.land/std@0.170.0/http/server.ts' import 'https://deno.land/x/xhr@0.2.1/mod.ts' import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.5.0' -import { oneLine, stripIndent } from 'https://esm.sh/common-tags@1.8.2' +import { codeBlock, oneLine } from 'https://esm.sh/common-tags@1.8.2' import GPT3Tokenizer from 'https://esm.sh/gpt3-tokenizer@1.1.5' -import { Configuration, CreateCompletionRequest, OpenAIApi } from 'https://esm.sh/openai@3.1.0' -import { ApplicationError, UserError } from './errors.ts' +import { + ChatCompletionRequestMessage, + ChatCompletionRequestMessageRoleEnum, + Configuration, + CreateChatCompletionRequest, + OpenAIApi, +} from 'https://esm.sh/openai@3.2.1' +import { ApplicationError, UserError } from '../common/errors.ts' const openAiKey = Deno.env.get('OPENAI_KEY') const supabaseUrl = Deno.env.get('SUPABASE_URL') @@ -46,6 +52,9 @@ serve(async (req) => { throw new UserError('Missing query in request data') } + // Intentionally log the query + console.log({ query }) + const sanitizedQuery = query.trim() const supabaseClient = createClient(supabaseUrl, supabaseServiceKey) @@ -107,7 +116,7 @@ serve(async (req) => { contextText += `${content.trim()}\n---\n` } - const prompt = stripIndent` + const prompt = codeBlock` ${oneLine` You are a very enthusiastic Supabase representative who loves to help people! Given the following sections from the Supabase @@ -127,15 +136,76 @@ serve(async (req) => { Answer as markdown (including related code snippets if available): ` - const completionOptions: CreateCompletionRequest = { - model: 'text-davinci-003', - prompt, - max_tokens: 512, + const messages: ChatCompletionRequestMessage[] = [ + { + role: ChatCompletionRequestMessageRoleEnum.System, + content: codeBlock` + ${oneLine` + You are a very enthusiastic Supabase AI who loves + to help people! Given the following information from + the Supabase documentation, answer the user's question using + only that information, outputted in markdown format. + `} + + ${oneLine` + If you are unsure + and the answer is not explicitly written in the documentation, say + "Sorry, I don't know how to help with that." + `} + + ${oneLine` + Always include related code snippets if available. + `} + `, + }, + { + role: ChatCompletionRequestMessageRoleEnum.User, + content: codeBlock` + Here is the Supabase documentation: + ${contextText} + `, + }, + { + role: ChatCompletionRequestMessageRoleEnum.User, + content: codeBlock` + ${oneLine` + Answer my next question using only the above documentation. + You must also follow the below rules when answering: + `} + ${oneLine` + - Do not make up answers that are not provided in the documentation. + `} + ${oneLine` + - If you are unsure and the answer is not explicitly written + in the documentation context, say + "Sorry, I don't know how to help with that." + `} + ${oneLine` + - Prefer splitting your response into multiple paragraphs. + `} + ${oneLine` + - Output as markdown with code snippets if available. + `} + `, + }, + { + role: ChatCompletionRequestMessageRoleEnum.User, + content: codeBlock` + Here is my question: + ${oneLine`${sanitizedQuery}`} + `, + }, + ] + + const completionOptions: CreateChatCompletionRequest = { + model: 'gpt-3.5-turbo', + messages, + max_tokens: 1024, temperature: 0, stream: true, } - const response = await fetch('https://api.openai.com/v1/completions', { + const response = await fetch('https://api.openai.com/v1/chat/completions', { headers: { Authorization: `Bearer ${openAiKey}`, 'Content-Type': 'application/json', diff --git a/supabase/functions/common/database-types.ts b/supabase/functions/common/database-types.ts new file mode 100644 index 00000000000..bf0addcb2cc --- /dev/null +++ b/supabase/functions/common/database-types.ts @@ -0,0 +1,319 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json } + | Json[] + +export interface Database { + graphql_public: { + Tables: { + [_ in never]: never + } + Views: { + [_ in never]: never + } + Functions: { + graphql: { + Args: { + operationName?: string + query?: string + variables?: Json + extensions?: Json + } + Returns: Json + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } + public: { + Tables: { + page: { + Row: { + checksum: string | null + id: number + meta: Json | null + parent_page_id: number | null + path: string + source: string | null + type: string | null + } + Insert: { + checksum?: string | null + id?: number + meta?: Json | null + parent_page_id?: number | null + path: string + source?: string | null + type?: string | null + } + Update: { + checksum?: string | null + id?: number + meta?: Json | null + parent_page_id?: number | null + path?: string + source?: string | null + type?: string | null + } + } + page_section: { + Row: { + content: string | null + embedding: unknown | null + heading: string | null + id: number + page_id: number + slug: string | null + token_count: number | null + } + Insert: { + content?: string | null + embedding?: unknown | null + heading?: string | null + id?: number + page_id: number + slug?: string | null + token_count?: number | null + } + Update: { + content?: string | null + embedding?: unknown | null + heading?: string | null + id?: number + page_id?: number + slug?: string | null + token_count?: number | null + } + } + } + Views: { + [_ in never]: never + } + Functions: { + get_page_parents: { + Args: { + page_id: number + } + Returns: { + id: number + parent_page_id: number + path: string + meta: Json + }[] + } + ivfflathandler: { + Args: { + "": unknown + } + Returns: unknown + } + match_page_sections: { + Args: { + embedding: unknown + match_threshold: number + match_count: number + min_content_length: number + } + Returns: { + id: number + page_id: number + slug: string + heading: string + content: string + similarity: number + }[] + } + vector_avg: { + Args: { + "": number[] + } + Returns: unknown + } + vector_dims: { + Args: { + "": unknown + } + Returns: number + } + vector_norm: { + Args: { + "": unknown + } + Returns: number + } + vector_out: { + Args: { + "": unknown + } + Returns: unknown + } + vector_send: { + Args: { + "": unknown + } + Returns: string + } + vector_typmod_in: { + Args: { + "": unknown[] + } + Returns: number + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } + storage: { + Tables: { + buckets: { + Row: { + created_at: string | null + id: string + name: string + owner: string | null + public: boolean | null + updated_at: string | null + } + Insert: { + created_at?: string | null + id: string + name: string + owner?: string | null + public?: boolean | null + updated_at?: string | null + } + Update: { + created_at?: string | null + id?: string + name?: string + owner?: string | null + public?: boolean | null + updated_at?: string | null + } + } + migrations: { + Row: { + executed_at: string | null + hash: string + id: number + name: string + } + Insert: { + executed_at?: string | null + hash: string + id: number + name: string + } + Update: { + executed_at?: string | null + hash?: string + id?: number + name?: string + } + } + objects: { + Row: { + bucket_id: string | null + created_at: string | null + id: string + last_accessed_at: string | null + metadata: Json | null + name: string | null + owner: string | null + path_tokens: string[] | null + updated_at: string | null + } + Insert: { + bucket_id?: string | null + created_at?: string | null + id?: string + last_accessed_at?: string | null + metadata?: Json | null + name?: string | null + owner?: string | null + path_tokens?: string[] | null + updated_at?: string | null + } + Update: { + bucket_id?: string | null + created_at?: string | null + id?: string + last_accessed_at?: string | null + metadata?: Json | null + name?: string | null + owner?: string | null + path_tokens?: string[] | null + updated_at?: string | null + } + } + } + Views: { + [_ in never]: never + } + Functions: { + extension: { + Args: { + name: string + } + Returns: string + } + filename: { + Args: { + name: string + } + Returns: string + } + foldername: { + Args: { + name: string + } + Returns: string[] + } + get_size_by_bucket: { + Args: Record + Returns: { + size: number + bucket_id: string + }[] + } + search: { + Args: { + prefix: string + bucketname: string + limits?: number + levels?: number + offsets?: number + search?: string + sortcolumn?: string + sortorder?: string + } + Returns: { + name: string + id: string + updated_at: string + created_at: string + last_accessed_at: string + metadata: Json + }[] + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } +} + diff --git a/supabase/functions/clippy-search/errors.ts b/supabase/functions/common/errors.ts similarity index 100% rename from supabase/functions/clippy-search/errors.ts rename to supabase/functions/common/errors.ts diff --git a/supabase/functions/og-images/README.md b/supabase/functions/og-images/README.md index 886b9f69b9e..c2a093815df 100644 --- a/supabase/functions/og-images/README.md +++ b/supabase/functions/og-images/README.md @@ -38,3 +38,11 @@ supabase functions serve og-images ``` Now we can visit [localhost:54321/functions/v1/og-images/?site=docs&title=Title&description=Description&type=Auth](http://localhost:54321/functions/v1/og-images/?site=docs&title=Title&description=Description&type=Auth) to see your changes localy. + +## Deploy + +To deploy this function, you currently need to depoy it locally. To do this follow the steps below. + +```bash +supabase functions deploy og-images --no-verify-jwt +``` diff --git a/supabase/functions/og-images/component/Docs.tsx b/supabase/functions/og-images/component/Docs.tsx index 5293a1b4bc2..5b38579c085 100644 --- a/supabase/functions/og-images/component/Docs.tsx +++ b/supabase/functions/og-images/component/Docs.tsx @@ -9,6 +9,18 @@ type Props = { const Docs = (props: Props) => { const { type, title, description, icon } = props + + let typeName: string | undefined = type?.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) + let typeIcon: string | undefined = type?.toLowerCase() + + if (type === 'functions' || type === 'function') { + typeName = 'Edge Functions' + } else if (type === 'self-hosting') { + typeIcon = 'resources' + } else if (type === 'cli') { + typeName = 'CLI' + typeIcon = 'reference-cli' + } return (

      { {type && ( <>
      - +
      - {type} + {typeName} )}
      -

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

      +

      {!title ? 'Supabase' : title}

      {description}

      diff --git a/supabase/functions/search/index.ts b/supabase/functions/search/index.ts new file mode 100644 index 00000000000..3d5c91c2904 --- /dev/null +++ b/supabase/functions/search/index.ts @@ -0,0 +1,160 @@ +import { serve } from 'https://deno.land/std@0.170.0/http/server.ts' +import 'https://deno.land/x/xhr@0.2.1/mod.ts' +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.8.0' +import { Configuration, OpenAIApi } from 'https://esm.sh/openai@3.1.0' +import { Database } from '../common/database-types.ts' +import { ApplicationError, UserError } from '../common/errors.ts' + +const openAiKey = Deno.env.get('OPENAI_KEY') +const supabaseUrl = Deno.env.get('SUPABASE_URL') +const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') + +export const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +} + +serve(async (req) => { + try { + // Handle CORS + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }) + } + + if (!openAiKey) { + throw new ApplicationError('Missing environment variable OPENAI_KEY') + } + + if (!supabaseUrl) { + throw new ApplicationError('Missing environment variable SUPABASE_URL') + } + + if (!supabaseServiceKey) { + throw new ApplicationError('Missing environment variable SUPABASE_SERVICE_ROLE_KEY') + } + + const requestData = await req.json() + + if (!requestData) { + throw new UserError('Missing request data') + } + + const { query } = requestData + + if (!query) { + throw new UserError('Missing query in request data') + } + + // Intentionally log the query + console.log({ query }) + + const sanitizedQuery = query.trim() + + const supabaseClient = createClient(supabaseUrl, supabaseServiceKey) + + const configuration = new Configuration({ apiKey: openAiKey }) + const openai = new OpenAIApi(configuration) + + // Moderate the content to comply with OpenAI T&C + const moderationResponse = await openai.createModeration({ input: sanitizedQuery }) + + const [results] = moderationResponse.data.results + + if (results.flagged) { + throw new UserError('Flagged content', { + flagged: true, + categories: results.categories, + }) + } + + const embeddingResponse = await openai.createEmbedding({ + model: 'text-embedding-ada-002', + input: sanitizedQuery.replaceAll('\n', ' '), + }) + + if (embeddingResponse.status !== 200) { + throw new ApplicationError('Failed to create embedding for question', embeddingResponse) + } + + const [{ embedding }] = embeddingResponse.data.data + const { error: matchError, data: pageSections } = await supabaseClient.rpc( + 'match_page_sections', + { + embedding, + match_threshold: 0.78, + match_count: 10, + min_content_length: 50, + } + ) + + if (matchError || !pageSections) { + throw new ApplicationError('Failed to match page sections', matchError ?? undefined) + } + + const uniquePageIds = pageSections + .map(({ page_id }) => page_id) + .filter((value, index, array) => array.indexOf(value) === index) + + const { error: fetchPagesError, data: pages } = await supabaseClient + .from('page') + .select() + .in('id', uniquePageIds) + + if (fetchPagesError || !pages) { + throw new ApplicationError(`Failed to fetch pages`, fetchPagesError) + } + + const combinedPages = pages + .map((page) => { + const sections = pageSections + .filter(({ page_id }) => page_id === page.id) + .map(({ content: _, ...pageSection }) => pageSection) + + const score = sections.reduce((sum, section) => sum + section.similarity, 0) + + return { + ...page, + sections, + score, + } + }) + .sort((a, b) => b.score - a.score) + + return new Response(JSON.stringify(combinedPages), { + headers: { + ...corsHeaders, + 'Content-Type': 'application/json', + }, + }) + } catch (err: unknown) { + if (err instanceof UserError) { + return new Response( + JSON.stringify({ + error: err.message, + data: err.data, + }), + { + status: 400, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + } + ) + } else if (err instanceof ApplicationError) { + // Print out application errors with their additional data + console.error(`${err.message}: ${JSON.stringify(err.data)}`) + } else { + // Print out unexpected errors as is to help with debugging + console.error(err) + } + + // TODO: include more response info in debug environments + return new Response( + JSON.stringify({ + error: 'There was an error processing your request', + }), + { + status: 500, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + } + ) + } +}) diff --git a/supabase/migrations/20230216195821_page_hierarchy.sql b/supabase/migrations/20230216195821_page_hierarchy.sql new file mode 100644 index 00000000000..9b5fbea0133 --- /dev/null +++ b/supabase/migrations/20230216195821_page_hierarchy.sql @@ -0,0 +1,2 @@ +alter table "public"."page" +add parent_page_id bigint references public.page; diff --git a/supabase/migrations/20230216232739_page_section_heading_slug.sql b/supabase/migrations/20230216232739_page_section_heading_slug.sql new file mode 100644 index 00000000000..7186a9fd510 --- /dev/null +++ b/supabase/migrations/20230216232739_page_section_heading_slug.sql @@ -0,0 +1,3 @@ +alter table "public"."page_section" +add column slug text, +add column heading text; diff --git a/supabase/migrations/20230217032716_page_hierarchy_function.sql b/supabase/migrations/20230217032716_page_hierarchy_function.sql new file mode 100644 index 00000000000..df34f9f053f --- /dev/null +++ b/supabase/migrations/20230217032716_page_hierarchy_function.sql @@ -0,0 +1,53 @@ +drop function match_page_sections; + +create or replace function match_page_sections(embedding vector(1536), match_threshold float, match_count int, min_content_length int) +returns table (id bigint, page_id bigint, slug text, heading text, content text, similarity float) +language plpgsql +as $$ +#variable_conflict use_variable +begin + return query + select + page_section.id, + page_section.page_id, + page_section.slug, + page_section.heading, + page_section.content, + (page_section.embedding <#> embedding) * -1 as similarity + from page_section + + -- We only care about sections that have a useful amount of content + where length(page_section.content) >= min_content_length + + -- The dot product is negative because of a Postgres limitation, so we negate it + and (page_section.embedding <#> embedding) * -1 > match_threshold + + -- OpenAI embeddings are normalized to length 1, so + -- cosine similarity and dot product will produce the same results. + -- Using dot product which can be computed slightly faster. + -- + -- For the different syntaxes, see https://github.com/pgvector/pgvector + order by page_section.embedding <#> embedding + + limit match_count; +end; +$$; + +create or replace function get_page_parents(page_id bigint) +returns table (id bigint, parent_page_id bigint, path text, meta jsonb) +language sql +as $$ + with recursive chain as ( + select * + from page + where id = page_id + + union all + + select child.* + from page as child + join chain on chain.parent_page_id = child.id + ) + select id, parent_page_id, path, meta + from chain; +$$; diff --git a/supabase/migrations/20230228205709_page_source.sql b/supabase/migrations/20230228205709_page_source.sql new file mode 100644 index 00000000000..04ef19ff810 --- /dev/null +++ b/supabase/migrations/20230228205709_page_source.sql @@ -0,0 +1,3 @@ +alter table "public"."page" +add column type text, +add column source text; diff --git a/tests/package-lock.json b/tests/package-lock.json deleted file mode 100644 index ebc3dba5068..00000000000 --- a/tests/package-lock.json +++ /dev/null @@ -1,9798 +0,0 @@ -{ - "name": "supabase-tests", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "supabase-tests", - "version": "1.0.0", - "license": "MIT", - "devDependencies": { - "@faker-js/faker": "^6.1.2", - "@supabase/supabase-js": "^2.0.0", - "@testdeck/jest": "^0.2.0", - "@types/jest": "^27.4.0", - "@types/node": "^17.0.24", - "@typescript-eslint/eslint-plugin": "^5.10.0", - "@typescript-eslint/parser": "^5.10.0", - "allure-commandline": "^2.17.2", - "allure-js-commons": "^2.0.0-beta.14", - "cross-fetch": "^3.1.5", - "eslint": "^8.7.0", - "jest": "^27.4.7", - "jest-allure2-adapter": "^0.3.11", - "postgres": "^3.0.5", - "ts-jest": "^27.1.3", - "ts-node": "^10.7.0", - "typescript": "^4.6.3" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", - "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz", - "integrity": "sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.7", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", - "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", - "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz", - "integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", - "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.8.tgz", - "integrity": "sha512-xe+H7JlvKsDQwXRsBhSnq1/+9c+LlQcCK3Tn/l5sbx02HYns/cn7ibp9+RV1sIUqu7hKg91NWsgHurO9dowITQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.8", - "@babel/types": "^7.16.8", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", - "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.2.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc/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==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/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==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/eslintrc/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==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@faker-js/faker": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-6.1.2.tgz", - "integrity": "sha512-QSvmexHCxeRUk1/yKmoEDaWB5Hohjvtim5g2JJwy8S/l0L4b3y/GxSpE6vN4SBoVGGahEQW21uqyRr7EofI35A==", - "dev": true, - "engines": { - "node": ">=14.0.0", - "npm": ">=6.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", - "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "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==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.4.6.tgz", - "integrity": "sha512-jauXyacQD33n47A44KrlOVeiXHEXDqapSdfb9kTekOchH/Pd18kBIO1+xxJQRLuG+LUuljFCwTG92ra4NW7SpA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.4.7.tgz", - "integrity": "sha512-n181PurSJkVMS+kClIFSX/LLvw9ExSb+4IMtD6YnfxZVerw9ANYtW0bPrm0MJu2pfe9SY9FJ9FtQ+MdZkrZwjg==", - "dev": true, - "dependencies": { - "@jest/console": "^27.4.6", - "@jest/reporters": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.4.2", - "jest-config": "^27.4.7", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-resolve-dependencies": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "jest-watcher": "^27.4.6", - "micromatch": "^4.0.4", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz", - "integrity": "sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "jest-mock": "^27.4.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz", - "integrity": "sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.4.6.tgz", - "integrity": "sha512-kAiwMGZ7UxrgPzu8Yv9uvWmXXxsy0GciNejlHvfPIfWkSxChzv6bgTS3YqBkGuHcis+ouMFI2696n2t+XYIeFw==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/types": "^27.4.2", - "expect": "^27.4.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.4.6.tgz", - "integrity": "sha512-+Zo9gV81R14+PSq4wzee4GC2mhAN9i9a7qgJWL90Gpx7fHYkWpTBvwWNZUXvJByYR9tAVBdc8VxDWqfJyIUrIQ==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/source-map": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.4.0.tgz", - "integrity": "sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.4.6.tgz", - "integrity": "sha512-fi9IGj3fkOrlMmhQqa/t9xum8jaJOOAi/lZlm6JXSc55rJMXKHxNDN1oCP39B0/DhNOa2OMupF9BcKZnNtXMOQ==", - "dev": true, - "dependencies": { - "@jest/console": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz", - "integrity": "sha512-3GL+nsf6E1PsyNsJuvPyIz+DwFuCtBdtvPpm/LMXVkBJbdFvQYCDpccYT56qq5BGniXWlE81n2qk1sdXfZebnw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-runtime": "^27.4.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.4.6.tgz", - "integrity": "sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.4.2", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-util": "^27.4.2", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/types": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", - "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.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==", - "dev": true, - "dependencies": { - "cross-fetch": "^3.1.5" - } - }, - "node_modules/@supabase/gotrue-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.0.1.tgz", - "integrity": "sha512-PCzHeFhz33lPWLZT+UiVDpGkZQI3K1wwhkhgAjvJdkyCbwK5u+LWWUzz0AX66bGGLpg1IYQIP7oz8V68XVjzMA==", - "dev": true, - "dependencies": { - "cross-fetch": "^3.1.5" - } - }, - "node_modules/@supabase/postgrest-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.0.0.tgz", - "integrity": "sha512-MRR4g8dThgSnlnjkL2Jvc6gS73W68hLq48/Q245CLvUPt3KtOh3WXpvo4rZDEDHYVkyVga+L9IQixo6YTsjMQg==", - "dev": true, - "dependencies": { - "cross-fetch": "^3.1.5" - } - }, - "node_modules/@supabase/realtime-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.0.0.tgz", - "integrity": "sha512-DJVt5Z76pik3Fxt1es4fmquq6GrwR3nLqcb2Q6VpHXSudtixML0Cyxsu0DPzxa6ruSQrO9s08s0OQgwaIA1C0Q==", - "dev": true, - "dependencies": { - "@types/phoenix": "^1.5.4", - "websocket": "^1.0.34" - } - }, - "node_modules/@supabase/storage-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.0.0.tgz", - "integrity": "sha512-7kXThdRt/xqnOOvZZxBqNkeX1CFNUWc0hYBJtNN/Uvt8ok9hD14foYmroWrHn046wEYFqUrB9U35JYsfTrvltA==", - "dev": true, - "dependencies": { - "cross-fetch": "^3.1.5" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.0.0.tgz", - "integrity": "sha512-8nNMXYzBlQVSDONFaNgyyxsAdUY+C9Q/7vEDYDdTFTyC3i+luXTDOSjHMLcjIky/+x5rIAcNQqqIJ48HJUluXA==", - "dev": true, - "dependencies": { - "@supabase/functions-js": "^2.0.0", - "@supabase/gotrue-js": "^2.0.1", - "@supabase/postgrest-js": "^1.0.0", - "@supabase/realtime-js": "^2.0.0", - "@supabase/storage-js": "^2.0.0", - "cross-fetch": "^3.1.5" - } - }, - "node_modules/@testdeck/core": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@testdeck/core/-/core-0.2.0.tgz", - "integrity": "sha512-2BAzKS18I+nFUb9kH2zK7Bs1CBD0WqaYikaLm+gRaOiQKYa8flvVzqlUGwjyOMmD16JFksgxYml4/7xm4tfEYA==", - "dev": true - }, - "node_modules/@testdeck/jest": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@testdeck/jest/-/jest-0.2.0.tgz", - "integrity": "sha512-YS8RupTg1Sb5TLkRGALppsI0iX8/LMDieRvPN0WGuJ+22zDugMXBBzN43X60pO+9HQ343qLUNzoV7RfQHHTQ+A==", - "dev": true, - "dependencies": { - "@testdeck/core": "^0.2.0" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true - }, - "node_modules/@types/allure-js-commons": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@types/allure-js-commons/-/allure-js-commons-0.0.1.tgz", - "integrity": "sha512-PuKCD4zeoF7Z6tKP0paOKmiMv3zXP2fKj/NiUylxDwBV1AL0PsD6AsbkieuEuRfbFUbG4C28RnmcSu9rPMWWjQ==", - "dev": true - }, - "node_modules/@types/babel__core": { - "version": "7.1.18", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", - "integrity": "sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.0.tgz", - "integrity": "sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==", - "dev": true, - "dependencies": { - "jest-diff": "^27.0.0", - "pretty-format": "^27.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.24.tgz", - "integrity": "sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==", - "dev": true - }, - "node_modules/@types/phoenix": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.4.tgz", - "integrity": "sha512-L5eZmzw89eXBKkiqVBcJfU1QGx9y+wurRIEgt0cuLH0hwNtVUxtx+6cu0R2STwWj468sjXyBYPYDtGclUd1kjQ==", - "dev": true - }, - "node_modules/@types/prettier": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz", - "integrity": "sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/strip-ansi": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@types/strip-ansi/-/strip-ansi-5.2.1.tgz", - "integrity": "sha512-1l5iM0LBkVU8JXxnIoBqNvg+yyOXxPeN6DNoD+7A9AN1B8FhYPSeIXgyNqwIqg1uzipTgVC2hmuDzB0u9qw/PA==", - "deprecated": "This is a stub types definition. strip-ansi provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "strip-ansi": "*" - } - }, - "node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.0.tgz", - "integrity": "sha512-XXVKnMsq2fuu9K2KsIxPUGqb6xAImz8MEChClbXmE3VbveFtBUU5bzM6IPVWqzyADIgdkS2Ws/6Xo7W2TeZWjQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.10.0", - "@typescript-eslint/type-utils": "5.10.0", - "@typescript-eslint/utils": "5.10.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.10.0.tgz", - "integrity": "sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.10.0", - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/typescript-estree": "5.10.0", - "debug": "^4.3.2" - }, - "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.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz", - "integrity": "sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/visitor-keys": "5.10.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/type-utils": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz", - "integrity": "sha512-TzlyTmufJO5V886N+hTJBGIfnjQDQ32rJYxPaeiyWKdjsv2Ld5l8cbS7pxim4DeNs62fKzRSt8Q14Evs4JnZyQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/utils": "5.10.0", - "debug": "^4.3.2", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.10.0.tgz", - "integrity": "sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ==", - "dev": true, - "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.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz", - "integrity": "sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/visitor-keys": "5.10.0", - "debug": "^4.3.2", - "globby": "^11.0.4", - "is-glob": "^4.0.3", - "semver": "^7.3.5", - "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/typescript-estree/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.10.0.tgz", - "integrity": "sha512-IGYwlt1CVcFoE2ueW4/ioEwybR60RAdGeiJX/iDAw0t5w0wK3S7QncDwpmsM70nKgGTuVchEWB8lwZwHqPAWRg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.10.0", - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/typescript-estree": "5.10.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "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" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz", - "integrity": "sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.10.0", - "eslint-visitor-keys": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/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-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.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/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.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==", - "dev": true, - "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/allure-commandline": { - "version": "2.17.2", - "resolved": "https://registry.npmjs.org/allure-commandline/-/allure-commandline-2.17.2.tgz", - "integrity": "sha512-2a0M0nX1KtVrg4y0rWEFn/OQkv7AaQSMBOEtlKfQl3heZoTEo0IdB08Uk5vU390+qPsEv3EO5igjyCrS0gX+FQ==", - "dev": true, - "bin": { - "allure": "bin/allure" - } - }, - "node_modules/allure-js-commons": { - "version": "2.0.0-beta.14", - "resolved": "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-2.0.0-beta.14.tgz", - "integrity": "sha512-5OlMp8kdc5FuxkquOV1ZXxJCAmFUwHnI5OqL2IlhJ65NKBeABpMwro6BbhlJqcV8iPQmh1w9bErTICSeB+Hj2A==", - "dev": true, - "dependencies": { - "mkdirp": "^1.0.4", - "properties": "^1.2.1", - "uuid": "^8.3.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "node_modules/babel-jest": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.4.6.tgz", - "integrity": "sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg==", - "dev": true, - "dependencies": { - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.4.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz", - "integrity": "sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz", - "integrity": "sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^27.4.0", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "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==", - "dev": true - }, - "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==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/bufferutil": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", - "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001300", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz", - "integrity": "sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "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/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", - "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", - "dev": true - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "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==", - "dev": true, - "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==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "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==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "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==", - "dev": true, - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", - "dev": true - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "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==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", - "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "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==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.47", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.47.tgz", - "integrity": "sha512-ZHc8i3/cgeCRK/vC7W2htAG6JqUmOUgDNn/f9yY9J8UjfLjwzwOVEt4MWmgJAdvmxyrsR5KIFA/6+kUHGY0eUA==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "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/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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "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": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/eslint": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.7.0.tgz", - "integrity": "sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.0.5", - "@humanwhocodes/config-array": "^0.9.2", - "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.0", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.2.0", - "espree": "^9.3.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "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", - "v8-compile-cache": "^2.0.3" - }, - "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-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.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==", - "dev": true, - "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==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", - "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/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==", - "dev": true - }, - "node_modules/eslint/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==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/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==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/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==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "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/eslint/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==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/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==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/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==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", - "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", - "dev": true, - "dependencies": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz", - "integrity": "sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "jest-get-type": "^27.4.0", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dev": true, - "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==", - "dev": true - }, - "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==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", - "dev": true - }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "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==", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "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-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "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/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/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==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "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": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "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==", - "dev": true - }, - "node_modules/is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "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-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz", - "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.4.7.tgz", - "integrity": "sha512-8heYvsx7nV/m8m24Vk26Y87g73Ba6ueUd0MWed/NXMhSZIm62U/llVbS0PJe1SHunbyXjJ/BqG1z9bFjGUIvTg==", - "dev": true, - "dependencies": { - "@jest/core": "^27.4.7", - "import-local": "^3.0.2", - "jest-cli": "^27.4.7" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-allure2-adapter": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/jest-allure2-adapter/-/jest-allure2-adapter-0.3.11.tgz", - "integrity": "sha512-Dz50gtzeZ890Iy1qJ+HAPBx059ZmRiBCqqZeQi38IJgLaXRcN5n2fQ5Al02A2uRoyLHG38Jq/t4m6Xr0z7tkBA==", - "dev": true, - "dependencies": { - "@types/allure-js-commons": "^0.0.1", - "@types/node": "^10.1.2", - "@types/strip-ansi": "^5.2.0", - "allure-js-commons": "^2.0.0-beta.9", - "strip-ansi": "^6.0.0", - "uuid-by-string": "^3.0.2" - } - }, - "node_modules/jest-allure2-adapter/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true - }, - "node_modules/jest-changed-files": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.4.2.tgz", - "integrity": "sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.4.6.tgz", - "integrity": "sha512-UA7AI5HZrW4wRM72Ro80uRR2Fg+7nR0GESbSI/2M+ambbzVuA63mn5T1p3Z/wlhntzGpIG1xx78GP2YIkf6PhQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.4.6", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-cli": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.4.7.tgz", - "integrity": "sha512-zREYhvjjqe1KsGV15mdnxjThKNDgza1fhDT+iUsXWLCq3sxe9w5xnvyctcYVT5PcdLSjv7Y5dCwTS3FCF1tiuw==", - "dev": true, - "dependencies": { - "@jest/core": "^27.4.7", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "jest-config": "^27.4.7", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "prompts": "^2.0.1", - "yargs": "^16.2.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.4.7.tgz", - "integrity": "sha512-xz/o/KJJEedHMrIY9v2ParIoYSrSVY6IVeE4z5Z3i101GoA5XgfbJz+1C8EYPsv7u7f39dS8F9v46BHDhn0vlw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.4.6", - "@jest/types": "^27.4.2", - "babel-jest": "^27.4.6", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-circus": "^27.4.6", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-jasmine2": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz", - "integrity": "sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.4.0", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.4.0.tgz", - "integrity": "sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.4.6.tgz", - "integrity": "sha512-n6QDq8y2Hsmn22tRkgAk+z6MCX7MeVlAzxmZDshfS2jLcaBlyhpF3tZSJLR+kXmh23GEvS0ojMR8i6ZeRvpQcA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz", - "integrity": "sha512-o3dx5p/kHPbUlRvSNjypEcEtgs6LmvESMzgRFQE6c+Prwl2JLA4RZ7qAnxc5VM8kutsGRTB15jXeeSbJsKN9iA==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2", - "jsdom": "^16.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.4.6.tgz", - "integrity": "sha512-yfHlZ9m+kzTKZV0hVfhVu6GuDxKAYeFHrfulmy7Jxwsq4V7+ZK7f+c0XP/tbVDMQW7E4neG2u147hFkuVz0MlQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", - "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.6.tgz", - "integrity": "sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.4.0", - "jest-serializer": "^27.4.0", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-jasmine2": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz", - "integrity": "sha512-uAGNXF644I/whzhsf7/qf74gqy9OuhvJ0XYp8SDecX2ooGeaPnmJMjXjKt0mqh1Rl5dtRGxJgNrHlBQIBfS5Nw==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.4.6", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-leak-detector": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz", - "integrity": "sha512-kkaGixDf9R7CjHm2pOzfTxZTQQQ2gHTIWKY/JZSiYTc90bZp8kSZnUMS3uLAfwTZwc0tcMRoEX74e14LG1WapA==", - "dev": true, - "dependencies": { - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz", - "integrity": "sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz", - "integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.4.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-mock": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz", - "integrity": "sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "@types/node": "*" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", - "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.4.6.tgz", - "integrity": "sha512-SFfITVApqtirbITKFAO7jOVN45UgFzcRdQanOFzjnbd+CACDoyeX7206JyU92l4cRr73+Qy/TlW51+4vHGt+zw==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz", - "integrity": "sha512-W85uJZcFXEVZ7+MZqIPCscdjuctruNGXUZ3OHSXOfXR9ITgbUKeHj+uGcies+0SsvI5GtUfTw4dY7u9qjTvQOw==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "jest-regex-util": "^27.4.0", - "jest-snapshot": "^27.4.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.4.6.tgz", - "integrity": "sha512-IDeFt2SG4DzqalYBZRgbbPmpwV3X0DcntjezPBERvnhwKGWTW7C5pbbA5lVkmvgteeNfdd/23gwqv3aiilpYPg==", - "dev": true, - "dependencies": { - "@jest/console": "^27.4.6", - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.4.0", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-haste-map": "^27.4.6", - "jest-leak-detector": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz", - "integrity": "sha512-eXYeoR/MbIpVDrjqy5d6cGCFOYBFFDeKaNWqTp0h6E74dK0zLHzASQXJpl5a2/40euBmKnprNLJ0Kh0LCndnWQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/globals": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-serializer": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz", - "integrity": "sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.4.6.tgz", - "integrity": "sha512-fafUCDLQfzuNP9IRcEqaFAMzEe7u5BF7mude51wyWv7VRex60WznZIC7DfKTgSIlJa8aFzYmXclmN328aqSDmQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-haste-map": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", - "natural-compare": "^1.4.0", - "pretty-format": "^27.4.6", - "semver": "^7.3.2" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz", - "integrity": "sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.4", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "leven": "^3.1.0", - "pretty-format": "^27.4.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.4.6.tgz", - "integrity": "sha512-yKQ20OMBiCDigbD0quhQKLkBO+ObGN79MO4nT7YaCuQ5SM+dkBNWE8cZX0FjU6czwMvWw6StWbe+Wv4jJPJ+fw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.4.2", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-worker": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", - "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/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/js-md5": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz", - "integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==", - "dev": true - }, - "node_modules/js-sha1": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/js-sha1/-/js-sha1-0.6.0.tgz", - "integrity": "sha512-01gwBFreYydzmU9BmZxpVk6svJJHrVxEN3IOiGl6VO93bVKYETJ0sIth6DASI6mIFdt7NmfX9UiByRzsYHGU9w==", - "dev": true - }, - "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==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "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==", - "dev": true - }, - "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": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "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.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "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==", - "dev": true - }, - "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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, - "dependencies": { - "mime-db": "1.51.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "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==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": 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==", - "dev": true - }, - "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==", - "dev": true, - "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-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/node-gyp-build": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", - "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", - "dev": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", - "dev": true - }, - "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/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "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==", - "dev": true, - "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true - }, - "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==", - "dev": true, - "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==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", - "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postgres": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.0.5.tgz", - "integrity": "sha512-/9VVVRG+bzLc/AB6QukEV+SERra2IRduGd8PDz/ZGu+1PQ6+TgCQYvX87GtPfxcYW4XZ58cyurJojnphdazW1g==", - "dev": true, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/porsager" - } - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-format": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", - "integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/properties/-/properties-1.2.1.tgz", - "integrity": "sha1-Dul6f8AgsaKlW4ZZ7aSqjYaQlL0=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "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==", - "dev": true, - "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/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "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": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", - "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", - "dev": true, - "dependencies": { - "is-core-module": "^2.8.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-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "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/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/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==", - "dev": true, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.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==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "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==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-jest": { - "version": "27.1.3", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.3.tgz", - "integrity": "sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^27.0.0", - "json5": "2.x", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "20.x" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@types/jest": "^27.0.0", - "babel-jest": ">=27.0.0 <28", - "esbuild": "~0.14.0", - "jest": "^27.0.0", - "typescript": ">=3.8 <5.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@types/jest": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-node": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", - "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.0", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "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==", - "dev": true - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/utf-8-validate": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", - "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/uuid-by-string": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/uuid-by-string/-/uuid-by-string-3.0.5.tgz", - "integrity": "sha512-nrb3NVafnbpuaL76HZnTJ1go2+TPvNXaP3HMFK0ulIg89gd4H7a41UDcQ/DG5yoif8CiLLwjjuwVQuFrPk2tIQ==", - "dev": true, - "dependencies": { - "js-md5": "^0.7.3", - "js-sha1": "^0.6.0" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/websocket": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", - "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "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==", - "dev": true, - "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": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "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==", - "dev": true, - "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==", - "dev": true - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/compat-data": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", - "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==", - "dev": true - }, - "@babel/core": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz", - "integrity": "sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.7", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", - "semver": "^6.3.0" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - }, - "@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", - "dev": true - }, - "@babel/helpers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", - "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", - "dev": true, - "requires": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/highlight": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", - "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz", - "integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==", - "dev": true - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", - "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/traverse": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.8.tgz", - "integrity": "sha512-xe+H7JlvKsDQwXRsBhSnq1/+9c+LlQcCK3Tn/l5sbx02HYns/cn7ibp9+RV1sIUqu7hKg91NWsgHurO9dowITQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.8", - "@babel/types": "^7.16.8", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true - }, - "@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", - "dev": true, - "requires": { - "@cspotcode/source-map-consumer": "0.8.0" - } - }, - "@eslint/eslintrc": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", - "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.2.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "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==", - "dev": true - } - } - }, - "@faker-js/faker": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-6.1.2.tgz", - "integrity": "sha512-QSvmexHCxeRUk1/yKmoEDaWB5Hohjvtim5g2JJwy8S/l0L4b3y/GxSpE6vN4SBoVGGahEQW21uqyRr7EofI35A==", - "dev": true - }, - "@humanwhocodes/config-array": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", - "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@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==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.4.6.tgz", - "integrity": "sha512-jauXyacQD33n47A44KrlOVeiXHEXDqapSdfb9kTekOchH/Pd18kBIO1+xxJQRLuG+LUuljFCwTG92ra4NW7SpA==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", - "slash": "^3.0.0" - } - }, - "@jest/core": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.4.7.tgz", - "integrity": "sha512-n181PurSJkVMS+kClIFSX/LLvw9ExSb+4IMtD6YnfxZVerw9ANYtW0bPrm0MJu2pfe9SY9FJ9FtQ+MdZkrZwjg==", - "dev": true, - "requires": { - "@jest/console": "^27.4.6", - "@jest/reporters": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.4.2", - "jest-config": "^27.4.7", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-resolve-dependencies": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "jest-watcher": "^27.4.6", - "micromatch": "^4.0.4", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "@jest/environment": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz", - "integrity": "sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==", - "dev": true, - "requires": { - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "jest-mock": "^27.4.6" - } - }, - "@jest/fake-timers": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz", - "integrity": "sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" - } - }, - "@jest/globals": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.4.6.tgz", - "integrity": "sha512-kAiwMGZ7UxrgPzu8Yv9uvWmXXxsy0GciNejlHvfPIfWkSxChzv6bgTS3YqBkGuHcis+ouMFI2696n2t+XYIeFw==", - "dev": true, - "requires": { - "@jest/environment": "^27.4.6", - "@jest/types": "^27.4.2", - "expect": "^27.4.6" - } - }, - "@jest/reporters": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.4.6.tgz", - "integrity": "sha512-+Zo9gV81R14+PSq4wzee4GC2mhAN9i9a7qgJWL90Gpx7fHYkWpTBvwWNZUXvJByYR9tAVBdc8VxDWqfJyIUrIQ==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" - } - }, - "@jest/source-map": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.4.0.tgz", - "integrity": "sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ==", - "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - } - }, - "@jest/test-result": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.4.6.tgz", - "integrity": "sha512-fi9IGj3fkOrlMmhQqa/t9xum8jaJOOAi/lZlm6JXSc55rJMXKHxNDN1oCP39B0/DhNOa2OMupF9BcKZnNtXMOQ==", - "dev": true, - "requires": { - "@jest/console": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz", - "integrity": "sha512-3GL+nsf6E1PsyNsJuvPyIz+DwFuCtBdtvPpm/LMXVkBJbdFvQYCDpccYT56qq5BGniXWlE81n2qk1sdXfZebnw==", - "dev": true, - "requires": { - "@jest/test-result": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-runtime": "^27.4.6" - } - }, - "@jest/transform": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.4.6.tgz", - "integrity": "sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.4.2", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-util": "^27.4.2", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - } - }, - "@jest/types": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", - "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@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==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@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==", - "dev": true - }, - "@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==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@supabase/functions-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.0.0.tgz", - "integrity": "sha512-ozb7bds2yvf5k7NM2ZzUkxvsx4S4i2eRKFSJetdTADV91T65g4gCzEs9L3LUXSrghcGIkUaon03VPzOrFredqg==", - "dev": true, - "requires": { - "cross-fetch": "^3.1.5" - } - }, - "@supabase/gotrue-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.0.1.tgz", - "integrity": "sha512-PCzHeFhz33lPWLZT+UiVDpGkZQI3K1wwhkhgAjvJdkyCbwK5u+LWWUzz0AX66bGGLpg1IYQIP7oz8V68XVjzMA==", - "dev": true, - "requires": { - "cross-fetch": "^3.1.5" - } - }, - "@supabase/postgrest-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.0.0.tgz", - "integrity": "sha512-MRR4g8dThgSnlnjkL2Jvc6gS73W68hLq48/Q245CLvUPt3KtOh3WXpvo4rZDEDHYVkyVga+L9IQixo6YTsjMQg==", - "dev": true, - "requires": { - "cross-fetch": "^3.1.5" - } - }, - "@supabase/realtime-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.0.0.tgz", - "integrity": "sha512-DJVt5Z76pik3Fxt1es4fmquq6GrwR3nLqcb2Q6VpHXSudtixML0Cyxsu0DPzxa6ruSQrO9s08s0OQgwaIA1C0Q==", - "dev": true, - "requires": { - "@types/phoenix": "^1.5.4", - "websocket": "^1.0.34" - } - }, - "@supabase/storage-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.0.0.tgz", - "integrity": "sha512-7kXThdRt/xqnOOvZZxBqNkeX1CFNUWc0hYBJtNN/Uvt8ok9hD14foYmroWrHn046wEYFqUrB9U35JYsfTrvltA==", - "dev": true, - "requires": { - "cross-fetch": "^3.1.5" - } - }, - "@supabase/supabase-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.0.0.tgz", - "integrity": "sha512-8nNMXYzBlQVSDONFaNgyyxsAdUY+C9Q/7vEDYDdTFTyC3i+luXTDOSjHMLcjIky/+x5rIAcNQqqIJ48HJUluXA==", - "dev": true, - "requires": { - "@supabase/functions-js": "^2.0.0", - "@supabase/gotrue-js": "^2.0.1", - "@supabase/postgrest-js": "^1.0.0", - "@supabase/realtime-js": "^2.0.0", - "@supabase/storage-js": "^2.0.0", - "cross-fetch": "^3.1.5" - } - }, - "@testdeck/core": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@testdeck/core/-/core-0.2.0.tgz", - "integrity": "sha512-2BAzKS18I+nFUb9kH2zK7Bs1CBD0WqaYikaLm+gRaOiQKYa8flvVzqlUGwjyOMmD16JFksgxYml4/7xm4tfEYA==", - "dev": true - }, - "@testdeck/jest": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@testdeck/jest/-/jest-0.2.0.tgz", - "integrity": "sha512-YS8RupTg1Sb5TLkRGALppsI0iX8/LMDieRvPN0WGuJ+22zDugMXBBzN43X60pO+9HQ343qLUNzoV7RfQHHTQ+A==", - "dev": true, - "requires": { - "@testdeck/core": "^0.2.0" - } - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true - }, - "@types/allure-js-commons": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@types/allure-js-commons/-/allure-js-commons-0.0.1.tgz", - "integrity": "sha512-PuKCD4zeoF7Z6tKP0paOKmiMv3zXP2fKj/NiUylxDwBV1AL0PsD6AsbkieuEuRfbFUbG4C28RnmcSu9rPMWWjQ==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.18", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", - "integrity": "sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.0.tgz", - "integrity": "sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==", - "dev": true, - "requires": { - "jest-diff": "^27.0.0", - "pretty-format": "^27.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "@types/node": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.24.tgz", - "integrity": "sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==", - "dev": true - }, - "@types/phoenix": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.4.tgz", - "integrity": "sha512-L5eZmzw89eXBKkiqVBcJfU1QGx9y+wurRIEgt0cuLH0hwNtVUxtx+6cu0R2STwWj468sjXyBYPYDtGclUd1kjQ==", - "dev": true - }, - "@types/prettier": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz", - "integrity": "sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/strip-ansi": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@types/strip-ansi/-/strip-ansi-5.2.1.tgz", - "integrity": "sha512-1l5iM0LBkVU8JXxnIoBqNvg+yyOXxPeN6DNoD+7A9AN1B8FhYPSeIXgyNqwIqg1uzipTgVC2hmuDzB0u9qw/PA==", - "dev": true, - "requires": { - "strip-ansi": "*" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.0.tgz", - "integrity": "sha512-XXVKnMsq2fuu9K2KsIxPUGqb6xAImz8MEChClbXmE3VbveFtBUU5bzM6IPVWqzyADIgdkS2Ws/6Xo7W2TeZWjQ==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.10.0", - "@typescript-eslint/type-utils": "5.10.0", - "@typescript-eslint/utils": "5.10.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/parser": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.10.0.tgz", - "integrity": "sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.10.0", - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/typescript-estree": "5.10.0", - "debug": "^4.3.2" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz", - "integrity": "sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/visitor-keys": "5.10.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz", - "integrity": "sha512-TzlyTmufJO5V886N+hTJBGIfnjQDQ32rJYxPaeiyWKdjsv2Ld5l8cbS7pxim4DeNs62fKzRSt8Q14Evs4JnZyQ==", - "dev": true, - "requires": { - "@typescript-eslint/utils": "5.10.0", - "debug": "^4.3.2", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.10.0.tgz", - "integrity": "sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz", - "integrity": "sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/visitor-keys": "5.10.0", - "debug": "^4.3.2", - "globby": "^11.0.4", - "is-glob": "^4.0.3", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/utils": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.10.0.tgz", - "integrity": "sha512-IGYwlt1CVcFoE2ueW4/ioEwybR60RAdGeiJX/iDAw0t5w0wK3S7QncDwpmsM70nKgGTuVchEWB8lwZwHqPAWRg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.10.0", - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/typescript-estree": "5.10.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz", - "integrity": "sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.10.0", - "eslint-visitor-keys": "^3.0.0" - } - }, - "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } - } - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "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 - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "allure-commandline": { - "version": "2.17.2", - "resolved": "https://registry.npmjs.org/allure-commandline/-/allure-commandline-2.17.2.tgz", - "integrity": "sha512-2a0M0nX1KtVrg4y0rWEFn/OQkv7AaQSMBOEtlKfQl3heZoTEo0IdB08Uk5vU390+qPsEv3EO5igjyCrS0gX+FQ==", - "dev": true - }, - "allure-js-commons": { - "version": "2.0.0-beta.14", - "resolved": "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-2.0.0-beta.14.tgz", - "integrity": "sha512-5OlMp8kdc5FuxkquOV1ZXxJCAmFUwHnI5OqL2IlhJ65NKBeABpMwro6BbhlJqcV8iPQmh1w9bErTICSeB+Hj2A==", - "dev": true, - "requires": { - "mkdirp": "^1.0.4", - "properties": "^1.2.1", - "uuid": "^8.3.0" - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "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==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "babel-jest": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.4.6.tgz", - "integrity": "sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg==", - "dev": true, - "requires": { - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.4.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz", - "integrity": "sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz", - "integrity": "sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^27.4.0", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "bufferutil": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", - "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", - "dev": true, - "requires": { - "node-gyp-build": "^4.3.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001300", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz", - "integrity": "sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "ci-info": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", - "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dev": true, - "requires": { - "node-fetch": "2.6.7" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", - "dev": true - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "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==", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "diff-sequences": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", - "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, - "electron-to-chromium": { - "version": "1.4.47", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.47.tgz", - "integrity": "sha512-ZHc8i3/cgeCRK/vC7W2htAG6JqUmOUgDNn/f9yY9J8UjfLjwzwOVEt4MWmgJAdvmxyrsR5KIFA/6+kUHGY0eUA==", - "dev": true - }, - "emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "dev": true - }, - "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 - }, - "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==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "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==", - "dev": true, - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "eslint": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.7.0.tgz", - "integrity": "sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.0.5", - "@humanwhocodes/config-array": "^0.9.2", - "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.0", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.2.0", - "espree": "^9.3.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "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", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "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==", - "dev": true - }, - "eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "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" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "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==", - "dev": true - } - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "dependencies": { - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - } - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", - "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", - "dev": true - }, - "espree": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", - "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", - "dev": true, - "requires": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expect": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz", - "integrity": "sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "jest-get-type": "^27.4.0", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6" - } - }, - "ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dev": true, - "requires": { - "type": "^2.7.2" - }, - "dependencies": { - "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - } - } - }, - "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==", - "dev": true - }, - "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "requires": { - "@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" - }, - "dependencies": { - "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, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "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==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "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==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "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==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", - "dev": true - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "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 - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "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" - } - }, - "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==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "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" - } - }, - "graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "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 - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz", - "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.4.7.tgz", - "integrity": "sha512-8heYvsx7nV/m8m24Vk26Y87g73Ba6ueUd0MWed/NXMhSZIm62U/llVbS0PJe1SHunbyXjJ/BqG1z9bFjGUIvTg==", - "dev": true, - "requires": { - "@jest/core": "^27.4.7", - "import-local": "^3.0.2", - "jest-cli": "^27.4.7" - } - }, - "jest-allure2-adapter": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/jest-allure2-adapter/-/jest-allure2-adapter-0.3.11.tgz", - "integrity": "sha512-Dz50gtzeZ890Iy1qJ+HAPBx059ZmRiBCqqZeQi38IJgLaXRcN5n2fQ5Al02A2uRoyLHG38Jq/t4m6Xr0z7tkBA==", - "dev": true, - "requires": { - "@types/allure-js-commons": "^0.0.1", - "@types/node": "^10.1.2", - "@types/strip-ansi": "^5.2.0", - "allure-js-commons": "^2.0.0-beta.9", - "strip-ansi": "^6.0.0", - "uuid-by-string": "^3.0.2" - }, - "dependencies": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true - } - } - }, - "jest-changed-files": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.4.2.tgz", - "integrity": "sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "execa": "^5.0.0", - "throat": "^6.0.1" - } - }, - "jest-circus": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.4.6.tgz", - "integrity": "sha512-UA7AI5HZrW4wRM72Ro80uRR2Fg+7nR0GESbSI/2M+ambbzVuA63mn5T1p3Z/wlhntzGpIG1xx78GP2YIkf6PhQ==", - "dev": true, - "requires": { - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.4.6", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - } - }, - "jest-cli": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.4.7.tgz", - "integrity": "sha512-zREYhvjjqe1KsGV15mdnxjThKNDgza1fhDT+iUsXWLCq3sxe9w5xnvyctcYVT5PcdLSjv7Y5dCwTS3FCF1tiuw==", - "dev": true, - "requires": { - "@jest/core": "^27.4.7", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "jest-config": "^27.4.7", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "prompts": "^2.0.1", - "yargs": "^16.2.0" - } - }, - "jest-config": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.4.7.tgz", - "integrity": "sha512-xz/o/KJJEedHMrIY9v2ParIoYSrSVY6IVeE4z5Z3i101GoA5XgfbJz+1C8EYPsv7u7f39dS8F9v46BHDhn0vlw==", - "dev": true, - "requires": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.4.6", - "@jest/types": "^27.4.2", - "babel-jest": "^27.4.6", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-circus": "^27.4.6", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-jasmine2": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", - "slash": "^3.0.0" - } - }, - "jest-diff": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz", - "integrity": "sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.4.0", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" - } - }, - "jest-docblock": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.4.0.tgz", - "integrity": "sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.4.6.tgz", - "integrity": "sha512-n6QDq8y2Hsmn22tRkgAk+z6MCX7MeVlAzxmZDshfS2jLcaBlyhpF3tZSJLR+kXmh23GEvS0ojMR8i6ZeRvpQcA==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6" - } - }, - "jest-environment-jsdom": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz", - "integrity": "sha512-o3dx5p/kHPbUlRvSNjypEcEtgs6LmvESMzgRFQE6c+Prwl2JLA4RZ7qAnxc5VM8kutsGRTB15jXeeSbJsKN9iA==", - "dev": true, - "requires": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2", - "jsdom": "^16.6.0" - } - }, - "jest-environment-node": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.4.6.tgz", - "integrity": "sha512-yfHlZ9m+kzTKZV0hVfhVu6GuDxKAYeFHrfulmy7Jxwsq4V7+ZK7f+c0XP/tbVDMQW7E4neG2u147hFkuVz0MlQ==", - "dev": true, - "requires": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" - } - }, - "jest-get-type": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", - "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", - "dev": true - }, - "jest-haste-map": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.6.tgz", - "integrity": "sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.4.0", - "jest-serializer": "^27.4.0", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - } - }, - "jest-jasmine2": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz", - "integrity": "sha512-uAGNXF644I/whzhsf7/qf74gqy9OuhvJ0XYp8SDecX2ooGeaPnmJMjXjKt0mqh1Rl5dtRGxJgNrHlBQIBfS5Nw==", - "dev": true, - "requires": { - "@jest/environment": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.4.6", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", - "throat": "^6.0.1" - } - }, - "jest-leak-detector": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz", - "integrity": "sha512-kkaGixDf9R7CjHm2pOzfTxZTQQQ2gHTIWKY/JZSiYTc90bZp8kSZnUMS3uLAfwTZwc0tcMRoEX74e14LG1WapA==", - "dev": true, - "requires": { - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" - } - }, - "jest-matcher-utils": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz", - "integrity": "sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" - } - }, - "jest-message-util": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz", - "integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.4.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-mock": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz", - "integrity": "sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "@types/node": "*" - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", - "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", - "dev": true - }, - "jest-resolve": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.4.6.tgz", - "integrity": "sha512-SFfITVApqtirbITKFAO7jOVN45UgFzcRdQanOFzjnbd+CACDoyeX7206JyU92l4cRr73+Qy/TlW51+4vHGt+zw==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz", - "integrity": "sha512-W85uJZcFXEVZ7+MZqIPCscdjuctruNGXUZ3OHSXOfXR9ITgbUKeHj+uGcies+0SsvI5GtUfTw4dY7u9qjTvQOw==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "jest-regex-util": "^27.4.0", - "jest-snapshot": "^27.4.6" - } - }, - "jest-runner": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.4.6.tgz", - "integrity": "sha512-IDeFt2SG4DzqalYBZRgbbPmpwV3X0DcntjezPBERvnhwKGWTW7C5pbbA5lVkmvgteeNfdd/23gwqv3aiilpYPg==", - "dev": true, - "requires": { - "@jest/console": "^27.4.6", - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.4.0", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-haste-map": "^27.4.6", - "jest-leak-detector": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - } - }, - "jest-runtime": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz", - "integrity": "sha512-eXYeoR/MbIpVDrjqy5d6cGCFOYBFFDeKaNWqTp0h6E74dK0zLHzASQXJpl5a2/40euBmKnprNLJ0Kh0LCndnWQ==", - "dev": true, - "requires": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/globals": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - } - }, - "jest-serializer": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz", - "integrity": "sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - } - }, - "jest-snapshot": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.4.6.tgz", - "integrity": "sha512-fafUCDLQfzuNP9IRcEqaFAMzEe7u5BF7mude51wyWv7VRex60WznZIC7DfKTgSIlJa8aFzYmXclmN328aqSDmQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-haste-map": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", - "natural-compare": "^1.4.0", - "pretty-format": "^27.4.6", - "semver": "^7.3.2" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "jest-util": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz", - "integrity": "sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.4", - "picomatch": "^2.2.3" - } - }, - "jest-validate": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "leven": "^3.1.0", - "pretty-format": "^27.4.6" - }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - } - } - }, - "jest-watcher": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.4.6.tgz", - "integrity": "sha512-yKQ20OMBiCDigbD0quhQKLkBO+ObGN79MO4nT7YaCuQ5SM+dkBNWE8cZX0FjU6czwMvWw6StWbe+Wv4jJPJ+fw==", - "dev": true, - "requires": { - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.4.2", - "string-length": "^4.0.1" - } - }, - "jest-worker": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", - "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "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, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-md5": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz", - "integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==", - "dev": true - }, - "js-sha1": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/js-sha1/-/js-sha1-0.6.0.tgz", - "integrity": "sha512-01gwBFreYydzmU9BmZxpVk6svJJHrVxEN3IOiGl6VO93bVKYETJ0sIth6DASI6mIFdt7NmfX9UiByRzsYHGU9w==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "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==", - "dev": true - }, - "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": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "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==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "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" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true - }, - "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, - "requires": { - "mime-db": "1.51.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, - "node-gyp-build": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", - "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", - "dev": true - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", - "dev": true - }, - "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 - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "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==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "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==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pirates": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", - "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "postgres": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.0.5.tgz", - "integrity": "sha512-/9VVVRG+bzLc/AB6QukEV+SERra2IRduGd8PDz/ZGu+1PQ6+TgCQYvX87GtPfxcYW4XZ58cyurJojnphdazW1g==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "pretty-format": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", - "integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/properties/-/properties-1.2.1.tgz", - "integrity": "sha1-Dul6f8AgsaKlW4ZZ7aSqjYaQlL0=", - "dev": true - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "resolve": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", - "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", - "dev": true, - "requires": { - "is-core-module": "^2.8.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "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==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "signal-exit": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "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, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "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==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - } - }, - "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==", - "dev": true - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - } - }, - "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "ts-jest": { - "version": "27.1.3", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.3.tgz", - "integrity": "sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^27.0.0", - "json5": "2.x", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "20.x" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "ts-node": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", - "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.0", - "yn": "3.1.1" - }, - "dependencies": { - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - } - } - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", - "dev": true - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "utf-8-validate": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", - "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", - "dev": true, - "requires": { - "node-gyp-build": "^4.3.0" - } - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - }, - "uuid-by-string": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/uuid-by-string/-/uuid-by-string-3.0.5.tgz", - "integrity": "sha512-nrb3NVafnbpuaL76HZnTJ1go2+TPvNXaP3HMFK0ulIg89gd4H7a41UDcQ/DG5yoif8CiLLwjjuwVQuFrPk2tIQ==", - "dev": true, - "requires": { - "js-md5": "^0.7.3", - "js-sha1": "^0.6.0" - } - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "websocket": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", - "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "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==", - "dev": true - }, - "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, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", - "dev": true, - "requires": {} - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - } - } -} diff --git a/tests/package.json b/tests/package.json index 4f6b77c6c84..fdb367b4e93 100644 --- a/tests/package.json +++ b/tests/package.json @@ -18,7 +18,7 @@ "license": "MIT", "devDependencies": { "@faker-js/faker": "^6.1.2", - "@supabase/supabase-js": "^2.0.0", + "@supabase/supabase-js": "^2.13.0", "@testdeck/jest": "^0.2.0", "@types/jest": "^27.4.0", "@types/node": "^17.0.24", diff --git a/turbo.json b/turbo.json index 176ef881d41..3ca755f081d 100644 --- a/turbo.json +++ b/turbo.json @@ -4,6 +4,43 @@ "dependsOn": ["^build"], "outputs": ["dist/**", ".next/**"] }, + "www#build": { + "dependsOn": ["^build"], + "env": [ + "SUPABASE_SERVICE_ROLE_SECRET", + "EMAIL_TO_ID_SECRET", + "SUPABASE_URL", + "GITHUB_OAUTH_CLIENT_SECRET" + ], + "outputs": [".next/**", "!.next/cache/**"] + }, + "docs#build": { + "dependsOn": ["^build"], + "env": ["SUPABASE_SERVICE_ROLE_KEY", "OPENAI_KEY", "ALGOLIA_SEARCH_ADMIN_KEY"], + "outputs": [".next/**", "!.next/cache/**"] + }, + "studio#build": { + "dependsOn": ["^build"], + "env": [ + "SITE_URL", + "AUTH0_AUDIENCE", + "AUTH0_CLIENT_ID", + "AUTH0_CLIENT_SECRET", + "AUTH0_DOMAIN", + "COOKIE_DOMAIN", + "INTERCOM_API_KEY", + "READ_ONLY_API_KEY", + "READ_ONLY_URL", + "SENTRY_AUTH_TOKEN", + "SENTRY_ORG", + "SENTRY_PROJECT", + "SENTRY_URL", + "SESSION_COOKIE_SECRET", + "SUPABASE_ENCRYPTION_KEY", + "PLATFORM_PG_META_URL" + ], + "outputs": [".next/**", "!.next/cache/**"] + }, "lint": { "outputs": [] },