diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml deleted file mode 100644 index f498b55..0000000 --- a/.github/workflows/deploy-website.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Deploy to GitHub Pages - -on: - push: - branches: - - master - -jobs: - deploy: - name: Deploy to GitHub Pages - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: yarn - - - name: Install dependencies - run: yarn install --frozen-lockfile - - name: Build website - run: yarn build - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.BOT_DEPLOYMENT_TOKEN }} - publish_dir: ./build - publish_branch: gh-pages - - # The following lines assign commit authorship to the official - # GH-Actions bot for deploys to `gh-pages` branch: - # https://github.com/actions/checkout/issues/13#issuecomment-724415212 - # The GH actions bot is used by default if you didn't specify the two fields. - # You can swap them out with your own user credentials. - # user_name: github-actions[bot] - # user_email: 41898282+github-actions[bot]@users.noreply.github.com \ No newline at end of file diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml deleted file mode 100644 index c3c0dd8..0000000 --- a/.github/workflows/pr-build.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: PR Build - -on: - pull_request: - branches: - - master - # Review gh actions docs if you want to further define triggers, paths, etc - # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on - -jobs: - test-deploy: - name: Test deployment - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: yarn - - - name: Install dependencies - run: yarn install --frozen-lockfile - - name: Test build website - run: yarn build \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b2d6de3..0000000 --- a/.gitignore +++ /dev/null @@ -1,20 +0,0 @@ -# Dependencies -/node_modules - -# Production -/build - -# Generated files -.docusaurus -.cache-loader - -# Misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/static/.nojekyll b/.nojekyll similarity index 100% rename from static/.nojekyll rename to .nojekyll diff --git a/404.html b/404.html new file mode 100644 index 0000000..07c8d52 --- /dev/null +++ b/404.html @@ -0,0 +1,24 @@ + + + + + +Page Not Found | GraphQL ASP.NET + + + + +
+
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

+ + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 0574cd6..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 GraphQL ASP.NET - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index d6bdcd3..0000000 --- a/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## GraphQL ASP.NET Documentation - -**Docs. Website**: [https://graphql-aspnet.github.io](https://graphql-aspnet.github.io) - -**Primary Repo**: [https://github.com/graphql-aspnet/graphql-aspnet](https://github.com/graphql-aspnet/graphql-aspnet) - -This repository contains the source code for the GraphQL ASP.NET documentation website. - -## How to Run The Docs Locally -0. Clone the `master` branch -1. Open a terminal at the repo directory -2. Execute `yarn install` -3. Execute `yarn start` -4. Website starts on `http://localhost:3000` by default - - -_Documentation created using [Docusaurus v2](https://docusaurus.io)_ \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 814b1b0..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,25 +0,0 @@ -Thanks for helping make GraphQL ASP.NET safe for everyone. - -We take the security of our library seriously, including all of the open source code repositories managed through our GitHub organization. - -## Reporting Security Issues - -If you believe you have found a security vulnerability in any of our repositories, please report it to us through coordinated disclosure. - -Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests. - -Instead, please send an email to security@graphql-aspnet.org - -Please include as much of the information listed below as you can to help us better understand and resolve the issue: - - The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) - Full paths of source file(s) related to the manifestation of the issue if known - The location of the affected source code (tag/branch/commit or direct URL) - Any special configuration required to reproduce the issue - Step-by-step instructions to reproduce the issue - Proof-of-concept or exploit code (if possible) - Impact of the issue, including how an attacker might exploit the issue - -This information will help us triage your report more quickly. - -Thank you! \ No newline at end of file diff --git a/assets/css/styles.cbe6f699.css b/assets/css/styles.cbe6f699.css new file mode 100644 index 0000000..ebd6642 --- /dev/null +++ b/assets/css/styles.cbe6f699.css @@ -0,0 +1 @@ +.col,.container{padding:0 var(--ifm-spacing-horizontal);width:100%}.markdown>h2,.markdown>h3,.markdown>h4,.markdown>h5,.markdown>h6{margin-bottom:calc(var(--ifm-heading-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown li,body{word-wrap:break-word}body,ol ol,ol ul,ul ol,ul ul{margin:0}pre,table{overflow:auto}blockquote,pre{margin:0 0 var(--ifm-spacing-vertical)}.breadcrumbs__link,.button{transition-timing-function:var(--ifm-transition-timing-default)}.button,code{vertical-align:middle}.button--outline.button--active,.button--outline:active,.button--outline:hover,:root{--ifm-button-color:var(--ifm-font-color-base-inverse)}.menu__link:hover,a{transition:color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.navbar--dark,:root{--ifm-navbar-link-hover-color:var(--ifm-color-primary)}.menu,.navbar-sidebar{overflow-x:hidden}:root,html[data-theme=dark]{--ifm-color-emphasis-500:var(--ifm-color-gray-500)}.toggleButton_gllP,html{-webkit-tap-highlight-color:transparent}.clean-list,.containsTaskList_mC6p,.details_lb9f>summary,.dropdown__menu,.menu__list{list-style:none}:root{--ifm-color-scheme:light;--ifm-dark-value:10%;--ifm-darker-value:15%;--ifm-darkest-value:30%;--ifm-light-value:15%;--ifm-lighter-value:30%;--ifm-lightest-value:50%;--ifm-contrast-background-value:90%;--ifm-contrast-foreground-value:70%;--ifm-contrast-background-dark-value:70%;--ifm-contrast-foreground-dark-value:90%;--ifm-color-primary:#3578e5;--ifm-color-secondary:#ebedf0;--ifm-color-success:#00a400;--ifm-color-info:#54c7ec;--ifm-color-warning:#ffba00;--ifm-color-danger:#fa383e;--ifm-color-primary-dark:#306cce;--ifm-color-primary-darker:#2d66c3;--ifm-color-primary-darkest:#2554a0;--ifm-color-primary-light:#538ce9;--ifm-color-primary-lighter:#72a1ed;--ifm-color-primary-lightest:#9abcf2;--ifm-color-primary-contrast-background:#ebf2fc;--ifm-color-primary-contrast-foreground:#102445;--ifm-color-secondary-dark:#d4d5d8;--ifm-color-secondary-darker:#c8c9cc;--ifm-color-secondary-darkest:#a4a6a8;--ifm-color-secondary-light:#eef0f2;--ifm-color-secondary-lighter:#f1f2f5;--ifm-color-secondary-lightest:#f5f6f8;--ifm-color-secondary-contrast-background:#fdfdfe;--ifm-color-secondary-contrast-foreground:#474748;--ifm-color-success-dark:#009400;--ifm-color-success-darker:#008b00;--ifm-color-success-darkest:#007300;--ifm-color-success-light:#26b226;--ifm-color-success-lighter:#4dbf4d;--ifm-color-success-lightest:#80d280;--ifm-color-success-contrast-background:#e6f6e6;--ifm-color-success-contrast-foreground:#003100;--ifm-color-info-dark:#4cb3d4;--ifm-color-info-darker:#47a9c9;--ifm-color-info-darkest:#3b8ba5;--ifm-color-info-light:#6ecfef;--ifm-color-info-lighter:#87d8f2;--ifm-color-info-lightest:#aae3f6;--ifm-color-info-contrast-background:#eef9fd;--ifm-color-info-contrast-foreground:#193c47;--ifm-color-warning-dark:#e6a700;--ifm-color-warning-darker:#d99e00;--ifm-color-warning-darkest:#b38200;--ifm-color-warning-light:#ffc426;--ifm-color-warning-lighter:#ffcf4d;--ifm-color-warning-lightest:#ffdd80;--ifm-color-warning-contrast-background:#fff8e6;--ifm-color-warning-contrast-foreground:#4d3800;--ifm-color-danger-dark:#e13238;--ifm-color-danger-darker:#d53035;--ifm-color-danger-darkest:#af272b;--ifm-color-danger-light:#fb565b;--ifm-color-danger-lighter:#fb7478;--ifm-color-danger-lightest:#fd9c9f;--ifm-color-danger-contrast-background:#ffebec;--ifm-color-danger-contrast-foreground:#4b1113;--ifm-color-white:#fff;--ifm-color-black:#000;--ifm-color-gray-0:var(--ifm-color-white);--ifm-color-gray-100:#f5f6f7;--ifm-color-gray-200:#ebedf0;--ifm-color-gray-300:#dadde1;--ifm-color-gray-400:#ccd0d5;--ifm-color-gray-500:#bec3c9;--ifm-color-gray-600:#8d949e;--ifm-color-gray-700:#606770;--ifm-color-gray-800:#444950;--ifm-color-gray-900:#1c1e21;--ifm-color-gray-1000:var(--ifm-color-black);--ifm-color-emphasis-0:var(--ifm-color-gray-0);--ifm-color-emphasis-100:var(--ifm-color-gray-100);--ifm-color-emphasis-200:var(--ifm-color-gray-200);--ifm-color-emphasis-300:var(--ifm-color-gray-300);--ifm-color-emphasis-400:var(--ifm-color-gray-400);--ifm-color-emphasis-600:var(--ifm-color-gray-600);--ifm-color-emphasis-700:var(--ifm-color-gray-700);--ifm-color-emphasis-800:var(--ifm-color-gray-800);--ifm-color-emphasis-900:var(--ifm-color-gray-900);--ifm-color-emphasis-1000:var(--ifm-color-gray-1000);--ifm-color-content:var(--ifm-color-emphasis-900);--ifm-color-content-inverse:var(--ifm-color-emphasis-0);--ifm-color-content-secondary:#525860;--ifm-background-color:#0000;--ifm-background-surface-color:var(--ifm-color-content-inverse);--ifm-global-border-width:1px;--ifm-global-radius:0.4rem;--ifm-hover-overlay:#0000000d;--ifm-font-color-base:var(--ifm-color-content);--ifm-font-color-base-inverse:var(--ifm-color-content-inverse);--ifm-font-color-secondary:var(--ifm-color-content-secondary);--ifm-font-family-base:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--ifm-font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--ifm-font-size-base:100%;--ifm-font-weight-light:300;--ifm-font-weight-normal:400;--ifm-font-weight-semibold:500;--ifm-font-weight-bold:700;--ifm-font-weight-base:var(--ifm-font-weight-normal);--ifm-line-height-base:1.65;--ifm-global-spacing:1rem;--ifm-spacing-vertical:var(--ifm-global-spacing);--ifm-spacing-horizontal:var(--ifm-global-spacing);--ifm-transition-fast:200ms;--ifm-transition-slow:400ms;--ifm-transition-timing-default:cubic-bezier(0.08,0.52,0.52,1);--ifm-global-shadow-lw:0 1px 2px 0 #0000001a;--ifm-global-shadow-md:0 5px 40px #0003;--ifm-global-shadow-tl:0 12px 28px 0 #0003,0 2px 4px 0 #0000001a;--ifm-z-index-dropdown:100;--ifm-z-index-fixed:200;--ifm-z-index-overlay:400;--ifm-container-width:1140px;--ifm-container-width-xl:1320px;--ifm-code-background:#f6f7f8;--ifm-code-border-radius:var(--ifm-global-radius);--ifm-code-font-size:90%;--ifm-code-padding-horizontal:0.1rem;--ifm-code-padding-vertical:0.1rem;--ifm-pre-background:var(--ifm-code-background);--ifm-pre-border-radius:var(--ifm-code-border-radius);--ifm-pre-color:inherit;--ifm-pre-line-height:1.45;--ifm-pre-padding:1rem;--ifm-heading-color:inherit;--ifm-heading-margin-top:0;--ifm-heading-margin-bottom:var(--ifm-spacing-vertical);--ifm-heading-font-family:var(--ifm-font-family-base);--ifm-heading-font-weight:var(--ifm-font-weight-bold);--ifm-heading-line-height:1.25;--ifm-h1-font-size:2rem;--ifm-h2-font-size:1.5rem;--ifm-h3-font-size:1.25rem;--ifm-h4-font-size:1rem;--ifm-h5-font-size:0.875rem;--ifm-h6-font-size:0.85rem;--ifm-image-alignment-padding:1.25rem;--ifm-leading-desktop:1.25;--ifm-leading:calc(var(--ifm-leading-desktop)*1rem);--ifm-list-left-padding:2rem;--ifm-list-margin:1rem;--ifm-list-item-margin:0.25rem;--ifm-list-paragraph-margin:1rem;--ifm-table-cell-padding:0.75rem;--ifm-table-background:#0000;--ifm-table-stripe-background:#00000008;--ifm-table-border-width:1px;--ifm-table-border-color:var(--ifm-color-emphasis-300);--ifm-table-head-background:inherit;--ifm-table-head-color:inherit;--ifm-table-head-font-weight:var(--ifm-font-weight-bold);--ifm-table-cell-color:inherit;--ifm-link-color:var(--ifm-color-primary);--ifm-link-decoration:none;--ifm-link-hover-color:var(--ifm-link-color);--ifm-link-hover-decoration:underline;--ifm-paragraph-margin-bottom:var(--ifm-leading);--ifm-blockquote-font-size:var(--ifm-font-size-base);--ifm-blockquote-border-left-width:2px;--ifm-blockquote-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-blockquote-padding-vertical:0;--ifm-blockquote-shadow:none;--ifm-blockquote-color:var(--ifm-color-emphasis-800);--ifm-blockquote-border-color:var(--ifm-color-emphasis-300);--ifm-hr-background-color:var(--ifm-color-emphasis-500);--ifm-hr-height:1px;--ifm-hr-margin-vertical:1.5rem;--ifm-scrollbar-size:7px;--ifm-scrollbar-track-background-color:#f1f1f1;--ifm-scrollbar-thumb-background-color:silver;--ifm-scrollbar-thumb-hover-background-color:#a7a7a7;--ifm-alert-background-color:inherit;--ifm-alert-border-color:inherit;--ifm-alert-border-radius:var(--ifm-global-radius);--ifm-alert-border-width:0px;--ifm-alert-border-left-width:5px;--ifm-alert-color:var(--ifm-font-color-base);--ifm-alert-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-alert-padding-vertical:var(--ifm-spacing-vertical);--ifm-alert-shadow:var(--ifm-global-shadow-lw);--ifm-avatar-intro-margin:1rem;--ifm-avatar-intro-alignment:inherit;--ifm-avatar-photo-size:3rem;--ifm-badge-background-color:inherit;--ifm-badge-border-color:inherit;--ifm-badge-border-radius:var(--ifm-global-radius);--ifm-badge-border-width:var(--ifm-global-border-width);--ifm-badge-color:var(--ifm-color-white);--ifm-badge-padding-horizontal:calc(var(--ifm-spacing-horizontal)*0.5);--ifm-badge-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-breadcrumb-border-radius:1.5rem;--ifm-breadcrumb-spacing:0.5rem;--ifm-breadcrumb-color-active:var(--ifm-color-primary);--ifm-breadcrumb-item-background-active:var(--ifm-hover-overlay);--ifm-breadcrumb-padding-horizontal:0.8rem;--ifm-breadcrumb-padding-vertical:0.4rem;--ifm-breadcrumb-size-multiplier:1;--ifm-breadcrumb-separator:url('data:image/svg+xml;utf8,');--ifm-breadcrumb-separator-filter:none;--ifm-breadcrumb-separator-size:0.5rem;--ifm-breadcrumb-separator-size-multiplier:1.25;--ifm-button-background-color:inherit;--ifm-button-border-color:var(--ifm-button-background-color);--ifm-button-border-width:var(--ifm-global-border-width);--ifm-button-font-weight:var(--ifm-font-weight-bold);--ifm-button-padding-horizontal:1.5rem;--ifm-button-padding-vertical:0.375rem;--ifm-button-size-multiplier:1;--ifm-button-transition-duration:var(--ifm-transition-fast);--ifm-button-border-radius:calc(var(--ifm-global-radius)*var(--ifm-button-size-multiplier));--ifm-button-group-spacing:2px;--ifm-card-background-color:var(--ifm-background-surface-color);--ifm-card-border-radius:calc(var(--ifm-global-radius)*2);--ifm-card-horizontal-spacing:var(--ifm-global-spacing);--ifm-card-vertical-spacing:var(--ifm-global-spacing);--ifm-toc-border-color:var(--ifm-color-emphasis-300);--ifm-toc-link-color:var(--ifm-color-content-secondary);--ifm-toc-padding-vertical:0.5rem;--ifm-toc-padding-horizontal:0.5rem;--ifm-dropdown-background-color:var(--ifm-background-surface-color);--ifm-dropdown-font-weight:var(--ifm-font-weight-semibold);--ifm-dropdown-link-color:var(--ifm-font-color-base);--ifm-dropdown-hover-background-color:var(--ifm-hover-overlay);--ifm-footer-background-color:var(--ifm-color-emphasis-100);--ifm-footer-color:inherit;--ifm-footer-link-color:var(--ifm-color-emphasis-700);--ifm-footer-link-hover-color:var(--ifm-color-primary);--ifm-footer-link-horizontal-spacing:0.5rem;--ifm-footer-padding-horizontal:calc(var(--ifm-spacing-horizontal)*2);--ifm-footer-padding-vertical:calc(var(--ifm-spacing-vertical)*2);--ifm-footer-title-color:inherit;--ifm-footer-logo-max-width:min(30rem,90vw);--ifm-hero-background-color:var(--ifm-background-surface-color);--ifm-hero-text-color:var(--ifm-color-emphasis-800);--ifm-menu-color:var(--ifm-color-emphasis-700);--ifm-menu-color-active:var(--ifm-color-primary);--ifm-menu-color-background-active:var(--ifm-hover-overlay);--ifm-menu-color-background-hover:var(--ifm-hover-overlay);--ifm-menu-link-padding-horizontal:0.75rem;--ifm-menu-link-padding-vertical:0.375rem;--ifm-menu-link-sublist-icon:url('data:image/svg+xml;utf8,');--ifm-menu-link-sublist-icon-filter:none;--ifm-navbar-background-color:var(--ifm-background-surface-color);--ifm-navbar-height:3.75rem;--ifm-navbar-item-padding-horizontal:0.75rem;--ifm-navbar-item-padding-vertical:0.25rem;--ifm-navbar-link-color:var(--ifm-font-color-base);--ifm-navbar-link-active-color:var(--ifm-link-color);--ifm-navbar-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-navbar-padding-vertical:calc(var(--ifm-spacing-vertical)*0.5);--ifm-navbar-shadow:var(--ifm-global-shadow-lw);--ifm-navbar-search-input-background-color:var(--ifm-color-emphasis-200);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-800);--ifm-navbar-search-input-placeholder-color:var(--ifm-color-emphasis-500);--ifm-navbar-search-input-icon:url('data:image/svg+xml;utf8,');--ifm-navbar-sidebar-width:83vw;--ifm-pagination-border-radius:var(--ifm-global-radius);--ifm-pagination-color-active:var(--ifm-color-primary);--ifm-pagination-font-size:1rem;--ifm-pagination-item-active-background:var(--ifm-hover-overlay);--ifm-pagination-page-spacing:0.2em;--ifm-pagination-padding-horizontal:calc(var(--ifm-spacing-horizontal)*1);--ifm-pagination-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-pagination-nav-border-radius:var(--ifm-global-radius);--ifm-pagination-nav-color-hover:var(--ifm-color-primary);--ifm-pills-color-active:var(--ifm-color-primary);--ifm-pills-color-background-active:var(--ifm-hover-overlay);--ifm-pills-spacing:0.125rem;--ifm-tabs-color:var(--ifm-font-color-secondary);--ifm-tabs-color-active:var(--ifm-color-primary);--ifm-tabs-color-active-border:var(--ifm-tabs-color-active);--ifm-tabs-padding-horizontal:1rem;--ifm-tabs-padding-vertical:1rem;--docusaurus-progress-bar-color:var(--ifm-color-primary);--ifm-color-primary:#2e8555;--ifm-color-primary-dark:#29784c;--ifm-color-primary-darker:#277148;--ifm-color-primary-darkest:#205d3b;--ifm-color-primary-light:#33925d;--ifm-color-primary-lighter:#359962;--ifm-color-primary-lightest:#3cad6e;--ifm-code-font-size:95%;--docusaurus-highlighted-code-line-bg:#0000001a;--docusaurus-tag-list-border:var(--ifm-color-emphasis-300);--docusaurus-announcement-bar-height:auto;--docusaurus-collapse-button-bg:#0000;--docusaurus-collapse-button-bg-hover:#0000001a;--doc-sidebar-width:300px;--doc-sidebar-hidden-width:30px}.badge--danger,.badge--info,.badge--primary,.badge--secondary,.badge--success,.badge--warning{--ifm-badge-border-color:var(--ifm-badge-background-color)}.button--link,.button--outline{--ifm-button-background-color:#0000}*{box-sizing:border-box}html{-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;text-size-adjust:100%;background-color:var(--ifm-background-color);color:var(--ifm-font-color-base);color-scheme:var(--ifm-color-scheme);font:var(--ifm-font-size-base)/var(--ifm-line-height-base) var(--ifm-font-family-base);text-rendering:optimizelegibility}iframe{border:0;color-scheme:auto}.container{margin:0 auto;max-width:var(--ifm-container-width)}.container--fluid{max-width:inherit}.row{display:flex;flex-wrap:wrap;margin:0 calc(var(--ifm-spacing-horizontal)*-1)}.margin-bottom--none,.margin-vert--none,.markdown>:last-child{margin-bottom:0!important}.margin-top--none,.margin-vert--none{margin-top:0!important}.row--no-gutters{margin-left:0;margin-right:0}.margin-horiz--none,.margin-right--none{margin-right:0!important}.row--no-gutters>.col{padding-left:0;padding-right:0}.row--align-top{align-items:flex-start}.row--align-bottom{align-items:flex-end}.menuExternalLink_NmtK,.row--align-center{align-items:center}.row--align-stretch{align-items:stretch}.row--align-baseline{align-items:baseline}.col{--ifm-col-width:100%;flex:1 0;margin-left:0;max-width:var(--ifm-col-width)}.padding-bottom--none,.padding-vert--none{padding-bottom:0!important}.padding-top--none,.padding-vert--none{padding-top:0!important}.padding-horiz--none,.padding-left--none{padding-left:0!important}.padding-horiz--none,.padding-right--none{padding-right:0!important}.col[class*=col--]{flex:0 0 var(--ifm-col-width)}.col--1{--ifm-col-width:8.33333%}.col--offset-1{margin-left:8.33333%}.col--2{--ifm-col-width:16.66667%}.col--offset-2{margin-left:16.66667%}.col--3{--ifm-col-width:25%}.col--offset-3{margin-left:25%}.col--4{--ifm-col-width:33.33333%}.col--offset-4{margin-left:33.33333%}.col--5{--ifm-col-width:41.66667%}.col--offset-5{margin-left:41.66667%}.col--6{--ifm-col-width:50%}.col--offset-6{margin-left:50%}.col--7{--ifm-col-width:58.33333%}.col--offset-7{margin-left:58.33333%}.col--8{--ifm-col-width:66.66667%}.col--offset-8{margin-left:66.66667%}.col--9{--ifm-col-width:75%}.col--offset-9{margin-left:75%}.col--10{--ifm-col-width:83.33333%}.col--offset-10{margin-left:83.33333%}.col--11{--ifm-col-width:91.66667%}.col--offset-11{margin-left:91.66667%}.col--12{--ifm-col-width:100%}.col--offset-12{margin-left:100%}.margin-horiz--none,.margin-left--none{margin-left:0!important}.margin--none{margin:0!important}.margin-bottom--xs,.margin-vert--xs{margin-bottom:.25rem!important}.margin-top--xs,.margin-vert--xs{margin-top:.25rem!important}.margin-horiz--xs,.margin-left--xs{margin-left:.25rem!important}.margin-horiz--xs,.margin-right--xs{margin-right:.25rem!important}.margin--xs{margin:.25rem!important}.margin-bottom--sm,.margin-vert--sm{margin-bottom:.5rem!important}.margin-top--sm,.margin-vert--sm{margin-top:.5rem!important}.margin-horiz--sm,.margin-left--sm{margin-left:.5rem!important}.margin-horiz--sm,.margin-right--sm{margin-right:.5rem!important}.margin--sm{margin:.5rem!important}.margin-bottom--md,.margin-vert--md{margin-bottom:1rem!important}.margin-top--md,.margin-vert--md{margin-top:1rem!important}.margin-horiz--md,.margin-left--md{margin-left:1rem!important}.margin-horiz--md,.margin-right--md{margin-right:1rem!important}.margin--md{margin:1rem!important}.margin-bottom--lg,.margin-vert--lg{margin-bottom:2rem!important}.margin-top--lg,.margin-vert--lg{margin-top:2rem!important}.margin-horiz--lg,.margin-left--lg{margin-left:2rem!important}.margin-horiz--lg,.margin-right--lg{margin-right:2rem!important}.margin--lg{margin:2rem!important}.margin-bottom--xl,.margin-vert--xl{margin-bottom:5rem!important}.margin-top--xl,.margin-vert--xl{margin-top:5rem!important}.margin-horiz--xl,.margin-left--xl{margin-left:5rem!important}.margin-horiz--xl,.margin-right--xl{margin-right:5rem!important}.margin--xl{margin:5rem!important}.padding--none{padding:0!important}.padding-bottom--xs,.padding-vert--xs{padding-bottom:.25rem!important}.padding-top--xs,.padding-vert--xs{padding-top:.25rem!important}.padding-horiz--xs,.padding-left--xs{padding-left:.25rem!important}.padding-horiz--xs,.padding-right--xs{padding-right:.25rem!important}.padding--xs{padding:.25rem!important}.padding-bottom--sm,.padding-vert--sm{padding-bottom:.5rem!important}.padding-top--sm,.padding-vert--sm{padding-top:.5rem!important}.padding-horiz--sm,.padding-left--sm{padding-left:.5rem!important}.padding-horiz--sm,.padding-right--sm{padding-right:.5rem!important}.padding--sm{padding:.5rem!important}.padding-bottom--md,.padding-vert--md{padding-bottom:1rem!important}.padding-top--md,.padding-vert--md{padding-top:1rem!important}.padding-horiz--md,.padding-left--md{padding-left:1rem!important}.padding-horiz--md,.padding-right--md{padding-right:1rem!important}.padding--md{padding:1rem!important}.padding-bottom--lg,.padding-vert--lg{padding-bottom:2rem!important}.padding-top--lg,.padding-vert--lg{padding-top:2rem!important}.padding-horiz--lg,.padding-left--lg{padding-left:2rem!important}.padding-horiz--lg,.padding-right--lg{padding-right:2rem!important}.padding--lg{padding:2rem!important}.padding-bottom--xl,.padding-vert--xl{padding-bottom:5rem!important}.padding-top--xl,.padding-vert--xl{padding-top:5rem!important}.padding-horiz--xl,.padding-left--xl{padding-left:5rem!important}.padding-horiz--xl,.padding-right--xl{padding-right:5rem!important}.padding--xl{padding:5rem!important}code{background-color:var(--ifm-code-background);border:.1rem solid #0000001a;border-radius:var(--ifm-code-border-radius);font-family:var(--ifm-font-family-monospace);font-size:var(--ifm-code-font-size);padding:var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal)}a code{color:inherit}pre{background-color:var(--ifm-pre-background);border-radius:var(--ifm-pre-border-radius);color:var(--ifm-pre-color);font:var(--ifm-code-font-size)/var(--ifm-pre-line-height) var(--ifm-font-family-monospace);padding:var(--ifm-pre-padding)}pre code{background-color:initial;border:none;font-size:100%;line-height:inherit;padding:0}kbd{background-color:var(--ifm-color-emphasis-0);border:1px solid var(--ifm-color-emphasis-400);border-radius:.2rem;box-shadow:inset 0 -1px 0 var(--ifm-color-emphasis-400);color:var(--ifm-color-emphasis-800);font:80% var(--ifm-font-family-monospace);padding:.15rem .3rem}h1,h2,h3,h4,h5,h6{color:var(--ifm-heading-color);font-family:var(--ifm-heading-font-family);font-weight:var(--ifm-heading-font-weight);line-height:var(--ifm-heading-line-height);margin:var(--ifm-heading-margin-top) 0 var(--ifm-heading-margin-bottom) 0}h1{font-size:var(--ifm-h1-font-size)}h2{font-size:var(--ifm-h2-font-size)}h3{font-size:var(--ifm-h3-font-size)}h4{font-size:var(--ifm-h4-font-size)}h5{font-size:var(--ifm-h5-font-size)}h6{font-size:var(--ifm-h6-font-size)}img{max-width:100%}img[align=right]{padding-left:var(--image-alignment-padding)}img[align=left]{padding-right:var(--image-alignment-padding)}.markdown{--ifm-h1-vertical-rhythm-top:3;--ifm-h2-vertical-rhythm-top:2;--ifm-h3-vertical-rhythm-top:1.5;--ifm-heading-vertical-rhythm-top:1.25;--ifm-h1-vertical-rhythm-bottom:1.25;--ifm-heading-vertical-rhythm-bottom:1}.markdown:after,.markdown:before{content:"";display:table}.markdown:after{clear:both}.markdown h1:first-child{--ifm-h1-font-size:3rem;margin-bottom:calc(var(--ifm-h1-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown>h2{--ifm-h2-font-size:2rem;margin-top:calc(var(--ifm-h2-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h3{--ifm-h3-font-size:1.5rem;margin-top:calc(var(--ifm-h3-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h4,.markdown>h5,.markdown>h6{margin-top:calc(var(--ifm-heading-vertical-rhythm-top)*var(--ifm-leading))}.markdown>p,.markdown>pre,.markdown>ul{margin-bottom:var(--ifm-leading)}.markdown li>p{margin-top:var(--ifm-list-paragraph-margin)}.markdown li+li{margin-top:var(--ifm-list-item-margin)}ol,ul{margin:0 0 var(--ifm-list-margin);padding-left:var(--ifm-list-left-padding)}ol ol,ul ol{list-style-type:lower-roman}ol ol ol,ol ul ol,ul ol ol,ul ul ol{list-style-type:lower-alpha}table{border-collapse:collapse;display:block;margin-bottom:var(--ifm-spacing-vertical)}table thead tr{border-bottom:2px solid var(--ifm-table-border-color)}table thead,table tr:nth-child(2n){background-color:var(--ifm-table-stripe-background)}table tr{background-color:var(--ifm-table-background);border-top:var(--ifm-table-border-width) solid var(--ifm-table-border-color)}table td,table th{border:var(--ifm-table-border-width) solid var(--ifm-table-border-color);padding:var(--ifm-table-cell-padding)}table th{background-color:var(--ifm-table-head-background);color:var(--ifm-table-head-color);font-weight:var(--ifm-table-head-font-weight)}table td{color:var(--ifm-table-cell-color)}strong{font-weight:var(--ifm-font-weight-bold)}a{color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}a:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}.button:hover,.text--no-decoration,.text--no-decoration:hover,a:not([href]){text-decoration:none}p{margin:0 0 var(--ifm-paragraph-margin-bottom)}blockquote{border-left:var(--ifm-blockquote-border-left-width) solid var(--ifm-blockquote-border-color);box-shadow:var(--ifm-blockquote-shadow);color:var(--ifm-blockquote-color);font-size:var(--ifm-blockquote-font-size);padding:var(--ifm-blockquote-padding-vertical) var(--ifm-blockquote-padding-horizontal)}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}hr{background-color:var(--ifm-hr-background-color);border:0;height:var(--ifm-hr-height);margin:var(--ifm-hr-margin-vertical) 0}.shadow--lw{box-shadow:var(--ifm-global-shadow-lw)!important}.shadow--md{box-shadow:var(--ifm-global-shadow-md)!important}.shadow--tl{box-shadow:var(--ifm-global-shadow-tl)!important}.text--primary,.wordWrapButtonEnabled_EoeP .wordWrapButtonIcon_Bwma{color:var(--ifm-color-primary)}.text--secondary{color:var(--ifm-color-secondary)}.text--success{color:var(--ifm-color-success)}.text--info{color:var(--ifm-color-info)}.text--warning{color:var(--ifm-color-warning)}.text--danger{color:var(--ifm-color-danger)}.text--center{text-align:center}.text--left{text-align:left}.text--justify{text-align:justify}.text--right{text-align:right}.text--capitalize{text-transform:capitalize}.text--lowercase{text-transform:lowercase}.admonitionHeading_tbUL,.alert__heading,.text--uppercase{text-transform:uppercase}.text--light{font-weight:var(--ifm-font-weight-light)}.text--normal{font-weight:var(--ifm-font-weight-normal)}.text--semibold{font-weight:var(--ifm-font-weight-semibold)}.text--bold{font-weight:var(--ifm-font-weight-bold)}.text--italic{font-style:italic}.text--truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text--break{word-wrap:break-word!important;word-break:break-word!important}.clean-btn{background:none;border:none;color:inherit;cursor:pointer;font-family:inherit;padding:0}.alert,.alert .close{color:var(--ifm-alert-foreground-color)}.clean-list{padding-left:0}.alert--primary{--ifm-alert-background-color:var(--ifm-color-primary-contrast-background);--ifm-alert-background-color-highlight:#3578e526;--ifm-alert-foreground-color:var(--ifm-color-primary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-primary-dark)}.alert--secondary{--ifm-alert-background-color:var(--ifm-color-secondary-contrast-background);--ifm-alert-background-color-highlight:#ebedf026;--ifm-alert-foreground-color:var(--ifm-color-secondary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-secondary-dark)}.alert--success{--ifm-alert-background-color:var(--ifm-color-success-contrast-background);--ifm-alert-background-color-highlight:#00a40026;--ifm-alert-foreground-color:var(--ifm-color-success-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-success-dark)}.alert--info{--ifm-alert-background-color:var(--ifm-color-info-contrast-background);--ifm-alert-background-color-highlight:#54c7ec26;--ifm-alert-foreground-color:var(--ifm-color-info-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-info-dark)}.alert--warning{--ifm-alert-background-color:var(--ifm-color-warning-contrast-background);--ifm-alert-background-color-highlight:#ffba0026;--ifm-alert-foreground-color:var(--ifm-color-warning-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-warning-dark)}.alert--danger{--ifm-alert-background-color:var(--ifm-color-danger-contrast-background);--ifm-alert-background-color-highlight:#fa383e26;--ifm-alert-foreground-color:var(--ifm-color-danger-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-danger-dark)}.alert{--ifm-code-background:var(--ifm-alert-background-color-highlight);--ifm-link-color:var(--ifm-alert-foreground-color);--ifm-link-hover-color:var(--ifm-alert-foreground-color);--ifm-link-decoration:underline;--ifm-tabs-color:var(--ifm-alert-foreground-color);--ifm-tabs-color-active:var(--ifm-alert-foreground-color);--ifm-tabs-color-active-border:var(--ifm-alert-border-color);background-color:var(--ifm-alert-background-color);border:var(--ifm-alert-border-width) solid var(--ifm-alert-border-color);border-left-width:var(--ifm-alert-border-left-width);border-radius:var(--ifm-alert-border-radius);box-shadow:var(--ifm-alert-shadow);padding:var(--ifm-alert-padding-vertical) var(--ifm-alert-padding-horizontal)}.alert__heading{align-items:center;display:flex;font:700 var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family);margin-bottom:.5rem}.alert__icon{display:inline-flex;margin-right:.4em}.alert__icon svg{fill:var(--ifm-alert-foreground-color);stroke:var(--ifm-alert-foreground-color);stroke-width:0}.alert .close{margin:calc(var(--ifm-alert-padding-vertical)*-1) calc(var(--ifm-alert-padding-horizontal)*-1) 0 0;opacity:.75}.alert .close:focus,.alert .close:hover{opacity:1}.alert a{text-decoration-color:var(--ifm-alert-border-color)}.alert a:hover{text-decoration-thickness:2px}.avatar{column-gap:var(--ifm-avatar-intro-margin);display:flex}.avatar__photo{border-radius:50%;display:block;height:var(--ifm-avatar-photo-size);overflow:hidden;width:var(--ifm-avatar-photo-size)}.card--full-height,.navbar__logo img,body,html{height:100%}.avatar__photo--sm{--ifm-avatar-photo-size:2rem}.avatar__photo--lg{--ifm-avatar-photo-size:4rem}.avatar__photo--xl{--ifm-avatar-photo-size:6rem}.avatar__intro{display:flex;flex:1 1;flex-direction:column;justify-content:center;text-align:var(--ifm-avatar-intro-alignment)}.badge,.breadcrumbs__item,.breadcrumbs__link,.button,.dropdown>.navbar__link:after{display:inline-block}.avatar__name{font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base)}.avatar__subtitle{margin-top:.25rem}.avatar--vertical{--ifm-avatar-intro-alignment:center;--ifm-avatar-intro-margin:0.5rem;align-items:center;flex-direction:column}.badge{background-color:var(--ifm-badge-background-color);border:var(--ifm-badge-border-width) solid var(--ifm-badge-border-color);border-radius:var(--ifm-badge-border-radius);color:var(--ifm-badge-color);font-size:75%;font-weight:var(--ifm-font-weight-bold);line-height:1;padding:var(--ifm-badge-padding-vertical) var(--ifm-badge-padding-horizontal)}.badge--primary{--ifm-badge-background-color:var(--ifm-color-primary)}.badge--secondary{--ifm-badge-background-color:var(--ifm-color-secondary);color:var(--ifm-color-black)}.breadcrumbs__link,.button.button--secondary.button--outline:not(.button--active):not(:hover){color:var(--ifm-font-color-base)}.badge--success{--ifm-badge-background-color:var(--ifm-color-success)}.badge--info{--ifm-badge-background-color:var(--ifm-color-info)}.badge--warning{--ifm-badge-background-color:var(--ifm-color-warning)}.badge--danger{--ifm-badge-background-color:var(--ifm-color-danger)}.breadcrumbs{margin-bottom:0;padding-left:0}.breadcrumbs__item:not(:last-child):after{background:var(--ifm-breadcrumb-separator) center;content:" ";display:inline-block;filter:var(--ifm-breadcrumb-separator-filter);height:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier));margin:0 var(--ifm-breadcrumb-spacing);opacity:.5;width:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier))}.breadcrumbs__item--active .breadcrumbs__link{background:var(--ifm-breadcrumb-item-background-active);color:var(--ifm-breadcrumb-color-active)}.breadcrumbs__link{border-radius:var(--ifm-breadcrumb-border-radius);font-size:calc(1rem*var(--ifm-breadcrumb-size-multiplier));padding:calc(var(--ifm-breadcrumb-padding-vertical)*var(--ifm-breadcrumb-size-multiplier)) calc(var(--ifm-breadcrumb-padding-horizontal)*var(--ifm-breadcrumb-size-multiplier));transition-duration:var(--ifm-transition-fast);transition-property:background,color}.breadcrumbs__link:any-link:hover,.breadcrumbs__link:link:hover,.breadcrumbs__link:visited:hover,area[href].breadcrumbs__link:hover{background:var(--ifm-breadcrumb-item-background-active);text-decoration:none}.breadcrumbs--sm{--ifm-breadcrumb-size-multiplier:0.8}.breadcrumbs--lg{--ifm-breadcrumb-size-multiplier:1.2}.button{background-color:var(--ifm-button-background-color);border:var(--ifm-button-border-width) solid var(--ifm-button-border-color);border-radius:var(--ifm-button-border-radius);cursor:pointer;font-size:calc(.875rem*var(--ifm-button-size-multiplier));font-weight:var(--ifm-button-font-weight);line-height:1.5;padding:calc(var(--ifm-button-padding-vertical)*var(--ifm-button-size-multiplier)) calc(var(--ifm-button-padding-horizontal)*var(--ifm-button-size-multiplier));text-align:center;transition-duration:var(--ifm-button-transition-duration);transition-property:color,background,border-color;-webkit-user-select:none;user-select:none;white-space:nowrap}.button,.button:hover{color:var(--ifm-button-color)}.button--outline{--ifm-button-color:var(--ifm-button-border-color)}.button--outline:hover{--ifm-button-background-color:var(--ifm-button-border-color)}.button--link{--ifm-button-border-color:#0000;color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}.button--link.button--active,.button--link:active,.button--link:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}.button.disabled,.button:disabled,.button[disabled]{opacity:.65;pointer-events:none}.button--sm{--ifm-button-size-multiplier:0.8}.button--lg{--ifm-button-size-multiplier:1.35}.button--block{display:block;width:100%}.button.button--secondary{color:var(--ifm-color-gray-900)}:where(.button--primary){--ifm-button-background-color:var(--ifm-color-primary);--ifm-button-border-color:var(--ifm-color-primary)}:where(.button--primary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-primary-dark);--ifm-button-border-color:var(--ifm-color-primary-dark)}.button--primary.button--active,.button--primary:active{--ifm-button-background-color:var(--ifm-color-primary-darker);--ifm-button-border-color:var(--ifm-color-primary-darker)}:where(.button--secondary){--ifm-button-background-color:var(--ifm-color-secondary);--ifm-button-border-color:var(--ifm-color-secondary)}:where(.button--secondary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-secondary-dark);--ifm-button-border-color:var(--ifm-color-secondary-dark)}.button--secondary.button--active,.button--secondary:active{--ifm-button-background-color:var(--ifm-color-secondary-darker);--ifm-button-border-color:var(--ifm-color-secondary-darker)}:where(.button--success){--ifm-button-background-color:var(--ifm-color-success);--ifm-button-border-color:var(--ifm-color-success)}:where(.button--success):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-success-dark);--ifm-button-border-color:var(--ifm-color-success-dark)}.button--success.button--active,.button--success:active{--ifm-button-background-color:var(--ifm-color-success-darker);--ifm-button-border-color:var(--ifm-color-success-darker)}:where(.button--info){--ifm-button-background-color:var(--ifm-color-info);--ifm-button-border-color:var(--ifm-color-info)}:where(.button--info):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-info-dark);--ifm-button-border-color:var(--ifm-color-info-dark)}.button--info.button--active,.button--info:active{--ifm-button-background-color:var(--ifm-color-info-darker);--ifm-button-border-color:var(--ifm-color-info-darker)}:where(.button--warning){--ifm-button-background-color:var(--ifm-color-warning);--ifm-button-border-color:var(--ifm-color-warning)}:where(.button--warning):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-warning-dark);--ifm-button-border-color:var(--ifm-color-warning-dark)}.button--warning.button--active,.button--warning:active{--ifm-button-background-color:var(--ifm-color-warning-darker);--ifm-button-border-color:var(--ifm-color-warning-darker)}:where(.button--danger){--ifm-button-background-color:var(--ifm-color-danger);--ifm-button-border-color:var(--ifm-color-danger)}:where(.button--danger):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-danger-dark);--ifm-button-border-color:var(--ifm-color-danger-dark)}.button--danger.button--active,.button--danger:active{--ifm-button-background-color:var(--ifm-color-danger-darker);--ifm-button-border-color:var(--ifm-color-danger-darker)}.button-group{display:inline-flex;gap:var(--ifm-button-group-spacing)}.button-group>.button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.button-group>.button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.button-group--block{display:flex;justify-content:stretch}.button-group--block>.button{flex-grow:1}.card{background-color:var(--ifm-card-background-color);border-radius:var(--ifm-card-border-radius);box-shadow:var(--ifm-global-shadow-lw);display:flex;flex-direction:column;overflow:hidden}.card__image{padding-top:var(--ifm-card-vertical-spacing)}.card__image:first-child{padding-top:0}.card__body,.card__footer,.card__header{padding:var(--ifm-card-vertical-spacing) var(--ifm-card-horizontal-spacing)}.card__body:not(:last-child),.card__footer:not(:last-child),.card__header:not(:last-child){padding-bottom:0}.card__body>:last-child,.card__footer>:last-child,.card__header>:last-child{margin-bottom:0}.card__footer{margin-top:auto}.table-of-contents{font-size:.8rem;margin-bottom:0;padding:var(--ifm-toc-padding-vertical) 0}.table-of-contents,.table-of-contents ul{list-style:none;padding-left:var(--ifm-toc-padding-horizontal)}.table-of-contents li{margin:var(--ifm-toc-padding-vertical) var(--ifm-toc-padding-horizontal)}.table-of-contents__left-border{border-left:1px solid var(--ifm-toc-border-color)}.table-of-contents__link{color:var(--ifm-toc-link-color);display:block}.table-of-contents__link--active,.table-of-contents__link--active code,.table-of-contents__link:hover,.table-of-contents__link:hover code{color:var(--ifm-color-primary);text-decoration:none}.close{color:var(--ifm-color-black);float:right;font-size:1.5rem;font-weight:var(--ifm-font-weight-bold);line-height:1;opacity:.5;padding:1rem;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.close:hover{opacity:.7}.close:focus,.theme-code-block-highlighted-line .codeLineNumber_Tfdd:before{opacity:.8}.dropdown{display:inline-flex;font-weight:var(--ifm-dropdown-font-weight);position:relative;vertical-align:top}.dropdown--hoverable:hover .dropdown__menu,.dropdown--show .dropdown__menu{opacity:1;pointer-events:all;transform:translateY(-1px);visibility:visible}#nprogress,.dropdown__menu,.navbar__item.dropdown .navbar__link:not([href]){pointer-events:none}.dropdown--right .dropdown__menu{left:inherit;right:0}.dropdown--nocaret .navbar__link:after{content:none!important}.dropdown__menu{background-color:var(--ifm-dropdown-background-color);border-radius:var(--ifm-global-radius);box-shadow:var(--ifm-global-shadow-md);left:0;max-height:80vh;min-width:10rem;opacity:0;overflow-y:auto;padding:.5rem;position:absolute;top:calc(100% - var(--ifm-navbar-item-padding-vertical) + .3rem);transform:translateY(-.625rem);transition-duration:var(--ifm-transition-fast);transition-property:opacity,transform,visibility;transition-timing-function:var(--ifm-transition-timing-default);visibility:hidden;z-index:var(--ifm-z-index-dropdown)}.menu__caret,.menu__link,.menu__list-item-collapsible{border-radius:.25rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.dropdown__link{border-radius:.25rem;color:var(--ifm-dropdown-link-color);display:block;font-size:.875rem;margin-top:.2rem;padding:.25rem .5rem;white-space:nowrap}.dropdown__link--active,.dropdown__link:hover{background-color:var(--ifm-dropdown-hover-background-color);color:var(--ifm-dropdown-link-color);text-decoration:none}.dropdown__link--active,.dropdown__link--active:hover{--ifm-dropdown-link-color:var(--ifm-link-color)}.dropdown>.navbar__link:after{border-color:currentcolor #0000;border-style:solid;border-width:.4em .4em 0;content:"";margin-left:.3em;position:relative;top:2px;transform:translateY(-50%)}.footer{background-color:var(--ifm-footer-background-color);color:var(--ifm-footer-color);padding:var(--ifm-footer-padding-vertical) var(--ifm-footer-padding-horizontal)}.footer--dark{--ifm-footer-background-color:#303846;--ifm-footer-color:var(--ifm-footer-link-color);--ifm-footer-link-color:var(--ifm-color-secondary);--ifm-footer-title-color:var(--ifm-color-white)}.footer__links{margin-bottom:1rem}.footer__link-item{color:var(--ifm-footer-link-color);line-height:2}.footer__link-item:hover{color:var(--ifm-footer-link-hover-color)}.footer__link-separator{margin:0 var(--ifm-footer-link-horizontal-spacing)}.footer__logo{margin-top:1rem;max-width:var(--ifm-footer-logo-max-width)}.footer__title{color:var(--ifm-footer-title-color);font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base);margin-bottom:var(--ifm-heading-margin-bottom)}.menu,.navbar__link{font-weight:var(--ifm-font-weight-semibold)}.docItemContainer_Djhp article>:first-child,.docItemContainer_Djhp header+*,.footer__item{margin-top:0}.admonitionContent_S0QG>:last-child,.collapsibleContent_i85q>:last-child,.footer__items{margin-bottom:0}.codeBlockStandalone_MEMb,[type=checkbox]{padding:0}.hero{align-items:center;background-color:var(--ifm-hero-background-color);color:var(--ifm-hero-text-color);display:flex;padding:4rem 2rem}.hero--primary{--ifm-hero-background-color:var(--ifm-color-primary);--ifm-hero-text-color:var(--ifm-font-color-base-inverse)}.hero--dark{--ifm-hero-background-color:#303846;--ifm-hero-text-color:var(--ifm-color-white)}.hero__title{font-size:3rem}.hero__subtitle{font-size:1.5rem}.menu__list{margin:0;padding-left:0}.menu__caret,.menu__link{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu__list .menu__list{flex:0 0 100%;margin-top:.25rem;padding-left:var(--ifm-menu-link-padding-horizontal)}.menu__list-item:not(:first-child){margin-top:.25rem}.menu__list-item--collapsed .menu__list{height:0;overflow:hidden}.details_lb9f[data-collapsed=false].isBrowser_bmU9>summary:before,.details_lb9f[open]:not(.isBrowser_bmU9)>summary:before,.menu__list-item--collapsed .menu__caret:before,.menu__list-item--collapsed .menu__link--sublist:after{transform:rotate(90deg)}.menu__list-item-collapsible{display:flex;flex-wrap:wrap;position:relative}.menu__caret:hover,.menu__link:hover,.menu__list-item-collapsible--active,.menu__list-item-collapsible:hover{background:var(--ifm-menu-color-background-hover)}.menu__list-item-collapsible .menu__link--active,.menu__list-item-collapsible .menu__link:hover{background:none!important}.menu__caret,.menu__link{align-items:center;display:flex}.navbar-sidebar,.navbar-sidebar__backdrop{bottom:0;opacity:0;transition-duration:var(--ifm-transition-fast);transition-timing-function:ease-in-out;top:0;left:0;visibility:hidden}.menu__link{color:var(--ifm-menu-color);flex:1;line-height:1.25}.menu__link:hover{color:var(--ifm-menu-color);text-decoration:none}.menu__caret:before,.menu__link--sublist-caret:after{height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast) linear;width:1.25rem;filter:var(--ifm-menu-link-sublist-icon-filter);content:""}.menu__link--sublist-caret:after{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem;margin-left:auto;min-width:1.25rem}.menu__link--active,.menu__link--active:hover{color:var(--ifm-menu-color-active)}.navbar__brand,.navbar__link{color:var(--ifm-navbar-link-color)}.menu__link--active:not(.menu__link--sublist){background-color:var(--ifm-menu-color-background-active)}.menu__caret:before{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem}.navbar--dark,html[data-theme=dark]{--ifm-menu-link-sublist-icon-filter:invert(100%) sepia(94%) saturate(17%) hue-rotate(223deg) brightness(104%) contrast(98%)}.navbar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-navbar-shadow);height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.navbar,.navbar>.container,.navbar>.container-fluid{display:flex}.navbar--fixed-top{position:sticky;top:0;z-index:var(--ifm-z-index-fixed)}.navbar__inner{display:flex;flex-wrap:wrap;justify-content:space-between;width:100%}.navbar__brand{align-items:center;display:flex;margin-right:1rem;min-width:0}.navbar__brand:hover{color:var(--ifm-navbar-link-hover-color);text-decoration:none}.announcementBarContent_xLdY,.navbar__title{flex:1 1 auto}.navbar__toggle{display:none;margin-right:.5rem}.navbar__logo{flex:0 0 auto;height:2rem;margin-right:.5rem}.navbar__items{align-items:center;display:flex;flex:1;min-width:0}.navbar__items--center{flex:0 0 auto}.navbar__items--center .navbar__brand{margin:0}.navbar__items--center+.navbar__items--right{flex:1}.navbar__items--right{flex:0 0 auto;justify-content:flex-end}.navbar__items--right>:last-child{padding-right:0}.navbar__item{display:inline-block;padding:var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal)}.navbar__link--active,.navbar__link:hover{color:var(--ifm-navbar-link-hover-color);text-decoration:none}.navbar--dark,.navbar--primary{--ifm-menu-color:var(--ifm-color-gray-300);--ifm-navbar-link-color:var(--ifm-color-gray-100);--ifm-navbar-search-input-background-color:#ffffff1a;--ifm-navbar-search-input-placeholder-color:#ffffff80;color:var(--ifm-color-white)}.navbar--dark{--ifm-navbar-background-color:#242526;--ifm-menu-color-background-active:#ffffff0d;--ifm-navbar-search-input-color:var(--ifm-color-white)}.navbar--primary{--ifm-navbar-background-color:var(--ifm-color-primary);--ifm-navbar-link-hover-color:var(--ifm-color-white);--ifm-menu-color-active:var(--ifm-color-white);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-500)}.navbar__search-input{-webkit-appearance:none;appearance:none;background:var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat .75rem center/1rem 1rem;border:none;border-radius:2rem;color:var(--ifm-navbar-search-input-color);cursor:text;display:inline-block;font-size:.9rem;height:2rem;padding:0 .5rem 0 2.25rem;width:12.5rem}.navbar__search-input::placeholder{color:var(--ifm-navbar-search-input-placeholder-color)}.navbar-sidebar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-global-shadow-md);position:fixed;transform:translate3d(-100%,0,0);transition-property:opacity,visibility,transform;width:var(--ifm-navbar-sidebar-width)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar__items{transform:translateZ(0)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar--show .navbar-sidebar__backdrop{opacity:1;visibility:visible}.navbar-sidebar__backdrop{background-color:#0009;position:fixed;right:0;transition-property:opacity,visibility}.navbar-sidebar__brand{align-items:center;box-shadow:var(--ifm-navbar-shadow);display:flex;flex:1;height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.navbar-sidebar__items{display:flex;height:calc(100% - var(--ifm-navbar-height));transition:transform var(--ifm-transition-fast) ease-in-out}.navbar-sidebar__items--show-secondary{transform:translate3d(calc((var(--ifm-navbar-sidebar-width))*-1),0,0)}.navbar-sidebar__item{flex-shrink:0;padding:.5rem;width:calc(var(--ifm-navbar-sidebar-width))}.navbar-sidebar__back{background:var(--ifm-menu-color-background-active);font-size:15px;font-weight:var(--ifm-button-font-weight);margin:0 0 .2rem -.5rem;padding:.6rem 1.5rem;position:relative;text-align:left;top:-.5rem;width:calc(100% + 1rem)}.navbar-sidebar__close{display:flex;margin-left:auto}.pagination{column-gap:var(--ifm-pagination-page-spacing);display:flex;font-size:var(--ifm-pagination-font-size);padding-left:0}.pagination--sm{--ifm-pagination-font-size:0.8rem;--ifm-pagination-padding-horizontal:0.8rem;--ifm-pagination-padding-vertical:0.2rem}.pagination--lg{--ifm-pagination-font-size:1.2rem;--ifm-pagination-padding-horizontal:1.2rem;--ifm-pagination-padding-vertical:0.3rem}.pagination__item{display:inline-flex}.pagination__item>span{padding:var(--ifm-pagination-padding-vertical)}.pagination__item--active .pagination__link{color:var(--ifm-pagination-color-active)}.pagination__item--active .pagination__link,.pagination__item:not(.pagination__item--active):hover .pagination__link{background:var(--ifm-pagination-item-active-background)}.pagination__item--disabled,.pagination__item[disabled]{opacity:.25;pointer-events:none}.pagination__link{border-radius:var(--ifm-pagination-border-radius);color:var(--ifm-font-color-base);display:inline-block;padding:var(--ifm-pagination-padding-vertical) var(--ifm-pagination-padding-horizontal);transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination__link:hover{text-decoration:none}.pagination-nav{grid-gap:var(--ifm-spacing-horizontal);display:grid;gap:var(--ifm-spacing-horizontal);grid-template-columns:repeat(2,1fr)}.pagination-nav__link{border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-pagination-nav-border-radius);display:block;height:100%;line-height:var(--ifm-heading-line-height);padding:var(--ifm-global-spacing);transition:border-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination-nav__link:hover{border-color:var(--ifm-pagination-nav-color-hover);text-decoration:none}.pagination-nav__link--next{grid-column:2/3;text-align:right}.pagination-nav__label{font-size:var(--ifm-h4-font-size);font-weight:var(--ifm-heading-font-weight);word-break:break-word}.pagination-nav__link--prev .pagination-nav__label:before{content:"« "}.pagination-nav__link--next .pagination-nav__label:after{content:" »"}.pagination-nav__sublabel{color:var(--ifm-color-content-secondary);font-size:var(--ifm-h5-font-size);font-weight:var(--ifm-font-weight-semibold);margin-bottom:.25rem}.pills__item,.tabs{font-weight:var(--ifm-font-weight-bold)}.pills{display:flex;gap:var(--ifm-pills-spacing);padding-left:0}.pills__item{border-radius:.5rem;cursor:pointer;display:inline-block;padding:.25rem 1rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.tabs,:not(.containsTaskList_mC6p>li)>.containsTaskList_mC6p{padding-left:0}.pills__item--active{color:var(--ifm-pills-color-active)}.pills__item--active,.pills__item:not(.pills__item--active):hover{background:var(--ifm-pills-color-background-active)}.pills--block{justify-content:stretch}.pills--block .pills__item{flex-grow:1;text-align:center}.tabs{color:var(--ifm-tabs-color);display:flex;margin-bottom:0;overflow-x:auto}.tabs__item{border-bottom:3px solid #0000;border-radius:var(--ifm-global-radius);cursor:pointer;display:inline-flex;padding:var(--ifm-tabs-padding-vertical) var(--ifm-tabs-padding-horizontal);transition:background-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.tabs__item--active{border-bottom-color:var(--ifm-tabs-color-active-border);border-bottom-left-radius:0;border-bottom-right-radius:0;color:var(--ifm-tabs-color-active)}.tabs__item:hover{background-color:var(--ifm-hover-overlay)}.tabs--block{justify-content:stretch}.tabs--block .tabs__item{flex-grow:1;justify-content:center}html[data-theme=dark]{--ifm-color-scheme:dark;--ifm-color-emphasis-0:var(--ifm-color-gray-1000);--ifm-color-emphasis-100:var(--ifm-color-gray-900);--ifm-color-emphasis-200:var(--ifm-color-gray-800);--ifm-color-emphasis-300:var(--ifm-color-gray-700);--ifm-color-emphasis-400:var(--ifm-color-gray-600);--ifm-color-emphasis-600:var(--ifm-color-gray-400);--ifm-color-emphasis-700:var(--ifm-color-gray-300);--ifm-color-emphasis-800:var(--ifm-color-gray-200);--ifm-color-emphasis-900:var(--ifm-color-gray-100);--ifm-color-emphasis-1000:var(--ifm-color-gray-0);--ifm-background-color:#1b1b1d;--ifm-background-surface-color:#242526;--ifm-hover-overlay:#ffffff0d;--ifm-color-content:#e3e3e3;--ifm-color-content-secondary:#fff;--ifm-breadcrumb-separator-filter:invert(64%) sepia(11%) saturate(0%) hue-rotate(149deg) brightness(99%) contrast(95%);--ifm-code-background:#ffffff1a;--ifm-scrollbar-track-background-color:#444;--ifm-scrollbar-thumb-background-color:#686868;--ifm-scrollbar-thumb-hover-background-color:#7a7a7a;--ifm-table-stripe-background:#ffffff12;--ifm-toc-border-color:var(--ifm-color-emphasis-200);--ifm-color-primary-contrast-background:#102445;--ifm-color-primary-contrast-foreground:#ebf2fc;--ifm-color-secondary-contrast-background:#474748;--ifm-color-secondary-contrast-foreground:#fdfdfe;--ifm-color-success-contrast-background:#003100;--ifm-color-success-contrast-foreground:#e6f6e6;--ifm-color-info-contrast-background:#193c47;--ifm-color-info-contrast-foreground:#eef9fd;--ifm-color-warning-contrast-background:#4d3800;--ifm-color-warning-contrast-foreground:#fff8e6;--ifm-color-danger-contrast-background:#4b1113;--ifm-color-danger-contrast-foreground:#ffebec}#nprogress .bar{background:var(--docusaurus-progress-bar-color);height:2px;left:0;position:fixed;top:0;width:100%;z-index:1031}#nprogress .peg{box-shadow:0 0 10px var(--docusaurus-progress-bar-color),0 0 5px var(--docusaurus-progress-bar-color);height:100%;opacity:1;position:absolute;right:0;transform:rotate(3deg) translateY(-4px);width:100px}[data-theme=dark]{--ifm-color-primary:#25c2a0;--ifm-color-primary-dark:#21af90;--ifm-color-primary-darker:#1fa588;--ifm-color-primary-darkest:#1a8870;--ifm-color-primary-light:#29d5b0;--ifm-color-primary-lighter:#32d8b4;--ifm-color-primary-lightest:#4fddbf;--docusaurus-highlighted-code-line-bg:#6765654d}.theme-doc-sidebar-item-category-level-1 .menu__list-item-collapsible a:first-of-type{color:#a3a3a3;font-size:1.1em}.header-github-link:before{background:url("data:image/svg+xml;charset=utf-8,%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") no-repeat;content:"";display:flex;height:24px;padding-right:5px;width:24px}[data-theme=dark] .header-github-link:before{background:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23fff' 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}.pill{border:none;border-radius:8px;padding:5px 10px}.pill,.pill-small{background-color:#3849df;color:#fff;display:inline-block;margin:4px 2px;text-align:center;text-decoration:none}.pill-small{border:none;border-radius:3px;cursor:pointer;font-size:14px;padding:3px 5px}.iconEdit_Z9Sw{margin-right:.3em;vertical-align:sub}.tag_zVej{border:1px solid var(--docusaurus-tag-list-border);transition:border var(--ifm-transition-fast)}.tag_zVej:hover{--docusaurus-tag-list-border:var(--ifm-link-color);text-decoration:none}.tagRegular_sFm0{border-radius:var(--ifm-global-radius);font-size:90%;padding:.2rem .5rem .3rem}.tagWithCount_h2kH{align-items:center;border-left:0;display:flex;padding:0 .5rem 0 1rem;position:relative}.tagWithCount_h2kH:after,.tagWithCount_h2kH:before{border:1px solid var(--docusaurus-tag-list-border);content:"";position:absolute;top:50%;transition:inherit}.tagWithCount_h2kH:before{border-bottom:0;border-right:0;height:1.18rem;right:100%;transform:translate(50%,-50%) rotate(-45deg);width:1.18rem}.tagWithCount_h2kH:after{border-radius:50%;height:.5rem;left:0;transform:translateY(-50%);width:.5rem}.tagWithCount_h2kH span{background:var(--ifm-color-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-color-black);font-size:.7rem;line-height:1.2;margin-left:.3rem;padding:.1rem .4rem}.tags_jXut{display:inline}.tag_QGVx{display:inline-block;margin:0 .4rem .5rem 0}.lastUpdated_vwxv{font-size:smaller;font-style:italic;margin-top:.2rem}.tocCollapsibleButton_TO0P{align-items:center;display:flex;font-size:inherit;justify-content:space-between;padding:.4rem .8rem;width:100%}.tocCollapsibleButton_TO0P:after{background:var(--ifm-menu-link-sublist-icon) 50% 50%/2rem 2rem no-repeat;content:"";filter:var(--ifm-menu-link-sublist-icon-filter);height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast);width:1.25rem}.tocCollapsibleButtonExpanded_MG3E:after,.tocCollapsibleExpanded_sAul{transform:none}.tocCollapsible_ETCw{background-color:var(--ifm-menu-color-background-active);border-radius:var(--ifm-global-radius);margin:1rem 0}.tocCollapsibleContent_vkbj>ul{border-left:none;border-top:1px solid var(--ifm-color-emphasis-300);font-size:15px;padding:.2rem 0}.tocCollapsibleContent_vkbj ul li{margin:.4rem .8rem}.tocCollapsibleContent_vkbj a{display:block}.tableOfContents_bqdL{max-height:calc(100vh - var(--ifm-navbar-height) - 2rem);overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 1rem)}.anchorWithStickyNavbar_LWe7{scroll-margin-top:calc(var(--ifm-navbar-height) + .5rem)}.anchorWithHideOnScrollNavbar_WYt5{scroll-margin-top:.5rem}.hash-link{opacity:0;padding-left:.5rem;transition:opacity var(--ifm-transition-fast);-webkit-user-select:none;user-select:none}.hash-link:before{content:"#"}.footerLogoLink_BH7S:hover,.hash-link:focus,:hover>.hash-link{opacity:1}.skipToContent_fXgn{background-color:var(--ifm-background-surface-color);color:var(--ifm-color-emphasis-900);left:100%;padding:calc(var(--ifm-global-spacing)/2) var(--ifm-global-spacing);position:fixed;top:1rem;z-index:calc(var(--ifm-z-index-fixed) + 1)}.skipToContent_fXgn:focus{box-shadow:var(--ifm-global-shadow-md);left:1rem}.closeButton_CVFx{line-height:0;padding:0}.content_knG7{font-size:85%;padding:5px 0;text-align:center}.content_knG7 a{color:inherit;text-decoration:underline}.announcementBar_mb4j{align-items:center;background-color:var(--ifm-color-white);border-bottom:1px solid var(--ifm-color-emphasis-100);color:var(--ifm-color-black);display:flex;height:var(--docusaurus-announcement-bar-height)}#docusaurus-base-url-issue-banner-container,.themedImage_ToTc,[data-theme=dark] .lightToggleIcon_pyhR,[data-theme=light] .darkToggleIcon_wfgR,html[data-announcement-bar-initially-dismissed=true] .announcementBar_mb4j{display:none}.announcementBarPlaceholder_vyr4{flex:0 0 10px}.announcementBarClose_gvF7{align-self:stretch;flex:0 0 30px}.toggle_vylO{height:2rem;width:2rem}.toggleButton_gllP{align-items:center;border-radius:50%;display:flex;height:100%;justify-content:center;transition:background var(--ifm-transition-fast);width:100%}.toggleButton_gllP:hover{background:var(--ifm-color-emphasis-200)}.toggleButtonDisabled_aARS{cursor:not-allowed}.darkNavbarColorModeToggle_X3D1:hover{background:var(--ifm-color-gray-800)}[data-theme=dark] .themedImage--dark_i4oU,[data-theme=light] .themedImage--light_HNdA{display:initial}.iconExternalLink_nPIU{margin-left:.3rem}.iconLanguage_nlXk{margin-right:5px;vertical-align:text-bottom}.navbarHideable_m1mJ{transition:transform var(--ifm-transition-fast) ease}.navbarHidden_jGov{transform:translate3d(0,calc(-100% - 2px),0)}.errorBoundaryError_a6uf{color:red;white-space:pre-wrap}.buttonGroup__atx button,.codeBlockContainer_Ckt0{background:var(--prism-background-color);color:var(--prism-color)}body:not(.navigation-with-keyboard) :not(input):focus{outline:0}.footerLogoLink_BH7S{opacity:.5;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.mainWrapper_z2l0{display:flex;flex:1 0 auto;flex-direction:column}.docusaurus-mt-lg{margin-top:3rem}#__docusaurus{display:flex;flex-direction:column;min-height:100%}.codeBlockContainer_Ckt0{border-radius:var(--ifm-code-border-radius);box-shadow:var(--ifm-global-shadow-lw);margin-bottom:var(--ifm-leading)}.codeBlockContent_biex{border-radius:inherit;direction:ltr;position:relative}.codeBlockTitle_Ktv7{border-bottom:1px solid var(--ifm-color-emphasis-300);border-top-left-radius:inherit;border-top-right-radius:inherit;font-size:var(--ifm-code-font-size);font-weight:500;padding:.75rem var(--ifm-pre-padding)}.codeBlock_bY9V{--ifm-pre-background:var(--prism-background-color);margin:0;padding:0}.codeBlockTitle_Ktv7+.codeBlockContent_biex .codeBlock_bY9V{border-top-left-radius:0;border-top-right-radius:0}.codeBlockLines_e6Vv{float:left;font:inherit;min-width:100%;padding:var(--ifm-pre-padding)}.codeBlockLinesWithNumbering_o6Pm{display:table;padding:var(--ifm-pre-padding) 0}.buttonGroup__atx{column-gap:.2rem;display:flex;position:absolute;right:calc(var(--ifm-pre-padding)/2);top:calc(var(--ifm-pre-padding)/2)}.buttonGroup__atx button{align-items:center;border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-global-radius);display:flex;line-height:0;opacity:0;padding:.4rem;transition:opacity var(--ifm-transition-fast) ease-in-out}.buttonGroup__atx button:focus-visible,.buttonGroup__atx button:hover{opacity:1!important}.theme-code-block:hover .buttonGroup__atx button{opacity:.4}:where(:root){--docusaurus-highlighted-code-line-bg:#484d5b}:where([data-theme=dark]){--docusaurus-highlighted-code-line-bg:#646464}.theme-code-block-highlighted-line{background-color:var(--docusaurus-highlighted-code-line-bg);display:block;margin:0 calc(var(--ifm-pre-padding)*-1);padding:0 var(--ifm-pre-padding)}.codeLine_lJS_{counter-increment:a;display:table-row}.codeLineNumber_Tfdd{background:var(--ifm-pre-background);display:table-cell;left:0;overflow-wrap:normal;padding:0 var(--ifm-pre-padding);position:sticky;text-align:right;width:1%}.codeLineNumber_Tfdd:before{content:counter(a);opacity:.4}.codeLineContent_feaV{padding-right:var(--ifm-pre-padding)}.theme-code-block:hover .copyButtonCopied_obH4{opacity:1!important}.copyButtonIcons_eSgA{height:1.125rem;position:relative;width:1.125rem}.copyButtonIcon_y97N,.copyButtonSuccessIcon_LjdS{fill:currentColor;height:inherit;left:0;opacity:inherit;position:absolute;top:0;transition:all var(--ifm-transition-fast) ease;width:inherit}.copyButtonSuccessIcon_LjdS{color:#00d600;left:50%;opacity:0;top:50%;transform:translate(-50%,-50%) scale(.33)}.copyButtonCopied_obH4 .copyButtonIcon_y97N{opacity:0;transform:scale(.33)}.copyButtonCopied_obH4 .copyButtonSuccessIcon_LjdS{opacity:1;transform:translate(-50%,-50%) scale(1);transition-delay:75ms}.wordWrapButtonIcon_Bwma{height:1.2rem;width:1.2rem}.details_lb9f{--docusaurus-details-summary-arrow-size:0.38rem;--docusaurus-details-transition:transform 200ms ease;--docusaurus-details-decoration-color:grey}.details_lb9f>summary{cursor:pointer;padding-left:1rem;position:relative}.details_lb9f>summary::-webkit-details-marker{display:none}.details_lb9f>summary:before{border-color:#0000 #0000 #0000 var(--docusaurus-details-decoration-color);border-style:solid;border-width:var(--docusaurus-details-summary-arrow-size);content:"";left:0;position:absolute;top:.45rem;transform:rotate(0);transform-origin:calc(var(--docusaurus-details-summary-arrow-size)/2) 50%;transition:var(--docusaurus-details-transition)}.collapsibleContent_i85q{border-top:1px solid var(--docusaurus-details-decoration-color);margin-top:1rem;padding-top:1rem}.details_b_Ee{--docusaurus-details-decoration-color:var(--ifm-alert-border-color);--docusaurus-details-transition:transform var(--ifm-transition-fast) ease;border:1px solid var(--ifm-alert-border-color);margin:0 0 var(--ifm-spacing-vertical)}.img_ev3q{height:auto}.admonition_LlT9{margin-bottom:1em}.admonitionHeading_tbUL{font:var(--ifm-heading-font-weight) var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family);margin-bottom:.3rem}.admonitionHeading_tbUL code{text-transform:none}.admonitionIcon_kALy{display:inline-block;margin-right:.4em;vertical-align:middle}.admonitionIcon_kALy svg{fill:var(--ifm-alert-foreground-color);display:inline-block;height:1.6em;width:1.6em}.breadcrumbHomeIcon_YNFT{height:1.1rem;position:relative;top:1px;vertical-align:top;width:1.1rem}.breadcrumbsContainer_Z_bl{--ifm-breadcrumb-size-multiplier:0.8;margin-bottom:.8rem}.backToTopButton_sjWU{background-color:var(--ifm-color-emphasis-200);border-radius:50%;bottom:1.3rem;box-shadow:var(--ifm-global-shadow-lw);height:3rem;opacity:0;position:fixed;right:1.3rem;transform:scale(0);transition:all var(--ifm-transition-fast) var(--ifm-transition-timing-default);visibility:hidden;width:3rem;z-index:calc(var(--ifm-z-index-fixed) - 1)}.backToTopButton_sjWU:after{background-color:var(--ifm-color-emphasis-1000);content:" ";display:inline-block;height:100%;-webkit-mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;width:100%}.backToTopButtonShow_xfvO{opacity:1;transform:scale(1);visibility:visible}[data-theme=dark]:root{--docusaurus-collapse-button-bg:#ffffff0d;--docusaurus-collapse-button-bg-hover:#ffffff1a}.collapseSidebarButton_PEFL{display:none;margin:0}.docSidebarContainer_b6E3,.sidebarLogo_isFc{display:none}.docMainContainer_gTbr,.docPage__5DB{display:flex;width:100%}.docPage__5DB{flex:1 0}.docsWrapper_BCFX{display:flex;flex:1 0 auto}.heroBanner_qdFl{overflow:hidden;padding:4rem 0;position:relative;text-align:center}.main-buttons_U_dc{column-gap:15px}.buttons_AeoN{align-items:center;display:flex;justify-content:center}@media (min-width:997px){.collapseSidebarButton_PEFL,.expandButton_m80_{background-color:var(--docusaurus-collapse-button-bg)}.lastUpdated_vwxv{text-align:right}.tocMobile_ITEo{display:none}:root{--docusaurus-announcement-bar-height:30px}.announcementBarClose_gvF7,.announcementBarPlaceholder_vyr4{flex-basis:50px}.searchBox_ZlJk{padding:var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal)}.docItemCol_VOVn{max-width:75%!important}.collapseSidebarButton_PEFL{border:1px solid var(--ifm-toc-border-color);border-radius:0;bottom:0;display:block!important;height:40px;position:sticky}.collapseSidebarButtonIcon_kv0_{margin-top:4px;transform:rotate(180deg)}.expandButtonIcon_BlDH,[dir=rtl] .collapseSidebarButtonIcon_kv0_{transform:rotate(0)}.collapseSidebarButton_PEFL:focus,.collapseSidebarButton_PEFL:hover,.expandButton_m80_:focus,.expandButton_m80_:hover{background-color:var(--docusaurus-collapse-button-bg-hover)}.menuHtmlItem_M9Kj{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu_SIkG{flex-grow:1;padding:.5rem}@supports (scrollbar-gutter:stable){.menu_SIkG{padding:.5rem 0 .5rem .5rem;scrollbar-gutter:stable}}.menuWithAnnouncementBar_GW3s{margin-bottom:var(--docusaurus-announcement-bar-height)}.sidebar_njMd{display:flex;flex-direction:column;height:100%;padding-top:var(--ifm-navbar-height);width:var(--doc-sidebar-width)}.sidebarWithHideableNavbar_wUlq{padding-top:0}.sidebarHidden_VK0M{opacity:0;visibility:hidden}.sidebarLogo_isFc{align-items:center;color:inherit!important;display:flex!important;margin:0 var(--ifm-navbar-padding-horizontal);max-height:var(--ifm-navbar-height);min-height:var(--ifm-navbar-height);text-decoration:none!important}.sidebarLogo_isFc img{height:2rem;margin-right:.5rem}.expandButton_m80_{align-items:center;display:flex;height:100%;justify-content:center;position:absolute;right:0;top:0;transition:background-color var(--ifm-transition-fast) ease;width:100%}[dir=rtl] .expandButtonIcon_BlDH{transform:rotate(180deg)}.docSidebarContainer_b6E3{border-right:1px solid var(--ifm-toc-border-color);-webkit-clip-path:inset(0);clip-path:inset(0);display:block;margin-top:calc(var(--ifm-navbar-height)*-1);transition:width var(--ifm-transition-fast) ease;width:var(--doc-sidebar-width);will-change:width}.docSidebarContainerHidden_b3ry{cursor:pointer;width:var(--doc-sidebar-hidden-width)}.sidebarViewport_Xe31{height:100%;max-height:100vh;position:sticky;top:0}.docMainContainer_gTbr{flex-grow:1;max-width:calc(100% - var(--doc-sidebar-width))}.docMainContainerEnhanced_Uz_u{max-width:calc(100% - var(--doc-sidebar-hidden-width))}.docItemWrapperEnhanced_czyv{max-width:calc(var(--ifm-container-width) + var(--doc-sidebar-width))!important}}@media (min-width:1440px){.container{max-width:var(--ifm-container-width-xl)}}@media only screen and (min-device-width:320px) and (max-device-width:480px) and (-webkit-min-device-pixel-ratio:2){.pill-header{display:none}}@media (max-width:996px){.col{--ifm-col-width:100%;flex-basis:var(--ifm-col-width);margin-left:0}.footer{--ifm-footer-padding-horizontal:0}.colorModeToggle_DEke,.footer__link-separator,.navbar__item,.tableOfContents_bqdL{display:none}.footer__col{margin-bottom:calc(var(--ifm-spacing-vertical)*3)}.footer__link-item{display:block}.hero{padding-left:0;padding-right:0}.navbar>.container,.navbar>.container-fluid{padding:0}.navbar__toggle{display:inherit}.navbar__search-input{width:9rem}.pills--block,.tabs--block{flex-direction:column}.docItemContainer_F8PC{padding:0 .3rem}.searchBox_ZlJk{position:absolute;right:var(--ifm-navbar-padding-horizontal)}}@media screen and (max-width:996px){.heroBanner_qdFl{padding:2rem}}@media (max-width:576px){.markdown h1:first-child{--ifm-h1-font-size:2rem}.markdown>h2{--ifm-h2-font-size:1.5rem}.markdown>h3{--ifm-h3-font-size:1.25rem}}@media (max-width:480px){.main-buttons_U_dc{justify-content:space-around;row-gap:5px}}@media (hover:hover){.backToTopButton_sjWU:hover{background-color:var(--ifm-color-emphasis-300)}}@media (pointer:fine){.thin-scrollbar{scrollbar-width:thin}.thin-scrollbar::-webkit-scrollbar{height:var(--ifm-scrollbar-size);width:var(--ifm-scrollbar-size)}.thin-scrollbar::-webkit-scrollbar-track{background:var(--ifm-scrollbar-track-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb{background:var(--ifm-scrollbar-thumb-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb:hover{background:var(--ifm-scrollbar-thumb-hover-background-color)}}@media (prefers-reduced-motion:reduce){:root{--ifm-transition-fast:0ms;--ifm-transition-slow:0ms}}@media print{.announcementBar_mb4j,.footer,.menu,.navbar,.pagination-nav,.table-of-contents,.tocMobile_ITEo{display:none}.tabs{page-break-inside:avoid}.codeBlockLines_e6Vv{white-space:pre-wrap}} \ No newline at end of file diff --git a/docs/assets/2022-10-graphql-aspnet-execution-diagrams.pdf b/assets/files/2022-10-graphql-aspnet-execution-diagrams-82ed1c157484c18822b05a009f20768d.pdf similarity index 100% rename from docs/assets/2022-10-graphql-aspnet-execution-diagrams.pdf rename to assets/files/2022-10-graphql-aspnet-execution-diagrams-82ed1c157484c18822b05a009f20768d.pdf diff --git a/docs/assets/2022-10-graphql-aspnet-structural-diagrams.pdf b/assets/files/2022-10-graphql-aspnet-structural-diagrams-2ce89a3328b83ac31eb4dac13986550a.pdf similarity index 100% rename from docs/assets/2022-10-graphql-aspnet-structural-diagrams.pdf rename to assets/files/2022-10-graphql-aspnet-structural-diagrams-2ce89a3328b83ac31eb4dac13986550a.pdf diff --git a/docs/assets/2023-01-subscription-server.pdf b/assets/files/2023-01-subscription-server-faaa899feed85da5e9e9e8625af3336f.pdf similarity index 100% rename from docs/assets/2023-01-subscription-server.pdf rename to assets/files/2023-01-subscription-server-faaa899feed85da5e9e9e8625af3336f.pdf diff --git a/docs/assets/benchmarks.png b/assets/images/benchmarks-52662cd730b8125900651edb7101250e.png similarity index 100% rename from docs/assets/benchmarks.png rename to assets/images/benchmarks-52662cd730b8125900651edb7101250e.png diff --git a/docs/assets/console-logger.png b/assets/images/console-logger-292925d3a6989984be2dcf13e62efdcb.png similarity index 100% rename from docs/assets/console-logger.png rename to assets/images/console-logger-292925d3a6989984be2dcf13e62efdcb.png diff --git a/docs/assets/create-new-web-api-project.png b/assets/images/create-new-web-api-project-f0bb6eebc200da5078430ce828d8bc60.png similarity index 100% rename from docs/assets/create-new-web-api-project.png rename to assets/images/create-new-web-api-project-f0bb6eebc200da5078430ce828d8bc60.png diff --git a/docs/assets/ef-core-error.png b/assets/images/ef-core-error-25cba617d4749a01fdb960bac7d221fb.png similarity index 100% rename from docs/assets/ef-core-error.png rename to assets/images/ef-core-error-25cba617d4749a01fdb960bac7d221fb.png diff --git a/docs/assets/how-it-works-1.png b/assets/images/how-it-works-1-8cf26d6348f0ddb7a1e9d22117fa3045.png similarity index 100% rename from docs/assets/how-it-works-1.png rename to assets/images/how-it-works-1-8cf26d6348f0ddb7a1e9d22117fa3045.png diff --git a/docs/assets/overview-sample-query-results.png b/assets/images/overview-sample-query-results-1425298ea533b77850b7c923c8d97983.png similarity index 100% rename from docs/assets/overview-sample-query-results.png rename to assets/images/overview-sample-query-results-1425298ea533b77850b7c923c8d97983.png diff --git a/assets/js/010df87a.3af7c1fe.js b/assets/js/010df87a.3af7c1fe.js new file mode 100644 index 0000000..5e41342 --- /dev/null +++ b/assets/js/010df87a.3af7c1fe.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[612],{5680:(e,t,a)=>{a.d(t,{xA:()=>u,yg:()=>d});var r=a(6540);function n(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function l(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,r)}return a}function i(e){for(var t=1;t=0||(n[a]=e[a]);return n}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(n[a]=e[a])}return n}var s=r.createContext({}),g=function(e){var t=r.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):i(i({},t),e)),a},u=function(e){var t=g(e.components);return r.createElement(s.Provider,{value:t},e.children)},p="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var a=e.components,n=e.mdxType,l=e.originalType,s=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),p=g(a),m=n,d=p["".concat(s,".").concat(m)]||p[m]||c[m]||l;return a?r.createElement(d,i(i({ref:t},u),{},{components:a})):r.createElement(d,i({ref:t},u))}));function d(e,t){var a=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var l=a.length,i=new Array(l);i[0]=m;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[p]="string"==typeof e?e:n,i[1]=o;for(var g=2;g{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>p,frontMatter:()=>l,metadata:()=>o,toc:()=>g});var r=a(8168),n=(a(6540),a(5680));const l={id:"performance",title:"Benchmarks & Performance",sidebar_label:"Benchmarks",sidebar_position:10},i=void 0,o={unversionedId:"reference/performance",id:"reference/performance",title:"Benchmarks & Performance",description:"Query Benchmarking",source:"@site/docs/reference/benchmarks.md",sourceDirName:"reference",slug:"/reference/performance",permalink:"/docs/reference/performance",draft:!1,tags:[],version:"current",sidebarPosition:10,frontMatter:{id:"performance",title:"Benchmarks & Performance",sidebar_label:"Benchmarks",sidebar_position:10},sidebar:"tutorialSidebar",previous:{title:"Demo Projects",permalink:"/docs/reference/demo-projects"},next:{title:"Vocabulary",permalink:"/docs/reference/vocabulary"}},s={},g=[{value:"Query Benchmarking",id:"query-benchmarking",level:2},{value:"Performance Testing",id:"performance-testing",level:2},{value:"Test Configuration and Specs",id:"test-configuration-and-specs",level:3},{value:"GraphQL ASP.NET Server:",id:"graphql-aspnet-server",level:4},{value:"Memory Profiling Load:",id:"memory-profiling-load",level:4},{value:"GraphQL Load:",id:"graphql-load",level:4},{value:"Subscription Client Load:",id:"subscription-client-load",level:4},{value:"REST Load:",id:"rest-load",level:4},{value:"GraphQL Max Throughput Load:",id:"graphql-max-throughput-load",level:4},{value:"Memory Profile Test",id:"memory-profile-test",level:3},{value:"General Usage Load Test",id:"general-usage-load-test",level:3},{value:"Max Throughput Test",id:"max-throughput-test",level:3},{value:"Subscription Event Load Test",id:"subscription-event-load-test",level:3}],u={toc:g};function p(e){let{components:t,...l}=e;return(0,n.yg)("wrapper",(0,r.A)({},u,l,{components:t,mdxType:"MDXLayout"}),(0,n.yg)("h2",{id:"query-benchmarking"},"Query Benchmarking"),(0,n.yg)("p",null,"GraphQL ASP.NET is designed to be fast. For our benchmarks, we are tracking a number of query types which measure performance via the various paths through the library; multi controller queries, queries with variables etc. These are executed against an in-memory data store without an attached database. "),(0,n.yg)("p",null,"The goal of our benchmarks is to measure the library's abiliy to process a query in isolation, to perform the query and execute user code; not how long an action method takes to query a database for data and send a request over the wire. Obviously, real world workloads are going to be slower than these theoretical values, but the faster we can make the benchmarks the faster all other scenarios will be."),(0,n.yg)("p",null,"As you can see all query types execute in sub-millisecond timeframes. If there is a specific query type or scenario that you are seeing a significant performance degregation with please open an issue on github and let us know! "),(0,n.yg)("p",null,(0,n.yg)("img",{alt:"benchmarks",src:a(3614).A,width:"1681",height:"880"}),"\n",(0,n.yg)("strong",{parentName:"p"},"Last Updated 2022-12-01; v0.14.0-beta")),(0,n.yg)("admonition",{title:"Your Milage May Vary",type:"note"},(0,n.yg)("p",{parentName:"admonition"}," Performance will vary depending on your hardware and environment conditions. You can execute your own test run via the bench marking solution located at ",(0,n.yg)("inlineCode",{parentName:"p"},"./src/graphql-aspnet-benchmarks.sln")," ")),(0,n.yg)("h2",{id:"performance-testing"},"Performance Testing"),(0,n.yg)("admonition",{title:"Run Your Own Performance Tests",type:"caution"},(0,n.yg)("p",{parentName:"admonition"},"These performance tests are not intended to be used as data points when determining scaling requirements for your own production workloads. Your use cases will be different and effected by factors not present in our lab environment (e.g. database connections, service orchrestration, business logic etc.). Be sure to execute your own load tests using queries indicative of your expected user base and act accordingly.")),(0,n.yg)("p",null,'We periodically execute tests against the library to measure throughput and stability, for a single server instance, under load. Our goal is to measure the theoretical limits in a multi-user, "production like" scenario.'),(0,n.yg)("p",null,"These tests are executed in a controlled setting with the following conditions:"),(0,n.yg)("h3",{id:"test-configuration-and-specs"},"Test Configuration and Specs"),(0,n.yg)("h4",{id:"graphql-aspnet-server"},"GraphQL ASP.NET Server:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"source code: ",(0,n.yg)("inlineCode",{parentName:"li"},"./src/ancillary-projects/benchmarking/graphql-aspnet-load-server/")),(0,n.yg)("li",{parentName:"ul"},".NET 7 Runtime"),(0,n.yg)("li",{parentName:"ul"},"Garbage collection executing in ",(0,n.yg)("a",{parentName:"li",href:"https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector#workstation-vs-server"},"server mode")),(0,n.yg)("li",{parentName:"ul"},"Local area, wired, gigabit network"),(0,n.yg)("li",{parentName:"ul"},"Simple queries to fetch or mutate a single, in-memory object")),(0,n.yg)("h4",{id:"memory-profiling-load"},"Memory Profiling Load:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"JMeter script: ",(0,n.yg)("inlineCode",{parentName:"li"},"graphql-memory-profiling.jmx")),(0,n.yg)("li",{parentName:"ul"},"15 concurrent users executing a graphql query to fetch a single object"),(0,n.yg)("li",{parentName:"ul"},"Each user executes 10,000 requests at most 30ms apart")),(0,n.yg)("h4",{id:"graphql-load"},"GraphQL Load:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"JMeter script: ",(0,n.yg)("inlineCode",{parentName:"li"},"graphql-load-generator.jmx")),(0,n.yg)("li",{parentName:"ul"},"300 concurrent users executing a graphql query to fetch a single object"),(0,n.yg)("li",{parentName:"ul"},"300 concurrent users executing a graphql mutation (and raising a subscription event)"),(0,n.yg)("li",{parentName:"ul"},"Each user executes 10,000 requests at most 30ms apart")),(0,n.yg)("h4",{id:"subscription-client-load"},"Subscription Client Load:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"Source Code: ",(0,n.yg)("inlineCode",{parentName:"li"},"~coming soon~")),(0,n.yg)("li",{parentName:"ul"},"A custom console app that registers subscribers to receive subscription events generated via the Load Generating Workstation mutations"),(0,n.yg)("li",{parentName:"ul"},"500 connected subscription clients"),(0,n.yg)("li",{parentName:"ul"},"2 registered subscription per client (1000 total client subscriptions)")),(0,n.yg)("h4",{id:"rest-load"},"REST Load:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"JMeter script: ",(0,n.yg)("inlineCode",{parentName:"li"},"rest-load-generator.jmx")),(0,n.yg)("li",{parentName:"ul"},"300 concurrent users executing a REST Query that fetches a single object"),(0,n.yg)("li",{parentName:"ul"},"300 concurrent users executing a REST Query that mutates and returns a single object"),(0,n.yg)("li",{parentName:"ul"},"Each user executes 10,000 requests at most 15ms apart"),(0,n.yg)("li",{parentName:"ul"},"This workload acts as a control to compare performance of the baseline web api against the overhead of the graphql library")),(0,n.yg)("h4",{id:"graphql-max-throughput-load"},"GraphQL Max Throughput Load:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"JMeter script: ",(0,n.yg)("inlineCode",{parentName:"li"},"graphql-max-load-generator.jmx")),(0,n.yg)("li",{parentName:"ul"},"Starts 20 new users each second until failure"),(0,n.yg)("li",{parentName:"ul"},"Each user executes a query every 0-15ms until failure")),(0,n.yg)("h3",{id:"memory-profile-test"},"Memory Profile Test"),(0,n.yg)("p",null,"A qualitative test executed with the server instance running in release mode, harnessed via ",(0,n.yg)("inlineCode",{parentName:"p"},"dotMemory"),". Given the artificial environment restrictions this imposes its difficult to pin down exact KPIs but in general this test is used to monitor:"),(0,n.yg)("table",null,(0,n.yg)("thead",{parentName:"table"},(0,n.yg)("tr",{parentName:"thead"},(0,n.yg)("th",{parentName:"tr",align:null},"Metric"),(0,n.yg)("th",{parentName:"tr",align:null},"Expectations"))),(0,n.yg)("tbody",{parentName:"table"},(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null},"Memory Allocation"),(0,n.yg)("td",{parentName:"tr",align:null},"Expect to see steady Gen0 allocation over time, with no extreme spikes.")),(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null},"Object Survival"),(0,n.yg)("td",{parentName:"tr",align:null},"Expect to see little to no objects surviving to Gen1 and Gen2 heap collections per GC cycle. When the test completes, the server returns to a steady state of memory usage prior to the test beginning.")))),(0,n.yg)("admonition",{title:"Goal",type:"info"},(0,n.yg)("p",{parentName:"admonition"}," The aim of this test is to ensure acceptable memory pressure and GC cycles on the server instance in a controlled usage scenario and ensure no memory leaks occur. ")),(0,n.yg)("p",null,(0,n.yg)("strong",{parentName:"p"},"Results")),(0,n.yg)("table",null,(0,n.yg)("thead",{parentName:"table"},(0,n.yg)("tr",{parentName:"thead"},(0,n.yg)("th",{parentName:"tr",align:null},"Date"),(0,n.yg)("th",{parentName:"tr",align:null},"Version"),(0,n.yg)("th",{parentName:"tr",align:null}))),(0,n.yg)("tbody",{parentName:"table"},(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null},"2022-11-30"),(0,n.yg)("td",{parentName:"tr",align:null},"v0.13.1-beta"),(0,n.yg)("td",{parentName:"tr",align:null},"Execution is consistant. While no objects make it to Gen1 or Gen2 a GC cycle occurs about every 20 seconds.")),(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null},"2022-11-30"),(0,n.yg)("td",{parentName:"tr",align:null},"v0.14.0-beta"),(0,n.yg)("td",{parentName:"tr",align:null},"Similar results to the v0.13.1-beta test in terms of generational memory allocations. There is a notable decrease in memory pressure. Time between GC cycles has improved to once every 33 seconds; a 65% increase in duration.")))),(0,n.yg)("h3",{id:"general-usage-load-test"},"General Usage Load Test"),(0,n.yg)("p",null,"A test with the server executing in release mode, WITHOUT the subscription server attached and with monitoring via passive ",(0,n.yg)("inlineCode",{parentName:"p"},"dotnet-counters"),". This test measures the throughput of queries and mutations through the runtime as well as the load those queries place on the server CPU. Using GraphQL (as opposed to REST) will generate some additional overhead to parse and execute the query on top of the REST request which invokes it. As a result, the metrics of this test are expressed in terms of % increases over a comperable REST workload via a baseline ASP.NET web api controller. "),(0,n.yg)("table",null,(0,n.yg)("thead",{parentName:"table"},(0,n.yg)("tr",{parentName:"thead"},(0,n.yg)("th",{parentName:"tr",align:null},"Metric"),(0,n.yg)("th",{parentName:"tr",align:null},"Expectations"))),(0,n.yg)("tbody",{parentName:"table"},(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null},"CPU Utilization"),(0,n.yg)("td",{parentName:"tr",align:null},"Using ",(0,n.yg)("a",{parentName:"td",href:"https://learn.microsoft.com/en-us/sysinternals/downloads/process-explorer"},"Process Explorer")," to measure utilization, no more than a 5% increase when compared to the REST control load.")),(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null},"GC % Time"),(0,n.yg)("td",{parentName:"tr",align:null},"Using the metrics obtained via ",(0,n.yg)("a",{parentName:"td",href:"https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters"},"dotnet-counters")," expect that the GC % time is within 1% of the REST control load")),(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null},"Throughput (req/sec)"),(0,n.yg)("td",{parentName:"tr",align:null},"Throughput, measured in requests per second, is within 10% of the peak load generated via the REST control load")))),(0,n.yg)("admonition",{title:"Goal",type:"info"},(0,n.yg)("p",{parentName:"admonition"}," The aim of this test is to ensure adequate single instance throughput and that the overhead for using graphql on top of web api is kept to a minimum.")),(0,n.yg)("p",null,(0,n.yg)("strong",{parentName:"p"},"Results")),(0,n.yg)("table",null,(0,n.yg)("thead",{parentName:"table"},(0,n.yg)("tr",{parentName:"thead"},(0,n.yg)("th",{parentName:"tr",align:null},"Date"),(0,n.yg)("th",{parentName:"tr",align:null},"Version"),(0,n.yg)("th",{parentName:"tr",align:null},"Metric"),(0,n.yg)("th",{parentName:"tr",align:null},"REST Workload"),(0,n.yg)("th",{parentName:"tr",align:null},"GraphQL Workload"),(0,n.yg)("th",{parentName:"tr",align:null},"Variance"))),(0,n.yg)("tbody",{parentName:"table"},(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null},"2022-11-30"),(0,n.yg)("td",{parentName:"tr",align:null},"v0.13.1-beta"),(0,n.yg)("td",{parentName:"tr",align:null},"CPU Utilization"),(0,n.yg)("td",{parentName:"tr",align:null},"0.01-2%"),(0,n.yg)("td",{parentName:"tr",align:null},"2-9%"),(0,n.yg)("td",{parentName:"tr",align:null},(0,n.yg)("span",{style:{color:"red"}}," +7% "))),(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null}),(0,n.yg)("td",{parentName:"tr",align:null}),(0,n.yg)("td",{parentName:"tr",align:null},"GC % Time"),(0,n.yg)("td",{parentName:"tr",align:null},"< 1%"),(0,n.yg)("td",{parentName:"tr",align:null},"2-4%"),(0,n.yg)("td",{parentName:"tr",align:null},(0,n.yg)("span",{style:{color:"red"}},"+3% "))),(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null}),(0,n.yg)("td",{parentName:"tr",align:null}),(0,n.yg)("td",{parentName:"tr",align:null},"Throughput"),(0,n.yg)("td",{parentName:"tr",align:null},"37,490 req/sec"),(0,n.yg)("td",{parentName:"tr",align:null},"29,830 req/sec"),(0,n.yg)("td",{parentName:"tr",align:null},(0,n.yg)("span",{style:{color:"red"}},"-8k (-20%) "))),(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null},"2022-12-1"),(0,n.yg)("td",{parentName:"tr",align:null},"v0.14.0-beta"),(0,n.yg)("td",{parentName:"tr",align:null},"CPU Utilization"),(0,n.yg)("td",{parentName:"tr",align:null},"0.01-2%"),(0,n.yg)("td",{parentName:"tr",align:null},"1-3%"),(0,n.yg)("td",{parentName:"tr",align:null},(0,n.yg)("span",{style:{color:"green"}}," +1% "))),(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null}),(0,n.yg)("td",{parentName:"tr",align:null}),(0,n.yg)("td",{parentName:"tr",align:null},"GC % Time"),(0,n.yg)("td",{parentName:"tr",align:null},"< 1%"),(0,n.yg)("td",{parentName:"tr",align:null},"< 1%"),(0,n.yg)("td",{parentName:"tr",align:null},(0,n.yg)("span",{style:{color:"green"}},"+0% "))),(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null}),(0,n.yg)("td",{parentName:"tr",align:null}),(0,n.yg)("td",{parentName:"tr",align:null},"Throughput"),(0,n.yg)("td",{parentName:"tr",align:null},"37,360 req/sec"),(0,n.yg)("td",{parentName:"tr",align:null},"36,509 req/sec"),(0,n.yg)("td",{parentName:"tr",align:null},(0,n.yg)("span",{style:{color:"green"}},"-2k (-2.3%)"))))),(0,n.yg)("h3",{id:"max-throughput-test"},"Max Throughput Test"),(0,n.yg)("p",null,"A simple test flooding the server with an ever-increasing amount of traffic until it begins to deny requests or fails completely. The throughput of each loading client is summed just prior to failure and recorded as the max throughput."),(0,n.yg)("p",null,(0,n.yg)("strong",{parentName:"p"},"Results")),(0,n.yg)("table",null,(0,n.yg)("thead",{parentName:"table"},(0,n.yg)("tr",{parentName:"thead"},(0,n.yg)("th",{parentName:"tr",align:null},"Date"),(0,n.yg)("th",{parentName:"tr",align:null},"Version"),(0,n.yg)("th",{parentName:"tr",align:null},"Metric"),(0,n.yg)("th",{parentName:"tr",align:null},"GraphQL Query"))),(0,n.yg)("tbody",{parentName:"table"},(0,n.yg)("tr",{parentName:"tbody"},(0,n.yg)("td",{parentName:"tr",align:null},"2022-12-1"),(0,n.yg)("td",{parentName:"tr",align:null},"v0.14.0-beta"),(0,n.yg)("td",{parentName:"tr",align:null},"Max Throughput"),(0,n.yg)("td",{parentName:"tr",align:null},(0,n.yg)("span",{style:{color:"cyan"}}," 57,919 req/sec * "))))),(0,n.yg)("p",null,"*"," We ran out of client machines and could not generate any more load against the test server. At the time, the server process indicated 5% CPU utilization and less than 750mb of committed memory."),(0,n.yg)("h3",{id:"subscription-event-load-test"},"Subscription Event Load Test"),(0,n.yg)("p",null,(0,n.yg)("em",{parentName:"p"},"Coming Soon")))}p.isMDXComponent=!0},3614:(e,t,a)=>{a.d(t,{A:()=>r});const r=a.p+"assets/images/benchmarks-52662cd730b8125900651edb7101250e.png"}}]); \ No newline at end of file diff --git a/assets/js/0536d272.aaf8898e.js b/assets/js/0536d272.aaf8898e.js new file mode 100644 index 0000000..023ac58 --- /dev/null +++ b/assets/js/0536d272.aaf8898e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[2692],{5680:(e,t,n)=>{n.d(t,{xA:()=>d,yg:()=>g});var r=n(6540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),u=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},d=function(e){var t=u(e.components);return r.createElement(s.Provider,{value:t},e.children)},c="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},y=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),c=u(n),y=a,g=c["".concat(s,".").concat(y)]||c[y]||p[y]||o;return n?r.createElement(g,i(i({ref:t},d),{},{components:n})):r.createElement(g,i({ref:t},d))}));function g(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,i=new Array(o);i[0]=y;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[c]="string"==typeof e?e:a,i[1]=l;for(var u=2;u{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var r=n(8168),a=(n(6540),n(5680));const o={id:"unit-testing",title:"Unit Testing",sidebar_label:"Unit Testing",sidebar_position:1},i=void 0,l={unversionedId:"development/unit-testing",id:"development/unit-testing",title:"Unit Testing",description:".NET 8+",source:"@site/docs/development/unit-testing.md",sourceDirName:"development",slug:"/development/unit-testing",permalink:"/docs/development/unit-testing",draft:!1,tags:[],version:"current",sidebarPosition:1,frontMatter:{id:"unit-testing",title:"Unit Testing",sidebar_label:"Unit Testing",sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Debugging",permalink:"/docs/development/debugging"},next:{title:"Entity Framework",permalink:"/docs/development/entity-framework"}},s={},u=[{value:"Install the Framework",id:"install-the-framework",level:2},{value:"Create a Test Server",id:"create-a-test-server",level:2},{value:"Execute a Query",id:"execute-a-query",level:2},{value:"Other Test Scenarios",id:"other-test-scenarios",level:2},{value:"Throwing Exceptions",id:"throwing-exceptions",level:3},{value:"Authn & Authz",id:"authn--authz",level:3},{value:"Demo project",id:"demo-project",level:2}],d={toc:u};function c(e){let{components:t,...n}=e;return(0,a.yg)("wrapper",(0,r.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("span",{className:"pill"},".NET 8+"),(0,a.yg)("br",null),(0,a.yg)("br",null),(0,a.yg)("admonition",{type:"info"},(0,a.yg)("p",{parentName:"admonition"},"Your test projects must target .NET 8 or greater to use the test framework")),(0,a.yg)("p",null,"GraphQL ASP.NET has more than ",(0,a.yg)("inlineCode",{parentName:"p"},"3500 unit tests and 91% code coverage"),". All the internal integration tests are powered by a framework designed to quickly build a configurable, fully mocked server instance to perform a query against the runtime. It may be helpful to use and extend the framework to test your own controllers."),(0,a.yg)("p",null,"This document explains how to perform some common test functions for your own controller methods."),(0,a.yg)("h2",{id:"install-the-framework"},"Install the Framework"),(0,a.yg)("p",null,"Add a reference to the ",(0,a.yg)("a",{parentName:"p",href:"https://www.nuget.org/packages/GraphQL.AspNet.TestFramework"},"Nuget Package")," ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL.AspNet.TestFramework")," to your unit test project. The framework is just a class library and not dependent on any individual testing framework like NUnit or XUnit. However, it does mock some runtime only objects and, as a result, is dependent on ",(0,a.yg)("a",{parentName:"p",href:"https://www.nuget.org/packages/Moq"},"Moq"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-powershell",metastring:'title="Install the Test Framework"',title:'"Install',the:!0,Test:!0,'Framework"':!0},"# Using the dotnet CLI\n> dotnet add package GraphQL.AspNet.TestFramework\n\n# Using Package Manager Console\n> Install-Package GraphQL.AspNet.TestFramework\n")),(0,a.yg)("h2",{id:"create-a-test-server"},"Create a Test Server"),(0,a.yg)("ol",null,(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("p",{parentName:"li"},"Create a new instance of the ",(0,a.yg)("inlineCode",{parentName:"p"},"TestServerBuilder"),". The builder takes in an optional set of flags to perform some auto configurations for common scenarios such as exposing exceptions or altering the casing of graph type names.")),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("p",{parentName:"li"},"Configure your test scenario"),(0,a.yg)("ul",{parentName:"li"},(0,a.yg)("li",{parentName:"ul"},"Use ",(0,a.yg)("inlineCode",{parentName:"li"},".User")," to add any permissions to the mocked user account"),(0,a.yg)("li",{parentName:"ul"},"Use ",(0,a.yg)("inlineCode",{parentName:"li"},".Authorization")," to add any security policy definitions if you wish to test security"),(0,a.yg)("li",{parentName:"ul"},"Use ",(0,a.yg)("inlineCode",{parentName:"li"},".AddGraphQL()")," to mimic the functionality of schema configuration used when your application starts."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"TestServerBuilder")," implements ",(0,a.yg)("inlineCode",{parentName:"li"},"IServiceCollection"),". Add any additional mocked services as needed to ensure your controllers are wired up correctly by the runtime."))),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("p",{parentName:"li"},"Build the server instance using ",(0,a.yg)("inlineCode",{parentName:"p"},".Build()")))),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Configuring a Test Server Instance"',title:'"Configuring',a:!0,Test:!0,Server:!0,'Instance"':!0},"[Test]\npublic async Task MyController_InvocationTest()\n{\n // Arrange\n var builder = new TestServerBuilder();\n builder.AddGraphQL(o => {\n o.AddController();\n });\n\n // Act\n var server = builder.Build();\n\n // continued...\n}\n\n")),(0,a.yg)("admonition",{title:"Custom Schemas",type:"tip"},(0,a.yg)("p",{parentName:"admonition"},"Use ",(0,a.yg)("inlineCode",{parentName:"p"},"TestServerBuild")," to test against a custom defined schema instance.")),(0,a.yg)("h2",{id:"execute-a-query"},"Execute a Query"),(0,a.yg)("p",null,"Follow these steps to execute a query against the runtime. If your controller is registered to the test server and the appropriate field is requested in the query, it will be invoked."),(0,a.yg)("ol",null,(0,a.yg)("li",{parentName:"ol"},"Create a builder to generate a mocked execution context (the object that the runtime acts on) using ",(0,a.yg)("inlineCode",{parentName:"li"},".CreateQueryContextBuilder()")),(0,a.yg)("li",{parentName:"ol"},"Configure the query text, variables etc. on the builder."),(0,a.yg)("li",{parentName:"ol"},"Build the context and submit it for processing:",(0,a.yg)("ul",{parentName:"li"},(0,a.yg)("li",{parentName:"ul"},"Use ",(0,a.yg)("inlineCode",{parentName:"li"},"server.ExecuteQuery()")," to process the context. ",(0,a.yg)("inlineCode",{parentName:"li"},"context.Result")," will be filled with the final ",(0,a.yg)("inlineCode",{parentName:"li"},"IQueryExecutionResult")," which can be inspected for resultant data fields and error messages."),(0,a.yg)("li",{parentName:"ul"},"Use ",(0,a.yg)("inlineCode",{parentName:"li"},"server.RenderResult()")," to generate the json string a client would recieve if they performed the query.")))),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Executing a Test Query"',title:'"Executing',a:!0,Test:!0,'Query"':!0},'[Test]\npublic async Task MyController_InvocationTest()\n{ \n // Arrange\n var builder = new TestServerBuilder();\n builder.AddGraphQL(o => {\n o.AddController();\n });\n\n var server = builder.Build();\n var queryBuilder = server.CreateQueryContextBuilder();\n queryBuilder.AddQueryText("query { controller { actionMethod { property1 } } }");\n\n var queryContext = queryBuilder.Build();\n\n // Act\n var result = await server.RenderResult(queryContext);\n\n /* result contains the string for:\n {\n "data" : {\n "controller": {\n "actionMethod" : {\n "property1" : "value1"\n }\n }\n }\n }\n */\n}\n')),(0,a.yg)("h2",{id:"other-test-scenarios"},"Other Test Scenarios"),(0,a.yg)("h3",{id:"throwing-exceptions"},"Throwing Exceptions"),(0,a.yg)("p",null,"If you need to test that your controller throws an appropriate exception you can inspect the response object (instead of rendering a result). The exception will be attached to an error message generated during the query execution."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Testing for Thrown Exceptions"',title:'"Testing',for:!0,Thrown:!0,'Exceptions"':!0},'[Test]\npublic async Task MyController_InvocationTest()\n{ \n // Arrange\n var builder = new TestServerBuilder();\n builder.AddGraphQL(o => {\n o.AddController();\n });\n\n var server = builder.Build();\n var queryBuilder = server.CreateQueryContextBuilder();\n queryBuilder.AddQueryText("query { controller { actionMethod { property1 } } }");\n\n var queryContext = queryBuilder.Build();\n\n // Act\n // Use ExecuteQuery instead of RenderResult to obtain the response object\n // highlight-next-line\n var result = await server.ExecuteQuery(queryContext);\n\n // Assert\n // ensure a message was captured\n Assert.IsNotNull(result); \n Assert.AreEqual(1, result.Messages.Count);\n Assert.IsInstanceOf(typeof(MyException), result.Messages[0].Exception);\n}\n')),(0,a.yg)("admonition",{type:"tip"},(0,a.yg)("p",{parentName:"admonition"},"Use ",(0,a.yg)("inlineCode",{parentName:"p"},"server.ExecuteQuery")," to obtain a reference to the response object. This allows you to interrogate the message data before its rendered as a json document.")),(0,a.yg)("h3",{id:"authn--authz"},"Authn & Authz"),(0,a.yg)("p",null,"Test authentication and authorization scenarios by configuring both the policies the server should support and the claims or roles the user will present during the test."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp"},'[Test]\npublic async Task WhenUserHasPolicy_ThenAllowExecution()\n{ \n // Arrange\n var builder = new TestServerBuilder();\n builder.AddGraphQL(o => {\n o.AddController();\n });\n\n // configure the policies the server will recognize\n // and the claims the user context will have.\n // This specific test assumes that the controller method \n // defines an authorization requirement of "policy1".\n // highlight-start\n builder.Authorization.AddClaimPolicy("policy1", "myClaimType", "myClaimValue");\n builder.UserContext.AddUserClaim("myClaimType", "myClaimValue")\n // highlight-end\n\n var server = builder.Build();\n var queryBuilder = server.CreateQueryContextBuilder();\n queryBuilder.AddQueryText("query { controller { actionMethod { property1 } } }");\n\n var queryContext = queryBuilder.Build();\n\n // Act\n var result = await server.RenderResult(queryContext);\n\n // Assert\n // ....\n}\n')),(0,a.yg)("p",null,"The user context is always injected when you run a query on the test server. By default it is an anonymous user and credentials are applied when you add a claim or policy to the context during setup. "),(0,a.yg)("h2",{id:"demo-project"},"Demo project"),(0,a.yg)("p",null,"See the ",(0,a.yg)("a",{parentName:"p",href:"/docs/reference/demo-projects"},"demos page")," for a working demo using XUnit."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/05cffa07.53c5d558.js b/assets/js/05cffa07.53c5d558.js new file mode 100644 index 0000000..239f8df --- /dev/null +++ b/assets/js/05cffa07.53c5d558.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[657],{5680:(e,t,n)=>{n.d(t,{xA:()=>u,yg:()=>m});var r=n(6540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),p=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},u=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},y=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,s=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),d=p(n),y=a,m=d["".concat(s,".").concat(y)]||d[y]||c[y]||i;return n?r.createElement(m,l(l({ref:t},u),{},{components:n})):r.createElement(m,l({ref:t},u))}));function m(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,l=new Array(i);l[0]=y;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[d]="string"==typeof e?e:a,l[1]=o;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var r=n(8168),a=(n(6540),n(5680));const i={id:"type-expressions",title:"Type Expressions",sidebar_label:"Type Expressions",sidebar_position:1},l=void 0,o={unversionedId:"advanced/type-expressions",id:"advanced/type-expressions",title:"Type Expressions",description:"The GraphQL specification states that when a field resolves a value that doesn't conform to the expected type expression of the field that the value is rejected, converted to null and an error added to the response.",source:"@site/docs/advanced/type-expressions.md",sourceDirName:"advanced",slug:"/advanced/type-expressions",permalink:"/docs/advanced/type-expressions",draft:!1,tags:[],version:"current",sidebarPosition:1,frontMatter:{id:"type-expressions",title:"Type Expressions",sidebar_label:"Type Expressions",sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Subscriptions",permalink:"/docs/advanced/subscriptions"},next:{title:"Directives",permalink:"/docs/advanced/directives"}},s={},p=[{value:"Field Type Expressions",id:"field-type-expressions",level:2},{value:"Input Argument Type Expressions",id:"input-argument-type-expressions",level:2},{value:"Runtime Type Validation",id:"runtime-type-validation",level:2}],u={toc:p};function d(e){let{components:t,...n}=e;return(0,a.yg)("wrapper",(0,r.A)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"The GraphQL specification states that when a field resolves a value that doesn't conform to the expected type expression of the field that the value is rejected, converted to null and an error added to the response."),(0,a.yg)("p",null,"When the library builds a schema it makes as few assumptions as possible about the data returned from your fields to result in as few errors as possible."),(0,a.yg)("p",null,"These assumptions are:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Fields that return reference types ",(0,a.yg)("strong",{parentName:"li"},"can be")," null"),(0,a.yg)("li",{parentName:"ul"},"Fields that return primatives or value types (including structs) ",(0,a.yg)("strong",{parentName:"li"},"cannot be")," null"),(0,a.yg)("li",{parentName:"ul"},"Fields that return Nullable primatives or value types (e.g. ",(0,a.yg)("inlineCode",{parentName:"li"},"int?"),") ",(0,a.yg)("strong",{parentName:"li"},"can be")," be null."),(0,a.yg)("li",{parentName:"ul"},"When a field returns an object that implements ",(0,a.yg)("inlineCode",{parentName:"li"},"IEnumerable"),' it will be presented to GraphQL as a "list of ',(0,a.yg)("inlineCode",{parentName:"li"},"TType"),'".')),(0,a.yg)("p",null,"Basically, if your method is able to return a value...then its valid as far as GraphQL is concerned."),(0,a.yg)("p",null,"Lets look at an example:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs"',title:'"BakeryController.cs"'},'[GraphRoute("bakery")]\npublic class BakeryController : GraphController\n{\n [Query("donut")]\n public Donut RetrieveDonut(int id)\n {/*...*/}\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query {\n bakery {\n donut(id: 15){\n name\n flavor\n }\n }\n}\n")),(0,a.yg)("p",null,"Assuming ",(0,a.yg)("inlineCode",{parentName:"p"},"Donut")," was a class (a reference type), this action method could return a donut object or ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),". But should the donut field, from a GraphQL perspective, allow a null return value? The code certainly does and the rules above say fields that return a reference type can be null...but that's not what's important. Its ultimately your decision to decide if a \"null donut\" is allowed, not the C# compiler and not the assumptions made by the library."),(0,a.yg)("p",null,"On one hand, if a null value is returned, regardless of it being valid, the ",(0,a.yg)("em",{parentName:"p"},"outcome")," of the field is the same. When we return a null no child fields are processed. On the other hand, if null is not allowed we need to tell someone, let them know its nulled out not because it simply ",(0,a.yg)("em",{parentName:"p"},"is")," null but because a schema violation occurred."),(0,a.yg)("h2",{id:"field-type-expressions"},"Field Type Expressions"),(0,a.yg)("p",null,"You can add more specificity to your fields by using the ",(0,a.yg)("inlineCode",{parentName:"p"},"TypeExpression")," property of the various field declaration attributes."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Example Custom Type Expressions"',title:'"Example',Custom:!0,Type:!0,'Expressions"':!0},'// Declare that a donut MUST be returned (null is invalid)\n// ----\n// Final Schema Syntax: Donut!\n// highlight-next-line\n[Query("donut", TypeExpression = "Type!")]\npublic Donut RetrieveDonut(string id)\n{/*...*/}\n\n\n// Declare that a list must be returned but the elements of the list\n// could be null:\n// valid: [donut1, null, donut2, donut3]\n// valid: []\n// invalid: null\n// ----\n// Final Schema Syntax: [Donut]!\n// highlight-next-line\n[Query("donut", TypeExpression = "[Type]!")]\npublic IEnumerable RetrieveDonut(string id)\n{/*...*/}\n\n\n// Declare that a list must be returned AND the elements of the list\n// must not be null:\n// valid: [donut1, donut2, donut3]\n// valid: []\n// invalid: [donut1, null, donut2]\n// invalid: null\n// ----\n// Final Schema Syntax: [Donut!]!\n// highlight-next-line\n[Query("donut", TypeExpression = "[Type!]!")]\npublic IEnumerable RetrieveDonut(string id)\n{/*...*/}\n')),(0,a.yg)("admonition",{type:"info"},(0,a.yg)("mdxAdmonitionTitle",{parentName:"admonition"},(0,a.yg)("inlineCode",{parentName:"mdxAdmonitionTitle"},"Type")," is a place holder "),(0,a.yg)("p",{parentName:"admonition"},"The type name used in the examples (e.g. ",(0,a.yg)("inlineCode",{parentName:"p"},"Type"),") is arbitrary and can be any valid string. The correct type name for the target field will be used in its place at runtime.")),(0,a.yg)("h2",{id:"input-argument-type-expressions"},"Input Argument Type Expressions"),(0,a.yg)("p",null,"Similar to fields, you can use the ",(0,a.yg)("inlineCode",{parentName:"p"},"TypeExpression")," property on ",(0,a.yg)("inlineCode",{parentName:"p"},"[FromGraphQL]")," to add more specificity to your input arguments."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Type Expression on an Argument"',title:'"Type',Expression:!0,on:!0,an:!0,'Argument"':!0},'// Force the argument "id" to supply a string (it cannot be supplied as null)\n// -----------------\n// Final Type Expression of the \'id\' arg: String!\n[Query]\n// highlight-next-line\npublic Donut RetrieveDonut([FromGraphQL(TypeExpression = "Type!")] string id)\n{/*...*/}\n')),(0,a.yg)("h2",{id:"runtime-type-validation"},"Runtime Type Validation"),(0,a.yg)("p",null,"Note that the library will accept your type string even if it would be impossible, from a C# perspective, to return data that would match."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Data and Type Expression Mismatch"',title:'"Data',and:!0,Type:!0,Expression:!0,'Mismatch"':!0},'// QUERY EXECUTION ERROR\n// GraphQL will attempt to process a single Donut as a list and will fail\n// highlight-next-line\n[Query("donut", TypeExpression ="[Type]")]\npublic Donut RetrieveDonut(string id)\n{/*...*/}\n')),(0,a.yg)("p",null,"When executing a query and resolving a field, should one of your action methods (or even your object properties) not return data conforming to the type expression that's defined for it, GraphQL will reject the data. The value is set to null and an error is registered in the response for the field in question. The runtime will not attempt to resolve any referenced child fields for a rejected value."),(0,a.yg)("p",null,"If the rejected field does not allow nulls, the error is propagated up to its parent, which is then also set to null. If that parent field can't return a null value the error continues up until it reaches a field that can be null or the entire field collection is nulled out. ","[","Spec \xa7 ",(0,a.yg)("a",{parentName:"p",href:"https://graphql.github.io/graphql-spec/October2021/#sec-Errors-and-Non-Nullability"},"6.4.4"),"]"),(0,a.yg)("admonition",{type:"danger"},(0,a.yg)("p",{parentName:"admonition"},"When declared, the runtime will use your ",(0,a.yg)("inlineCode",{parentName:"p"},"TypeExpression")," as law for any field declarations; skipping its internal checks. You can setup a scenario where by you could return data that the runtime could never validate as being correct and GraphQL will happily process it and return an error every time. ")),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},'"With great power comes great responsibility" -Uncle Ben')))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0ee64cde.ebc10e9b.js b/assets/js/0ee64cde.ebc10e9b.js new file mode 100644 index 0000000..bab3686 --- /dev/null +++ b/assets/js/0ee64cde.ebc10e9b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[4209],{5680:(e,t,n)=>{n.d(t,{xA:()=>u,yg:()=>m});var r=n(6540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var c=r.createContext({}),s=function(e){var t=r.useContext(c),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},u=function(e){var t=s(e.components);return r.createElement(c.Provider,{value:t},e.children)},g="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},d=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,c=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),g=s(n),d=a,m=g["".concat(c,".").concat(d)]||g[d]||p[d]||i;return n?r.createElement(m,l(l({ref:t},u),{},{components:n})):r.createElement(m,l({ref:t},u))}));function m(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,l=new Array(i);l[0]=d;var o={};for(var c in t)hasOwnProperty.call(t,c)&&(o[c]=t[c]);o.originalType=e,o[g]="string"==typeof e?e:a,l[1]=o;for(var s=2;s{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>l,default:()=>g,frontMatter:()=>i,metadata:()=>o,toc:()=>s});var r=n(8168),a=(n(6540),n(5680));const i={id:"global-configuration",title:"Global Configuration",sidebar_label:"Global Configuration",sidebar_position:2},l=void 0,o={unversionedId:"reference/global-configuration",id:"reference/global-configuration",title:"Global Configuration",description:"Global configuration settings affect the entire server instance, they are not restricted to a single schema registration. All global settings are optional and define resonable default values. Use these to fine tune your server environment. You should change any global settings BEFORE calling .AddGraphQL().",source:"@site/docs/reference/global-configuration.md",sourceDirName:"reference",slug:"/reference/global-configuration",permalink:"/docs/reference/global-configuration",draft:!1,tags:[],version:"current",sidebarPosition:2,frontMatter:{id:"global-configuration",title:"Global Configuration",sidebar_label:"Global Configuration",sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"Schema Configuration",permalink:"/docs/reference/schema-configuration"},next:{title:"Attributes",permalink:"/docs/reference/attributes"}},c={},s=[{value:"General",id:"general",level:2},{value:"ControllerServiceLifetime",id:"controllerservicelifetime",level:3},{value:"Subscriptions",id:"subscriptions",level:2},{value:"MaxConcurrentReceiverCount",id:"maxconcurrentreceivercount",level:3},{value:"MaxConnectedClientCount",id:"maxconnectedclientcount",level:3}],u={toc:s};function g(e){let{components:t,...n}=e;return(0,a.yg)("wrapper",(0,r.A)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"Global configuration settings affect the entire server instance, they are not restricted to a single schema registration. All global settings are optional and define resonable default values. Use these to fine tune your server environment. You should change any global settings BEFORE calling ",(0,a.yg)("inlineCode",{parentName:"p"},".AddGraphQL()"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Adding Schema Configuration Options"',title:'"Adding',Schema:!0,Configuration:!0,'Options"':!0},"// -------------------\n// Configure Global Options before calling AddGraphQL\n// -------------------\n\nservices.AddGraphQL();\n")),(0,a.yg)("h2",{id:"general"},"General"),(0,a.yg)("h3",{id:"controllerservicelifetime"},"ControllerServiceLifetime"),(0,a.yg)("p",null,"The configured service lifetime that all discovered controllers and directives will be registered as within the DI container during any schema's setup\nprocess."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp"},"GraphQLServerSettings.ControllerServiceLifetime = ServiceLifetime.Transient;\n")),(0,a.yg)("table",null,(0,a.yg)("thead",{parentName:"table"},(0,a.yg)("tr",{parentName:"thead"},(0,a.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,a.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,a.yg)("tbody",{parentName:"table"},(0,a.yg)("tr",{parentName:"tbody"},(0,a.yg)("td",{parentName:"tr",align:null},(0,a.yg)("inlineCode",{parentName:"td"},"Transient ")),(0,a.yg)("td",{parentName:"tr",align:null},(0,a.yg)("inlineCode",{parentName:"td"},"Transient"),", ",(0,a.yg)("inlineCode",{parentName:"td"},"Scoped"),", ",(0,a.yg)("inlineCode",{parentName:"td"},"Singleton"))))),(0,a.yg)("admonition",{type:"danger"},(0,a.yg)("p",{parentName:"admonition"}," Registering GraphControllers as anything other than transient can cause unexpected behavior and result in unexplained crashes, data loss, data exposure and security issues in some scenarios. Consider restructuring your application before changing this setting. Adjusting this value should be a last resort, not a first option.")),(0,a.yg)("h2",{id:"subscriptions"},"Subscriptions"),(0,a.yg)("h3",{id:"maxconcurrentreceivercount"},"MaxConcurrentReceiverCount"),(0,a.yg)("p",null,"Indicates the maximum number of entities (i.e. client connections) that will receive a raised subscription event on this server instance. If there are more receivers than this configured limit the others are queued and will recieve the event in turn once as others finish processing it."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp"},"GraphQLSubscriptionServerSettings.MaxConcurrentReceiverCount = 500;\n")),(0,a.yg)("table",null,(0,a.yg)("thead",{parentName:"table"},(0,a.yg)("tr",{parentName:"thead"},(0,a.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,a.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,a.yg)("tbody",{parentName:"table"},(0,a.yg)("tr",{parentName:"tbody"},(0,a.yg)("td",{parentName:"tr",align:null},(0,a.yg)("inlineCode",{parentName:"td"},"500")),(0,a.yg)("td",{parentName:"tr",align:null},"> 0")))),(0,a.yg)("h3",{id:"maxconnectedclientcount"},"MaxConnectedClientCount"),(0,a.yg)("p",null,"Indicates the maximum number of client connections this server instance will accept, combined, across all schemas. If this limit is reached a new connection will be automatically rejected even if ASP.NET was willing to accept it."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp"},"GraphQLSubscriptionServerSettings.MaxConnectedClientCount = null;\n")),(0,a.yg)("table",null,(0,a.yg)("thead",{parentName:"table"},(0,a.yg)("tr",{parentName:"thead"},(0,a.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,a.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,a.yg)("tbody",{parentName:"table"},(0,a.yg)("tr",{parentName:"tbody"},(0,a.yg)("td",{parentName:"tr",align:null},(0,a.yg)("em",{parentName:"td"},"-not set-")),(0,a.yg)("td",{parentName:"tr",align:null},"null OR > 0")))),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},"Note: By default this value is not set, indicating there is no limit. GraphQL will accept any connection passed by the ASP.NET runtime.")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1774.a7cc4885.js b/assets/js/1774.a7cc4885.js new file mode 100644 index 0000000..764e0f3 --- /dev/null +++ b/assets/js/1774.a7cc4885.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[1774],{1774:(e,t,n)=>{n.r(t),n.d(t,{default:()=>i});var a=n(6540),l=n(1312),o=n(1003),r=n(9408);function i(){return a.createElement(a.Fragment,null,a.createElement(o.be,{title:(0,l.T)({id:"theme.NotFound.title",message:"Page Not Found"})}),a.createElement(r.A,null,a.createElement("main",{className:"container margin-vert--xl"},a.createElement("div",{className:"row"},a.createElement("div",{className:"col col--6 col--offset-3"},a.createElement("h1",{className:"hero__title"},a.createElement(l.A,{id:"theme.NotFound.title",description:"The title of the 404 page"},"Page Not Found")),a.createElement("p",null,a.createElement(l.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page"},"We could not find what you were looking for.")),a.createElement("p",null,a.createElement(l.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page"},"Please contact the owner of the site that linked you to the original URL and let them know their link is broken.")))))))}}}]); \ No newline at end of file diff --git a/assets/js/17896441.aa8b80cd.js b/assets/js/17896441.aa8b80cd.js new file mode 100644 index 0000000..9ae2e81 --- /dev/null +++ b/assets/js/17896441.aa8b80cd.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[8401],{5680:(e,t,n)=>{n.d(t,{xA:()=>m,yg:()=>f});var a=n(6540);function l(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(l[n]=e[n]);return l}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(l[n]=e[n])}return l}var c=a.createContext({}),s=function(e){var t=a.useContext(c),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},m=function(e){var t=s(e.components);return a.createElement(c.Provider,{value:t},e.children)},d="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},p=a.forwardRef((function(e,t){var n=e.components,l=e.mdxType,r=e.originalType,c=e.parentName,m=i(e,["components","mdxType","originalType","parentName"]),d=s(n),p=l,f=d["".concat(c,".").concat(p)]||d[p]||u[p]||r;return n?a.createElement(f,o(o({ref:t},m),{},{components:n})):a.createElement(f,o({ref:t},m))}));function f(e,t){var n=arguments,l=t&&t.mdxType;if("string"==typeof e||l){var r=n.length,o=new Array(r);o[0]=p;var i={};for(var c in t)hasOwnProperty.call(t,c)&&(i[c]=t[c]);i.originalType=e,i[d]="string"==typeof e?e:l,o[1]=i;for(var s=2;s{n.r(t),n.d(t,{default:()=>nt});var a=n(6540),l=n(1003),r=n(9532);const o=a.createContext(null);function i(e){let{children:t,content:n}=e;const l=function(e){return(0,a.useMemo)((()=>({metadata:e.metadata,frontMatter:e.frontMatter,assets:e.assets,contentTitle:e.contentTitle,toc:e.toc})),[e])}(n);return a.createElement(o.Provider,{value:l},t)}function c(){const e=(0,a.useContext)(o);if(null===e)throw new r.dV("DocProvider");return e}function s(){const{metadata:e,frontMatter:t,assets:n}=c();return a.createElement(l.be,{title:e.title,description:e.description,keywords:t.keywords,image:n.image??t.image})}var m=n(53),d=n(4581),u=n(8168),p=n(1312),f=n(5489);function h(e){const{permalink:t,title:n,subLabel:l,isNext:r}=e;return a.createElement(f.A,{className:(0,m.A)("pagination-nav__link",r?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t},l&&a.createElement("div",{className:"pagination-nav__sublabel"},l),a.createElement("div",{className:"pagination-nav__label"},n))}function v(e){const{previous:t,next:n}=e;return a.createElement("nav",{className:"pagination-nav docusaurus-mt-lg","aria-label":(0,p.T)({id:"theme.docs.paginator.navAriaLabel",message:"Docs pages navigation",description:"The ARIA label for the docs pagination"})},t&&a.createElement(h,(0,u.A)({},t,{subLabel:a.createElement(p.A,{id:"theme.docs.paginator.previous",description:"The label used to navigate to the previous doc"},"Previous")})),n&&a.createElement(h,(0,u.A)({},n,{subLabel:a.createElement(p.A,{id:"theme.docs.paginator.next",description:"The label used to navigate to the next doc"},"Next"),isNext:!0})))}function b(){const{metadata:e}=c();return a.createElement(v,{previous:e.previous,next:e.next})}var E=n(4586),g=n(4070),A=n(7559),N=n(5597),y=n(2252);const L={unreleased:function(e){let{siteTitle:t,versionMetadata:n}=e;return a.createElement(p.A,{id:"theme.docs.versions.unreleasedVersionLabel",description:"The label used to tell the user that he's browsing an unreleased doc version",values:{siteTitle:t,versionLabel:a.createElement("b",null,n.label)}},"This is unreleased documentation for {siteTitle} {versionLabel} version.")},unmaintained:function(e){let{siteTitle:t,versionMetadata:n}=e;return a.createElement(p.A,{id:"theme.docs.versions.unmaintainedVersionLabel",description:"The label used to tell the user that he's browsing an unmaintained doc version",values:{siteTitle:t,versionLabel:a.createElement("b",null,n.label)}},"This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.")}};function T(e){const t=L[e.versionMetadata.banner];return a.createElement(t,e)}function _(e){let{versionLabel:t,to:n,onClick:l}=e;return a.createElement(p.A,{id:"theme.docs.versions.latestVersionSuggestionLabel",description:"The label used to tell the user to check the latest version",values:{versionLabel:t,latestVersionLink:a.createElement("b",null,a.createElement(f.A,{to:n,onClick:l},a.createElement(p.A,{id:"theme.docs.versions.latestVersionLinkLabel",description:"The label used for the latest version suggestion link label"},"latest version")))}},"For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).")}function C(e){let{className:t,versionMetadata:n}=e;const{siteConfig:{title:l}}=(0,E.A)(),{pluginId:r}=(0,g.vT)({failfast:!0}),{savePreferredVersionName:o}=(0,N.g1)(r),{latestDocSuggestion:i,latestVersionSuggestion:c}=(0,g.HW)(r),s=i??(d=c).docs.find((e=>e.id===d.mainDocId));var d;return a.createElement("div",{className:(0,m.A)(t,A.G.docs.docVersionBanner,"alert alert--warning margin-bottom--md"),role:"alert"},a.createElement("div",null,a.createElement(T,{siteTitle:l,versionMetadata:n})),a.createElement("div",{className:"margin-top--md"},a.createElement(_,{versionLabel:c.label,to:s.path,onClick:()=>o(c.name)})))}function x(e){let{className:t}=e;const n=(0,y.r)();return n.banner?a.createElement(C,{className:t,versionMetadata:n}):null}function k(e){let{className:t}=e;const n=(0,y.r)();return n.badge?a.createElement("span",{className:(0,m.A)(t,A.G.docs.docVersionBadge,"badge badge--secondary")},a.createElement(p.A,{id:"theme.docs.versionBadge.label",values:{versionLabel:n.label}},"Version: {versionLabel}")):null}function w(e){let{lastUpdatedAt:t,formattedLastUpdatedAt:n}=e;return a.createElement(p.A,{id:"theme.lastUpdated.atDate",description:"The words used to describe on which date a page has been last updated",values:{date:a.createElement("b",null,a.createElement("time",{dateTime:new Date(1e3*t).toISOString()},n))}}," on {date}")}function H(e){let{lastUpdatedBy:t}=e;return a.createElement(p.A,{id:"theme.lastUpdated.byUser",description:"The words used to describe by who the page has been last updated",values:{user:a.createElement("b",null,t)}}," by {user}")}function O(e){let{lastUpdatedAt:t,formattedLastUpdatedAt:n,lastUpdatedBy:l}=e;return a.createElement("span",{className:A.G.common.lastUpdated},a.createElement(p.A,{id:"theme.lastUpdated.lastUpdatedAtBy",description:"The sentence used to display when a page has been last updated, and by who",values:{atDate:t&&n?a.createElement(w,{lastUpdatedAt:t,formattedLastUpdatedAt:n}):"",byUser:l?a.createElement(H,{lastUpdatedBy:l}):""}},"Last updated{atDate}{byUser}"),!1)}const M="iconEdit_Z9Sw";function U(e){let{className:t,...n}=e;return a.createElement("svg",(0,u.A)({fill:"currentColor",height:"20",width:"20",viewBox:"0 0 40 40",className:(0,m.A)(M,t),"aria-hidden":"true"},n),a.createElement("g",null,a.createElement("path",{d:"m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"})))}function P(e){let{editUrl:t}=e;return a.createElement("a",{href:t,target:"_blank",rel:"noreferrer noopener",className:A.G.common.editThisPage},a.createElement(U,null),a.createElement(p.A,{id:"theme.common.editThisPage",description:"The link label to edit the current page"},"Edit this page"))}const S="tag_zVej",B="tagRegular_sFm0",V="tagWithCount_h2kH";function z(e){let{permalink:t,label:n,count:l}=e;return a.createElement(f.A,{href:t,className:(0,m.A)(S,l?V:B)},n,l&&a.createElement("span",null,l))}const D="tags_jXut",j="tag_QGVx";function I(e){let{tags:t}=e;return a.createElement(a.Fragment,null,a.createElement("b",null,a.createElement(p.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list"},"Tags:")),a.createElement("ul",{className:(0,m.A)(D,"padding--none","margin-left--sm")},t.map((e=>{let{label:t,permalink:n}=e;return a.createElement("li",{key:n,className:j},a.createElement(z,{label:t,permalink:n}))}))))}const R="lastUpdated_vwxv";function G(e){return a.createElement("div",{className:(0,m.A)(A.G.docs.docFooterTagsRow,"row margin-bottom--sm")},a.createElement("div",{className:"col"},a.createElement(I,e)))}function F(e){let{editUrl:t,lastUpdatedAt:n,lastUpdatedBy:l,formattedLastUpdatedAt:r}=e;return a.createElement("div",{className:(0,m.A)(A.G.docs.docFooterEditMetaRow,"row")},a.createElement("div",{className:"col"},t&&a.createElement(P,{editUrl:t})),a.createElement("div",{className:(0,m.A)("col",R)},(n||l)&&a.createElement(O,{lastUpdatedAt:n,formattedLastUpdatedAt:r,lastUpdatedBy:l})))}function q(){const{metadata:e}=c(),{editUrl:t,lastUpdatedAt:n,formattedLastUpdatedAt:l,lastUpdatedBy:r,tags:o}=e,i=o.length>0,s=!!(t||n||r);return i||s?a.createElement("footer",{className:(0,m.A)(A.G.docs.docFooter,"docusaurus-mt-lg")},i&&a.createElement(G,{tags:o}),s&&a.createElement(F,{editUrl:t,lastUpdatedAt:n,lastUpdatedBy:r,formattedLastUpdatedAt:l})):null}var W=n(1422),$=n(6342);function Y(e){const t=e.map((e=>({...e,parentIndex:-1,children:[]}))),n=Array(7).fill(-1);t.forEach(((e,t)=>{const a=n.slice(2,e.level);e.parentIndex=Math.max(...a),n[e.level]=t}));const a=[];return t.forEach((e=>{const{parentIndex:n,...l}=e;n>=0?t[n].children.push(l):a.push(l)})),a}function Q(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:a}=e;return t.flatMap((e=>{const t=Q({toc:e.children,minHeadingLevel:n,maxHeadingLevel:a});return function(e){return e.level>=n&&e.level<=a}(e)?[{...e,children:t}]:t}))}function X(e){const t=e.getBoundingClientRect();return t.top===t.bottom?X(e.parentNode):t}function Z(e,t){let{anchorTopOffset:n}=t;const a=e.find((e=>X(e).top>=n));if(a){return function(e){return e.top>0&&e.bottom{e.current=t?0:document.querySelector(".navbar").clientHeight}),[t]),e}function K(e){const t=(0,a.useRef)(void 0),n=J();(0,a.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:a,linkActiveClassName:l,minHeadingLevel:r,maxHeadingLevel:o}=e;function i(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(a),i=function(e){let{minHeadingLevel:t,maxHeadingLevel:n}=e;const a=[];for(let l=t;l<=n;l+=1)a.push(`h${l}.anchor`);return Array.from(document.querySelectorAll(a.join()))}({minHeadingLevel:r,maxHeadingLevel:o}),c=Z(i,{anchorTopOffset:n.current}),s=e.find((e=>c&&c.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,n){n?(t.current&&t.current!==e&&t.current.classList.remove(l),e.classList.add(l),t.current=e):e.classList.remove(l)}(e,e===s)}))}return document.addEventListener("scroll",i),document.addEventListener("resize",i),i(),()=>{document.removeEventListener("scroll",i),document.removeEventListener("resize",i)}}),[e,n])}function ee(e){let{toc:t,className:n,linkClassName:l,isChild:r}=e;return t.length?a.createElement("ul",{className:r?void 0:n},t.map((e=>a.createElement("li",{key:e.id},a.createElement("a",{href:`#${e.id}`,className:l??void 0,dangerouslySetInnerHTML:{__html:e.value}}),a.createElement(ee,{isChild:!0,toc:e.children,className:n,linkClassName:l}))))):null}const te=a.memo(ee);function ne(e){let{toc:t,className:n="table-of-contents table-of-contents__left-border",linkClassName:l="table-of-contents__link",linkActiveClassName:r,minHeadingLevel:o,maxHeadingLevel:i,...c}=e;const s=(0,$.p)(),m=o??s.tableOfContents.minHeadingLevel,d=i??s.tableOfContents.maxHeadingLevel,p=function(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:l}=e;return(0,a.useMemo)((()=>Q({toc:Y(t),minHeadingLevel:n,maxHeadingLevel:l})),[t,n,l])}({toc:t,minHeadingLevel:m,maxHeadingLevel:d});return K((0,a.useMemo)((()=>{if(l&&r)return{linkClassName:l,linkActiveClassName:r,minHeadingLevel:m,maxHeadingLevel:d}}),[l,r,m,d])),a.createElement(te,(0,u.A)({toc:p,className:n,linkClassName:l},c))}const ae="tocCollapsibleButton_TO0P",le="tocCollapsibleButtonExpanded_MG3E";function re(e){let{collapsed:t,...n}=e;return a.createElement("button",(0,u.A)({type:"button"},n,{className:(0,m.A)("clean-btn",ae,!t&&le,n.className)}),a.createElement(p.A,{id:"theme.TOCCollapsible.toggleButtonLabel",description:"The label used by the button on the collapsible TOC component"},"On this page"))}const oe="tocCollapsible_ETCw",ie="tocCollapsibleContent_vkbj",ce="tocCollapsibleExpanded_sAul";function se(e){let{toc:t,className:n,minHeadingLevel:l,maxHeadingLevel:r}=e;const{collapsed:o,toggleCollapsed:i}=(0,W.u)({initialState:!0});return a.createElement("div",{className:(0,m.A)(oe,!o&&ce,n)},a.createElement(re,{collapsed:o,onClick:i}),a.createElement(W.N,{lazy:!0,className:ie,collapsed:o},a.createElement(ne,{toc:t,minHeadingLevel:l,maxHeadingLevel:r})))}const me="tocMobile_ITEo";function de(){const{toc:e,frontMatter:t}=c();return a.createElement(se,{toc:e,minHeadingLevel:t.toc_min_heading_level,maxHeadingLevel:t.toc_max_heading_level,className:(0,m.A)(A.G.docs.docTocMobile,me)})}const ue="tableOfContents_bqdL";function pe(e){let{className:t,...n}=e;return a.createElement("div",{className:(0,m.A)(ue,"thin-scrollbar",t)},a.createElement(ne,(0,u.A)({},n,{linkClassName:"table-of-contents__link toc-highlight",linkActiveClassName:"table-of-contents__link--active"})))}function fe(){const{toc:e,frontMatter:t}=c();return a.createElement(pe,{toc:e,minHeadingLevel:t.toc_min_heading_level,maxHeadingLevel:t.toc_max_heading_level,className:A.G.docs.docTocDesktop})}const he="anchorWithStickyNavbar_LWe7",ve="anchorWithHideOnScrollNavbar_WYt5";function be(e){let{as:t,id:n,...l}=e;const{navbar:{hideOnScroll:r}}=(0,$.p)();if("h1"===t||!n)return a.createElement(t,(0,u.A)({},l,{id:void 0}));const o=(0,p.T)({id:"theme.common.headingLinkTitle",message:"Direct link to {heading}",description:"Title for link to heading"},{heading:"string"==typeof l.children?l.children:n});return a.createElement(t,(0,u.A)({},l,{className:(0,m.A)("anchor",r?ve:he,l.className),id:n}),l.children,a.createElement(f.A,{className:"hash-link",to:`#${n}`,"aria-label":o,title:o},"\u200b"))}var Ee=n(5680),ge=n(5260);var Ae=n(7964);var Ne=n(2303);const ye="details_lb9f",Le="isBrowser_bmU9",Te="collapsibleContent_i85q";function _e(e){return!!e&&("SUMMARY"===e.tagName||_e(e.parentElement))}function Ce(e,t){return!!e&&(e===t||Ce(e.parentElement,t))}function xe(e){let{summary:t,children:n,...l}=e;const r=(0,Ne.A)(),o=(0,a.useRef)(null),{collapsed:i,setCollapsed:c}=(0,W.u)({initialState:!l.open}),[s,d]=(0,a.useState)(l.open),p=a.isValidElement(t)?t:a.createElement("summary",null,t??"Details");return a.createElement("details",(0,u.A)({},l,{ref:o,open:s,"data-collapsed":i,className:(0,m.A)(ye,r&&Le,l.className),onMouseDown:e=>{_e(e.target)&&e.detail>1&&e.preventDefault()},onClick:e=>{e.stopPropagation();const t=e.target;_e(t)&&Ce(t,o.current)&&(e.preventDefault(),i?(c(!1),d(!0)):c(!0))}}),p,a.createElement(W.N,{lazy:!1,collapsed:i,disableSSRStyle:!0,onCollapseTransitionEnd:e=>{c(e),d(!e)}},a.createElement("div",{className:Te},n)))}const ke="details_b_Ee";function we(e){let{...t}=e;return a.createElement(xe,(0,u.A)({},t,{className:(0,m.A)("alert alert--info",ke,t.className)}))}function He(e){return a.createElement(be,e)}const Oe="containsTaskList_mC6p";const Me="img_ev3q";const Ue="admonition_LlT9",Pe="admonitionHeading_tbUL",Se="admonitionIcon_kALy",Be="admonitionContent_S0QG";const Ve={note:{infimaClassName:"secondary",iconComponent:function(){return a.createElement("svg",{viewBox:"0 0 14 16"},a.createElement("path",{fillRule:"evenodd",d:"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"}))},label:a.createElement(p.A,{id:"theme.admonition.note",description:"The default label used for the Note admonition (:::note)"},"note")},tip:{infimaClassName:"success",iconComponent:function(){return a.createElement("svg",{viewBox:"0 0 12 16"},a.createElement("path",{fillRule:"evenodd",d:"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"}))},label:a.createElement(p.A,{id:"theme.admonition.tip",description:"The default label used for the Tip admonition (:::tip)"},"tip")},danger:{infimaClassName:"danger",iconComponent:function(){return a.createElement("svg",{viewBox:"0 0 12 16"},a.createElement("path",{fillRule:"evenodd",d:"M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"}))},label:a.createElement(p.A,{id:"theme.admonition.danger",description:"The default label used for the Danger admonition (:::danger)"},"danger")},info:{infimaClassName:"info",iconComponent:function(){return a.createElement("svg",{viewBox:"0 0 14 16"},a.createElement("path",{fillRule:"evenodd",d:"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"}))},label:a.createElement(p.A,{id:"theme.admonition.info",description:"The default label used for the Info admonition (:::info)"},"info")},caution:{infimaClassName:"warning",iconComponent:function(){return a.createElement("svg",{viewBox:"0 0 16 16"},a.createElement("path",{fillRule:"evenodd",d:"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"}))},label:a.createElement(p.A,{id:"theme.admonition.caution",description:"The default label used for the Caution admonition (:::caution)"},"caution")}},ze={secondary:"note",important:"info",success:"tip",warning:"danger"};function De(e){const{mdxAdmonitionTitle:t,rest:n}=function(e){const t=a.Children.toArray(e),n=t.find((e=>a.isValidElement(e)&&"mdxAdmonitionTitle"===e.props?.mdxType)),l=a.createElement(a.Fragment,null,t.filter((e=>e!==n)));return{mdxAdmonitionTitle:n,rest:l}}(e.children);return{...e,title:e.title??t,children:n}}const je={head:function(e){const t=a.Children.map(e.children,(e=>a.isValidElement(e)?function(e){if(e.props?.mdxType&&e.props.originalType){const{mdxType:t,originalType:n,...l}=e.props;return a.createElement(e.props.originalType,l)}return e}(e):e));return a.createElement(ge.A,e,t)},code:function(e){const t=["a","abbr","b","br","button","cite","code","del","dfn","em","i","img","input","ins","kbd","label","object","output","q","ruby","s","small","span","strong","sub","sup","time","u","var","wbr"];return a.Children.toArray(e.children).every((e=>"string"==typeof e&&!e.includes("\n")||(0,a.isValidElement)(e)&&t.includes(e.props?.mdxType)))?a.createElement("code",e):a.createElement(Ae.A,e)},a:function(e){return a.createElement(f.A,e)},pre:function(e){return a.createElement(Ae.A,(0,a.isValidElement)(e.children)&&"code"===e.children.props?.originalType?e.children.props:{...e})},details:function(e){const t=a.Children.toArray(e.children),n=t.find((e=>a.isValidElement(e)&&"summary"===e.props?.mdxType)),l=a.createElement(a.Fragment,null,t.filter((e=>e!==n)));return a.createElement(we,(0,u.A)({},e,{summary:n}),l)},ul:function(e){return a.createElement("ul",(0,u.A)({},e,{className:(t=e.className,(0,m.A)(t,t?.includes("contains-task-list")&&Oe))}));var t},img:function(e){return a.createElement("img",(0,u.A)({loading:"lazy"},e,{className:(t=e.className,(0,m.A)(t,Me))}));var t},h1:e=>a.createElement(He,(0,u.A)({as:"h1"},e)),h2:e=>a.createElement(He,(0,u.A)({as:"h2"},e)),h3:e=>a.createElement(He,(0,u.A)({as:"h3"},e)),h4:e=>a.createElement(He,(0,u.A)({as:"h4"},e)),h5:e=>a.createElement(He,(0,u.A)({as:"h5"},e)),h6:e=>a.createElement(He,(0,u.A)({as:"h6"},e)),admonition:function(e){const{children:t,type:n,title:l,icon:r}=De(e),o=function(e){const t=ze[e]??e,n=Ve[t];return n||(console.warn(`No admonition config found for admonition type "${t}". Using Info as fallback.`),Ve.info)}(n),i=l??o.label,{iconComponent:c}=o,s=r??a.createElement(c,null);return a.createElement("div",{className:(0,m.A)(A.G.common.admonition,A.G.common.admonitionType(e.type),"alert",`alert--${o.infimaClassName}`,Ue)},a.createElement("div",{className:Pe},a.createElement("span",{className:Se},s),i),a.createElement("div",{className:Be},t))},mermaid:n(418).A};function Ie(e){let{children:t}=e;return a.createElement(Ee.xA,{components:je},t)}function Re(e){let{children:t}=e;const n=function(){const{metadata:e,frontMatter:t,contentTitle:n}=c();return t.hide_title||void 0!==n?null:e.title}();return a.createElement("div",{className:(0,m.A)(A.G.docs.docMarkdown,"markdown")},n&&a.createElement("header",null,a.createElement(be,{as:"h1"},n)),a.createElement(Ie,null,t))}var Ge=n(1754),Fe=n(9169),qe=n(6025);function We(e){return a.createElement("svg",(0,u.A)({viewBox:"0 0 24 24"},e),a.createElement("path",{d:"M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z",fill:"currentColor"}))}const $e="breadcrumbHomeIcon_YNFT";function Ye(){const e=(0,qe.A)("/");return a.createElement("li",{className:"breadcrumbs__item"},a.createElement(f.A,{"aria-label":(0,p.T)({id:"theme.docs.breadcrumbs.home",message:"Home page",description:"The ARIA label for the home page in the breadcrumbs"}),className:"breadcrumbs__link",href:e},a.createElement(We,{className:$e})))}const Qe="breadcrumbsContainer_Z_bl";function Xe(e){let{children:t,href:n,isLast:l}=e;const r="breadcrumbs__link";return l?a.createElement("span",{className:r,itemProp:"name"},t):n?a.createElement(f.A,{className:r,href:n,itemProp:"item"},a.createElement("span",{itemProp:"name"},t)):a.createElement("span",{className:r},t)}function Ze(e){let{children:t,active:n,index:l,addMicrodata:r}=e;return a.createElement("li",(0,u.A)({},r&&{itemScope:!0,itemProp:"itemListElement",itemType:"https://schema.org/ListItem"},{className:(0,m.A)("breadcrumbs__item",{"breadcrumbs__item--active":n})}),t,a.createElement("meta",{itemProp:"position",content:String(l+1)}))}function Je(){const e=(0,Ge.OF)(),t=(0,Fe.Dt)();return e?a.createElement("nav",{className:(0,m.A)(A.G.docs.docBreadcrumbs,Qe),"aria-label":(0,p.T)({id:"theme.docs.breadcrumbs.navAriaLabel",message:"Breadcrumbs",description:"The ARIA label for the breadcrumbs"})},a.createElement("ul",{className:"breadcrumbs",itemScope:!0,itemType:"https://schema.org/BreadcrumbList"},t&&a.createElement(Ye,null),e.map(((t,n)=>{const l=n===e.length-1;return a.createElement(Ze,{key:n,active:l,index:n,addMicrodata:!!t.href},a.createElement(Xe,{href:t.href,isLast:l},t.label))})))):null}const Ke="docItemContainer_Djhp",et="docItemCol_VOVn";function tt(e){let{children:t}=e;const n=function(){const{frontMatter:e,toc:t}=c(),n=(0,d.l)(),l=e.hide_table_of_contents,r=!l&&t.length>0;return{hidden:l,mobile:r?a.createElement(de,null):void 0,desktop:!r||"desktop"!==n&&"ssr"!==n?void 0:a.createElement(fe,null)}}();return a.createElement("div",{className:"row"},a.createElement("div",{className:(0,m.A)("col",!n.hidden&&et)},a.createElement(x,null),a.createElement("div",{className:Ke},a.createElement("article",null,a.createElement(Je,null),a.createElement(k,null),n.mobile,a.createElement(Re,null,t),a.createElement(q,null)),a.createElement(b,null))),n.desktop&&a.createElement("div",{className:"col col--3"},n.desktop))}function nt(e){const t=`docs-doc-id-${e.content.metadata.unversionedId}`,n=e.content;return a.createElement(i,{content:e.content},a.createElement(l.e3,{className:t},a.createElement(s,null),a.createElement(tt,null,a.createElement(n,null))))}},2252:(e,t,n)=>{n.d(t,{n:()=>o,r:()=>i});var a=n(6540),l=n(9532);const r=a.createContext(null);function o(e){let{children:t,version:n}=e;return a.createElement(r.Provider,{value:n},t)}function i(){const e=(0,a.useContext)(r);if(null===e)throw new l.dV("DocsVersionProvider");return e}}}]); \ No newline at end of file diff --git a/assets/js/1a387cfc.9c6040f9.js b/assets/js/1a387cfc.9c6040f9.js new file mode 100644 index 0000000..c6061b1 --- /dev/null +++ b/assets/js/1a387cfc.9c6040f9.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[3515],{5680:(e,n,t)=>{t.d(n,{xA:()=>s,yg:()=>g});var a=t(6540);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function l(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function i(e){for(var n=1;n=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var u=a.createContext({}),p=function(e){var n=a.useContext(u),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},s=function(e){var n=p(e.components);return a.createElement(u.Provider,{value:n},e.children)},m="mdxType",y={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},c=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,l=e.originalType,u=e.parentName,s=o(e,["components","mdxType","originalType","parentName"]),m=p(t),c=r,g=m["".concat(u,".").concat(c)]||m[c]||y[c]||l;return t?a.createElement(g,i(i({ref:n},s),{},{components:t})):a.createElement(g,i({ref:n},s))}));function g(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var l=t.length,i=new Array(l);i[0]=c;var o={};for(var u in n)hasOwnProperty.call(n,u)&&(o[u]=n[u]);o.originalType=e,o[m]="string"==typeof e?e:r,i[1]=o;for(var p=2;p{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>i,default:()=>m,frontMatter:()=>l,metadata:()=>o,toc:()=>p});var a=t(8168),r=(t(6540),t(5680));const l={id:"enums",title:"Enums",sidebar_label:"Enums",sidebar_position:4},i=void 0,o={unversionedId:"types/enums",id:"types/enums",title:"Enums",description:"The ENUM graph type is represented by an enum type in .NET. The naming and exclusion rules used with object types apply in the same manner to enums.",source:"@site/docs/types/enums.md",sourceDirName:"types",slug:"/types/enums",permalink:"/docs/types/enums",draft:!1,tags:[],version:"current",sidebarPosition:4,frontMatter:{id:"enums",title:"Enums",sidebar_label:"Enums",sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"Unions",permalink:"/docs/types/unions"},next:{title:"Scalars",permalink:"/docs/types/scalars"}},u={},p=[{value:"Including an Enum in Your Schema",id:"including-an-enum-in-your-schema",level:2},{value:"Excluding an Enum Value",id:"excluding-an-enum-value",level:2},{value:"Custom Type Name",id:"custom-type-name",level:2},{value:"Custom Value Names",id:"custom-value-names",level:2},{value:"Value Name Formatting",id:"value-name-formatting",level:2}],s={toc:p};function m(e){let{components:n,...t}=e;return(0,r.yg)("wrapper",(0,a.A)({},s,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"ENUM")," graph type is represented by an ",(0,r.yg)("inlineCode",{parentName:"p"},"enum")," type in .NET. The naming and exclusion rules used with ",(0,r.yg)("a",{parentName:"p",href:"./objects"},"object types")," apply in the same manner to enums."),(0,r.yg)("p",null,"By Default:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"An ",(0,r.yg)("inlineCode",{parentName:"li"},"ENUM")," graph type will have the same name as its ",(0,r.yg)("inlineCode",{parentName:"li"},"enum")," type in your code."),(0,r.yg)("li",{parentName:"ul"},"All declared enum values are included, including compound values.")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="DonutType.cs"',title:'"DonutType.cs"'},"public enum DonutType\n{\n Glazed,\n Cake,\n Custard,\n Jelly,\n SugarCoated,\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="DonutType Type Definition"',title:'"DonutType',Type:!0,'Definition"':!0},"enum DonutType {\n GLAZED\n CAKE\n CUSTARD\n JELLY\n SUGARCOATED\n}\n")),(0,r.yg)("p",null,"Compound Values are represented as their own enum value option."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="DonutType.cs"',title:'"DonutType.cs"'},"public enum DonutType\n{\n Glazed,\n Cake,\n Custard,\n Jelly,\n SugarCoated,\n // highlight-next-line\n Filled = Custard | Jelly\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="DonutType Type Definition"',title:'"DonutType',Type:!0,'Definition"':!0},"enum DonutType {\n GLAZED\n CAKE\n CUSTARD\n JELLY\n SUGARCOATED\n // highlight-next-line\n FILLED\n}\n")),(0,r.yg)("h2",{id:"including-an-enum-in-your-schema"},"Including an Enum in Your Schema"),(0,r.yg)("p",null,"An enum graph type is automatically created from a .NET enum if it is:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Used as a parameter to an action method"),(0,r.yg)("li",{parentName:"ul"},"Used as a return value of an action method"),(0,r.yg)("li",{parentName:"ul"},"Used as a parameter or return type of any property or method of any included class or struct.")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="DonutController.cs"',title:'"DonutController.cs"'},"public class DonutController : GraphController \n{\n [QueryRoot]\n // highlight-next-line\n public int RetrieveDonutCount(DonutType donutType)\n {\n /* ... */\n }\n}\n")),(0,r.yg)("p",null,"You can also explicitly add an enum at start up:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Startup code"',title:'"Startup','code"':!0},"services.AddGraphQL(options => \n{\n options.AddGraphType();\n});\n")),(0,r.yg)("h2",{id:"excluding-an-enum-value"},"Excluding an Enum Value"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphSkip]")," attribute to omit a value from the schema. A query will be rejected if it attempts to submit an omitted enum value."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="DonutType.cs"',title:'"DonutType.cs"'},"public enum DonutType\n{\n Glazed,\n Cake,\n Custard,\n Jelly,\n\n // highlight-next-line\n [GraphSkip]\n SugarCoated,\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="DonutType Type Definition"',title:'"DonutType',Type:!0,'Definition"':!0},"# Sugar Coated is not part of the enum type\nenum DonutType {\n GLAZED\n CAKE\n CUSTARD\n JELLY\n}\n")),(0,r.yg)("admonition",{type:"caution"},(0,r.yg)("p",{parentName:"admonition"},"An excluded enum value is not just hidden, its NOT part of the schema. Any attempt to use it as a value in a query, a variable or as a result from a field resolution will cause a validation error.")),(0,r.yg)("h2",{id:"custom-type-name"},"Custom Type Name"),(0,r.yg)("p",null,"Like with other graph types, use the ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphType]")," attribute to indicate a custom name for the enum in the schema."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="DonutType.cs"',title:'"DonutType.cs"'},'// highlight-next-line\n[GraphType("Donut_Type")]\npublic enum DonutType\n{\n Glazed,\n Cake,\n Custard,\n Jelly,\n SugarCoated,\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="DonutType Type Definition"',title:'"DonutType',Type:!0,'Definition"':!0},"// highlight-next-line\nenum Donut_Type {\n GLAZED\n CAKE\n CUSTARD\n JELLY\n SUGARCOATED\n}\n")),(0,r.yg)("h2",{id:"custom-value-names"},"Custom Value Names"),(0,r.yg)("p",null,"Use ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphEnumValue]")," to declare a custom name for the enum value and GraphQL will automatically handle the name translation when parsing a query document. A target schema's naming format rules will be applied and enforced on the value provided."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="DonutType.cs"',title:'"DonutType.cs"'},'public enum DonutType\n{\n Glazed,\n Cake,\n Custard,\n Jelly,\n\n [GraphEnumValue("Sugar_Coated")]\n SugarCoated,\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="DonutType Type Definition"',title:'"DonutType',Type:!0,'Definition"':!0},"enum DonutType {\n GLAZED\n CAKE\n CUSTARD\n JELLY\n SUGAR_COATED\n}\n")),(0,r.yg)("h2",{id:"value-name-formatting"},"Value Name Formatting"),(0,r.yg)("p",null,"By default, enum values are rendered in all CAPITAL LETTERS. This is the standard convention for GraphQL. If, however; you'd prefer something different you can override the defaults by creating a new ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphNameFormatter")," on your ",(0,r.yg)("a",{parentName:"p",href:"/docs/reference/schema-configuration#graphnamingformatter"},"schema configuration"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Startup Code"',title:'"Startup','Code"':!0},"services.AddGraphQL(o => {\n // highlight-start\n var customFormatter = new GraphNameFormatter(enumValueStrategy: GraphNameFormatStrategy.ProperCase);\n o.DeclarationOptions.GraphNamingFormatter = customFormatter;\n // highlight-end\n})\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Formatting"',title:'"Sample','Formatting"':!0},"enum DonutType {\n Glazed\n Cake\n Custard\n Jelly\n}\n")),(0,r.yg)("admonition",{type:"tip"},(0,r.yg)("p",{parentName:"admonition"},"If you need something even more exotic, inherit from ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphNameFormatter")," and override the various methods as you see fit.")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1ae49e3a.c9c49699.js b/assets/js/1ae49e3a.c9c49699.js new file mode 100644 index 0000000..e70cd37 --- /dev/null +++ b/assets/js/1ae49e3a.c9c49699.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[3040],{5680:(e,t,r)=>{r.d(t,{xA:()=>l,yg:()=>h});var n=r(6540);function o(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function a(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function s(e){for(var t=1;t=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var p=n.createContext({}),c=function(e){var t=n.useContext(p),r=t;return e&&(r="function"==typeof e?e(t):s(s({},t),e)),r},l=function(e){var t=c(e.components);return n.createElement(p.Provider,{value:t},e.children)},u="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},g=n.forwardRef((function(e,t){var r=e.components,o=e.mdxType,a=e.originalType,p=e.parentName,l=i(e,["components","mdxType","originalType","parentName"]),u=c(r),g=o,h=u["".concat(p,".").concat(g)]||u[g]||m[g]||a;return r?n.createElement(h,s(s({ref:t},l),{},{components:r})):n.createElement(h,s({ref:t},l))}));function h(e,t){var r=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var a=r.length,s=new Array(a);s[0]=g;var i={};for(var p in t)hasOwnProperty.call(t,p)&&(i[p]=t[p]);i.originalType=e,i[u]="string"==typeof e?e:o,s[1]=i;for(var c=2;c{r.r(t),r.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>u,frontMatter:()=>a,metadata:()=>i,toc:()=>c});var n=r(8168),o=(r(6540),r(5680));const a={id:"demo-projects",title:"Demo Projects",sidebar_label:"Demo Projects",sidebar_position:9},s=void 0,i={unversionedId:"reference/demo-projects",id:"reference/demo-projects",title:"Demo Projects",description:"General",source:"@site/docs/reference/demo-projects.md",sourceDirName:"reference",slug:"/reference/demo-projects",permalink:"/docs/reference/demo-projects",draft:!1,tags:[],version:"current",sidebarPosition:9,frontMatter:{id:"demo-projects",title:"Demo Projects",sidebar_label:"Demo Projects",sidebar_position:9},sidebar:"tutorialSidebar",previous:{title:"Query Caching",permalink:"/docs/reference/query-cache"},next:{title:"Benchmarks",permalink:"/docs/reference/performance"}},p={},c=[{value:"General",id:"general",level:3},{value:"Authentication & Authorization",id:"authentication--authorization",level:3},{value:"Subscriptions",id:"subscriptions",level:3},{value:"Extensions",id:"extensions",level:3}],l={toc:c};function u(e){let{components:t,...r}=e;return(0,o.yg)("wrapper",(0,n.A)({},l,r,{components:t,mdxType:"MDXLayout"}),(0,o.yg)("h3",{id:"general"},"General"),(0,o.yg)("p",null,"\ud83d\udccc ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/demo-projects/tree/master/Custom-Directives"},"Custom Directives")," ",(0,o.yg)("br",null),"\nDemostrates creating and applying a type system directive and a custom execution directive."),(0,o.yg)("p",null,"\ud83d\udccc ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/demo-projects/tree/master/LoggingProvider"},"Logging Provider")," ",(0,o.yg)("br",null),"\nDemonstrates the creation of a custom ",(0,o.yg)("inlineCode",{parentName:"p"},"ILogProvider")," to intercept logging events and writing them to a json file."),(0,o.yg)("p",null,"\ud83d\udccc ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/demo-projects/tree/master/Custom-HttpProcessor"},"Custom Http Processor"),(0,o.yg)("br",null),"\nDemonstrates overriding the default HTTP Processor to conditionally process entire queries at the ASP.NET level."),(0,o.yg)("p",null,"\ud83d\udccc ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/demo-projects/tree/master/Unit-Testing"},"Unit Test Framework"),(0,o.yg)("br",null),"\nAn example project that utilizes the test framework nuget package to execute some unit tests against a graph controller using xUnit."),(0,o.yg)("br",null),(0,o.yg)("h3",{id:"authentication--authorization"},"Authentication & Authorization"),(0,o.yg)("p",null,"\ud83d\udccc ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/demo-projects/tree/master/Authorization"},"Field Authorization"),(0,o.yg)("br",null),"\nDemonstrates fields with authorization requirements and how access denied messages are returned to the client in the various authorization modes."),(0,o.yg)("p",null,"\ud83d\udccc ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/demo-projects/tree/master/Firebase-Authentication"},"Firebase Authentication"),(0,o.yg)("br",null),"\nDemonstrates how to setup a ",(0,o.yg)("a",{parentName:"p",href:"https://firebase.google.com/"},"Firebase")," project and link a GraphQL ASP.NET project to it."),(0,o.yg)("br",null),(0,o.yg)("h3",{id:"subscriptions"},"Subscriptions"),(0,o.yg)("p",null,"\ud83d\udccc ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/demo-projects/tree/master/Subscriptions-AzureServiceBus"},"Subscriptions w/ Azure Service Bus"),(0,o.yg)("br",null),"\nDemonstrates the use of an external subscription event publisher and a consumer to deserialize and route events. "),(0,o.yg)("blockquote",null,(0,o.yg)("p",{parentName:"blockquote"},"Use of this demo project requires your own ",(0,o.yg)("a",{parentName:"p",href:"https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview"},"Azure Service Bus")," namespace.")),(0,o.yg)("p",null,"\ud83d\udccc ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/demo-projects/tree/master/Subscriptions-ReactApolloClient"},"Subscriptions w/ React & Apollo Client"),(0,o.yg)("br",null),"\nA sample react application that makes use of the ",(0,o.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/react/"},"apollo client")," to connect to a GraphQL ASP.NET server."),(0,o.yg)("br",null),(0,o.yg)("h3",{id:"extensions"},"Extensions"),(0,o.yg)("p",null,"\ud83d\udccc ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/demo-projects/tree/master/File-Uploads"},"File Uploads"),(0,o.yg)("br",null),"\nDemonstrates the use the ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"graphql-multipart-form-spec")," compliant extension to perform file uploads as part of a graphql query."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1be78505.cf141f73.js b/assets/js/1be78505.cf141f73.js new file mode 100644 index 0000000..12cd779 --- /dev/null +++ b/assets/js/1be78505.cf141f73.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[1774,8714],{10:(e,t,n)=>{n.r(t),n.d(t,{default:()=>Ce});var a=n(6540),l=n(53),r=n(1003),o=n(7559),c=n(2967),i=n(1754),s=n(2252),d=n(6588),m=n(9408),u=n(1312),b=n(3104),p=n(5062);const h="backToTopButton_sjWU",E="backToTopButtonShow_xfvO";function f(){const{shown:e,scrollToTop:t}=function(e){let{threshold:t}=e;const[n,l]=(0,a.useState)(!1),r=(0,a.useRef)(!1),{startScroll:o,cancelScroll:c}=(0,b.gk)();return(0,b.Mq)(((e,n)=>{let{scrollY:a}=e;const o=n?.scrollY;o&&(r.current?r.current=!1:a>=o?(c(),l(!1)):a{e.location.hash&&(r.current=!0,l(!1))})),{shown:n,scrollToTop:()=>o(0)}}({threshold:300});return a.createElement("button",{"aria-label":(0,u.T)({id:"theme.BackToTopButton.buttonAriaLabel",message:"Scroll back to top",description:"The ARIA label for the back to top button"}),className:(0,l.A)("clean-btn",o.G.common.backToTopButton,h,e&&E),type:"button",onClick:t})}var v=n(6347),g=n(4581),_=n(6342),A=n(3465),C=n(8168);function k(e){return a.createElement("svg",(0,C.A)({width:"20",height:"20","aria-hidden":"true"},e),a.createElement("g",{fill:"#7a7a7a"},a.createElement("path",{d:"M9.992 10.023c0 .2-.062.399-.172.547l-4.996 7.492a.982.982 0 01-.828.454H1c-.55 0-1-.453-1-1 0-.2.059-.403.168-.551l4.629-6.942L.168 3.078A.939.939 0 010 2.528c0-.548.45-.997 1-.997h2.996c.352 0 .649.18.828.45L9.82 9.472c.11.148.172.347.172.55zm0 0"}),a.createElement("path",{d:"M19.98 10.023c0 .2-.058.399-.168.547l-4.996 7.492a.987.987 0 01-.828.454h-3c-.547 0-.996-.453-.996-1 0-.2.059-.403.168-.551l4.625-6.942-4.625-6.945a.939.939 0 01-.168-.55 1 1 0 01.996-.997h3c.348 0 .649.18.828.45l4.996 7.492c.11.148.168.347.168.55zm0 0"})))}const S="collapseSidebarButton_PEFL",N="collapseSidebarButtonIcon_kv0_";function T(e){let{onClick:t}=e;return a.createElement("button",{type:"button",title:(0,u.T)({id:"theme.docs.sidebar.collapseButtonTitle",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),"aria-label":(0,u.T)({id:"theme.docs.sidebar.collapseButtonAriaLabel",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),className:(0,l.A)("button button--secondary button--outline",S),onClick:t},a.createElement(k,{className:N}))}var I=n(5041),y=n(9532);const w=Symbol("EmptyContext"),x=a.createContext(w);function M(e){let{children:t}=e;const[n,l]=(0,a.useState)(null),r=(0,a.useMemo)((()=>({expandedItem:n,setExpandedItem:l})),[n]);return a.createElement(x.Provider,{value:r},t)}var L=n(1422),B=n(9169),H=n(5489),P=n(2303);function G(e){let{categoryLabel:t,onClick:n}=e;return a.createElement("button",{"aria-label":(0,u.T)({id:"theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel",message:"Toggle the collapsible sidebar category '{label}'",description:"The ARIA label to toggle the collapsible sidebar category"},{label:t}),type:"button",className:"clean-btn menu__caret",onClick:n})}function F(e){let{item:t,onItemClick:n,activePath:r,level:c,index:s,...d}=e;const{items:m,label:u,collapsible:b,className:p,href:h}=t,{docs:{sidebar:{autoCollapseCategories:E}}}=(0,_.p)(),f=function(e){const t=(0,P.A)();return(0,a.useMemo)((()=>e.href?e.href:!t&&e.collapsible?(0,i._o)(e):void 0),[e,t])}(t),v=(0,i.w8)(t,r),g=(0,B.ys)(h,r),{collapsed:A,setCollapsed:k}=(0,L.u)({initialState:()=>!!b&&(!v&&t.collapsed)}),{expandedItem:S,setExpandedItem:N}=function(){const e=(0,a.useContext)(x);if(e===w)throw new y.dV("DocSidebarItemsExpandedStateProvider");return e}(),T=function(e){void 0===e&&(e=!A),N(e?null:s),k(e)};return function(e){let{isActive:t,collapsed:n,updateCollapsed:l}=e;const r=(0,y.ZC)(t);(0,a.useEffect)((()=>{t&&!r&&n&&l(!1)}),[t,r,n,l])}({isActive:v,collapsed:A,updateCollapsed:T}),(0,a.useEffect)((()=>{b&&null!=S&&S!==s&&E&&k(!0)}),[b,S,s,k,E]),a.createElement("li",{className:(0,l.A)(o.G.docs.docSidebarItemCategory,o.G.docs.docSidebarItemCategoryLevel(c),"menu__list-item",{"menu__list-item--collapsed":A},p)},a.createElement("div",{className:(0,l.A)("menu__list-item-collapsible",{"menu__list-item-collapsible--active":g})},a.createElement(H.A,(0,C.A)({className:(0,l.A)("menu__link",{"menu__link--sublist":b,"menu__link--sublist-caret":!h&&b,"menu__link--active":v}),onClick:b?e=>{n?.(t),h?T(!1):(e.preventDefault(),T())}:()=>{n?.(t)},"aria-current":g?"page":void 0,"aria-expanded":b?!A:void 0,href:b?f??"#":f},d),u),h&&b&&a.createElement(G,{categoryLabel:u,onClick:e=>{e.preventDefault(),T()}})),a.createElement(L.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:A},a.createElement(q,{items:m,tabIndex:A?-1:0,onItemClick:n,activePath:r,level:c+1})))}var D=n(6654),W=n(3186);const V="menuExternalLink_NmtK";function z(e){let{item:t,onItemClick:n,activePath:r,level:c,index:s,...d}=e;const{href:m,label:u,className:b,autoAddBaseUrl:p}=t,h=(0,i.w8)(t,r),E=(0,D.A)(m);return a.createElement("li",{className:(0,l.A)(o.G.docs.docSidebarItemLink,o.G.docs.docSidebarItemLinkLevel(c),"menu__list-item",b),key:u},a.createElement(H.A,(0,C.A)({className:(0,l.A)("menu__link",!E&&V,{"menu__link--active":h}),autoAddBaseUrl:p,"aria-current":h?"page":void 0,to:m},E&&{onClick:n?()=>n(t):void 0},d),u,!E&&a.createElement(W.A,null)))}const R="menuHtmlItem_M9Kj";function U(e){let{item:t,level:n,index:r}=e;const{value:c,defaultStyle:i,className:s}=t;return a.createElement("li",{className:(0,l.A)(o.G.docs.docSidebarItemLink,o.G.docs.docSidebarItemLinkLevel(n),i&&[R,"menu__list-item"],s),key:r,dangerouslySetInnerHTML:{__html:c}})}function j(e){let{item:t,...n}=e;switch(t.type){case"category":return a.createElement(F,(0,C.A)({item:t},n));case"html":return a.createElement(U,(0,C.A)({item:t},n));default:return a.createElement(z,(0,C.A)({item:t},n))}}function K(e){let{items:t,...n}=e;return a.createElement(M,null,t.map(((e,t)=>a.createElement(j,(0,C.A)({key:t,item:e,index:t},n)))))}const q=(0,a.memo)(K),X="menu_SIkG",Y="menuWithAnnouncementBar_GW3s";function O(e){let{path:t,sidebar:n,className:r}=e;const c=function(){const{isActive:e}=(0,I.Mj)(),[t,n]=(0,a.useState)(e);return(0,b.Mq)((t=>{let{scrollY:a}=t;e&&n(0===a)}),[e]),e&&t}();return a.createElement("nav",{"aria-label":(0,u.T)({id:"theme.docs.sidebar.navAriaLabel",message:"Docs sidebar",description:"The ARIA label for the sidebar navigation"}),className:(0,l.A)("menu thin-scrollbar",X,c&&Y,r)},a.createElement("ul",{className:(0,l.A)(o.G.docs.docSidebarMenu,"menu__list")},a.createElement(q,{items:n,activePath:t,level:1})))}const Z="sidebar_njMd",$="sidebarWithHideableNavbar_wUlq",J="sidebarHidden_VK0M",Q="sidebarLogo_isFc";function ee(e){let{path:t,sidebar:n,onCollapse:r,isHidden:o}=e;const{navbar:{hideOnScroll:c},docs:{sidebar:{hideable:i}}}=(0,_.p)();return a.createElement("div",{className:(0,l.A)(Z,c&&$,o&&J)},c&&a.createElement(A.A,{tabIndex:-1,className:Q}),a.createElement(O,{path:t,sidebar:n}),i&&a.createElement(T,{onClick:r}))}const te=a.memo(ee);var ne=n(5600),ae=n(9876);const le=e=>{let{sidebar:t,path:n}=e;const r=(0,ae.M)();return a.createElement("ul",{className:(0,l.A)(o.G.docs.docSidebarMenu,"menu__list")},a.createElement(q,{items:t,activePath:n,onItemClick:e=>{"category"===e.type&&e.href&&r.toggle(),"link"===e.type&&r.toggle()},level:1}))};function re(e){return a.createElement(ne.GX,{component:le,props:e})}const oe=a.memo(re);function ce(e){const t=(0,g.l)(),n="desktop"===t||"ssr"===t,l="mobile"===t;return a.createElement(a.Fragment,null,n&&a.createElement(te,e),l&&a.createElement(oe,e))}const ie="expandButton_m80_",se="expandButtonIcon_BlDH";function de(e){let{toggleSidebar:t}=e;return a.createElement("div",{className:ie,title:(0,u.T)({id:"theme.docs.sidebar.expandButtonTitle",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),"aria-label":(0,u.T)({id:"theme.docs.sidebar.expandButtonAriaLabel",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),tabIndex:0,role:"button",onKeyDown:t,onClick:t},a.createElement(k,{className:se}))}const me={docSidebarContainer:"docSidebarContainer_b6E3",docSidebarContainerHidden:"docSidebarContainerHidden_b3ry",sidebarViewport:"sidebarViewport_Xe31"};function ue(e){let{children:t}=e;const n=(0,d.t)();return a.createElement(a.Fragment,{key:n?.name??"noSidebar"},t)}function be(e){let{sidebar:t,hiddenSidebarContainer:n,setHiddenSidebarContainer:r}=e;const{pathname:c}=(0,v.zy)(),[i,s]=(0,a.useState)(!1),d=(0,a.useCallback)((()=>{i&&s(!1),r((e=>!e))}),[r,i]);return a.createElement("aside",{className:(0,l.A)(o.G.docs.docSidebarContainer,me.docSidebarContainer,n&&me.docSidebarContainerHidden),onTransitionEnd:e=>{e.currentTarget.classList.contains(me.docSidebarContainer)&&n&&s(!0)}},a.createElement(ue,null,a.createElement("div",{className:(0,l.A)(me.sidebarViewport,i&&me.sidebarViewportHidden)},a.createElement(ce,{sidebar:t,path:c,onCollapse:d,isHidden:i}),i&&a.createElement(de,{toggleSidebar:d}))))}const pe={docMainContainer:"docMainContainer_gTbr",docMainContainerEnhanced:"docMainContainerEnhanced_Uz_u",docItemWrapperEnhanced:"docItemWrapperEnhanced_czyv"};function he(e){let{hiddenSidebarContainer:t,children:n}=e;const r=(0,d.t)();return a.createElement("main",{className:(0,l.A)(pe.docMainContainer,(t||!r)&&pe.docMainContainerEnhanced)},a.createElement("div",{className:(0,l.A)("container padding-top--md padding-bottom--lg",pe.docItemWrapper,t&&pe.docItemWrapperEnhanced)},n))}const Ee="docPage__5DB",fe="docsWrapper_BCFX";function ve(e){let{children:t}=e;const n=(0,d.t)(),[l,r]=(0,a.useState)(!1);return a.createElement(m.A,{wrapperClassName:fe},a.createElement(f,null),a.createElement("div",{className:Ee},n&&a.createElement(be,{sidebar:n.items,hiddenSidebarContainer:l,setHiddenSidebarContainer:r}),a.createElement(he,{hiddenSidebarContainer:l},t)))}var ge=n(1774),_e=n(1463);function Ae(e){const{versionMetadata:t}=e;return a.createElement(a.Fragment,null,a.createElement(_e.A,{version:t.version,tag:(0,c.tU)(t.pluginId,t.version)}),a.createElement(r.be,null,t.noIndex&&a.createElement("meta",{name:"robots",content:"noindex, nofollow"})))}function Ce(e){const{versionMetadata:t}=e,n=(0,i.mz)(e);if(!n)return a.createElement(ge.default,null);const{docElement:c,sidebarName:m,sidebarItems:u}=n;return a.createElement(a.Fragment,null,a.createElement(Ae,e),a.createElement(r.e3,{className:(0,l.A)(o.G.wrapper.docsPages,o.G.page.docsDocPage,e.versionMetadata.className)},a.createElement(s.n,{version:t},a.createElement(d.V,{name:m,items:u},a.createElement(ve,null,c)))))}},1774:(e,t,n)=>{n.r(t),n.d(t,{default:()=>c});var a=n(6540),l=n(1312),r=n(1003),o=n(9408);function c(){return a.createElement(a.Fragment,null,a.createElement(r.be,{title:(0,l.T)({id:"theme.NotFound.title",message:"Page Not Found"})}),a.createElement(o.A,null,a.createElement("main",{className:"container margin-vert--xl"},a.createElement("div",{className:"row"},a.createElement("div",{className:"col col--6 col--offset-3"},a.createElement("h1",{className:"hero__title"},a.createElement(l.A,{id:"theme.NotFound.title",description:"The title of the 404 page"},"Page Not Found")),a.createElement("p",null,a.createElement(l.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page"},"We could not find what you were looking for.")),a.createElement("p",null,a.createElement(l.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page"},"Please contact the owner of the site that linked you to the original URL and let them know their link is broken.")))))))}},2252:(e,t,n)=>{n.d(t,{n:()=>o,r:()=>c});var a=n(6540),l=n(9532);const r=a.createContext(null);function o(e){let{children:t,version:n}=e;return a.createElement(r.Provider,{value:n},t)}function c(){const e=(0,a.useContext)(r);if(null===e)throw new l.dV("DocsVersionProvider");return e}}}]); \ No newline at end of file diff --git a/assets/js/1c85f73a.6cae0578.js b/assets/js/1c85f73a.6cae0578.js new file mode 100644 index 0000000..2035bfe --- /dev/null +++ b/assets/js/1c85f73a.6cae0578.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[4531],{5680:(e,t,a)=>{a.d(t,{xA:()=>u,yg:()=>h});var n=a(6540);function l(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function r(e){for(var t=1;t=0||(l[a]=e[a]);return l}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(l[a]=e[a])}return l}var s=n.createContext({}),p=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):r(r({},t),e)),a},u=function(e){var t=p(e.components);return n.createElement(s.Provider,{value:t},e.children)},d="mdxType",g={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var a=e.components,l=e.mdxType,i=e.originalType,s=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),d=p(a),m=l,h=d["".concat(s,".").concat(m)]||d[m]||g[m]||i;return a?n.createElement(h,r(r({ref:t},u),{},{components:a})):n.createElement(h,r({ref:t},u))}));function h(e,t){var a=arguments,l=t&&t.mdxType;if("string"==typeof e||l){var i=a.length,r=new Array(i);r[0]=m;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[d]="string"==typeof e?e:l,r[1]=o;for(var p=2;p{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var n=a(8168),l=(a(6540),a(5680));const i={id:"multipart-requests",title:"Multipart Form Request Extension",sidebar_label:"File Uploads & Batching",sidebar_position:0},r=void 0,o={unversionedId:"server-extensions/multipart-requests",id:"server-extensions/multipart-requests",title:"Multipart Form Request Extension",description:".NET 8+",source:"@site/docs/server-extensions/multipart-requests.md",sourceDirName:"server-extensions",slug:"/server-extensions/multipart-requests",permalink:"/docs/server-extensions/multipart-requests",draft:!1,tags:[],version:"current",sidebarPosition:0,frontMatter:{id:"multipart-requests",title:"Multipart Form Request Extension",sidebar_label:"File Uploads & Batching",sidebar_position:0},sidebar:"tutorialSidebar",previous:{title:"Entity Framework",permalink:"/docs/development/entity-framework"},next:{title:"How it Works",permalink:"/docs/reference/how-it-works"}},s={},p=[{value:"Multipart Request Specification",id:"multipart-request-specification",level:2},{value:"Enable The Extension",id:"enable-the-extension",level:2},{value:"File Uploads",id:"file-uploads",level:2},{value:"A Basic Controller",id:"a-basic-controller",level:3},{value:"Handling Arrays of Files",id:"handling-arrays-of-files",level:3},{value:"Handling an Unknown Number of Files",id:"handling-an-unknown-number-of-files",level:3},{value:"Skipping Array Indexes",id:"skipping-array-indexes",level:3},{value:"File Uploads on Batched Queries",id:"file-uploads-on-batched-queries",level:3},{value:"FileUpload Scalar",id:"fileupload-scalar",level:3},{value:"Opening a File Stream",id:"opening-a-file-stream",level:3},{value:"Custom File Handling",id:"custom-file-handling",level:3},{value:"Timeouts and File Uploads",id:"timeouts-and-file-uploads",level:3},{value:"Batch Queries",id:"batch-queries",level:2},{value:"Processing a Batch of Queries",id:"processing-a-batch-of-queries",level:3},{value:"Processing a Single Query",id:"processing-a-single-query",level:3},{value:"Batch Execution Order is Never Guaranteed",id:"batch-execution-order-is-never-guaranteed",level:3},{value:"Configuration",id:"configuration",level:2},{value:"MapMode",id:"mapmode",level:3},{value:"RequestMode",id:"requestmode",level:3},{value:"MaxFileCount",id:"maxfilecount",level:3},{value:"MaxBlobCount",id:"maxblobcount",level:3},{value:"RegisterMultipartRequestHttpProcessor",id:"registermultipartrequesthttpprocessor",level:3},{value:"RequireMultipartRequestHttpProcessor",id:"requiremultipartrequesthttpprocessor",level:3},{value:"Demo Project",id:"demo-project",level:2}],u={toc:p};function d(e){let{components:t,...a}=e;return(0,l.yg)("wrapper",(0,n.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("span",{className:"pill"},".NET 8+"),(0,l.yg)("h2",{id:"multipart-request-specification"},"Multipart Request Specification"),(0,l.yg)("p",null,"GraphQL ASP.NET provides built in support for batch query processing and file uploads via an implementation of the ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"GraphQL Multipart Request Specification"),"."),(0,l.yg)("p",null,"This extension requires a minimum version of ",(0,l.yg)("inlineCode",{parentName:"p"},"v1.2.0")," of the main library and you must target .NET 8 or later. This extension will not work with the .NET standard implementation."),(0,l.yg)("admonition",{type:"info"},(0,l.yg)("p",{parentName:"admonition"},"This document covers how to submit a batch query and upload files that conform to the above specification. It provides sample curl requests that would be accepted for the given sample code but does not explain in detail the various form fields required to complete a request. It is highly recommended to use a ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"supported client")," when enabling this server extension.")),(0,l.yg)("h2",{id:"enable-the-extension"},"Enable The Extension"),(0,l.yg)("p",null,"While the multipart form extension is shipped as part of the main library it is disabled by default and must be explicitly enabled on each schema. "),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp",metastring:"title='Register the Server Extension'",title:"'Register",the:!0,Server:!0,"Extension'":!0},"// Startup Code\n// other code omitted for brevity\nservices.AddGraphQL(options => {\n options.AddMultipartRequestSupport();\n});\n")),(0,l.yg)("h2",{id:"file-uploads"},"File Uploads"),(0,l.yg)("p",null,"Files submitted on a post request are automatically routed to your controllers as a custom scalar. Out of the box, any .NET ",(0,l.yg)("inlineCode",{parentName:"p"},"IFormFile")," and any form field not explicitly declared by the specification will be converted into a file scalar and can be mapped into your query's variables collection. "),(0,l.yg)("h3",{id:"a-basic-controller"},"A Basic Controller"),(0,l.yg)("p",null,"Files are received as a special C# class named ",(0,l.yg)("inlineCode",{parentName:"p"},"FileUpload"),". Use it in your action methods like you would any other scalar (e.g. int, string etc.). Note that even though it is a class, as opposed to a primitative, GraphQL still handles it as a scalar; much in the same way ",(0,l.yg)("inlineCode",{parentName:"p"},"Uri")," is also considered a scalar."),(0,l.yg)("span",{style:{color:"red"}},"Warning: Be sure to dispose of the file stream when you are finished with it."),(0,l.yg)("br",null),(0,l.yg)("br",null),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp",metastring:"title=ExampleFile Upload Controller",title:"ExampleFile",Upload:!0,Controller:!0},'public class FileUploadController : GraphController\n{\n [MutationRoot("singleFileUpload")]\n // highlight-next-line\n public async Task UploadFile(FileUpload fileRef)\n {\n using var stream = await fileRef.OpenFileAsync();\n // do something with the file stream\n\n return 0;\n }\n}\n')),(0,l.yg)("p",null,"The scalar in your schema is named ",(0,l.yg)("inlineCode",{parentName:"p"},"Upload")," per the specification. Be sure to declare your graphql variables as an ",(0,l.yg)("inlineCode",{parentName:"p"},"Upload")," type to indicate an uploaded file."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Use the Upload graph type for variables"',title:'"Use',the:!0,Upload:!0,graph:!0,type:!0,for:!0,'variables"':!0},"mutation ($file: Upload) { \n singleFileUpload(file: $file) \n}\n")),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-bash",metastring:'title="Sample curl Query"',title:'"Sample',curl:!0,'Query"':!0},'curl localhost:3000/graphql \\\n # highlight-next-line\n -F operations=\'{ "query": "mutation ($file: Upload) { singleFileUpload(file: $file) }", "variables": { "file": null } }\' \\\n -F map=\'{ "0": ["variables.file"] }\' \\\n -F 0=@a.txt\n')),(0,l.yg)("h3",{id:"handling-arrays-of-files"},"Handling Arrays of Files"),(0,l.yg)("p",null,"Arrays of files work just like any other list in GraphQL. When declaring the map variable for the multi-part request, be sure\nto indicate which index you are mapping the file to. The extension will not magically append files to an array. Each mapped file must explicitly declare the element index in an array where it is being placed."),(0,l.yg)("span",{style:{color:"red"}},"Warning: Be sure to dispose of each file stream when you are finished with it."),(0,l.yg)("br",null),(0,l.yg)("br",null),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Example File Upload Controller"',title:'"Example',File:!0,Upload:!0,'Controller"':!0},'using GraphQL.AspNet.ServerExtensions.MultipartRequests;\n\npublic class FileUploadController : GraphController\n{\n [MutationRoot("multiFileUpload")]\n // highlight-next-line\n public async Task UploadFile(IEnumerable files)\n {\n foreach(var file in files)\n {\n using var stream = await fileRef.OpenFileAsync();\n // do something with each file stream\n }\n\n return 0;\n }\n}\n')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Declaring a list of files on a graphql query"',title:'"Declaring',a:!0,list:!0,of:!0,files:!0,on:!0,graphql:!0,'query"':!0},"# highlight-next-line\nmutation ($files: [Upload]) { \n multiFileUpload(file: $files) \n}\n")),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-bash",metastring:'title="Sample Curl"',title:'"Sample','Curl"':!0},'curl localhost:3000/graphql \\\n# highlight-next-line\n -F operations=\'{ "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", "variables": { "files": [null, null] } }\' \\ \n -F map=\'{ "firstFile": ["variables", "files", 0], "secondFile": ["variables", "files", 1] }\' \\\n -F firstFile=@a.txt\n -F secondFile=@b.txt\n')),(0,l.yg)("h3",{id:"handling-an-unknown-number-of-files"},"Handling an Unknown Number of Files"),(0,l.yg)("p",null,"There are scenarios where you may ask your users to select a few files to upload without knowing how many they might choose. As long each declaration in your ",(0,l.yg)("inlineCode",{parentName:"p"},"map")," field points to a position that ",(0,l.yg)("em",{parentName:"p"},"could be")," a valid index, the target array will be resized accordingly. "),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-bash",metastring:'title="Adding Two Files"',title:'"Adding',Two:!0,'Files"':!0},'curl localhost:3000/graphql \\\n -F operations=\'{ \n "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", \n # highlight-next-line\n "variables": { "files": [] } }\' \\\n -F map=\'{ "firstFile": ["variables", "files", 0], "secondFile": ["variables", "files", 1] }\' \\\n -F firstFile=@a.txt\n -F secondFile=@b.txt\n')),(0,l.yg)("p",null,"In the above example, the ",(0,l.yg)("inlineCode",{parentName:"p"},"files")," array will be automatically expanded to include indexes 0 and 1 as requested by the ",(0,l.yg)("inlineCode",{parentName:"p"},"map"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="Resultant Operations Object"',title:'"Resultant',Operations:!0,'Object"':!0},'{\n "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", \n "variables": { "files": [, ] }\n}\n')),(0,l.yg)("h3",{id:"skipping-array-indexes"},"Skipping Array Indexes"),(0,l.yg)("p",null,"If you skip any indexes in your ",(0,l.yg)("inlineCode",{parentName:"p"},"map")," declaration, the target array will be expanded to to include the out of sequence index. This can produce null values in your array and result in an error if your variable declaration does not allow nulls."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-bash",metastring:'title="Adding One File To Index 5"',title:'"Adding',One:!0,File:!0,To:!0,Index:!0,'5"':!0},'# Only one file is supplied but its mapped to index 5\n# the final array at \'variables.files` will be 6 elements long with 5 null elements.\ncurl localhost:3000/graphql \\\n -F operations=\'{ \n "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", \n # highlight-next-line\n "variables": { "files": [] } }\' \\\n # highlight-next-line\n -F map=\'{ "firstFile": ["variables", "files", 5] }\' \\\n -F firstFile=@a.txt\n')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="Resultant Operations Object"',title:'"Resultant',Operations:!0,'Object"':!0},'{\n "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", \n // highlight-next-line\n "variables": { "files": [null, null, null, null, null, ] }\n}\n')),(0,l.yg)("h3",{id:"file-uploads-on-batched-queries"},"File Uploads on Batched Queries"),(0,l.yg)("p",null,"File uploads work in conjunction with batched queries. When processing a multi-part request as a batch, prefix each of the mapped object-path references with an index of the batch you want the file to apply to. As you might guess this is usually handled by a supported client automatically."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-bash",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'curl localhost:3000/graphql \\\n -F operations=\'[\n { "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", "variables": { "files": [] } },\n { "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", "variables": { "files": [] } },\n ]\' \\\n # highlight-next-line\n -F map=\'{ "firstFile": [0, "variables", "files", 0], "secondFile": [1, "variables", "files", 0] }\' \\\n -F firstFile=@a.txt\n -F secondFile=@b.txt\n')),(0,l.yg)("h3",{id:"fileupload-scalar"},"FileUpload Scalar"),(0,l.yg)("p",null,"The following properties on the ",(0,l.yg)("inlineCode",{parentName:"p"},"FileUpload")," C# class can be useful:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"FileName")," - The name of the file that was uploaded. This property will be null if a non-file form field is referenced."),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"MapKey")," - The key value used to place this file within a variable collection. This is usually the form field name on the multi-part request."),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"ContentType")," - The supplied ",(0,l.yg)("inlineCode",{parentName:"li"},"content-type")," value sent with the file. This value will be null for non-file fields."),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"Headers")," - A collection of all the headers provided with the uploaded file. This value will be null for non-file fields.")),(0,l.yg)("h3",{id:"opening-a-file-stream"},"Opening a File Stream"),(0,l.yg)("p",null,"When opening a file stream you need to await a call ",(0,l.yg)("inlineCode",{parentName:"p"},"FileUpload.OpenFileAsync()"),". This method is an abstraction on top of an internal wrapper that standardizes file streams across all implementions (see below for implementing your own file processor). When working with the standard ",(0,l.yg)("inlineCode",{parentName:"p"},"IFormFile")," interface provided by ASP.NET this call is a simple wrapper for ",(0,l.yg)("inlineCode",{parentName:"p"},"IFormFile.OpenReadStream()"),". "),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp",metastring:"title=ExampleFile Upload Controller",title:"ExampleFile",Upload:!0,Controller:!0},'using GraphQL.AspNet.ServerExtensions.MultipartRequests;\n\npublic class FileUploadController : GraphController\n{\n [MutationRoot("singleFileUpload")]\n public async Task UploadFile(FileUpload fileRef)\n {\n // do something with the file stream\n // it is your responsibility to close and dispose of it\n // highlight-next-line\n using var stream = await fileRef.OpenFileStreamAsync(); \n\n return 0;\n }\n}\n')),(0,l.yg)("h3",{id:"custom-file-handling"},"Custom File Handling"),(0,l.yg)("p",null,"By default, this extension splits the POST request on an ",(0,l.yg)("inlineCode",{parentName:"p"},"HttpContext")," and presents the different parts to the query engine in a manner it expects. This means that any uploaded files are consumed under the hood as ASP.NET's built in ",(0,l.yg)("inlineCode",{parentName:"p"},"IFormFile")," interface. While this is fine for most users, it can be troublesome with regard to timeouts and large file requests. Also, there may be scenarios where you want to save off files prior to executing a query or perhaps you'll need to process the file stream multiple times. "),(0,l.yg)("p",null,"You can implement and register your own ",(0,l.yg)("inlineCode",{parentName:"p"},"IFileUploadScalarValueMaker")," to add custom processing logic for each file or blob BEFORE graphql gets ahold of it. For instance, some users may want to write incoming files to local disk or cloud storage and present GraphQL with a stream that points to that local reference, rather than the file reference on the http request."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"}," public interface IFileUploadScalarValueMaker\n{\n // This overload is used when processing traditional files received as part of a \n // multi-part form through ASP.NET's HttpContext\n Task CreateFileScalar(IFormFile aspNetFile);\n\n // This overload is used when processing data received on a \n // multi-part form field rather than as a formal file upload.\n Task CreateFileScalar(string mapKey, byte[] blobData);\n}\n")),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Register Your Custom Value Maker"',title:'"Register',Your:!0,Custom:!0,Value:!0,'Maker"':!0},"// other startup code omitted\n\n// register your scalar value maker BEFORE calling .AddGraphQL\nservices.AddSingleton();\n\nservices.AddGraphQL(options => {\n options.AddMultipartRequestSupport();\n});\n")),(0,l.yg)("admonition",{type:"tip"},(0,l.yg)("p",{parentName:"admonition"},"You can inherit from ",(0,l.yg)("inlineCode",{parentName:"p"},"FileUpload")," and extend it as needed on your custom maker. However, be sure to declare your method parameters as ",(0,l.yg)("inlineCode",{parentName:"p"},"FileUpload")," in your controllers so that GraphQL knows what scalar you are requesting.")),(0,l.yg)("p",null,"Take a look at the ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet/ServerExtensions/MultipartRequests/Engine/TypeMakers/DefaultFileUploadScalarValueMaker.cs"},"default upload scalar value maker")," for some helpful details when trying to implement your own."),(0,l.yg)("h3",{id:"timeouts-and-file-uploads"},"Timeouts and File Uploads"),(0,l.yg)("p",null,"Be mindful of any query timeouts you have set for your schemas. ASP.NET may start processing your query before all the file contents are made available to the server as long as it has the initial POST request. This also means that your graphql queries may start executing before the file contents arrive."),(0,l.yg)("p",null,"While this asysncronicty usually works to your advantage, allowing your queries to begin processing before all the files are uploaded to the server; you may find that your queries pause on ",(0,l.yg)("inlineCode",{parentName:"p"},".OpenFileStreamAsync()")," waiting for the file stream to become available if there is a network delay or a large file being uploaded. If you have a ",(0,l.yg)("a",{parentName:"p",href:"/docs/reference/schema-configuration#querytimeout"},"custom timeout")," configured for a schema, it may trigger while waiting for the file. Be sure to set your timeouts to a long enough period of time to avoid this scenario."),(0,l.yg)("h2",{id:"batch-queries"},"Batch Queries"),(0,l.yg)("h3",{id:"processing-a-batch-of-queries"},"Processing a Batch of Queries"),(0,l.yg)("p",null,'Provide an "operations" form field that represents an array of graphql requests, the engine will automatically detect the array and return an array of responses in the same order as they were received. Each query is processed asyncronously and independently. '),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-bash",metastring:'title="Example Batch Query"',title:'"Example',Batch:!0,'Query"':!0},'curl localhost:3000/graphql \\\n #highlight-start\n -F operations=\'[\n { "query": "query { findUser(lastName: \\"Smith\\") {firstName lastName} }" },\n { "query": "query { findUser(lastName: \\"Jones\\") {firstName lastName} }" },\n ]\' \\\n # highlight-end\n')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="Example Json Serialized Response"',title:'"Example',Json:!0,Serialized:!0,'Response"':!0},'[\n {\n "data": {\n "findUser": {\n "firstName": "Baily",\n "lastName": "Smith"\n }\n }\n },\n {\n "data": {\n "findUser": {\n "firstName": "Caleb",\n "lastName": "Jones"\n }\n }\n }\n]\n')),(0,l.yg)("h3",{id:"processing-a-single-query"},"Processing a Single Query"),(0,l.yg)("p",null,'Provide an "operations" form field that represents a single query and the engine will automatically detect and return a normal graphql response.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-bash",metastring:'title="Example Batch Query"',title:'"Example',Batch:!0,'Query"':!0},'curl localhost:3000/graphql \\\n #highlight-next-line\n -F operations=\'{ "query": "query { findUser(lastName: \\"Smith\\") {firstName lastName} }" }\' \\\n')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="Example Json Serialized Response"',title:'"Example',Json:!0,Serialized:!0,'Response"':!0},'{\n "data": {\n "findUser": {\n "firstName": "Baily",\n "lastName": "Smith"\n }\n }\n}\n')),(0,l.yg)("admonition",{type:"info"},(0,l.yg)("p",{parentName:"admonition"},"The extension is backwards compatible with standard graphql http request processing. If a request is recieved that is not a multi-part form POST request, normal graphql processing will occur.")),(0,l.yg)("h3",{id:"batch-execution-order-is-never-guaranteed"},"Batch Execution Order is Never Guaranteed"),(0,l.yg)("p",null,"While the order of the results is guaranteed to be the same order in which the queries were received, there is no guarantee that the queries are executed in any specific order. This means if you submit a batch of 5 requests, each requests may complete in a randomized order. If the same batch is submitted 3 times, its possible that the execution order will be different each time. "),(0,l.yg)("p",null,"For queries this is usally not an issue, but if you are batching mutations, make sure you don't have any unexpected dependencies or side effects between queries. If your controllers perform business logic against an existing object and that object is modified by more than of your mutations its highly possible that the state of the object may be unexpectedly modified in some executions but not in others. "),(0,l.yg)("p",null,"Take this controller and query:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Example Controller"',title:'"Example','Controller"':!0},'public class FileUploadController : GraphController\n{\n [MutationRoot("addMoney")]\n // highlight-next-line\n public async Task AddMoney(int itemId, int dollarsToAdd)\n {\n var item =await _service.RetrieveItem(itemId);\n item.CurrentTotal += dollarsToAdd;\n\n await _service.UpdateItem(item);\n return item;\n }\n}\n')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-bash",metastring:'title="Example Batch Query"',title:'"Example',Batch:!0,'Query"':!0},'curl localhost:3000/graphql \\\n #highlight-start\n -F operations=\'[\n { "query": "mutation { addMoney(itemId: 34, dollarsToAdd: 5) {id currentTotal} }" },\n { "query": "mutation { addThreeDollars(itemId: 34, , dollarsToAdd: 3) {id currentTotal} }" },\n ]\' \\\n # highlight-end\n')),(0,l.yg)("p",null,"Assuming that the initial value of ",(0,l.yg)("inlineCode",{parentName:"p"},"currentTotal")," was 0, all three of these responses are equally likely to occur depending on the order in which the execution engine decides to process the queries."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-json",metastring:"title=Sample Json Results",title:"Sample",Json:!0,Results:!0},'// When the queries are executed in declared order\n[\n {\n "data": {\n "addMoney": {\n "id": 34,\n "currentTotal": 5\n }\n }\n },\n {\n "data": {\n "addMoney": {\n "id": 34,\n "currentTotal": 8\n }\n }\n },\n]\n\n// When the queries are executed in reverse order\n[\n {\n "data": {\n "addMoney": {\n "id": 34,\n "currentTotal": 8\n }\n }\n },\n {\n "data": {\n "addMoney": {\n "id": 34,\n "currentTotal": 3\n }\n }\n },\n]\n\n// When the queries are executed simultaniously\n// The final result updated to the datastore is unknown\n[\n {\n "data": {\n "addMoney": {\n "id": 34,\n "currentTotal": 5\n }\n }\n },\n {\n "data": {\n "addMoney": {\n "id": 34,\n "currentTotal": 3\n }\n }\n },\n]\n')),(0,l.yg)("p",null,"Under the hood, the batch process will parse and submit all queries to the engine simultaniously and wait for them to finish before structuring a result object. "),(0,l.yg)("admonition",{type:"caution"},(0,l.yg)("p",{parentName:"admonition"},"Ensure there are no dependencies between queries in a batch. An expected order of execution is never guaranteed.")),(0,l.yg)("h2",{id:"configuration"},"Configuration"),(0,l.yg)("p",null,"There are several configuration settings specific to extension. They can all be toggled when the extension is registered. Each configuration is specific\nto the targeted schema."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp",metastring:"title='Configuring the Server Extension'",title:"'Configuring",the:!0,Server:!0,"Extension'":!0},"// Startup Code\n// other code omitted for brevity\nservices.AddGraphQL(options => {\n // highlight-start\n options.AddMultipartRequestSupport(mpOptions => {\n // set mpOptions here\n });\n // highlight-end\n});\n")),(0,l.yg)("h3",{id:"mapmode"},"MapMode"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage example\nmpOptions.MapMode = MultipartRequestMapHandlingMode.Default;\n")),(0,l.yg)("p",null,"A bitwise flag enumeration allowing the inclusion of different types of values for the ",(0,l.yg)("inlineCode",{parentName:"p"},"map")," field dictated by the specification. Both options are enabled by default."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Option"),(0,l.yg)("th",{parentName:"tr",align:null},"Description"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"AllowStringPaths")),(0,l.yg)("td",{parentName:"tr",align:null},"When enabled, the short-hand syntax for ",(0,l.yg)("inlineCode",{parentName:"td"},"object-path"),", which uses a dot-delimited string instead of an array to indicate a json path, is acceptable for a map value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"SplitDotDelimitedSingleElementArrays")),(0,l.yg)("td",{parentName:"tr",align:null},"When enabled, the extension will examine single element arrays and, if that element is a string, treat it as a string, allowing it to be split as a dot-delimited string if the option is enabled. When not enabled, single element arrays are treated as a single path value.")))),(0,l.yg)("h3",{id:"requestmode"},"RequestMode"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage example\nmpOptions.RequestMode = MultipartRequestMode.Default;\n")),(0,l.yg)("p",null,"A bitwise flag enumeration that controls which parts of the multi-part request extension are enabled. By default, both batch queries and file uploads are enabled."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Option"),(0,l.yg)("th",{parentName:"tr",align:null},"Description"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"FileUploads")),(0,l.yg)("td",{parentName:"tr",align:null},"When enabled, the server extension will process file uploads. When disabled, any included files or form fields treated as files will cause the request to be rejected.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"BatchQueries")),(0,l.yg)("td",{parentName:"tr",align:null},"When enabled, the extension will attempt to process properly formatted batch queries. When disabled, any attempt to submit a batch query will cause the request to be rejected.")))),(0,l.yg)("h3",{id:"maxfilecount"},"MaxFileCount"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage example\nmpOptions.MaxFileCount = 15;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"null")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"null"),", number")))),(0,l.yg)("p",null,"When set, the extension will process, at most, the indicated amount of files. If more files appear on the request than the value indicated the request is automatically rejected. By default this value is set to ",(0,l.yg)("inlineCode",{parentName:"p"},"null")," or no limit."),(0,l.yg)("h3",{id:"maxblobcount"},"MaxBlobCount"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage example\nmpOptions.MaxBlobCount = 15;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"null")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"null"),", number")))),(0,l.yg)("p",null,"When set, the extension will process, at most, the indicated amount of additional, non-spec form fields (e.g. additional text blobs). If more blobs appear on the request than the value indicated the request is automatically rejected. By default this value is set to ",(0,l.yg)("inlineCode",{parentName:"p"},"null")," or no limit."),(0,l.yg)("h3",{id:"registermultipartrequesthttpprocessor"},"RegisterMultipartRequestHttpProcessor"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage example\nmpOptions.RegisterMultipartRequestHttpProcessor = true;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"Determines if, when registering the extension, the default multipart http processor is registered. When set to true, the extension will attempt to replace any other registered http processor(i.e. the object that is handed an HttpContext via a route). When false, no processor is registered. You are expected to provide your own handling for multipart requests. The extension will always register its other required objects (the form parser, the custom scalar etc.)."),(0,l.yg)("h3",{id:"requiremultipartrequesthttpprocessor"},"RequireMultipartRequestHttpProcessor"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage example\nmpOptions.RequireMultipartRequestHttpProcessor = true;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"Determines if, when starting up the application, the extension will check that the required http processor is registered. When set to true, if the required processor is not registered a configuration exception will be thrown and the server will fail to start. This can be helpful when registering multiple extensions to ensure that a valid processor is registered such that multipart-form requests will be handled correctly."),(0,l.yg)("h2",{id:"demo-project"},"Demo Project"),(0,l.yg)("p",null,"See the ",(0,l.yg)("a",{parentName:"p",href:"/docs/reference/demo-projects"},"demo projects")," for a sample project utilizing ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/apollo-upload-client"},"jaydenseric's apollo-upload-client")," as a front end for performing file uploads against this extension."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2b3b1ca0.93ca4834.js b/assets/js/2b3b1ca0.93ca4834.js new file mode 100644 index 0000000..de6b9fd --- /dev/null +++ b/assets/js/2b3b1ca0.93ca4834.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[6667],{5680:(e,t,n)=>{n.d(t,{xA:()=>p,yg:()=>d});var a=n(6540);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=a.createContext({}),c=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=c(e.components);return a.createElement(l.Provider,{value:t},e.children)},u="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},h=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),u=c(n),h=r,d=u["".concat(l,".").concat(h)]||u[h]||m[h]||i;return n?a.createElement(d,o(o({ref:t},p),{},{components:n})):a.createElement(d,o({ref:t},p))}));function d(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,o=new Array(i);o[0]=h;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[u]="string"==typeof e?e:r,o[1]=s;for(var c=2;c{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>s,toc:()=>c});var a=n(8168),r=(n(6540),n(5680));const i={id:"multi-schema-support",title:"Multi-Schema Support",sidebar_label:"Multi-Schema Support",sidebar_position:5},o=void 0,s={unversionedId:"advanced/multi-schema-support",id:"advanced/multi-schema-support",title:"Multi-Schema Support",description:"GraphQL ASP.NET supports multiple schemas on the same server out of the box. Each schema is recognized by its concrete .NET type.",source:"@site/docs/advanced/multiple-schema.md",sourceDirName:"advanced",slug:"/advanced/multi-schema-support",permalink:"/docs/advanced/multi-schema-support",draft:!1,tags:[],version:"current",sidebarPosition:5,frontMatter:{id:"multi-schema-support",title:"Multi-Schema Support",sidebar_label:"Multi-Schema Support",sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"Action Results",permalink:"/docs/advanced/graph-action-results"},next:{title:"Structured Logging",permalink:"/docs/logging/structured-logging"}},l={},c=[{value:"Create a Custom Schema",id:"create-a-custom-schema",level:2},{value:"Register Each Schema",id:"register-each-schema",level:2},{value:"Disable Local Graph Entity Registration",id:"disable-local-graph-entity-registration",level:2}],p={toc:c};function u(e){let{components:t,...n}=e;return(0,r.yg)("wrapper",(0,a.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL ASP.NET supports multiple schemas on the same server out of the box. Each schema is recognized by its concrete .NET type. "),(0,r.yg)("h2",{id:"create-a-custom-schema"},"Create a Custom Schema"),(0,r.yg)("p",null,"To register multiple schemas you'll need to create your own class that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"ISchema"),". While it is possible to implement ",(0,r.yg)("inlineCode",{parentName:"p"},"ISchema")," directly, if you don't require any extra functionality in your schema its easier to just inherit from the default ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphSchema"),". Updating the ",(0,r.yg)("inlineCode",{parentName:"p"},"Name")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"Description")," is highly encouraged as the information is referenced in several different messages and can be very helpful in debugging."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Declaring Custom Schemas"',title:'"Declaring',Custom:!0,'Schemas"':!0},'// highlight-next-line\npublic class EmployeeSchema : GraphSchema\n{\n // The schema name may be referenced in some error messages\n // and log entries.\n public override string Name => "Employee Schema";\n\n // The description is publically available via introspection queries.\n public override string Description => "Employee Related Data";\n}\n\n// highlight-next-line\npublic class CustomerSchema : GraphSchema\n{\n public override string Name => "Customer Schema";\n public override string Description => "Customer Related Data";\n}\n')),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},"Implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"ISchema")," and its dependencies from scratch is not a trivial task and is beyond the scope of this documentation.")),(0,r.yg)("h2",{id:"register-each-schema"},"Register Each Schema"),(0,r.yg)("p",null,"Each schema can be registered using an overload of ",(0,r.yg)("inlineCode",{parentName:"p"},".AddGraphQL()")," during startup."),(0,r.yg)("p",null,"By default, the query handler will attempt to register a schema to ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql")," as its URL. You'll want to ensure that each schema has its own endpoint by updating individual routes as necessary. "),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Adding Multiple Schemas"',title:'"Adding',Multiple:!0,'Schemas"':!0},'var builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddGraphQL((options) =>\n{\n // highlight-next-line\n options.QueryHandler.Route = "/graphql_employees";\n // add assembly or graph type references here\n});\n\nbuilder.Services.AddGraphQL((options) =>\n{\n // highlight-next-line\n options.QueryHandler.Route = "/graphql_customers";\n // add assembly or graph type references here\n});\n\nvar app = builder.Build();\n\n// highlight-next-line\napp.UseGraphQL();\napp.Run();\n')),(0,r.yg)("admonition",{type:"note"},(0,r.yg)("p",{parentName:"admonition"},"Each schema ",(0,r.yg)("strong",{parentName:"p"},"must")," be configured to use its own endpoint.")),(0,r.yg)("h2",{id:"disable-local-graph-entity-registration"},"Disable Local Graph Entity Registration"),(0,r.yg)("p",null,"(optional) You may want to disable the registering of local graph entities (the entities in the startup assembly) on one or both schemas lest you want each schema to contain the same controllers and graph types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Startup Code"',title:'"Startup','Code"':!0},"// Optionally Disable Local Entity Registration\nservices.AddGraphQL(o => \n{\n // highlight-next-line\n o.AutoRegisterLocalEntities = false;\n});\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2f931191.79719bcb.js b/assets/js/2f931191.79719bcb.js new file mode 100644 index 0000000..f8cafaa --- /dev/null +++ b/assets/js/2f931191.79719bcb.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[4303],{5680:(e,r,t)=>{t.d(r,{xA:()=>p,yg:()=>y});var n=t(6540);function a(e,r,t){return r in e?Object.defineProperty(e,r,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[r]=t,e}function o(e,r){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);r&&(n=n.filter((function(r){return Object.getOwnPropertyDescriptor(e,r).enumerable}))),t.push.apply(t,n)}return t}function l(e){for(var r=1;r=0||(a[t]=e[t]);return a}(e,r);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var s=n.createContext({}),c=function(e){var r=n.useContext(s),t=r;return e&&(t="function"==typeof e?e(r):l(l({},r),e)),t},p=function(e){var r=c(e.components);return n.createElement(s.Provider,{value:r},e.children)},d="mdxType",u={inlineCode:"code",wrapper:function(e){var r=e.children;return n.createElement(n.Fragment,{},r)}},h=n.forwardRef((function(e,r){var t=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,p=i(e,["components","mdxType","originalType","parentName"]),d=c(t),h=a,y=d["".concat(s,".").concat(h)]||d[h]||u[h]||o;return t?n.createElement(y,l(l({ref:r},p),{},{components:t})):n.createElement(y,l({ref:r},p))}));function y(e,r){var t=arguments,a=r&&r.mdxType;if("string"==typeof e||a){var o=t.length,l=new Array(o);l[0]=h;var i={};for(var s in r)hasOwnProperty.call(r,s)&&(i[s]=r[s]);i.originalType=e,i[d]="string"==typeof e?e:a,l[1]=i;for(var c=2;c{t.r(r),t.d(r,{assets:()=>s,contentTitle:()=>l,default:()=>d,frontMatter:()=>o,metadata:()=>i,toc:()=>c});var n=t(8168),a=(t(6540),t(5680));const o={id:"graph-controller",title:"Graph Controller",sidebar_label:"GraphController",sidebar_position:4},l=void 0,i={unversionedId:"reference/graph-controller",id:"reference/graph-controller",title:"Graph Controller",description:"\u2705 See the section on Controllers & Actions for a detailed explination on how action methods work and how to declare them.",source:"@site/docs/reference/graph-controller.md",sourceDirName:"reference",slug:"/reference/graph-controller",permalink:"/docs/reference/graph-controller",draft:!1,tags:[],version:"current",sidebarPosition:4,frontMatter:{id:"graph-controller",title:"Graph Controller",sidebar_label:"GraphController",sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"Attributes",permalink:"/docs/reference/attributes"},next:{title:"GraphDirective",permalink:"/docs/reference/graph-directive"}},s={},c=[{value:"ModelState",id:"modelstate",level:2},{value:"Request",id:"request",level:2},{value:"Notable Items on the Request",id:"notable-items-on-the-request",level:3},{value:"User",id:"user",level:2},{value:"Schema",id:"schema",level:2}],p={toc:c};function d(e){let{components:r,...t}=e;return(0,a.yg)("wrapper",(0,n.A)({},p,t,{components:r,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"\u2705 See the section on ",(0,a.yg)("a",{parentName:"p",href:"/docs/controllers/actions"},"Controllers & Actions")," for a detailed explination on how action methods work and how to declare them."),(0,a.yg)("p",null,"The ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphController"),", from which all of your controllers inherit, is a core object used throughout graphql. This page details some lesser known and lesser used object referenced made available to each controller."),(0,a.yg)("h2",{id:"modelstate"},"ModelState"),(0,a.yg)("p",null,"The completed model state dictionary contains an entry for each validated parameter."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp"},"public class CharacterController : GraphController\n{\n [Query]\n public IGraphActionResult CreateCharacter(Character characterModel)\n {\n // highlight-next-line\n if(!this.ModelState.IsValid)\n return this.BadRequest(this.ModelState);\n\n //...\n }\n}\n")),(0,a.yg)("h2",{id:"request"},"Request"),(0,a.yg)("p",null,"The field request generated via the execution pipeline. It contains all the necessary information used by graphql to resolve the current field."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp"},"public class CharacterController : GraphController\n{\n [Query]\n public IGraphActionResult Hero(Episode episode = Episode.EMPIRE)\n {\n // highlight-next-line\n if(this.Request.Field.IsLeaf)\n {\n // ...\n }\n }\n}\n")),(0,a.yg)("h3",{id:"notable-items-on-the-request"},"Notable Items on the Request"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},(0,a.yg)("inlineCode",{parentName:"p"},"Request.Field"),": A reference to the graph field definition, on the target schema, for the field currently being resolved."),(0,a.yg)("ul",{parentName:"li"},(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},".TypeExpression"),": The type expression describing the return value of this field"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},".IsLeaf"),": Indicates whether this field returns a leaf value (enum or scalar) or an object."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},".Mode"),': Indicates the processing mode of this field (Batch or "per item")'),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},".FieldSource"),": Indicates what member type generated the field; property, method, action etc."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},".DataSource"),": The source data item being supplied to the field to be resolved."))),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},(0,a.yg)("inlineCode",{parentName:"p"},"Request.Items"),": A collection of key/value pairs accessible to all fields and directives in this individual request pipeline."))),(0,a.yg)("h2",{id:"user"},"User"),(0,a.yg)("p",null,"The User property contains the ",(0,a.yg)("inlineCode",{parentName:"p"},"ClaimsPrincipal")," created by ASP.NET when this request was authorized. "),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp"},'public class CharacterController : GraphController\n{\n [Query]\n public IGraphActionResult Hero(Episode episode = Episode.EMPIRE)\n {\n // highlight-next-line\n if(this.User.Identity.Name == "DebbieEast")\n {\n // ...\n }\n }\n}\n')),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},"See the section on ",(0,a.yg)("a",{parentName:"p",href:"/docs/controllers/authorization"},"authorization")," for more details on how users are authenticated and authorized to action methods.")),(0,a.yg)("h2",{id:"schema"},"Schema"),(0,a.yg)("p",null,"The ",(0,a.yg)("inlineCode",{parentName:"p"},"Schema")," property contains a reference to the singleton instance of the schema the current controller is resolving a field for. This object is considered read-only and should not be modified."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp"},"public class CharacterController : GraphController\n{\n [Query]\n public IGraphActionResult Hero(Episode episode = Episode.EMPIRE)\n {\n // highlight-next-line\n IObjectGraphType droidType = this.Schema.KnownTypes.FindGraphType(typeof(Droid), TypeKind.OBJECT);\n // ...\n }\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/34c61599.65b67f00.js b/assets/js/34c61599.65b67f00.js new file mode 100644 index 0000000..7af340e --- /dev/null +++ b/assets/js/34c61599.65b67f00.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[2477],{5680:(e,t,n)=>{n.d(t,{xA:()=>c,yg:()=>y});var r=n(6540);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function a(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=r.createContext({}),u=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):a(a({},t),e)),n},c=function(e){var t=u(e.components);return r.createElement(s.Provider,{value:t},e.children)},d="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},h=r.forwardRef((function(e,t){var n=e.components,o=e.mdxType,i=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),d=u(n),h=o,y=d["".concat(s,".").concat(h)]||d[h]||p[h]||i;return n?r.createElement(y,a(a({ref:t},c),{},{components:n})):r.createElement(y,a({ref:t},c))}));function y(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var i=n.length,a=new Array(i);a[0]=h;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[d]="string"==typeof e?e:o,a[1]=l;for(var u=2;u{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>a,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>u});var r=n(8168),o=(n(6540),n(5680));const i={id:"authorization",title:"Authorization",sidebar_label:"Authorization",sidebar_position:3},a=void 0,l={unversionedId:"controllers/authorization",id:"controllers/authorization",title:"Authorization",description:"Quick Examples",source:"@site/docs/controllers/authorization.md",sourceDirName:"controllers",slug:"/controllers/authorization",permalink:"/docs/controllers/authorization",draft:!1,tags:[],version:"current",sidebarPosition:3,frontMatter:{id:"authorization",title:"Authorization",sidebar_label:"Authorization",sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Field Paths",permalink:"/docs/controllers/field-paths"},next:{title:"Type Extensions",permalink:"/docs/controllers/type-extensions"}},s={},u=[{value:"Quick Examples",id:"quick-examples",level:2},{value:"Use of IAuthorizationService",id:"use-of-iauthorizationservice",level:2},{value:"When does Authorization Occur?",id:"when-does-authorization-occur",level:2},{value:"Field Authorizations",id:"field-authorizations",level:2},{value:"Field Authorization Failures are Obfuscated",id:"field-authorization-failures-are-obfuscated",level:3},{value:"Authorization on Execution Directives",id:"authorization-on-execution-directives",level:2},{value:"Authorization Methods",id:"authorization-methods",level:2},{value:"Performance Considerations",id:"performance-considerations",level:2}],c={toc:u};function d(e){let{components:t,...n}=e;return(0,o.yg)("wrapper",(0,r.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,o.yg)("h2",{id:"quick-examples"},"Quick Examples"),(0,o.yg)("p",null,"If you've wired up ASP.NET authorization before, you'll likely familiar with the ",(0,o.yg)("inlineCode",{parentName:"p"},"[Authorize]")," attribute and how its used to enforce security. "),(0,o.yg)("p",null,"GraphQL ASP.NET works the same way."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="General Authorization Check"',title:'"General',Authorization:!0,'Check"':!0},'public class BakeryController : GraphController\n{\n // highlight-next-line\n [Authorize]\n [MutationRoot("orderDonuts", typeof(CompletedDonutOrder))]\n public async Task OrderDonuts(DonutOrderModel order)\n {/*...*/}\n}\n')),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Restrict by Policy"',title:'"Restrict',by:!0,'Policy"':!0},'public class BakeryController : GraphController\n{\n // highlight-next-line\n [Authorize(Policy = "CustomerLoyaltyProgram")]\n [MutationRoot("orderDonuts", typeof(CompletedDonutOrder))]\n public async Task OrderDonuts(DonutOrderModel order)\n {/*...*/}\n}\n')),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Restrict by Role"',title:'"Restrict',by:!0,'Role"':!0},'public class BakeryController : GraphController\n{\n // highlight-next-line\n [Authorize(Roles = "Admin, Employee")]\n [MutationRoot("purchaseDough")]\n public async Task PurchaseDough(int kilosOfDough)\n {/*...*/}\n}\n')),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Multiple Authorization Requirements"',title:'"Multiple',Authorization:!0,'Requirements"':!0},'// The library supports nested policy and role checks at Controller and Action levels.\n// highlight-next-line\n[Authorize(Policy = "CurrentCustomer")]\npublic class BakeryController : GraphController\n{\n // The user would have to pass the CurrentCustomer policy\n // and the LoyaltyProgram policy to access the `orderDonuts` field\n\n // highlight-next-line\n [Authorize(Policy = "LoyaltyProgram")]\n [MutationRoot("orderDonuts", typeof(CompletedDonutOrder))]\n public async Task OrderDonuts(DonutOrderModel order)\n {/*...*/}\n}\n')),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Use of [AllowAnonymous]"',title:'"Use',of:!0,'[AllowAnonymous]"':!0},'[Authorize]\npublic class BakeryController : GraphController\n{\n [Authorize(Policy = "CustomerLoyaltyProgram")]\n [MutationRoot("orderDonuts", typeof(CompletedDonutOrder))]\n public async Task OrderDonuts(DonutOrderModel order)\n {/*...*/}\n\n // No Authorization checks on RetrieveDonutList\n // highlight-next-line\n [AllowAnonymous]\n [Mutation("donutList")]\n public async Task> RetrieveDonutList()\n {/*...*/}\n}\n')),(0,o.yg)("h2",{id:"use-of-iauthorizationservice"},"Use of IAuthorizationService"),(0,o.yg)("p",null,"Under the hood, GraphQL taps into your ",(0,o.yg)("inlineCode",{parentName:"p"},"IServiceProvider")," to obtain a reference to the ",(0,o.yg)("inlineCode",{parentName:"p"},"IAuthorizationService")," that gets created when you configure ",(0,o.yg)("inlineCode",{parentName:"p"},".AddAuthorization()")," at startup. Take a look at the ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Middleware/SchemaItemSecurity"},"Schema Item Security Pipeline Components")," for the full picture."),(0,o.yg)("h2",{id:"when-does-authorization-occur"},"When does Authorization Occur?"),(0,o.yg)("p",null,"GraphQL ASP.NET makes use of the result from ",(0,o.yg)("a",{parentName:"p",href:"https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction"},"ASP.NET's security pipeline"),". Whether you use Kerberos tokens, oauth2, username/password, API tokens or if you support 2-factor authentication or one-time-use passwords, GraphQL doesn't care. The entirety of your authentication and authorization scheme is executed by GraphQL, no special arrangements or configuration is needed."),(0,o.yg)("blockquote",null,(0,o.yg)("p",{parentName:"blockquote"},"GraphQL ASP.NET draws from your configured authentication/authorization solution.")),(0,o.yg)("p",null,"Execution directives and field resolutions are passed through the libraries internal ",(0,o.yg)("a",{parentName:"p",href:"../reference/how-it-works#middleware-pipelines"},"pipeline")," where securty is enforced as a series of middleware components before the respective resolvers are invoked. Should a requestor not be authorized for a given schema item they are informed via an error message and denied access to the item."),(0,o.yg)("h2",{id:"field-authorizations"},"Field Authorizations"),(0,o.yg)("p",null,"If a requestor is not authorized to a requested field a value of ",(0,o.yg)("inlineCode",{parentName:"p"},"null")," is used as the resolved value and an error message is recorded to the query results. "),(0,o.yg)("p",null,'Null propagation rules still apply to unauthorized fields meaning if the field cannot accept a null value, its propagated up the field chain potentially nulling out a parent or "parent of a parent" depending on your schema.'),(0,o.yg)("p",null,"By default, a single unauthorized field result does not necessarily kill an entire query, it depends on the structure of your object graph and the query being executed. When a field request is terminated any down-stream child fields are discarded immediately but sibling fields or unrelated ancestors continue to execute as normal."),(0,o.yg)("p",null,'Since this authorization occurs "per field" and not "per controller action" its possible to define the same security chain for POCO properties. This allows you to effectively deny access, by policy, to a single property of an instantiated object. Performing security checks for every field of data (especially in parent/child scenarios) has a performance cost though, especially for larger data sets. For most scenarios enforcing security at the controller level is sufficient.'),(0,o.yg)("h3",{id:"field-authorization-failures-are-obfuscated"},"Field Authorization Failures are Obfuscated"),(0,o.yg)("p",null,"When GraphQL denies a requestor access to a field a message naming the field path is added to the response. This message is generic on purpose. Suppose there was a query where the user requests the ",(0,o.yg)("inlineCode",{parentName:"p"},"allDonuts")," field but is denied access:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-graphql"}," {\n donut(id: 5) {\n name\n }\n allDonuts {\n name\n }\n\n }\n\n")),(0,o.yg)("p",null,"The result might look like this:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="Denied Field Access"',title:'"Denied',Field:!0,'Access"':!0},'{\n "errors": [\n // highlight-start\n { \n "message": "Access Denied to field [query]/allDonuts",\n "locations": [\n {\n "line": 7,\n "column": 3\n }\n ],\n "path": [\n "allDonuts"\n ],\n "extensions": {\n "code": "ACCESS_DENIED",\n "timestamp": "2022-12-22T22:22:25.017-07:00",\n "severity": "CRITICAL"\n }\n }\n // highlight-end\n ],\n "data": {\n "donut": {\n "name": "Super Mega Donut",\n },\n // highlight-next-line\n "allDonuts": null\n }\n}\n')),(0,o.yg)("admonition",{type:"tip"},(0,o.yg)("p",{parentName:"admonition"}," To view more details authorization failure reasons, such as specific policy failures, you'll need to expose exceptions on the request or turn on ",(0,o.yg)("a",{parentName:"p",href:"../logging/structured-logging"},"logging"),". "),(0,o.yg)("p",{parentName:"admonition"}," GraphQL automatically raises the ",(0,o.yg)("inlineCode",{parentName:"p"},"SchemaItemAuthorizationCompleted")," log event at a ",(0,o.yg)("inlineCode",{parentName:"p"},"Warning")," level when a security check fails.")),(0,o.yg)("h2",{id:"authorization-on-execution-directives"},"Authorization on Execution Directives"),(0,o.yg)("p",null,"Execution directives are applied to the ",(0,o.yg)("em",{parentName:"p"},"query document"),", before a query plan is created to fulfill the request. However, it is the query plan that determines which field resolvers should be called. As a result, execution directives have the potential to alter the document structure and change how a query plan might be structured. Because of this, not executing a query directive has the potential to cause a the expected query to be different than what the requestor intended. "),(0,o.yg)("p",null,"Therefore, if an execution directive fails authorization the query is rejected and not executed. The caller will receive an error message as part of the response indicating the unauthorized directive. Like field authorization failures, the message is obfuscated and contains only a generic message. You'll need to expose exception on the request or turn on logging to see additional details."),(0,o.yg)("h2",{id:"authorization-methods"},"Authorization Methods"),(0,o.yg)("p",null,"GraphQL ASP.NET supports two methods of applying the authorization rules out of the box."),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("p",{parentName:"li"},(0,o.yg)("inlineCode",{parentName:"p"},"PerField"),": Each field is authorized individually. If a query references some fields the user can access and some they cannot, those fields the user can access are resolved as expected. A ",(0,o.yg)("inlineCode",{parentName:"p"},"null")," value is assigned to the fields the user cannot access.")),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("p",{parentName:"li"},(0,o.yg)("inlineCode",{parentName:"p"},"PerRequest"),": All fields that require authorization are authorized at once. If the user is unauthorized on 1 or more fields the entire request is denied and not executed."))),(0,o.yg)("p",null,"Configure the authorization method at startup:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Startup"',title:'"Startup"'},"services.AddGraphQL(schemaOptions =>\n{\n schemaOptions.AuthorizationOptions.Method = AuthorizationMethod.PerRequest;\n});\n")),(0,o.yg)("h2",{id:"performance-considerations"},"Performance Considerations"),(0,o.yg)("p",null,"Authorization is not free. There is a minor, but real, performance cost to inspecting and evaluating policies on each field. This true regardless of yor choice of ",(0,o.yg)("inlineCode",{parentName:"p"},"PerField")," or ",(0,o.yg)("inlineCode",{parentName:"p"},"PerRequest")," authorization. Every secured field still needs to be evaluated, whether it is done up front or as the query progresses. In a REST query, you generally only secure your top-level controller methods, consider doing the same with your GraphQL queries."),(0,o.yg)("admonition",{type:"tip"},(0,o.yg)("p",{parentName:"admonition"},"Centralize your authorization checks to your controller methods. There is usually no need to apply ",(0,o.yg)("inlineCode",{parentName:"p"},"[Authorize]")," attributes to each and every method and property across your entire schema.")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/37bd4da2.dec9a7e0.js b/assets/js/37bd4da2.dec9a7e0.js new file mode 100644 index 0000000..bc54171 --- /dev/null +++ b/assets/js/37bd4da2.dec9a7e0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[570],{5680:(e,n,t)=>{t.d(n,{xA:()=>s,yg:()=>c});var a=t(6540);function i(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function r(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function l(e){for(var n=1;n=0||(i[t]=e[t]);return i}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}var p=a.createContext({}),d=function(e){var n=a.useContext(p),t=n;return e&&(t="function"==typeof e?e(n):l(l({},n),e)),t},s=function(e){var n=d(e.components);return a.createElement(p.Provider,{value:n},e.children)},y="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},h=a.forwardRef((function(e,n){var t=e.components,i=e.mdxType,r=e.originalType,p=e.parentName,s=o(e,["components","mdxType","originalType","parentName"]),y=d(t),h=i,c=y["".concat(p,".").concat(h)]||y[h]||u[h]||r;return t?a.createElement(c,l(l({ref:n},s),{},{components:t})):a.createElement(c,l({ref:n},s))}));function c(e,n){var t=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var r=t.length,l=new Array(r);l[0]=h;var o={};for(var p in n)hasOwnProperty.call(n,p)&&(o[p]=n[p]);o.originalType=e,o[y]="string"==typeof e?e:i,l[1]=o;for(var d=2;d{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>l,default:()=>y,frontMatter:()=>r,metadata:()=>o,toc:()=>d});var a=t(8168),i=(t(6540),t(5680));const r={id:"attributes",title:"Attributes",sidebar_label:"Attributes",sidebar_position:3},l=void 0,o={unversionedId:"reference/attributes",id:"reference/attributes",title:"Attributes",description:"This document contains an alphabetical reference of each of the class, property and method attributes used by GraphQL ASP.NET.",source:"@site/docs/reference/attributes.md",sourceDirName:"reference",slug:"/reference/attributes",permalink:"/docs/reference/attributes",draft:!1,tags:[],version:"current",sidebarPosition:3,frontMatter:{id:"attributes",title:"Attributes",sidebar_label:"Attributes",sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Global Configuration",permalink:"/docs/reference/global-configuration"},next:{title:"GraphController",permalink:"/docs/reference/graph-controller"}},p={},d=[{value:"ApplyDirective",id:"applydirective",level:2},{value:"BatchTypeExtension",id:"batchtypeextension",level:2},{value:"[BatchTypeExtension(typeToExtend, fieldName)]",id:"batchtypeextensiontypetoextend-fieldname",level:4},{value:"[BatchTypeExtension(typeToExtend, fieldName, returnType)]",id:"batchtypeextensiontypetoextend-fieldname-returntype",level:4},{value:"[BatchTypeExtension(typeToExtend, fieldName, unionName, unionTypeA, unionTypeB, additionalUnionTypes)]",id:"batchtypeextensiontypetoextend-fieldname-unionname-uniontypea-uniontypeb-additionaluniontypes",level:4},{value:"Deprecated",id:"deprecated",level:2},{value:"[Deprecated]",id:"deprecated-1",level:4},{value:"[Deprecated(reasonText)]",id:"deprecatedreasontext",level:4},{value:"Description",id:"description",level:2},{value:"[Description(text)]",id:"descriptiontext",level:4},{value:"DirectiveLocations",id:"directivelocations",level:2},{value:"[DirectiveLocations(directiveLocation)]",id:"directivelocationsdirectivelocation",level:4},{value:"FromGraphQL",id:"fromgraphql",level:2},{value:"[FromGraphQL(argumentName)]",id:"fromgraphqlargumentname",level:4},{value:"[FromGraphQL(TypeExpression = "Type!")]",id:"fromgraphqltypeexpression--type",level:4},{value:"GraphEnumValue",id:"graphenumvalue",level:2},{value:"[GraphEnumValue]",id:"graphenumvalue-1",level:4},{value:"[GraphEnumValue(name)]",id:"graphenumvaluename",level:4},{value:"GraphField",id:"graphfield",level:2},{value:"[GraphField]",id:"graphfield-1",level:4},{value:"[GraphField(name)]",id:"graphfieldname",level:4},{value:"[GraphField(TypeExpression = "Type!")]",id:"graphfieldtypeexpression--type",level:4},{value:"GraphRoot",id:"graphroot",level:2},{value:"[GraphRoot]",id:"graphroot-1",level:4},{value:"GraphRoute",id:"graphroute",level:2},{value:"GraphRoute(template)",id:"graphroutetemplate",level:4},{value:"GraphSkip",id:"graphskip",level:2},{value:"[GraphSkip]",id:"graphskip-1",level:4},{value:"GraphType",id:"graphtype",level:2},{value:"[GraphType(name)]",id:"graphtypename",level:4},{value:"[GraphType(name, inputName)]",id:"graphtypename-inputname",level:4},{value:"Mutation, MutationRoot, Query, QueryRoot",id:"mutation-mutationroot-query-queryroot",level:2},{value:"[Query(template)]",id:"querytemplate",level:4},{value:"[Query(returnType, params otherTypes)]",id:"queryreturntype-params-othertypes",level:4},{value:"[Query(template, returnType)]",id:"querytemplate-returntype",level:4},{value:"[Query(template, unionName, unionTypeA, unionTypeB, additionalUnionTypes)]",id:"querytemplate-unionname-uniontypea-uniontypeb-additionaluniontypes",level:4},{value:"PossibleTypes",id:"possibletypes",level:2},{value:"[PossibleTypes(typeof(TypeA), typeof(TypeB) ...)]",id:"possibletypestypeoftypea-typeoftypeb-",level:4},{value:"SpecifiedBy",id:"specifiedby",level:2},{value:"[SpecifiedBy(url)]",id:"specifiedbyurl",level:4},{value:"TypeExtension",id:"typeextension",level:2},{value:"[TypeExtension(typeToExtend, fieldName)]",id:"typeextensiontypetoextend-fieldname",level:4},{value:"[TypeExtension(typeToExtend, fieldName, returnType)]",id:"typeextensiontypetoextend-fieldname-returntype",level:4},{value:"[TypeExtension(typeToExtend, fieldName, unionName, unionTypeA, unionTypeB, additionalUnionTypes)]",id:"typeextensiontypetoextend-fieldname-unionname-uniontypea-uniontypeb-additionaluniontypes",level:4}],s={toc:d};function y(e){let{components:n,...t}=e;return(0,i.yg)("wrapper",(0,a.A)({},s,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"This document contains an alphabetical reference of each of the class, property and method attributes used by GraphQL ASP.NET."),(0,i.yg)("h2",{id:"applydirective"},"ApplyDirective"),(0,i.yg)("p",null,"Declares that a given type system directive should be applied to the target schema item (an object, a field, an enum etc.). See the page on ",(0,i.yg)("a",{parentName:"p",href:"/docs/advanced/directives#type-system-directives"},"type system directives")," for complete details on how to build your own. Directives can be applied by type, by name and with or without parameters."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class Person \n{\n // apply by registered system type\n // highlight-next-line\n [ApplyDirective(typeof(DeprecatedDirective))]\n public string FirstName{ get; set; }\n\n // apply by name, also with a reason parameter\n // highlight-next-line\n [ApplyDirective("deprecated", "Last Name is deprecated")]\n public string LastName{ get; set; }\n}\n')),(0,i.yg)("h2",{id:"batchtypeextension"},"BatchTypeExtension"),(0,i.yg)("p",null,"Declares a controller action method as a field on another graph type rather than a query or mutation action. All source items needing this field resolved will be resolved in a single field request.\nThe batch method must declare a parameter of ",(0,i.yg)("inlineCode",{parentName:"p"},"IEnumerable"),"."),(0,i.yg)("h4",{id:"batchtypeextensiontypetoextend-fieldname"},(0,i.yg)("inlineCode",{parentName:"h4"},"[BatchTypeExtension(typeToExtend, fieldName)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"typeToExtend")," - The graph type to which this field will be added"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"fieldName")," - The name to give to this field.")),(0,i.yg)("p",null,"Declares a batch type extension with the given field name. The return type of this field will be taken from the return type of the method. The return type of the method must be ",(0,i.yg)("inlineCode",{parentName:"p"},"IDictionary")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class HeroController : GraphController\n{\n // highlight-next-line\n [BatchTypeExtension(typeof(Human), "droids")]\n public IDictionary> Hero(IEnumerable humans)\n {\n //....\n }\n}\n')),(0,i.yg)("h4",{id:"batchtypeextensiontypetoextend-fieldname-returntype"},(0,i.yg)("inlineCode",{parentName:"h4"},"[BatchTypeExtension(typeToExtend, fieldName, returnType)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"typeToExtend")," - The graph type to which this field will be added"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"fieldName")," - The name to give to this field."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"returnType")," - The type of data returned from this field")),(0,i.yg)("p",null,"Declares a batch type extension return type explicitly allowing use of ",(0,i.yg)("inlineCode",{parentName:"p"},"IGraphActionResult")," and more importantly, ",(0,i.yg)("inlineCode",{parentName:"p"},"this.StartBatch()")," for generating\na batch collection result."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class HeroController : GraphController\n{\n // highlight-next-line\n [BatchTypeExtension(typeof(Human), "droids", typeof(IEnumerable))]\n public IGraphActionResult Hero(IEnumerable humans)\n {\n //....\n }\n}\n')),(0,i.yg)("h4",{id:"batchtypeextensiontypetoextend-fieldname-unionname-uniontypea-uniontypeb-additionaluniontypes"},(0,i.yg)("inlineCode",{parentName:"h4"},"[BatchTypeExtension(typeToExtend, fieldName, unionName, unionTypeA, unionTypeB, additionalUnionTypes)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"typeToExtend")," - The graph type to which this field will be added"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"fieldName")," - The name to give to this field."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"unionName")," - The name to give to the union in the object graph"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"unionTypeA")," - The first member type of the union (must be an object, not an interface)"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"unionTypeB")," - The second member type of the union (must be an object, not an interface)"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"additionalUnionTypes")," - N additional union types to declare as part of this union")),(0,i.yg)("p",null,"Declares the batch type extension as returning a union rather than a single specific data type."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class HeroController : GraphController\n{\n // highlight-next-line\n [BatchTypeExtension(typeof(Human), "bestFriend", "DroidOrHuman", typeof(Droid), typeof(Human))]\n public IGraphActionResult Hero(IEnumerable humans)\n {\n //....\n }\n}\n')),(0,i.yg)("h2",{id:"deprecated"},"Deprecated"),(0,i.yg)("p",null,"Indicates to any introspection queries that the field or action method is deprecated and due to be removed."),(0,i.yg)("h4",{id:"deprecated-1"},(0,i.yg)("inlineCode",{parentName:"h4"},"[Deprecated]")),(0,i.yg)("h4",{id:"deprecatedreasontext"},(0,i.yg)("inlineCode",{parentName:"h4"},"[Deprecated(reasonText)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"reasonText")," - The reason for the deprecation that is displayed in an introspection query.")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class CharacterController : GraphController\n{\n [Query]\n // highlight-next-line\n [Deprecated("Use the field SuperHero, this field will be removed soon")]\n public IGraphActionResult Hero(Episode episode = Episode.EMPIRE)\n {\n //....\n }\n}\n')),(0,i.yg)("h2",{id:"description"},"Description"),(0,i.yg)("p",null,"Adds a human-readable description to any type, interface, field, parameter, enum value etc."),(0,i.yg)("h4",{id:"descriptiontext"},(0,i.yg)("inlineCode",{parentName:"h4"},"[Description(text)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"text")," - The text to display in an introspection query.")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'[Description("A field containing information related to the characters of Star Wars")]\npublic class CharacterController : GraphController\n{\n [Query]\n // highlight-next-line\n [Description("The hero of a given Star Wars Episode (Default: EMPIRE)")]\n public IGraphActionResult Hero(Episode episode = Episode.EMPIRE)\n {\n //....\n }\n}\n')),(0,i.yg)("h2",{id:"directivelocations"},"DirectiveLocations"),(0,i.yg)("p",null,"A set of flags indicating where in a query document the given directive can be declared. Also serves to indicate which directive action\nmethod should be invoked for a particular location."),(0,i.yg)("h4",{id:"directivelocationsdirectivelocation"},(0,i.yg)("inlineCode",{parentName:"h4"},"[DirectiveLocations(directiveLocation)]")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public sealed class AllowFragment : GraphDirective\n{\n // highlight-next-line\n [DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)]\n public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument)\n {\n return ifArgument ? this.Ok() : this.Cancel();\n }\n}\n')),(0,i.yg)("h2",{id:"fromgraphql"},"FromGraphQL"),(0,i.yg)("p",null,"Indicates additional or non-standard settings related to the method parameter its attached to. Can be used for controller action methods\nand directive action methods."),(0,i.yg)("h4",{id:"fromgraphqlargumentname"},(0,i.yg)("inlineCode",{parentName:"h4"},"[FromGraphQL(argumentName)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"argumentName"),": The name of this parameter in the object graph")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class CharacterController : GraphController\n{\n [Query]\n // highlight-next-line \n public IGraphActionResult Hero([FromGraphQL("id")] int heroId)\n {\n //....\n }\n}\n')),(0,i.yg)("h4",{id:"fromgraphqltypeexpression--type"},(0,i.yg)("inlineCode",{parentName:"h4"},'[FromGraphQL(TypeExpression = "Type!")]')),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"TypeExpression"),": A custom type expression, in query syntax language, to declare explicit nullability and list rules for this parameter.")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class CharacterController : GraphController\n{\n [Query]\n // highlight-next-line\n public IGraphActionResult Hero([FromGraphQL(TypeExpression = "Type!")] string heroId)\n {\n //....\n }\n}\n')),(0,i.yg)("h2",{id:"graphenumvalue"},"GraphEnumValue"),(0,i.yg)("p",null,"Acts to explicitly declare an enumeration value as being exposed on an enumeration graph type."),(0,i.yg)("h4",{id:"graphenumvalue-1"},(0,i.yg)("inlineCode",{parentName:"h4"},"[GraphEnumValue]")),(0,i.yg)("h4",{id:"graphenumvaluename"},(0,i.yg)("inlineCode",{parentName:"h4"},"[GraphEnumValue(name)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"name"),": the name to use in the object graph for this enum value.")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public enum Episode\n{\n NewHope,\n Empire,\n\n // highlight-next-line\n [GraphEnumValue("Jedi")]\n ReturnOfTheJedi,\n}\n')),(0,i.yg)("h2",{id:"graphfield"},"GraphField"),(0,i.yg)("p",null,"Acts to explicitly declare a method or property as being part of a graph type."),(0,i.yg)("h4",{id:"graphfield-1"},(0,i.yg)("inlineCode",{parentName:"h4"},"[GraphField]")),(0,i.yg)("h4",{id:"graphfieldname"},(0,i.yg)("inlineCode",{parentName:"h4"},"[GraphField(name)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"name")," - The name of this field as it should appear in the object graph to be queried")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class Human\n{\n public int Id{get; set; }\n\n // highlight-next-line\n [GraphField("name")]\n public string FullName { get; set; }\n}\n')),(0,i.yg)("h4",{id:"graphfieldtypeexpression--type"},(0,i.yg)("inlineCode",{parentName:"h4"},'[GraphField(TypeExpression = "Type!")]')),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"TypeExpression")," - Define a custom type expression; useful in setting a normally optional input field (such as a string or other object) to being required. Supply the type expression as a valid graphql syntax type expression. ")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class Human\n{\n public int Id{get; set; }\n\n // highlight-next-line\n [GraphField(TypeExpression = "Type!")]\n public Employer Boss { get; set; }\n}\n')),(0,i.yg)("h2",{id:"graphroot"},"GraphRoot"),(0,i.yg)("p",null,"Indicates that the controller should not attempt to register a virtual field for itself and that all methods should be extended off the their respective root types."),(0,i.yg)("h4",{id:"graphroot-1"},(0,i.yg)("inlineCode",{parentName:"h4"},"[GraphRoot]")),(0,i.yg)("div",{class:"sideBySideCode hljs"},(0,i.yg)("div",null,(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},"// highlight-next-line\n[GraphRoot]\npublic class HeroController : GraphController\n{\n [Query]\n public Human Hero(Episode episode)\n {\n //..\n }\n}\n"))),(0,i.yg)("div",null,(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"# GraphQL Query\nquery {\n hero(episode: EMPIRE) {\n name\n homePlanet\n }\n}\n")))),(0,i.yg)("h2",{id:"graphroute"},"GraphRoute"),(0,i.yg)("p",null,"Indicates a field path in each root graph type where this controller should append its action methods."),(0,i.yg)("h4",{id:"graphroutetemplate"},"[GraphRoute(template)]"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"template")," - A set of ",(0,i.yg)("inlineCode",{parentName:"li"},"/")," separated path segments representing a nested set of fields where the controller should reside.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},'"[controller]"')," meta tag can be used and will be replaced by the controller name at runtime.")))),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'// highlight-next-line\n[GraphRoute("starWars/characters")]\npublic class HeroController : GraphController\n{\n [Query]\n public Human Hero(Episode episode)\n {\n //..\n }\n}\n')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query {\n starWars {\n characters {\n hero(episode: EMPIRE) {\n name\n homePlanet\n }\n }\n }\n}\n")),(0,i.yg)("h2",{id:"graphskip"},"GraphSkip"),(0,i.yg)("p",null,"Indicates that the entity to which its attached should be skipped and not included in a schema. GraphSkip can be defined on any controller, method, property, interface, enum or enum value."),(0,i.yg)("h4",{id:"graphskip-1"},(0,i.yg)("inlineCode",{parentName:"h4"},"[GraphSkip]")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="C# Class with GraphSkip"',title:'"C#',Class:!0,with:!0,'GraphSkip"':!0},"public class Donut\n{\n public int Id{get; set;}\n public string Name{get;set;}\n\n // highlight-next-line\n [GraphSkip]\n public string Recipe {get; set;}\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="GraphQL Type Definition"',title:'"GraphQL',Type:!0,'Definition"':!0},"# Recipe is not included in the schema\ntype Donut {\n Id: String\n Name: String \n}\n")),(0,i.yg)("h2",{id:"graphtype"},"GraphType"),(0,i.yg)("p",null,"Indicates additional or non-standard settings for the the class, interface or enum to which its attached. Also indicates the item is explicitly declared as a graph type and should be included in a schema."),(0,i.yg)("h4",{id:"graphtypename"},(0,i.yg)("inlineCode",{parentName:"h4"},"[GraphType(name)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"name")," : The name of graph type as it should appear in the object graph")),(0,i.yg)("h4",{id:"graphtypename-inputname"},(0,i.yg)("inlineCode",{parentName:"h4"},"[GraphType(name, inputName)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"name")," : The name of graph type as it should appear in the schema when used as an ",(0,i.yg)("inlineCode",{parentName:"li"},"OBJECT")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"inputName"),": The name of the graph type in the schema when used as an ",(0,i.yg)("inlineCode",{parentName:"li"},"INPUT_OBJECT")," ")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'// highlight-next-line\n[GraphType("person", "personModel")]\npublic class Human\n{\n public int Id{get; set; }\n public string FullName { get; set; }\n}\n')),(0,i.yg)("h2",{id:"mutation-mutationroot-query-queryroot"},"Mutation, MutationRoot, Query, QueryRoot"),(0,i.yg)("p",null,'Controller action method attributes that indicate the method belongs to the specified operation type (query or mutation). When declared as "Root" (i.e. ',(0,i.yg)("inlineCode",{parentName:"p"},"QueryRoot"),"), it indicates that the action method should be declared directly on its operation graph type and not nested underneath a controller's virtual field."),(0,i.yg)("admonition",{type:"tip"},(0,i.yg)("p",{parentName:"admonition"},(0,i.yg)("inlineCode",{parentName:"p"},"[Query]"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"[QueryRoot]"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"[Mutation]")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"[MutationRoot]")," all have identical constructor options.")),(0,i.yg)("h4",{id:"querytemplate"},(0,i.yg)("inlineCode",{parentName:"h4"},"[Query(template)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"template")," - The field path template to use for this method.")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class CharacterController : GraphController\n{\n // highlight-next-line\n [Query("hero")]\n public Human RetrieveTheHero(Episode episode)\n {\n // ....\n }\n}\n')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'query {\n character {\n # field is named "hero" not "RetrieveTheHero"\n hero(episode: EMPIRE) { \n name\n homePlanet\n }\n }\n}\n')),(0,i.yg)("h4",{id:"queryreturntype-params-othertypes"},(0,i.yg)("inlineCode",{parentName:"h4"},"[Query(returnType, params otherTypes)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"returnType"),": the expected return type of this field.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("strong",{parentName:"li"},"Must")," be used when this field returns an ",(0,i.yg)("inlineCode",{parentName:"li"},"IGraphActionResult")))),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"otherTypes"),": additional possible types this field could return.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Can be used to declare possible concrete types when this field returns an interface.")))),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},"public class CharacterController : GraphController\n{\n // highlight-next-line\n [Query(typeof(Droid), typeof(Human))]\n public ICharacter Hero(Episode episode)\n {\n // ....\n }\n}\n")),(0,i.yg)("h4",{id:"querytemplate-returntype"},(0,i.yg)("inlineCode",{parentName:"h4"},"[Query(template, returnType)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"template")," - The field path template to use for this method."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"returnType"),": the expected return type of this field.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("strong",{parentName:"li"},"Must")," be used when this field returns an ",(0,i.yg)("inlineCode",{parentName:"li"},"IGraphActionResult"))))),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class CharacterController : GraphController\n{\n // highlight-next-line\n [Query("hero", typeof(Human))]\n public IGraphActionResult RetrieveTheHero(Episode episode)\n {\n // ....\n }\n}\n')),(0,i.yg)("h4",{id:"querytemplate-unionname-uniontypea-uniontypeb-additionaluniontypes"},(0,i.yg)("inlineCode",{parentName:"h4"},"[Query(template, unionName, unionTypeA, unionTypeB, additionalUnionTypes)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"template")," - The field path template to use for this method."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"unionName")," - The name to give to the union in the object graph"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"unionTypeA")," - The first member type of the union (must be an object, not an interface)"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"unionTypeB")," - The second member type of the union (must be an object, not an interface)"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"additionalUnionTypes")," - N additional union types to declare as part of this union")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class CharacterController : GraphController\n{\n // highlight-next-line\n [Query("hero", "DroidOrHuman", typeof(Droid), typeof(Human))]\n public IGraphActionResult RetrieveCharacter(int id)\n {\n // ....\n }\n}\n')),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"}," Additional Properties ")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"TypeExpression"),": Define a custom type expression; useful in setting a normally optional field (such as a string or other object) to being required. Supply the type expression as a valid graphql syntax type expression.")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class CharacterController : GraphController\n{\n // declare that this field must return a value (a null human is not allowed)\n // highlight-next-line\n [Query("hero", typeof(Human), TypeExpression = "Type!")]\n public IGraphActionResult RetrieveTheHero(Episode episode)\n {\n // ....\n }\n}\n')),(0,i.yg)("h2",{id:"possibletypes"},"PossibleTypes"),(0,i.yg)("p",null,(0,i.yg)("em",{parentName:"p"},"(optional)")," When returning an interface from an action method, this attribute allows for the declaration of additional object types to help reduce clutter in the primary query or mutation declaration."),(0,i.yg)("h4",{id:"possibletypestypeoftypea-typeoftypeb-"},(0,i.yg)("inlineCode",{parentName:"h4"},"[PossibleTypes(typeof(TypeA), typeof(TypeB) ...)]")),(0,i.yg)("p",null,"These two controller examples are identical:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Example A"',title:'"Example','A"':!0},'public class CharacterController : GraphController\n{\n // highlight-next-line\n [Query("hero", typeof(Human), typeof(Droid), typeof(Gungan)]\n public ICharacter RetrieveTheHero(Episode episode)\n {\n // ....\n }\n}\n')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Example B"',title:'"Example','B"':!0},'public class CharacterController : GraphController\n{\n [Query("hero")] \n // highlight-next-line\n [PossibleTypes(typeof(Human), typeof(Droid), typeof(Gungan))]\n public ICharacter RetrieveTheHero(Episode episode)\n {\n // ....\n }\n}\n')),(0,i.yg)("h2",{id:"specifiedby"},"SpecifiedBy"),(0,i.yg)("p",null,(0,i.yg)("em",{parentName:"p"},"(optional)")," Provides a convienent way to apply the ",(0,i.yg)("inlineCode",{parentName:"p"},"@specifiedBy")," directive to a custom scalar. When not used, no url is provided to introspection requests for the scalar information."),(0,i.yg)("h4",{id:"specifiedbyurl"},(0,i.yg)("inlineCode",{parentName:"h4"},"[SpecifiedBy(url)]")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'// highlight-next-line\n[SpecifiedBy("https://documentation.example.com/api/money-scalar")]\npublic class MoneyScalar : IScalarGraphType\n{\n // details ommited...\n}\n')),(0,i.yg)("h2",{id:"typeextension"},"TypeExtension"),(0,i.yg)("p",null,"Declares a controller action method as a field on another graph type rather than a query or mutation action."),(0,i.yg)("h4",{id:"typeextensiontypetoextend-fieldname"},(0,i.yg)("inlineCode",{parentName:"h4"},"[TypeExtension(typeToExtend, fieldName)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"typeToExtend")," - The graph type to which this field will be added"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"fieldName")," - The name to give to this field.")),(0,i.yg)("p",null,"Declares a type extension with the given field name. The return type of this field will be taken from the return type of the method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class DroidController : GraphController\n{\n // highlight-next-line\n [TypeExtension(typeof(Droid), "ownedBy")]\n public Human RetrieveDroidOwner(Droid droid)\n {\n //....\n }\n}\n')),(0,i.yg)("h4",{id:"typeextensiontypetoextend-fieldname-returntype"},(0,i.yg)("inlineCode",{parentName:"h4"},"[TypeExtension(typeToExtend, fieldName, returnType)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"typeToExtend")," - The graph type to which this field will be added"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"fieldName")," - The name to give to this field."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"returnType")," - The type of data returned from this field")),(0,i.yg)("p",null,"Declares a type extension with an explicit return type. useful when returning ",(0,i.yg)("inlineCode",{parentName:"p"},"IGraphActionResult"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class HeroController : GraphController\n{\n // highlight-next-line\n [TypeExtension(typeof(Human), "ownedBy", typeof(Droid))]\n public IGraphActionResult RetrieveDroidOwner(Droid droid)\n {\n //....\n }\n}\n')),(0,i.yg)("h4",{id:"typeextensiontypetoextend-fieldname-unionname-uniontypea-uniontypeb-additionaluniontypes"},(0,i.yg)("inlineCode",{parentName:"h4"},"[TypeExtension(typeToExtend, fieldName, unionName, unionTypeA, unionTypeB, additionalUnionTypes)]")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"typeToExtend")," - The graph type to which this field will be added"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"fieldName")," - The name to give to this field."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"unionName")," - The name to give to the union in the object graph"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"unionTypeA")," - The first member type of the union (must be an object, not an interface)"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"unionTypeB")," - The second member type of the union (must be an object, not an interface)"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"additionalUnionTypes")," - N additional union types to declare as part of this union")),(0,i.yg)("p",null,"Declares the type extension as returning a union rather than a specific data type."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class HeroController : GraphController\n{\n // highlight-next-line\n [TypeExtension(typeof(Droid), "bestFriend", "DroidOrHuman", typeof(Droid), typeof(Human))]\n public IGraphActionResult RetrieveDroidsBestFriend(Droid droid)\n {\n //....\n }\n}\n')))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/39a1aa48.a18be5c1.js b/assets/js/39a1aa48.a18be5c1.js new file mode 100644 index 0000000..958b408 --- /dev/null +++ b/assets/js/39a1aa48.a18be5c1.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[7219],{5680:(e,t,a)=>{a.d(t,{xA:()=>s,yg:()=>d});var n=a(6540);function i(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function r(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function l(e){for(var t=1;t=0||(i[a]=e[a]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(i[a]=e[a])}return i}var p=n.createContext({}),u=function(e){var t=n.useContext(p),a=t;return e&&(a="function"==typeof e?e(t):l(l({},t),e)),a},s=function(e){var t=u(e.components);return n.createElement(p.Provider,{value:t},e.children)},c="mdxType",y={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var a=e.components,i=e.mdxType,r=e.originalType,p=e.parentName,s=o(e,["components","mdxType","originalType","parentName"]),c=u(a),m=i,d=c["".concat(p,".").concat(m)]||c[m]||y[m]||r;return a?n.createElement(d,l(l({ref:t},s),{},{components:a})):n.createElement(d,l({ref:t},s))}));function d(e,t){var a=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=a.length,l=new Array(r);l[0]=m;var o={};for(var p in t)hasOwnProperty.call(t,p)&&(o[p]=t[p]);o.originalType=e,o[c]="string"==typeof e?e:i,l[1]=o;for(var u=2;u{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>c,frontMatter:()=>r,metadata:()=>o,toc:()=>u});var n=a(8168),i=(a(6540),a(5680));const r={id:"malicious-queries",title:"Dealing with Malicious Queries",sidebar_label:"Malicious Queries",sidebar_position:1},l=void 0,o={unversionedId:"execution/malicious-queries",id:"execution/malicious-queries",title:"Dealing with Malicious Queries",description:"When GraphQL ASP.NET parses a query it creates two values that attempt to describe the query in terms of impact and server load; Max Depth and Estimated Complexity. There also exists limiters to these values that can be set in the schema configuration such that should any query plan exceed the limits you set, the plan will be rejected and the query not fulfilled.",source:"@site/docs/execution/malicious-queries.md",sourceDirName:"execution",slug:"/execution/malicious-queries",permalink:"/docs/execution/malicious-queries",draft:!1,tags:[],version:"current",sidebarPosition:1,frontMatter:{id:"malicious-queries",title:"Dealing with Malicious Queries",sidebar_label:"Malicious Queries",sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Query Profiling",permalink:"/docs/execution/metrics"},next:{title:"Debugging",permalink:"/docs/development/debugging"}},p={},u=[{value:"Maximum Allowed Field Depth",id:"maximum-allowed-field-depth",level:2},{value:"Query Complexity",id:"query-complexity",level:2},{value:"Calculating Query Complexity",id:"calculating-query-complexity",level:3},{value:"Setting a Complexity Weight",id:"setting-a-complexity-weight",level:3},{value:"Implement Your Own Complexity Calculation",id:"implement-your-own-complexity-calculation",level:2}],s={toc:u};function c(e){let{components:t,...a}=e;return(0,i.yg)("wrapper",(0,n.A)({},s,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"When GraphQL ASP.NET parses a query it creates two values that attempt to describe the query in terms of impact and server load; Max Depth and Estimated Complexity. There also exists limiters to these values that can be set in the schema configuration such that should any query plan exceed the limits you set, the plan will be rejected and the query not fulfilled."),(0,i.yg)("h2",{id:"maximum-allowed-field-depth"},"Maximum Allowed Field Depth"),(0,i.yg)("p",null,"Field depth refers to how deeply nested a field is within a query."),(0,i.yg)("p",null,'In this example, for instance, the "search" field has a depth of 4 and the maximum depth reached is 6: ',(0,i.yg)("inlineCode",{parentName:"p"},"groceryStore > bakery > pastries > recipe > ingredients > name")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'query SearchGroceryStore {\n groceryStore {\n bakery {\n pastries {\n search(nameLike: "chocolate"){\n name\n type\n }\n recipe(id: 15) {\n name\n ingredients {\n name\n }\n }\n }\n }\n }\n}\n')),(0,i.yg)("p",null,"This becomes important on large object graphs where its possible for a requestor to submit a query that is 10s or 100s of nodes deep. Running such a large query can have performance implications if ran en masse. Think of large, deeply nested queries run as part of a DDos attack."),(0,i.yg)("p",null,"To combat this you can set a maximum allowed depth for any query targeting your schema. During the parsing phase, once GraphQL has gathered enough information about the query document and target operation, it will inspect the maximum depth and if it violates your constraint, immediately reject the query without executing it."),(0,i.yg)("p",null,"To set a maximum allowed depth, set the appropriate property in your schema configuration at startup:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Configure Max Query Depth"',title:'"Configure',Max:!0,Query:!0,'Depth"':!0},"services.AddGraphQL(options =>\n{\n options.ExecutionOptions.MaxQueryDepth = 15;\n});\n")),(0,i.yg)("admonition",{title:"Default Max Query Depth",type:"info"},(0,i.yg)("p",{parentName:"admonition"},"The default value for ",(0,i.yg)("inlineCode",{parentName:"p"},"MaxQueryDepth")," is ",(0,i.yg)("inlineCode",{parentName:"p"},"null")," (i.e. no limit).")),(0,i.yg)("h2",{id:"query-complexity"},"Query Complexity"),(0,i.yg)("p",null,"The field depth is only part of the picture though. The way in which your fields interact with each other also plays a role."),(0,i.yg)("p",null,"Take for instance this query:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query PhoneManufacturer {\n allParts {\n id\n name\n suppliers {\n name\n address\n }\n }\n}\n")),(0,i.yg)("p",null,"It would not be far fetched to assume that this phone manufacturer has at least 500 parts in their inventory and that those parts might be sourced from 2-3 individual suppliers. If that's the case our result is going to contain 3000 field resolutions (500 parts ","*"," 3 suppliers ","*"," 2 fields per supplier) just to show the name and address of each supplier. Thats a lot of data!!!! What if we added order history per supplier? Now we'd looking at 100,000+ results. The take away here is that your field resolutions can balloon quickly, even on small queries, if you're not careful."),(0,i.yg)("p",null,"While this query only has a field depth of 3, ",(0,i.yg)("inlineCode",{parentName:"p"},"allParts > suppliers > name"),", the performance implications are much more impactful than the bakery in the first example because of the type of data involved. (Side note: this is a perfect example where a ",(0,i.yg)("a",{parentName:"p",href:"../controllers/batch-operations"},"batch operation")," would improve performance exponentially.)"),(0,i.yg)("p",null,"GraphQL will assign an ",(0,i.yg)("inlineCode",{parentName:"p"},"estimated complexity")," score to each query plan to help gauge the load its likely to incur on the server when trying to execute. As you might expect you can set a maximum allowed complexity value and reject any queries that exceed your limit:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Configure Max Allowed Query Complexity"',title:'"Configure',Max:!0,Allowed:!0,Query:!0,'Complexity"':!0},"services.AddGraphQL(options =>\n{\n options.ExecutionOptions.MaxQueryComplexity = 50.00;\n});\n")),(0,i.yg)("admonition",{title:"Default Max Complexity",type:"info"},(0,i.yg)("p",{parentName:"admonition"},"The default value for ",(0,i.yg)("inlineCode",{parentName:"p"},"MaxQueryComplexity")," is ",(0,i.yg)("inlineCode",{parentName:"p"},"null")," (i.e. no maximum).")),(0,i.yg)("h3",{id:"calculating-query-complexity"},"Calculating Query Complexity"),(0,i.yg)("p",null,"After a query plan is generated, the chosen operation is inspected and weights are applied to each of the fields then summed together to generate a final score."),(0,i.yg)("p",null,"A complexity score is derived from these attributes:"),(0,i.yg)("table",null,(0,i.yg)("thead",{parentName:"table"},(0,i.yg)("tr",{parentName:"thead"},(0,i.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,i.yg)("th",{parentName:"tr",align:null},"Description"))),(0,i.yg)("tbody",{parentName:"table"},(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"Operation Type"),(0,i.yg)("td",{parentName:"tr",align:null},"This refers to the operation being a ",(0,i.yg)("inlineCode",{parentName:"td"},"mutation")," or a ",(0,i.yg)("inlineCode",{parentName:"td"},"query"),". Mutations are weighted more than queries.")),(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"Execution Mode"),(0,i.yg)("td",{parentName:"tr",align:null},'Whether or not a given field is being executed as a batch operation or "per source item".')),(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"Resolver Type"),(0,i.yg)("td",{parentName:"tr",align:null},"The type of resolver being invoked. For example, controller actions are weighted more heavily than simple property resolvers.")),(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"Type Expression"),(0,i.yg)("td",{parentName:"tr",align:null},"Does the field produce 1 single item or a collection of items?")),(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"Complexity Factor"),(0,i.yg)("td",{parentName:"tr",align:null},"A user controlled value to influence the calculation for queries or mutations that are particularly long running")))),(0,i.yg)("p",null,"The code for calculating the value can be seen in ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet/Engine/DefaultOperationComplexityCalculator%7BTSchema%7D.cs"},(0,i.yg)("inlineCode",{parentName:"a"},"DefaultOperationComplexityCalculator"))),(0,i.yg)("h3",{id:"setting-a-complexity-weight"},"Setting a Complexity Weight"),(0,i.yg)("p",null,"You can influence the complexity value of any given field by applying a weight to the field as part of its declaration."),(0,i.yg)("p",null,"The attributes ",(0,i.yg)("inlineCode",{parentName:"p"},"[GraphField]"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"[Query]"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"[Mutation]"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"[QueryRoot]"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"[MutationRoot]")," expose access to this value."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs"',title:'"BakeryController.cs"'},"public class BakeryController : GraphController\n{\n // Complexity is a float value\n // highlight-next-line\n [QueryRoot(Complexity = 1.3)]\n public Donut RetrieveDonutType(int id){/*...*/}\n}\n")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"A factor greater than 1 will increase the weight applied to this field"),(0,i.yg)("li",{parentName:"ul"},"A factor less than 1 will decrease the weight"),(0,i.yg)("li",{parentName:"ul"},"The minimum value is ",(0,i.yg)("inlineCode",{parentName:"li"},"0")," and the default value is ",(0,i.yg)("inlineCode",{parentName:"li"},"1"))),(0,i.yg)("p",null,"Complexity scores that do not exceed the limit are written to ",(0,i.yg)("inlineCode",{parentName:"p"},"QueryPlanGenerated (EventId: 86400)"),", a debug level event, after the query plan is successfully generated. Complexity scores that do exceed the limit are written directly to the errors collection on the query response."),(0,i.yg)("admonition",{title:"Profile Your Queries",type:"tip"},(0,i.yg)("p",{parentName:"admonition"},"There is no magic bullet for choosing complexity values or setting a maximum allowed value as its going to be largely dependent on your data and how customers query it. Spend time profiling your queries, investigate their calculated complexities and act accordingly. ")),(0,i.yg)("h2",{id:"implement-your-own-complexity-calculation"},"Implement Your Own Complexity Calculation"),(0,i.yg)("p",null,"You can override how the library calculates the complexity of any given query operation. Implement ",(0,i.yg)("inlineCode",{parentName:"p"},"IQueryOperationComplexityCalculator")," and inject it into your DI container before calling ",(0,i.yg)("inlineCode",{parentName:"p"},".AddGraphQL()"),"."),(0,i.yg)("p",null,"This interface has one method where ",(0,i.yg)("inlineCode",{parentName:"p"},"IGraphFieldExecutableOperation")," represents the collection of requested fields contexts along with the input arguments, child fields and directives that are about to be executed:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="IQueryOperationComplexityCalculator.cs"',title:'"IQueryOperationComplexityCalculator.cs"'},"public interface IQueryOperationComplexityCalculator\n{\n float Calculate(IGraphFieldExecutableOperation operation);\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/3ebc953e.631dc830.js b/assets/js/3ebc953e.631dc830.js new file mode 100644 index 0000000..c7f3434 --- /dev/null +++ b/assets/js/3ebc953e.631dc830.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[2552],{5680:(e,n,t)=>{t.d(n,{xA:()=>u,yg:()=>g});var r=t(6540);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function i(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var s=r.createContext({}),p=function(e){var n=r.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},u=function(e){var n=p(e.components);return r.createElement(s.Provider,{value:n},e.children)},c="mdxType",d={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},m=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),c=p(t),m=a,g=c["".concat(s,".").concat(m)]||c[m]||d[m]||o;return t?r.createElement(g,i(i({ref:n},u),{},{components:t})):r.createElement(g,i({ref:n},u))}));function g(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,i=new Array(o);i[0]=m;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[c]="string"==typeof e?e:a,i[1]=l;for(var p=2;p{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var r=t(8168),a=(t(6540),t(5680));const o={id:"code-examples",title:"Code Examples",sidebar_label:"Code Examples",sidebar_position:2},i=void 0,l={unversionedId:"quick/code-examples",id:"quick/code-examples",title:"Code Examples",description:"Below is a quick introduction to some common scenarios and the C# code to support them.",source:"@site/docs/quick/code-examples.md",sourceDirName:"quick",slug:"/quick/code-examples",permalink:"/docs/quick/code-examples",draft:!1,tags:[],version:"current",sidebarPosition:2,frontMatter:{id:"code-examples",title:"Code Examples",sidebar_label:"Code Examples",sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"Your First App",permalink:"/docs/quick/create-app"},next:{title:"What is GraphQL?",permalink:"/docs/introduction/what-is-graphql"}},s={},p=[{value:"Configuring Services",id:"configuring-services",level:2},{value:"A Basic Controller",id:"a-basic-controller",level:2},{value:"Using an Interface",id:"using-an-interface",level:2},{value:"Field Paths",id:"field-paths",level:2},{value:"Dependency Injection",id:"dependency-injection",level:2},{value:"Authorization",id:"authorization",level:2},{value:"\u2705 Notes on Authorization",id:"-notes-on-authorization",level:4},{value:"Mutations & Model State",id:"mutations--model-state",level:2},{value:"Action Results",id:"action-results",level:2}],u={toc:p};function c(e){let{components:n,...t}=e;return(0,a.yg)("wrapper",(0,r.A)({},u,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"Below is a quick introduction to some common scenarios and the C# code to support them. "),(0,a.yg)("h2",{id:"configuring-services"},"Configuring Services"),(0,a.yg)("p",null,'The library uses a standard "Add & Use" pattern for configuring services with your application. A route is added to the ASP.NET request pipeline to handle GET and POST requests when you call ',(0,a.yg)("inlineCode",{parentName:"p"},".UseGraphQL()"),". Place it as appropriate amongst any other configurations, routes, authorization etc. when you build your pipeline."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Program.cs"',title:'"Program.cs"'},"var builder = WebApplication.CreateBuilder(args);\n\n// Add graphql services to the DI container\n// highlight-next-line\nbuilder.Services.AddGraphQL();\n\nvar app = builder.Build();\n\n// Configure the HTTP request pipeline\n// highlight-next-line\napp.UseGraphQL();\napp.Run();\n")),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},(0,a.yg)("em",{parentName:"p"},"The configuration steps may vary slightly when using a Startup.cs file; typical for .NET 5 or earlier "))),(0,a.yg)("h2",{id:"a-basic-controller"},"A Basic Controller"),(0,a.yg)("p",null,"A simple controller to return data based on a string value. "),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},"Notice we inherit from ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphController")," not the standard web api Controller.")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="HeroController.cs"',title:'"HeroController.cs"'},'// highlight-next-line\npublic class HeroController : GraphController\n{\n [QueryRoot]\n public Human Hero(string episode)\n {\n if(episode == "Empire")\n {\n return new Human()\n {\n Id = 1000,\n Name = "Han Solo",\n HomePlanet = "Corellia",\n }\n }\n else\n {\n return new Human()\n {\n Id = 1001,\n Name = "Luke SkyWalker",\n HomePlanet = "Tatooine",\n }\n }\n }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'query {\n hero(episode: "Empire") {\n name\n homePlanet\n }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="JSON Result"',title:'"JSON','Result"':!0},'{\n "data" : {\n "hero": {\n "name" : "Han Solo",\n "homePlanet" : "Corellia"\n }\n }\n}\n')),(0,a.yg)("admonition",{title:"Did you notice?",type:"info"},(0,a.yg)("p",{parentName:"admonition"},"In the query the hero field is ",(0,a.yg)("inlineCode",{parentName:"p"},"camelCased")," but in C# the method is ",(0,a.yg)("inlineCode",{parentName:"p"},"ProperCased"),"? Field names are automatically translated to standard GraphQL conventions. The same goes for your graph type names, enum values etc."),(0,a.yg)("p",{parentName:"admonition"},"You can also implement your own ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphNameFormatter")," and alter the name formats for each of your registered schemas.")),(0,a.yg)("h2",{id:"using-an-interface"},"Using an Interface"),(0,a.yg)("p",null,"If your models share a common interface just return it from a controller action and the library will create the appropriate graph types for you. "),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},"Don't forget to declare the object types that implement your interface (e.g. Droid and Human) or the library won't know what resolvers to invoke at runtime. In this example, we've declared them inline but you can easily add them at startup to reduce the noise.")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="HeroController.cs"',title:'"HeroController.cs"'},'public class HeroController : GraphController\n{\n // highlight-next-line\n [QueryRoot(typeof(Droid), typeof(Human))]\n // highlight-next-line\n public ICharacter Hero(Episode episode)\n {\n if(episode == Episode.Empire)\n {\n return new Human()\n {\n Id = 1000,\n Name = "Han Solo",\n HomePlanet = "Corellia",\n }\n }\n else\n {\n return new Droid()\n {\n Id = 2000,\n Name = "R2R2",\n Type = DroidType.AstroMech\n }\n }\n }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="GraphQL Query"',title:'"GraphQL','Query"':!0},"query {\n hero(episode: JEDI) {\n id\n name\n\n ... on Human {\n homePlanet\n }\n\n ... on Droid {\n type\n }\n }\n}\n")),(0,a.yg)("h2",{id:"field-paths"},"Field Paths"),(0,a.yg)("p",null,"We've used ",(0,a.yg)("inlineCode",{parentName:"p"},"[QueryRoot]")," so far to force a controller action to be a root field on the ",(0,a.yg)("inlineCode",{parentName:"p"},"query")," type. But we can use an approximation of Web API's url templates to create any combination of nested fields needed. When you have 50 controllers with 20-40 actions each, organizing your object hierarchy becomes trivial."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="RebelAllianceController.cs"',title:'"RebelAllianceController.cs"'},'// highlight-next-line\n[GraphRoute("rebels")]\npublic class RebelAllianceController : GraphController\n{\n // highlight-next-line\n [Query("directory/hero")]\n public Human RetrieveHero(Episode episode)\n {\n // Wedge is the true hero\n return new Human()\n {\n Id = 1003,\n Name = "Wedge Antilles",\n HomePlanet = "Corellia",\n };\n }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query {\n rebels {\n directory {\n hero(episode: EMPIRE) {\n name\n homePlanet\n }\n }\n }\n}\n")),(0,a.yg)("h2",{id:"dependency-injection"},"Dependency Injection"),(0,a.yg)("p",null,"At runtime, GraphQL invokes your graph controllers and injected services with the same dependency scope as the original HTTP Request. Add a known service to a controller's constructor and it will be automatically resolved with its configured scope."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-cs",metastring:'title="PersonsController.cs"',title:'"PersonsController.cs"'},'public class PersonsController : GraphController\n{\n private IPersonService _personService;\n // highlight-next-line\n public PersonsController(IPersonService service)\n {\n _personService = service;\n }\n\n [QueryRoot("person")]\n public async Task RetrievePerson(int id)\n {\n return await _personService.RetrievePerson(id);\n }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Query"',title:'"Query"'},"query {\n person(id: 1000) {\n id\n name\n homePlanet\n }\n}\n")),(0,a.yg)("admonition",{title:"Did You Notice? ",type:"info"},(0,a.yg)("p",{parentName:"admonition"},"We switched to an asynchronous method with ",(0,a.yg)("inlineCode",{parentName:"p"},"Task"),". GraphQL ASP.NET follows your lead and will execute your actions asynchronously or synchronously as needed.")),(0,a.yg)("h2",{id:"authorization"},"Authorization"),(0,a.yg)("p",null,"Add the ",(0,a.yg)("inlineCode",{parentName:"p"},"[Authorize]")," attribute and you're done. GraphQL ASP.NET uses the same authorization pipeline as your application."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="PersonsController.cs"',title:'"PersonsController.cs"'},'public class PersonsController : GraphController\n{\n private IPersonService _personService;\n public PersonsController(IPersonService service)\n {\n _personService = service;\n }\n\n // highlight-next-line\n [Authorize]\n [QueryRoot("self")]\n public async Task RetrievePerson()\n {\n return await _personService.RetrievePerson(this.User.Name);\n }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query {\n self {\n id\n name\n title\n }\n}\n")),(0,a.yg)("h4",{id:"-notes-on-authorization"},"\u2705 Notes on Authorization"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Your controller actions have full access to the same ",(0,a.yg)("inlineCode",{parentName:"li"},"ClaimsPrincipal")," that you get with ",(0,a.yg)("inlineCode",{parentName:"li"},"this.User")," on an web api controller. In fact, its the same object reference."),(0,a.yg)("li",{parentName:"ul"},"Out of the box, the library performs authorization on a \"per field\" basis. This includes POCO object properties! If you have a piece of sensitive data attached to a property, say Birthday, on your Person model, you can apply a policy or role to it. Unauthorized user's won't be able to query for that field, even if they can access the controller that produced the object its attached or every other field on the object.",(0,a.yg)("ul",{parentName:"li"},(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"Note: You'll have to implement .NET's")," ",(0,a.yg)("inlineCode",{parentName:"li"},"IAuthorizeData")," ",(0,a.yg)("em",{parentName:"li"},"interface on your own custom attribute, the")," ",(0,a.yg)("inlineCode",{parentName:"li"},"[Authorize]")," ",(0,a.yg)("em",{parentName:"li"},"attribute provided by .NET does not allow targeting of properties.")))),(0,a.yg)("li",{parentName:"ul"},"GraphQL obeys layered authorization requirements as well. Place an authorization attribute at the controller level and it'll be checked before any method level requirements.")),(0,a.yg)("h2",{id:"mutations--model-state"},"Mutations & Model State"),(0,a.yg)("p",null,"GraphQL ASP.NET will automatically enforce the query specification rules for you, but that doesn't help for business-level requirements like string length or integer ranges. For that, it uses the familiar goodness of Validation Attributes (e.g. ",(0,a.yg)("inlineCode",{parentName:"p"},"[StringLength]"),", ",(0,a.yg)("inlineCode",{parentName:"p"},"[Range]")," etc.)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="PersonsController.cs"',title:'"PersonsController.cs"'},'public class PersonsController : GraphController\n{\n /* constructor hidden for brevity */\n\n [MutationRoot("joinTheResistance")]\n public async Task CreatePerson(Human model)\n {\n // ***************************\n // Check if the model passed validation\n // requirements before using it\n // ***************************\n // highlight-start\n if(!this.ModelState.IsValid)\n return null;\n // highlight-end\n\n return await _service.CreatePerson(model);\n }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Human.cs"',title:'"Human.cs"'},"public class Human\n{\n public int? Id{ get; set; }\n\n // highlight-next-line\n [StringLength(35)]\n public string Name { get; set; }\n\n public string HomePlanet { get; set; }\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'mutation {\n joinTheResistance(\n newPerson: {\n name: "Lando Calrissian"\n homePlanet: "Bespin" }) {\n id\n name\n homePlanet\n }\n}\n')),(0,a.yg)("admonition",{title:"Did You Notice?",type:"info"},(0,a.yg)("p",{parentName:"admonition"},"We used ",(0,a.yg)("inlineCode",{parentName:"p"},"Human")," as an input argument ",(0,a.yg)("strong",{parentName:"p"},"and")," as the returned data object. The library will automatically generate the appropriate graph types for ",(0,a.yg)("inlineCode",{parentName:"p"},"INPUT_OBJECT")," and ",(0,a.yg)("inlineCode",{parentName:"p"},"OBJECT"),", respectively, add them to your schema when needed.")),(0,a.yg)("h2",{id:"action-results"},"Action Results"),(0,a.yg)("p",null,"Just as Web API makes use of ",(0,a.yg)("inlineCode",{parentName:"p"},"IActionResult")," to perform post processing on the result of a controller method, GraphQL ASP.NET makes use of ",(0,a.yg)("inlineCode",{parentName:"p"},"IGraphActionResult"),"."),(0,a.yg)("p",null,"Reusing the previous example, here we make use of ",(0,a.yg)("inlineCode",{parentName:"p"},"this.BadRequest()")," to automatically generate an appropriate error message in the response when model validation fails. Field origin information including the path array and line/column number of the original query are wired up automatically."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp"},'// C# Controller\npublic class PersonsController : GraphController\n{\n [MutationRoot("joinTheResistance", typeof(Human))]\n public async IGraphActionResult CreatePerson(Human model)\n {\n // ***************************\n // Check if the model passes validation\n // requirements before using it\n // ***************************\n if(!this.ModelState.IsValid)\n // highlight-next-line\n return this.BadRequest(this.ModelState);\n\n var result = await _service.CreatePerson(model);\n return result != null\n // highlight-start\n ? this.Ok(result)\n : this.Error("Woops Something broke");\n // highlight-end\n }\n}\n\npublic class Human\n{\n public int? Id{ get; set; }\n\n [StringLength(35)]\n public string Name { get; set; }\n\n public string HomePlanet { get; set; }\n}\n')),(0,a.yg)("admonition",{title:"GraphQL is not Rest",type:"note"},(0,a.yg)("p",{parentName:"admonition"}," Unlike WebAPI, ",(0,a.yg)("inlineCode",{parentName:"p"},"BadRequest()"),' doesn\'t generate a HTTP Status 400 error for the request. If there are multiple controller methods being resolved GraphQL can still generate a partial response and render data for other parts of the query. Most "error" related action results add a standard error message to the result with different reason codes.')))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/40dc0bc8.8c2db3a2.js b/assets/js/40dc0bc8.8c2db3a2.js new file mode 100644 index 0000000..fe1ca6a --- /dev/null +++ b/assets/js/40dc0bc8.8c2db3a2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[275],{5680:(e,t,i)=>{i.d(t,{xA:()=>p,yg:()=>m});var n=i(6540);function a(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function r(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function l(e){for(var t=1;t=0||(a[i]=e[i]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(a[i]=e[i])}return a}var o=n.createContext({}),c=function(e){var t=n.useContext(o),i=t;return e&&(i="function"==typeof e?e(t):l(l({},t),e)),i},p=function(e){var t=c(e.components);return n.createElement(o.Provider,{value:t},e.children)},d="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},h=n.forwardRef((function(e,t){var i=e.components,a=e.mdxType,r=e.originalType,o=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),d=c(i),h=a,m=d["".concat(o,".").concat(h)]||d[h]||u[h]||r;return i?n.createElement(m,l(l({ref:t},p),{},{components:i})):n.createElement(m,l({ref:t},p))}));function m(e,t){var i=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=i.length,l=new Array(r);l[0]=h;var s={};for(var o in t)hasOwnProperty.call(t,o)&&(s[o]=t[o]);s.originalType=e,s[d]="string"==typeof e?e:a,l[1]=s;for(var c=2;c{i.r(t),i.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>c});var n=i(8168),a=(i(6540),i(5680));const r={id:"directives",title:"Directives",sidebar_label:"Directives",sidebar_position:2},l=void 0,s={unversionedId:"advanced/directives",id:"advanced/directives",title:"Directives",description:"What is a Directive?",source:"@site/docs/advanced/directives.md",sourceDirName:"advanced",slug:"/advanced/directives",permalink:"/docs/advanced/directives",draft:!1,tags:[],version:"current",sidebarPosition:2,frontMatter:{id:"directives",title:"Directives",sidebar_label:"Directives",sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"Type Expressions",permalink:"/docs/advanced/type-expressions"},next:{title:"Custom Scalars",permalink:"/docs/advanced/custom-scalars"}},o={},c=[{value:"What is a Directive?",id:"what-is-a-directive",level:2},{value:"Anatomy of a Directive",id:"anatomy-of-a-directive",level:2},{value:"Action Results",id:"action-results",level:3},{value:"Helpful Properties",id:"helpful-properties",level:3},{value:"Directive Arguments",id:"directive-arguments",level:3},{value:"Execution Directives",id:"execution-directives",level:2},{value:"Example: @include",id:"example-include",level:3},{value:"Directive Execution Order",id:"directive-execution-order",level:3},{value:"Influencing Field Resolution",id:"influencing-field-resolution",level:3},{value:"Working with Batch Extensions",id:"working-with-batch-extensions",level:4},{value:"Type System Directives",id:"type-system-directives",level:2},{value:"Example: @toLower",id:"example-tolower",level:3},{value:"Example: @deprecated",id:"example-deprecated",level:3},{value:"Applying Type System Directives",id:"applying-type-system-directives",level:3},{value:"Using the [ApplyDirective] attribute",id:"using-the-applydirective-attribute",level:4},{value:"Using Schema Options",id:"using-schema-options",level:4},{value:"Repeatable Directives",id:"repeatable-directives",level:3},{value:"Understanding the Type System",id:"understanding-the-type-system",level:3},{value:"Directives as Services",id:"directives-as-services",level:2},{value:"Directive Security",id:"directive-security",level:2},{value:"Security Scenarios",id:"security-scenarios",level:3},{value:"Demo Project",id:"demo-project",level:2}],p={toc:c};function d(e){let{components:t,...r}=e;return(0,a.yg)("wrapper",(0,n.A)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("h2",{id:"what-is-a-directive"},"What is a Directive?"),(0,a.yg)("p",null,"Directives decorate parts of your schema or a query document to perform some sort of custom logic. What that logic is, is entirely up to you. There are several directives built into graphql:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"@include")," : An execution directive that conditionally includes a field or fragment in the results of a graphql query"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"@skip")," : An execution directive that conditionally excludes a field or fragment from the results of a graphql query"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"@deprecated")," : A type system directive that marks a field definition or enum value as deprecated, indicating that it may be removed in a future release of your graph."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"@specifiedBy")," : A type system directive for a custom scalar that adds a URL pointing to documentation about how the scalar is used. This url is returned as part of an introspection query.")),(0,a.yg)("p",null,"Beyond this you can create directives to perform any sort of action against your graph or query document as seems fit to your use case."),(0,a.yg)("h2",{id:"anatomy-of-a-directive"},"Anatomy of a Directive"),(0,a.yg)("p",null,"Directives are implemented in much the same way as a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphController")," but where you'd indicate an action method as being for a query or mutation, directive action methods must indicate the location(s) they can be applied in either a query document or the type system."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="SkipDirective.cs"',title:'"SkipDirective.cs"'},'public sealed class SkipDirective : GraphDirective\n{\n [DirectiveLocations(DirectiveLocation.FIELD | DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)]\n public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument)\n {\n if (this.DirectiveTarget is IIncludeableDocumentPart docPart)\n docPart.IsIncluded = !ifArgument;\n\n return this.Ok();\n }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Quring using @skip"',title:'"Quring',using:!0,'@skip"':!0},"# skip including flavor\nquery {\n donut(id: 15) {\n id \n name\n flavor @skip(if: true)\n }\n}\n")),(0,a.yg)("p",null,"\u2705 All directives must:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Inherit from ",(0,a.yg)("inlineCode",{parentName:"li"},"GraphQL.AspNet.Directives.GraphDirective")),(0,a.yg)("li",{parentName:"ul"},"Provide at least one action method that indicates at least 1 valid ",(0,a.yg)("inlineCode",{parentName:"li"},"DirectiveLocation"),".")),(0,a.yg)("p",null,"\u2705 All directive action methods must:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Share the same method signature"),(0,a.yg)("li",{parentName:"ul"},"The input arguments must match exactly in type, name, casing and declaration order."),(0,a.yg)("li",{parentName:"ul"},"Return a ",(0,a.yg)("inlineCode",{parentName:"li"},"IGraphActionResult")," or ",(0,a.yg)("inlineCode",{parentName:"li"},"Task"))),(0,a.yg)("h3",{id:"action-results"},"Action Results"),(0,a.yg)("p",null,"Directives have two built in action results that can be returned:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.Ok()"),(0,a.yg)("ul",{parentName:"li"},(0,a.yg)("li",{parentName:"ul"},"Indicates that the directive completed successfully and processing should continue."))),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.Cancel()"),(0,a.yg)("ul",{parentName:"li"},(0,a.yg)("li",{parentName:"ul"},"Indicates that the directive did NOT complete successfully and processing should stop."),(0,a.yg)("li",{parentName:"ul"},"If this is a type system directive, the target schema will not be generated and the server will fail to start."),(0,a.yg)("li",{parentName:"ul"},"If this is an execution directive, the query will be abandoned and the caller will receive an error result.")))),(0,a.yg)("h3",{id:"helpful-properties"},"Helpful Properties"),(0,a.yg)("p",null,"The following properties are available to all directive action methods:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.DirectiveTarget")," - The ",(0,a.yg)("inlineCode",{parentName:"li"},"ISchemaItem")," or ",(0,a.yg)("inlineCode",{parentName:"li"},"IDocumentPart")," to which the directive is being applied."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.Request")," - The directive invocation request for the currently executing directive. Contains lots of advanced information such as execution phase, the directive type declared on the schema etc.")),(0,a.yg)("h3",{id:"directive-arguments"},"Directive Arguments"),(0,a.yg)("p",null,"Directives may contain input arguments just like fields. However, its important to note that while a directive may declare multiple action methods for different locations to seperate your logic better, it is only a single entity in the schema. ALL action methods must share a common signature. The runtime will throw an exception while creating your schema if the signatures of the action methods differ."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Arguments for Directives"',title:'"Arguments',for:!0,'Directives"':!0},"public class MyValidDirective : GraphDirective\n{\n [DirectiveLocations(DirectiveLocation.FIELD)]\n public IGraphActionResult ExecuteField(int arg1, string arg2) { /.../ }\n\n [DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD)]\n public Task ExecuteFragSpread(int arg1, string arg2) { /.../ }\n}\n\npublic class MyInvalidDirective : GraphDirective\n{\n [DirectiveLocations(DirectiveLocation.FIELD)]\n // highlight-next-line\n public IGraphActionResult ExecuteField(int arg1, int arg2) { /.../ }\n\n // method parameters MUST match for all directive action methods.\n [DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD)]\n // highlight-next-line\n public IGraphActionResult ExecuteFragSpread(int arg1, string arg2) { /.../ }\n}\n")),(0,a.yg)("admonition",{type:"info"},(0,a.yg)("p",{parentName:"admonition"}," Directive arguments must match in name, data type and position for all action methods. Being able to use different methods for different locations is a convenience; to GraphQL there is only one directive with one set of parameters.")),(0,a.yg)("h2",{id:"execution-directives"},"Execution Directives"),(0,a.yg)("p",null,"(",(0,a.yg)("em",{parentName:"p"},(0,a.yg)("strong",{parentName:"em"},"a.k.a. Operation Directives")),")"),(0,a.yg)("p",null,"Execution Directives are applied to query documents and executed only on the request in which they are encountered. "),(0,a.yg)("h3",{id:"example-include"},"Example: @include"),(0,a.yg)("p",null,"This is the code for the built in ",(0,a.yg)("inlineCode",{parentName:"p"},"@include")," directive:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp"},'[GraphType("include")]\npublic sealed class IncludeDirective : GraphDirective\n{\n [DirectiveLocations(DirectiveLocation.FIELD | DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)]\n public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument)\n {\n if (this.DirectiveTarget is IIncludeableDocumentPart idp)\n idp.IsIncluded = ifArgument;\n\n return this.Ok();\n }\n}\n')),(0,a.yg)("p",null,"This Directive:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Declares its name using the ",(0,a.yg)("inlineCode",{parentName:"li"},"[GraphType]")," attribute",(0,a.yg)("ul",{parentName:"li"},(0,a.yg)("li",{parentName:"ul"},"The name will be derived from the class name if the attribute is omitted"))),(0,a.yg)("li",{parentName:"ul"},"Declares that it can be applied to a query document at all field selection locations using the ",(0,a.yg)("inlineCode",{parentName:"li"},"[DirectiveLocations]")," attribute"),(0,a.yg)("li",{parentName:"ul"},"Uses the ",(0,a.yg)("inlineCode",{parentName:"li"},"[FromGraphQL]")," attribute to declare the input argument's name in the schema",(0,a.yg)("ul",{parentName:"li"},(0,a.yg)("li",{parentName:"ul"},"This is because ",(0,a.yg)("inlineCode",{parentName:"li"},"if")," is a keyword in C# and we don't want the argument being named ",(0,a.yg)("inlineCode",{parentName:"li"},"ifArgument")," in the schema."))),(0,a.yg)("li",{parentName:"ul"},"Is executed once for each field, fragment spread or inline fragment to which its applied in a query document.")),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},"The action method name ",(0,a.yg)("inlineCode",{parentName:"p"},"Execute")," in this example is arbitrary. Method names can be whatever makes the most sense to you.")),(0,a.yg)("h3",{id:"directive-execution-order"},"Directive Execution Order"),(0,a.yg)("p",null,"When more than one directive is encountered for a single location, they are executed in the order encountered, from left to right, in the source text."),(0,a.yg)("p",null,"In this example :"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Using Multiple Execution Directives"',title:'"Using',Multiple:!0,Execution:!0,'Directives"':!0},"query {\n bakery {\n allPastries{\n id @directiveA @directiveB\n name\n }\n }\n}\n")),(0,a.yg)("p",null,"The directives attached to the ",(0,a.yg)("inlineCode",{parentName:"p"},"id")," field are executed in order from left to right:"),(0,a.yg)("ol",null,(0,a.yg)("li",{parentName:"ol"},"@directiveA"),(0,a.yg)("li",{parentName:"ol"},"@directiveB")),(0,a.yg)("h3",{id:"influencing-field-resolution"},"Influencing Field Resolution"),(0,a.yg)("p",null,"Execution directives are applied to document parts, not schema items. As a result they aren't directly involved in resolving fields but instead influence the document that is eventually translated into a query plan and executed. However, one common use case for execution directives includes augmenting the results of a field after its resolved. For instance, perhaps you had a directive that could conditionally turn a string field into an upper case string when applied (i.e. ",(0,a.yg)("inlineCode",{parentName:"p"},"@toUpper"),")."),(0,a.yg)("p",null,"For this reason it is possible to apply a 'PostResolver' directly to an ",(0,a.yg)("inlineCode",{parentName:"p"},"IFieldDocumentPart"),". This post resolver is executed immediately after the primary field resolver is executed."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="ToUpperDirective.cs"',title:'"ToUpperDirective.cs"'},'public class ToUpperDirective : GraphDirective\n{\n [DirectiveLocations(DirectiveLocation.FIELD)]\n public IGraphActionResult UpdateResolver()\n {\n if (this.DirectiveTarget is IFieldDocumentPart fieldPart)\n {\n //\n if (fieldPart.Field?.ObjectType != typeof(string))\n throw new GraphExecutionException("ONLY STRINGS!"); // - hulk\n\n // add a post resolver to the target field document\n // part to perform the conversion when the query is\n // ran\n // highlight-next-line\n fieldPart.PostResolver = ConvertToUpper;\n }\n\n return this.Ok();\n }\n\n private static Task ConvertToUpper(\n FieldResolutionContext context,\n CancellationToken token)\n {\n if (context.Result is string)\n context.Result = context.Result?.ToString().ToUpperInvariant();\n\n return Task.CompletedTask;\n }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Using @toUpper"',title:'"Using','@toUpper"':!0},"query {\n bakery {\n allPastries{\n id\n name @toUpper\n }\n }\n}\n")),(0,a.yg)("h4",{id:"working-with-batch-extensions"},"Working with Batch Extensions"),(0,a.yg)("p",null,"Batch extensions work differently than standard field resolvers; they don't resolve a single item at a time. This means our ",(0,a.yg)("inlineCode",{parentName:"p"},"@toUpper")," example above won't work as ",(0,a.yg)("inlineCode",{parentName:"p"},"context.Result")," won't be a string. Should you employ a post resolver that may be applied to a batch extension you'll need to handle the resultant dictionary differently than you would a single field value. The dictionary will always be of the format ",(0,a.yg)("inlineCode",{parentName:"p"},"IDictionary")," where ",(0,a.yg)("inlineCode",{parentName:"p"},"TSource")," is the data type of the field that owns the field the directive was applied to and ",(0,a.yg)("inlineCode",{parentName:"p"},"TResult")," is the data type or an ",(0,a.yg)("inlineCode",{parentName:"p"},"IEnumerable")," of the data type the target field returns. The dictionary is always keyed by source item reference."),(0,a.yg)("admonition",{title:"Be Careful with Batch Type Extensions",type:"caution"},(0,a.yg)("p",{parentName:"admonition"}," Batch Extensions will return a dictionary of data not a single item. Your post resolver must be able to handle this dictionary if applied to a field that is a ",(0,a.yg)("inlineCode",{parentName:"p"},"[BatchExtensionType]"),".")),(0,a.yg)("h2",{id:"type-system-directives"},"Type System Directives"),(0,a.yg)("p",null,"(",(0,a.yg)("em",{parentName:"p"},(0,a.yg)("strong",{parentName:"em"},"a.k.a. Schema Directives")),")"),(0,a.yg)("p",null,"Type System directives are applied to schema items and executed at start up while the schema is being created. "),(0,a.yg)("h3",{id:"example-tolower"},"Example: @toLower"),(0,a.yg)("p",null,"This directive will extend the resolver of a field, as its declared ",(0,a.yg)("strong",{parentName:"p"},"in the schema"),", to turn any strings into lower case letters."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Example: ToLowerDirective.cs"',title:'"Example:','ToLowerDirective.cs"':!0},'public class ToLowerDirective : GraphDirective\n{\n [DirectiveLocations(DirectiveLocation.FIELD_DEFINITION)]\n public IGraphActionResult Execute()\n {\n // ensure we are working with a graph field definition and that it returns a string\n if (this.DirectiveTarget is IGraphField field)\n {\n // ObjectType represents the .NET Type of the data returned by the field\n if (field.ObjectType != typeof(string))\n throw new Exception("This directive can only be applied to string fields");\n\n // update the resolver to execute the orignal\n // resolver then apply lower casing to the string result\n var resolver = field.Resolver.Extend(ConvertToLower);\n field.UpdateResolver(resolver);\n }\n\n return this.Ok();\n }\n\n private static Task ConvertToLower(FieldResolutionContext context, CancellationToken token)\n {\n if (context.Result is string)\n context.Result = context.Result?.ToString().ToLower();\n\n return Task.CompletedTask;\n }\n}\n')),(0,a.yg)("p",null,"This Directive:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Targets a FIELD_DEFINITION."),(0,a.yg)("li",{parentName:"ul"},"Ensures that the target field returns a string."),(0,a.yg)("li",{parentName:"ul"},"Extends the field's resolver to convert the result to a lower-case string."),(0,a.yg)("li",{parentName:"ul"},"The directive is executed once per field definition its applied to when the schema is created. The extended resolver method is executed on every field resolution.")),(0,a.yg)("admonition",{title:"Type System Directives",type:"info"},(0,a.yg)("p",{parentName:"admonition"}," Notice the difference in this type system directive vs. the ",(0,a.yg)("inlineCode",{parentName:"p"},"@toUpper")," execution directive above. Where as toUpper was declared as a PostResolver on the document part, this directive extends the primary resolver of an ",(0,a.yg)("inlineCode",{parentName:"p"},"IGraphField")," and affects ALL queries that request this field.")),(0,a.yg)("h3",{id:"example-deprecated"},"Example: @deprecated"),(0,a.yg)("p",null,"The ",(0,a.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive is a built in type system directive provided by graphql to indicate deprecation on a field definition or enum value. Below is the code for its implementation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp"},'public sealed class DeprecatedDirective : GraphDirective\n{\n [DirectiveLocations(DirectiveLocation.FIELD_DEFINITION | DirectiveLocation.ENUM_VALUE)]\n public IGraphActionResult Execute([FromGraphQL("reason")] string reason = "No longer supported")\n {\n if (this.DirectiveTarget is IGraphField field)\n {\n field.IsDeprecated = true;\n field.DeprecationReason = reason;\n }\n else if (this.DirectiveTarget is IEnumValue enumValue)\n {\n enumValue.IsDeprecated = true;\n enumValue.DeprecationReason = reason;\n }\n\n return this.Ok();\n }\n}\n')),(0,a.yg)("p",null,"This Directive:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Targets a FIELD_DEFINITION or ENUM_VALUE."),(0,a.yg)("li",{parentName:"ul"},"Marks the field or enum value as deprecated and attaches the provided deprecation reason"),(0,a.yg)("li",{parentName:"ul"},"The directive is executed once per field definition and enum value its applied to when the schema is created.")),(0,a.yg)("h3",{id:"applying-type-system-directives"},"Applying Type System Directives"),(0,a.yg)("h4",{id:"using-the-applydirective-attribute"},"Using the ",(0,a.yg)("inlineCode",{parentName:"h4"},"[ApplyDirective]")," attribute"),(0,a.yg)("p",null,"If you have access to the source code of a given type you can use the ",(0,a.yg)("inlineCode",{parentName:"p"},"[ApplyDirective]")," attribute:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Person.cs"',title:'"Person.cs"'},"public class Person\n{\n [ApplyDirective(typeof(ToLowerDirective))]\n public string Name{ get; set; }\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Person Type Definition"',title:'"Person',Type:!0,'Definition"':!0},"type Person {\n name: String @toLower\n}\n")),(0,a.yg)("p",null,"If different schemas on your server will use different implementations of the directive you can also specify the directive by name. This name is case sensitive and must match the name of the registered directive in the target schema. At runtime, the concrete class declared as the directive in each schema will be instantiated and used."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Apply a Directive By Name"',title:'"Apply',a:!0,Directive:!0,By:!0,'Name"':!0},'[ApplyDirective("monitor")]\npublic class Person\n{\n public string Name{ get; set; }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Person Type Definition"',title:'"Person',Type:!0,'Definition"':!0},"type Person @monitor {\n name: String\n}\n")),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Adding Argument Values with ","[ApplyDirective]")),(0,a.yg)("p",null,"Arguments added to the apply directive attribute will be passed to the directive in the order they are encountered. The supplied values must be coercable into the expected data types for any input parameters."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Applying Directive Arguments"',title:'"Applying',Directive:!0,'Arguments"':!0},'public class Person\n{\n [ApplyDirective("deprecated", "Names don\'t matter")]\n public string Name{ get; set; }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Person Type Definition"',title:'"Person',Type:!0,'Definition"':!0},'type Person {\n name: String @deprecated("Names don\'t matter")\n}\n')),(0,a.yg)("h4",{id:"using-schema-options"},"Using Schema Options"),(0,a.yg)("p",null,"Alternatively, instead of using attributes to apply directives you can apply directives during schema configuration:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Apply Directives at Startup"',title:'"Apply',Directives:!0,at:!0,'Startup"':!0},'services.AddGraphQL(options =>\n{\n options.AddGraphType();\n\n // mark Person.Name as deprecated\n options.ApplyDirective("monitor")\n .ToItems(schemaItem => schemaItem.IsObjectGraphType());\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Person Type Definition"',title:'"Person',Type:!0,'Definition"':!0},"type Person @monitor {\n name: String\n}\n")),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},"The ",(0,a.yg)("inlineCode",{parentName:"p"},"ToItems")," filter can be invoked multiple times. A schema item must match all filter criteria in order for the directive to be applied.")),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},"Type system directives are applied in order of declaration with the ",(0,a.yg)("inlineCode",{parentName:"p"},"[ApplyDirective]")," attributes taking precedence over the ",(0,a.yg)("inlineCode",{parentName:"p"},".ApplyDirective()")," method.")),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Adding arguments via .ApplyDirective()")),(0,a.yg)("p",null,"Adding Arguments via schema options is a lot more flexible than via attributes. Use the ",(0,a.yg)("inlineCode",{parentName:"p"},".WithArguments")," method to supply either a static set of arguments for all matched schema items\nor a ",(0,a.yg)("inlineCode",{parentName:"p"},"Func")," that returns a collection of any parameters you want on a per item basis."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Apply Directives at Startup With Arguments"',title:'"Apply',Directives:!0,at:!0,Startup:!0,With:!0,'Arguments"':!0},'// startup code\nservices.AddGraphQL(options =>\n{\n options.AddGraphType();\n // highlight-start\n options.ApplyDirective("@deprecated")\n .WithArguments("Names don\'t matter")\n .ToItems(schemaItem => schemaItem.IsGraphField("name"));\n // highlight-end\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Person Type Definition"',title:'"Person',Type:!0,'Definition"':!0},'type Person {\n name: String @deprecated("Names don\'t matter")\n}\n')),(0,a.yg)("h3",{id:"repeatable-directives"},"Repeatable Directives"),(0,a.yg)("p",null,"GraphQL ASP.NET supports repeatable type system directives. Sometimes it can be helpful to apply your directive to an schema item more than once, especially if you supply different parameters on each application."),(0,a.yg)("p",null,"Add the ",(0,a.yg)("inlineCode",{parentName:"p"},"[Repeatable]")," attribute to the directive definition and you can then apply it multiple times using the standard methods. GraphQL tools that support this the repeatable syntax will be able to properly interprete your schema."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Repeatable Directives"',title:'"Repeatable','Directives"':!0},'[Repeatable]\npublic sealed class ScanItemDirective : GraphDirective\n{\n [DirectiveLocations(DirectiveLocation.OBJECT)]\n public IGraphActionResult Execute(string scanType)\n { /* ... */}\n}\n\n// Option 1: Apply the directive to the class directly\n// highlight-start\n[ApplyDirective("@scanItem", "medium")]\n[ApplyDirective("@scanItem", "high")]\n// highlight-end\npublic class Person\n{}\n\n// Option 2: Apply the directive at startup\nservices.AddGraphQL(o => {\n // ...\n // highlight-start\n o.ApplyDirective("@scanItem")\n .WithArguments("medium")\n .ToItems(item => item.IsObjectGraphType());\n o.ApplyDirective("@scanItem")\n .WithArguments("high")\n .ToItems(item => item.IsObjectGraphType());\n // highlight-end\n});\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Person Type Definition"',title:'"Person',Type:!0,'Definition"':!0},'type Person @scanItem("medium") @scanItem("high") {\n name: String \n}\n')),(0,a.yg)("h3",{id:"understanding-the-type-system"},"Understanding the Type System"),(0,a.yg)("p",null,"GraphQL ASP.NET builds your schema and all of its types from your controllers and objects. In general, this is done behind the scenes and you do not need to interact with it. However, when applying type system directives you are affecting the generated schema and need to understand the various parts of it. If you have a question don't be afraid to ask on ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/graphql-aspnet"},"github"),"."),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"UML Diagrams")),(0,a.yg)("p",null,"These ",(0,a.yg)("a",{target:"_blank",href:i(1220).A},"uml diagrams")," detail the major interfaces and their most useful properties of the type system. However, these diagrams are not exaustive. Look at the ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Interfaces/Schema"},"source code")," for the full definitions."),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Helpful Extensions")),(0,a.yg)("p",null,"There are a robust set of of built in extensions for ",(0,a.yg)("inlineCode",{parentName:"p"},"ISchemaItem")," that can help you filter your data when applying directives. See the ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet/Configuration/SchemaItemFilterExtensions.cs"},"full source code")," for details."),(0,a.yg)("h2",{id:"directives-as-services"},"Directives as Services"),(0,a.yg)("p",null,"Directives are invoked as services through your DI container when they are executed. When you add types to your schema during its initial configuration, GraphQL ASP.NET will automatically register any directives it finds attached to your entities as services in your ",(0,a.yg)("inlineCode",{parentName:"p"},"IServiceCollection")," instance. However, there are times when it cannot do this, such as when you apply a directive by its string declared name. These late-bound directives may still be discoverable later and graphql will attempt to add them to your schema whenever it can. However, it may do this after the opportunity to register them with the DI container has passed."),(0,a.yg)("p",null,"When this occurs, if your directive contains a public, parameterless constructor graphql will still instantiate and use your directive as normal. If the directive contains dependencies in the constructor that it can't resolve, execution of that directive will fail and an exception will be thrown. To be safe, make sure to add any directives you may use to your schema during the ",(0,a.yg)("inlineCode",{parentName:"p"},".AddGraphQL()")," configuration method. Directives are directly discoverable and will be included via the ",(0,a.yg)("inlineCode",{parentName:"p"},"options.AddAssembly()")," helper method as well."),(0,a.yg)("p",null,"The benefit of ensuring your directives are part of your ",(0,a.yg)("inlineCode",{parentName:"p"},"IServiceCollection")," should be apparent:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"The directive instance will obey lifetime scopes (e.g. transient, scoped, singleton)."),(0,a.yg)("li",{parentName:"ul"},"The directive can be instantiated with any dependencies or services you wish; making for a much richer experience.")),(0,a.yg)("h2",{id:"directive-security"},"Directive Security"),(0,a.yg)("p",null,"Directives can be secured like controller actions. However, where a controller action represents a field in the graph, a directive action does not. Regardless of the number of action methods, there is only one directive definition in your schema. As a result, the directive is secured at the class level not the method level. Any applied security parameters effect ALL action methods equally."),(0,a.yg)("p",null,"Take for example that the graph schema included a field of data that, by default, was always rendered in a redacted state (meaning it was obsecured) such as social security number. You could have a directive that, when supplied by the requestor, would unredact the field and allow the value to be displayed."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Applying Authorization to Directives"',title:'"Applying',Authorization:!0,to:!0,'Directives"':!0},'// highlight-next-line\n[Authorize(Policy = "admin")]\npublic sealed class UnRedactDirective : GraphDirective\n{\n [DirectiveLocations(DirectiveLocation.FIELD)]\n public IGraphActionResult Execute()\n { /* ... */}\n}\n')),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},"A user must adhere to the requirements of the ",(0,a.yg)("inlineCode",{parentName:"p"},"admin")," policy in order to apply the ",(0,a.yg)("inlineCode",{parentName:"p"},"@unRedact")," directive to a field. If the user is not part of this policy and they attempt to apply the directive, the query will be rejected.")),(0,a.yg)("h3",{id:"security-scenarios"},"Security Scenarios"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},(0,a.yg)("strong",{parentName:"p"},"Execution Directives")," - These directives execute using the same security context and ",(0,a.yg)("inlineCode",{parentName:"p"},"ClaimsPrincipal")," applied to the HTTP request; such as an oAuth token. Execution directives are evaluated against the source document while its being constructed, BEFORE it is executed. As a result, if an execution directive fails authorization, the document fails to be constructed and no fields are resolved. This is true regardless of the authorization method assigned to the schema.")),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},(0,a.yg)("strong",{parentName:"p"},"Type System Directives")," - These directives are executed during server startup, WITHOUT a ",(0,a.yg)("inlineCode",{parentName:"p"},"ClaimsPrincipal"),", while the schema is being built. As a result, type system directives should not contain any security requirements, they will fail to execute if any security parameters are defined."))),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},"Since type system directives execute outside of a specific user context, only apply type system directives that you trust.")),(0,a.yg)("h2",{id:"demo-project"},"Demo Project"),(0,a.yg)("p",null,"See the ",(0,a.yg)("a",{parentName:"p",href:"/docs/reference/demo-projects"},"Demo Projects")," page for a demonstration on creating a type system directive for extending a field resolver and an execution directives\nthat manipulates a string field result at runtime."))}d.isMDXComponent=!0},1220:(e,t,i)=>{i.d(t,{A:()=>n});const n=i.p+"assets/files/2022-10-graphql-aspnet-structural-diagrams-2ce89a3328b83ac31eb4dac13986550a.pdf"}}]); \ No newline at end of file diff --git a/assets/js/43b0182c.3fd2562b.js b/assets/js/43b0182c.3fd2562b.js new file mode 100644 index 0000000..4d43868 --- /dev/null +++ b/assets/js/43b0182c.3fd2562b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[4368],{5680:(e,n,t)=>{t.d(n,{xA:()=>u,yg:()=>y});var a=t(6540);function o(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function r(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function i(e){for(var n=1;n=0||(o[t]=e[t]);return o}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(o[t]=e[t])}return o}var s=a.createContext({}),p=function(e){var n=a.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},u=function(e){var n=p(e.components);return a.createElement(s.Provider,{value:n},e.children)},c="mdxType",h={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},d=a.forwardRef((function(e,n){var t=e.components,o=e.mdxType,r=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),c=p(t),d=o,y=c["".concat(s,".").concat(d)]||c[d]||h[d]||r;return t?a.createElement(y,i(i({ref:n},u),{},{components:t})):a.createElement(y,i({ref:n},u))}));function y(e,n){var t=arguments,o=n&&n.mdxType;if("string"==typeof e||o){var r=t.length,i=new Array(r);i[0]=d;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[c]="string"==typeof e?e:o,i[1]=l;for(var p=2;p{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>c,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var a=t(8168),o=(t(6540),t(5680));const r={id:"unions",title:"Unions",sidebar_label:"Unions",sidebar_position:3},i=void 0,l={unversionedId:"types/unions",id:"types/unions",title:"Unions",description:"Unions are an aggregate graph type representing multiple, different OBJECT types with no guaranteed fields or interfaces in common; for instance, Salad or House. Because of this, unions define no fields themselves but provide a common way to query the fields of the union members when one is encountered.",source:"@site/docs/types/unions.md",sourceDirName:"types",slug:"/types/unions",permalink:"/docs/types/unions",draft:!1,tags:[],version:"current",sidebarPosition:3,frontMatter:{id:"unions",title:"Unions",sidebar_label:"Unions",sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Interfaces",permalink:"/docs/types/interfaces"},next:{title:"Enums",permalink:"/docs/types/enums"}},s={},p=[{value:"Declaring a Union",id:"declaring-a-union",level:2},{value:"What to Return for a Union",id:"what-to-return-for-a-union",level:3},{value:"Returning a List of Objects",id:"returning-a-list-of-objects",level:4},{value:"Union Proxies",id:"union-proxies",level:2},{value:"Union Name Uniqueness",id:"union-name-uniqueness",level:2},{value:"Liskov Substitutions",id:"liskov-substitutions",level:2},{value:"IGraphUnionProxy.MapType",id:"igraphunionproxymaptype",level:4}],u={toc:p};function c(e){let{components:n,...t}=e;return(0,o.yg)("wrapper",(0,a.A)({},u,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"Unions are an aggregate graph type representing multiple, different ",(0,o.yg)("inlineCode",{parentName:"p"},"OBJECT")," types with no guaranteed fields or interfaces in common; for instance, ",(0,o.yg)("inlineCode",{parentName:"p"},"Salad")," or ",(0,o.yg)("inlineCode",{parentName:"p"},"House"),". Because of this, unions define no fields themselves but provide a common way to query the fields of the union members when one is encountered."),(0,o.yg)("p",null,"Unlike other graph types there is no concrete representation of unions. Where a ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," is an object graph type or a .NET ",(0,o.yg)("inlineCode",{parentName:"p"},"enum")," is an enum graph type there is no analog for unions. Instead unions are semi-virtual types that are created from proxy classes that represent them at design time."),(0,o.yg)("h2",{id:"declaring-a-union"},"Declaring a Union"),(0,o.yg)("p",null,"You can declare a union in your action method using one of the many overloads to the query and mutation attributes:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:"title=\"Declaring an 'inline' Union on an Action Method\"",title:'"Declaring',an:!0,"'inline'":!0,Union:!0,on:!0,Action:!0,'Method"':!0},'public class DataController : GraphController\n{\n // highlight-next-line\n [QueryRoot("search", "SaladOrHouse", typeof(Salad), typeof(House))]\n public ????? SearchData(string name)\n {/* ... */}\n}\n')),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Example Query"',title:'"Example','Query"':!0},'query {\n search(name: "green*") {\n ...on Salad {\n name\n hasCroutons\n }\n\n ...on House {\n postalCode\n squareFeet\n }\n }\n}\n')),(0,o.yg)("p",null,"In this example we :"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"Declared an action method named ",(0,o.yg)("inlineCode",{parentName:"li"},"SearchData")," with a graph field name of ",(0,o.yg)("inlineCode",{parentName:"li"},"search")),(0,o.yg)("li",{parentName:"ul"},"Declared a union type on our graph named ",(0,o.yg)("inlineCode",{parentName:"li"},"SaladOrHouse")),(0,o.yg)("li",{parentName:"ul"},"Included two object types in the union: ",(0,o.yg)("inlineCode",{parentName:"li"},"Salad")," and ",(0,o.yg)("inlineCode",{parentName:"li"},"House"))),(0,o.yg)("admonition",{type:"tip"},(0,o.yg)("p",{parentName:"admonition"},"Unlike with ",(0,o.yg)("a",{parentName:"p",href:"./interfaces"},"interfaces")," where the possible types returned from an action method can be declared else where, you MUST provide all of the types to include in the union in the declaration.")),(0,o.yg)("h3",{id:"what-to-return-for-a-union"},"What to Return for a Union"),(0,o.yg)("p",null,"Notice we have a big question mark on what the action method returns in the above example. From a C# perspective, in this example, there is no ",(0,o.yg)("inlineCode",{parentName:"p"},"IDataItem")," interface shared between ",(0,o.yg)("inlineCode",{parentName:"p"},"Salad")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"House"),". This represents a problem for static-typed languages like C#. Since unions are virtual types there exists no common type that you can return for generated data. ",(0,o.yg)("inlineCode",{parentName:"p"},"System.Object")," might work but it tends be too general and the runtime will reject it as a safe guard."),(0,o.yg)("p",null,"So what do you do? Return an ",(0,o.yg)("inlineCode",{parentName:"p"},"IGraphActionResult")," instead and let the runtime handle the details."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Return IGraphActionResult When Working With Unions"',title:'"Return',IGraphActionResult:!0,When:!0,Working:!0,With:!0,'Unions"':!0},'public class DataController : GraphController\n{\n // service injection omitted for brevity\n\n [QueryRoot("search", "SaladOrHouse", typeof(Salad), typeof(House))]\n // highlight-next-line\n public async Task SearchData(string text)\n {\n if(name.Contains("green"))\n {\n Salad salad = await _saladService.FindSalad(text);\n return this.Ok(salad);\n }\n else\n {\n House house = await _houses.FindHouse(text);\n return this.Ok(house);\n }\n }\n}\n')),(0,o.yg)("admonition",{type:"info"},(0,o.yg)("p",{parentName:"admonition"},"Any controller action that declares a union MUST return an ",(0,o.yg)("inlineCode",{parentName:"p"},"IGraphActionResult"))),(0,o.yg)("h4",{id:"returning-a-list-of-objects"},"Returning a List of Objects"),(0,o.yg)("p",null,"Perhaps the most complex scenario when working with unions is returning a list of objects. Since there there is no way to declare a ",(0,o.yg)("inlineCode",{parentName:"p"},"List")," that the library could analyze we have to explicitly declare the field to let GraphQL what is going on."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Return a List of Objects"',title:'"Return',a:!0,List:!0,of:!0,'Objects"':!0},'public class DataController : GraphController\n{\n // service injection omitted for brevity\n // highlight-next-line\n [QueryRoot("search", "SaladOrHouse", typeof(Salad), typeof(House), TypeExpression = "[Type]")]\n public async Task SearchData(string text)\n {\n Salad salad = await _saladService.FindSalad(text);\n House house = await _houses.FindHouse(text);\n\n var dataItems = new List();\n dataItems.Add(salad);\n dataItems.Add(house);\n\n return this.Ok(dataItems);\n }\n}\n')),(0,o.yg)("blockquote",null,(0,o.yg)("p",{parentName:"blockquote"},"Here we've added a custom type expression to tell GraphQL that this field returns a list of objects. GraphQL will then process each item on the list according to the rules of the union.")),(0,o.yg)("h2",{id:"union-proxies"},"Union Proxies"),(0,o.yg)("p",null,"In the example above, we declare the union inline on the query attribute. But what if we wanted to reuse the ",(0,o.yg)("inlineCode",{parentName:"p"},"SaladOrHouse")," union in multiple places. You could declare the union exactly the same on each method or use a union proxy. "),(0,o.yg)("p",null,"Create a class that implements ",(0,o.yg)("inlineCode",{parentName:"p"},"IGraphUnionProxy")," or inherits from ",(0,o.yg)("inlineCode",{parentName:"p"},"GraphUnionProxy")," to encapsulate the details, then add that as a reference in your controller methods instead of the individual types. This can also be handy for uncluttering your code if you have a lot of possible types for the union. The return type of your method will still need to be ",(0,o.yg)("inlineCode",{parentName:"p"},"IGraphActionResult"),". You cannot return a proxy as a value."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Example Using IGraphUnionProxy"',title:'"Example',Using:!0,'IGraphUnionProxy"':!0},'public class KitchenController : GraphController\n{\n // highlight-next-line\n [QueryRoot("searchFood", typeof(SaladOrHouse))]\n public async Task SearchFood(string name)\n {/* ... */}\n}\n\n// highlight-next-line\npublic class SaladOrHouse : GraphUnionProxy\n{\n public SaladOrHouse()\n {\n this.Name = "SaladOrHouse";\n this.AddType(typeof(Salad));\n this.AddType(typeof(House));\n }\n}\n')),(0,o.yg)("blockquote",null,(0,o.yg)("p",{parentName:"blockquote"},"If you don't supply a name, graphql will use the class name of the proxy as the name of the union.")),(0,o.yg)("h2",{id:"union-name-uniqueness"},"Union Name Uniqueness"),(0,o.yg)("p",null,"Union names must be unique in a schema. If you do declare a union in multiple action methods without a proxy, GraphQL will attempt to merge the references by name and included types. As long as all declarations are the same, that is the name and the set of included types, then graphql will accept the union. Otherwise, a ",(0,o.yg)("inlineCode",{parentName:"p"},"GraphTypeDeclarationException")," will be thrown at startup."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="An Invalid Union Declaration"',title:'"An',Invalid:!0,Union:!0,'Declaration"':!0},'public class KitchenController : GraphController\n{\n // highlight-next-line\n [QueryRoot("search", "SaladOrHouse", typeof(Salad), typeof(House))]\n public async Task SearchData(string name)\n {/* ... */}\n\n // ERROR: Union members for \'SaladOrHouse\' are different\n // -----------------\n // highlight-next-line\n [QueryRoot("fetch", "SaladOrHouse", typeof(Salad), typeof(House), typeof(GameConsole))]\n public async Task RetrieveItem(int id)\n {/* ... */}\n}\n')),(0,o.yg)("h2",{id:"liskov-substitutions"},"Liskov Substitutions"),(0,o.yg)("p",null,(0,o.yg)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Liskov_substitution_principle"},"Liskov substitutions")," (the L in ",(0,o.yg)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/SOLID"},"SOLID"),") are an important part of object oriented programming. To be able to have one class masquerade as another allows us to easily extend our code's capabilities without any rework."),(0,o.yg)("p",null,"For Example, the Oven object below can bake any type of bread!"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Liskov Substitution Example"',title:'"Liskov',Substitution:!0,'Example"':!0},"public class Bread\n{}\n\npublic class Roll : Bread\n{}\n\npublic class Bagel : Roll\n{}\n\npublic class Oven \n{\n // highlight-next-line\n public void Bake(Bread bread)\n {\n // We can pass in Bread, Roll or Bagel to the oven.\n }\n}\n")),(0,o.yg)("p",null,"However, this presents a problem when when dealing with UNIONs and GraphQL:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs"',title:'"BakeryController.cs"'},'public class BakeryController : GraphController\n{\n // highlight-next-line\n [QueryRoot("searchFood", "RollOrBread", typeof(Roll), typeof(Bread))]\n public IGraphActionResult SearchFood(string name)\n {\n // Should GraphQL treat a bagel \n // as a Roll or Bread ??\n // highlight-next-line\n var myBagel = new Bagel();\n return this.Ok(myBagel);\n }\n}\n')),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'query {\n searchFood(name: "Everything"){\n ... on Bread { \n name, \n type \n }\n ... on Roll { \n name, \n hardness \n }\n }\n}\n')),(0,o.yg)("p",null,"Most of the time, graphql can correctly interpret the correct type of a returned data object and continue processing the query. However, in the above example, we declare a union, ",(0,o.yg)("inlineCode",{parentName:"p"},"RollOrBread"),", that is of types ",(0,o.yg)("inlineCode",{parentName:"p"},"Roll")," or ",(0,o.yg)("inlineCode",{parentName:"p"},"Bread")," yet we return a ",(0,o.yg)("inlineCode",{parentName:"p"},"Bagel")," from the action method. "),(0,o.yg)("p",null,"Since ",(0,o.yg)("inlineCode",{parentName:"p"},"Bagel")," is both a ",(0,o.yg)("inlineCode",{parentName:"p"},"Roll")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"Bread")," which type should graphql match against when executing the inline fragments? Since it could be either, graphql will be unable to determine which type to use and can't advance the query to select the appropriate fields. The query result is said to be indeterminate. "),(0,o.yg)("h4",{id:"igraphunionproxymaptype"},"IGraphUnionProxy.MapType"),(0,o.yg)("p",null,"Luckily there is a way to allow you to take control of your unions and make the determination on your own. The ",(0,o.yg)("inlineCode",{parentName:"p"},"MapType")," method provided by ",(0,o.yg)("inlineCode",{parentName:"p"},"IGraphUnionProxy")," will be called whenever a query result is indeterminate, allowing you to choose which of your union's allowed types should be used. "),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using a Custom Type Mapper"',title:'"Using',a:!0,Custom:!0,Type:!0,'Mapper"':!0},"public class RollOrBread : GraphUnionProxy\n{\n public RollOrBread()\n {\n this.AddType(typeof(Roll));\n this.AddType(typeof(Bread));\n }\n\n // highlight-start\n public override Type MapType(Type runtimeObjectType)\n {\n if (runtimeObjectType == typeof(Bagel))\n return typeof(Roll);\n else\n return typeof(Bread);\n }\n // highlight-end\n}\n")),(0,o.yg)("p",null,"The query will now interpret all ",(0,o.yg)("inlineCode",{parentName:"p"},"Bagels")," as ",(0,o.yg)("inlineCode",{parentName:"p"},"Rolls")," and be able to process the query correctly."),(0,o.yg)("p",null,"If, via your logic you are unable to determine which of your Union's types to use then return ",(0,o.yg)("inlineCode",{parentName:"p"},"null")," and GraphQL will supply the caller with an appropriate error message stating the query was indeterminate. Also, returning any type other than one that was formally declared as part of your Union will result in the same indeterminate state."),(0,o.yg)("p",null,"Most of the time GraphQL ASP.NET will never call ",(0,o.yg)("inlineCode",{parentName:"p"},"MapType")," on your union proxy. If your union types do not share an inheritance chain, for instance, the method will never be called."),(0,o.yg)("admonition",{type:"caution"},(0,o.yg)("p",{parentName:"admonition"}," The ",(0,o.yg)("inlineCode",{parentName:"p"},"MapType()")," function is not based on a resolved value, but only on the ",(0,o.yg)("inlineCode",{parentName:"p"},"System.Type")," that was encountered. This is by design to guarantee consistency in query execution. ")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/49d4cdfe.175849e0.js b/assets/js/49d4cdfe.175849e0.js new file mode 100644 index 0000000..88d4fa0 --- /dev/null +++ b/assets/js/49d4cdfe.175849e0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[5176],{5680:(e,t,n)=>{n.d(t,{xA:()=>g,yg:()=>u});var a=n(6540);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var p=a.createContext({}),o=function(e){var t=a.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},g=function(e){var t=o(e.components);return a.createElement(p.Provider,{value:t},e.children)},d="mdxType",y={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,p=e.parentName,g=s(e,["components","mdxType","originalType","parentName"]),d=o(n),m=r,u=d["".concat(p,".").concat(m)]||d[m]||y[m]||i;return n?a.createElement(u,l(l({ref:t},g),{},{components:n})):a.createElement(u,l({ref:t},g))}));function u(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,l=new Array(i);l[0]=m;var s={};for(var p in t)hasOwnProperty.call(t,p)&&(s[p]=t[p]);s.originalType=e,s[d]="string"==typeof e?e:r,l[1]=s;for(var o=2;o{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>o});var a=n(8168),r=(n(6540),n(5680));const i={id:"subscription-events",title:"Subscription Logging Events",sidebar_label:"Subscription Events",sidebar_position:2},l=void 0,s={unversionedId:"logging/subscription-events",id:"logging/subscription-events",title:"Subscription Logging Events",description:"GraphQL ASP.NET tracks some special events related to the management of subscriptions. They are outlined below.",source:"@site/docs/logging/subscription-events.md",sourceDirName:"logging",slug:"/logging/subscription-events",permalink:"/docs/logging/subscription-events",draft:!1,tags:[],version:"current",sidebarPosition:2,frontMatter:{id:"subscription-events",title:"Subscription Logging Events",sidebar_label:"Subscription Events",sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"Standard Events",permalink:"/docs/logging/standard-events"},next:{title:"Query Profiling",permalink:"/docs/execution/metrics"}},p={},o=[{value:"Server Level Events",id:"server-level-events",level:2},{value:"Subscription Event Dispatch Queue Alert",id:"subscription-event-dispatch-queue-alert",level:3},{value:"Schema Level Events",id:"schema-level-events",level:2},{value:"Subscription Route Registered",id:"subscription-route-registered",level:3},{value:"Client Connection Events",id:"client-connection-events",level:2},{value:"Client Registered",id:"client-registered",level:3},{value:"Client Dropped",id:"client-dropped",level:3},{value:"Unsupported Client Protocol",id:"unsupported-client-protocol",level:3},{value:"Client Messaging Events",id:"client-messaging-events",level:2},{value:"Subscription Event Received",id:"subscription-event-received",level:3},{value:"Subscription Registered",id:"subscription-registered",level:3},{value:"Subscription Registered",id:"subscription-registered-1",level:3},{value:"Client Message Received",id:"client-message-received",level:3},{value:"Client Message Sent",id:"client-message-sent",level:3},{value:"Subscription Events",id:"subscription-events",level:2},{value:"Subscription Event Published",id:"subscription-event-published",level:3},{value:"Subscription Event Received",id:"subscription-event-received-1",level:3}],g={toc:o};function d(e){let{components:t,...n}=e;return(0,r.yg)("wrapper",(0,a.A)({},g,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL ASP.NET tracks some special events related to the management of ",(0,r.yg)("a",{parentName:"p",href:"/docs/advanced/subscriptions"},"subscriptions"),". They are outlined below."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Common Event Properties")),(0,r.yg)("p",null,"The common event properties outlined on the ",(0,r.yg)("a",{parentName:"p",href:"/docs/logging/standard-events"},"standard events")," page apply to all subscription events as well."),(0,r.yg)("h2",{id:"server-level-events"},"Server Level Events"),(0,r.yg)("h3",{id:"subscription-event-dispatch-queue-alert"},"Subscription Event Dispatch Queue Alert"),(0,r.yg)("p",null,"This event is recorded when the server's schema-agnostic, internal dispatch queue reaches a given threshold. The internal dispatch queue is where all subscription events destined for connected clients are staged before being processed. The thresholds at which this alert is recorded can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/advanced/subscriptions#dispatch-queue-monitoring"},"customized"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ThresholdLevelReached")),(0,r.yg)("td",{parentName:"tr",align:null},"The declared threshold level that was reached causing this entry to be recorded. (Expressed in # of queued events)")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"EventQueueCount")),(0,r.yg)("td",{parentName:"tr",align:null},"The actual number of events currently queued.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"CustomMessage")),(0,r.yg)("td",{parentName:"tr",align:null},"An optional, staticly declared message registered with the threshold level.")))),(0,r.yg)("h2",{id:"schema-level-events"},"Schema Level Events"),(0,r.yg)("h3",{id:"subscription-route-registered"},"Subscription Route Registered"),(0,r.yg)("p",null,"This event is recorded when GraphQL successfully registers an entry in the ASP.NET route table to accept requests for a target schema as well as\nregister the middleware component necessary to receive websocket requests."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full name of your your schema type. For most single schema applications this will be ",(0,r.yg)("inlineCode",{parentName:"td"},"GraphQL.AspNet.Schemas.GraphSchema"),".")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaSubscriptionRoutePath")),(0,r.yg)("td",{parentName:"tr",align:null},"The relative URL that was registered for the schema type, e.g. ",(0,r.yg)("inlineCode",{parentName:"td"},"'/graphql"))))),(0,r.yg)("h2",{id:"client-connection-events"},"Client Connection Events"),(0,r.yg)("h3",{id:"client-registered"},"Client Registered"),(0,r.yg)("p",null,'This event is recorded when GraphQL successfully accepts a client and has assigned a client proxy to manage the connection. This event is recorded just prior to the connection is "started" and messaging begins.'),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id asigned to the client when it first connected.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full name of your your schema type. For most single schema applications this will be ",(0,r.yg)("inlineCode",{parentName:"td"},"GraphQL.AspNet.Schemas.GraphSchema"),".")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full name of the assigned client proxy type. In general, a different type is used per messaging protocol.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientProtocol")),(0,r.yg)("td",{parentName:"tr",align:null},"The protocol negotiated by the client and server that will be used for the duration of the connection.")))),(0,r.yg)("h3",{id:"client-dropped"},"Client Dropped"),(0,r.yg)("p",null,'This event is recorded when GraphQL is releasing a client. The connection has been "stopped" and no additional messagings are being broadcast. This event occurs just before the HTTP connection is closed.'),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id asigned to the client when it first connected.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full name of the assigned client proxy type. In general, a different type is used per messaging protocol.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientProtocol")),(0,r.yg)("td",{parentName:"tr",align:null},"The protocol negotiated by the client and server that will be used for the duration of the connection.")))),(0,r.yg)("h3",{id:"unsupported-client-protocol"},"Unsupported Client Protocol"),(0,r.yg)("p",null,"This event is recorded when GraphQL attempts to create an appropriate proxy class for the connection but no such proxy could be deteremined from the details providied in the initial request. In general, this means the provided websocket sub protocols did not match a supported protocol for this server and schema combination."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full name of your your schema type. For most single schema applications this will be ",(0,r.yg)("inlineCode",{parentName:"td"},"GraphQL.AspNet.Schemas.GraphSchema"),".")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientProtocol")),(0,r.yg)("td",{parentName:"tr",align:null},"The protocol(s) requested by the client connection that were not accepted")))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id asigned to the client when it first connected.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full name of the assigned client proxy type. In general, a different type is used per messaging protocol.")))),(0,r.yg)("h2",{id:"client-messaging-events"},"Client Messaging Events"),(0,r.yg)("h3",{id:"subscription-event-received"},"Subscription Event Received"),(0,r.yg)("p",null,"This event is recorded by a client proxy when it received an event from the router and has determined that it should be handled."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id asigned to the client when it first connected.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full name of your your schema type. For most single schema applications this will be ",(0,r.yg)("inlineCode",{parentName:"td"},"GraphQL.AspNet.Schemas.GraphSchema"),".")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SubscriptionPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The path to the target top-level subscription field in the schema")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SubscriptionCount")),(0,r.yg)("td",{parentName:"tr",align:null},"The number of registered subscriptions, for this client, that will receive this event.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SubscriptionIds")),(0,r.yg)("td",{parentName:"tr",align:null},"A comma seperated list of id values representing the subscriptions that will receive this event.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"MachineName")),(0,r.yg)("td",{parentName:"tr",align:null},"The ",(0,r.yg)("inlineCode",{parentName:"td"},"Environment.MachineName")," of the current server.")))),(0,r.yg)("h3",{id:"subscription-registered"},"Subscription Registered"),(0,r.yg)("p",null,"This event is recorded by a client proxy when it starts a new subscription on behalf of its connected client."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id asigned to the client when it first connected.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SubscriptionPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The path to the target top-level subscription field in the schema")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SubscriptionId")),(0,r.yg)("td",{parentName:"tr",align:null},"The subscription id requested by the client.")))),(0,r.yg)("h3",{id:"subscription-registered-1"},"Subscription Registered"),(0,r.yg)("p",null,"This event is recorded by a client proxy when it unregistered and abandons an existing subscription. This may be due to the server ending the subscription or the client requesting it be stopped."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id asigned to the client when it first connected.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SubscriptionPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The path to the target top-level subscription field in the schema")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SubscriptionId")),(0,r.yg)("td",{parentName:"tr",align:null},"The subscription id requested by the client.")))),(0,r.yg)("h3",{id:"client-message-received"},"Client Message Received"),(0,r.yg)("p",null,"This event is recorded by a client proxy when it successfully receives and deserializes a message from its connected client. Not all client proxies may record this event. The messages a client proxy defines must implement ",(0,r.yg)("inlineCode",{parentName:"p"},"ILoggableClientProxyMessage")," in order to use this event."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id asigned to the client when it first connected.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"MessageType")),(0,r.yg)("td",{parentName:"tr",align:null},"A string value representing the type of the message that was received")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"MessageId")),(0,r.yg)("td",{parentName:"tr",align:null},"The globally unique message id that was assigned to the incoming message.")))),(0,r.yg)("h3",{id:"client-message-sent"},"Client Message Sent"),(0,r.yg)("p",null,"This event is recorded by a client proxy when it successfully serializes and transmits a message to its connected client. Not all client proxies may record this event. The messages a client proxy defines must implement ",(0,r.yg)("inlineCode",{parentName:"p"},"ILoggableClientProxyMessage")," in order to use this event."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ClientId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id asigned to the client when it first connected.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"MessageType")),(0,r.yg)("td",{parentName:"tr",align:null},"A string value representing the type of the message that was received")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"MessageId")),(0,r.yg)("td",{parentName:"tr",align:null},"The globally unique message id that was assigned to the incoming message.")))),(0,r.yg)("h2",{id:"subscription-events"},"Subscription Events"),(0,r.yg)("p",null,"Subscription events refer to the events that are raised from mutations and processed by client proxies with registered subscriptions against those events."),(0,r.yg)("h3",{id:"subscription-event-published"},"Subscription Event Published"),(0,r.yg)("p",null," This event is recorded just after an event is handed off to a ",(0,r.yg)("inlineCode",{parentName:"p"},"ISubscriptionEventPublisher")," for publishing to a storage medium. Custom publishers do not need to record this event manually."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaType")),(0,r.yg)("td",{parentName:"tr",align:null},"The schema type name as it was published. This will likely include additional information not recorded in standard schema level events.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"DataType")),(0,r.yg)("td",{parentName:"tr",align:null},"The data type name of the data object that was published.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SubscriptionEventId")),(0,r.yg)("td",{parentName:"tr",align:null},"The globally unique id of the subscription event.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SubscriptionEventName")),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the event as its defined in the schema.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"MachineName")),(0,r.yg)("td",{parentName:"tr",align:null},"The ",(0,r.yg)("inlineCode",{parentName:"td"},"Environment.MachineName")," of the current server.")))),(0,r.yg)("h3",{id:"subscription-event-received-1"},"Subscription Event Received"),(0,r.yg)("p",null," This event is recorded by the event router just after it receives an event. The router will then proceed to forward the event to the correct client instances for processing."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaType")),(0,r.yg)("td",{parentName:"tr",align:null},"The schema type name as it was recevied. This will likely include additional information not recorded in standard schema level events.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"DataType")),(0,r.yg)("td",{parentName:"tr",align:null},"The data type name of the data object that was received.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SubscriptionEventId")),(0,r.yg)("td",{parentName:"tr",align:null},"The globally unique id of the subscription event.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SubscriptionEventName")),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the event as its defined in the schema.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"MachineName")),(0,r.yg)("td",{parentName:"tr",align:null},"The ",(0,r.yg)("inlineCode",{parentName:"td"},"Environment.MachineName")," of the current server.")))))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/522ea0c7.ed2454d0.js b/assets/js/522ea0c7.ed2454d0.js new file mode 100644 index 0000000..654a7ac --- /dev/null +++ b/assets/js/522ea0c7.ed2454d0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[9644],{5680:(e,t,r)=>{r.d(t,{xA:()=>c,yg:()=>y});var n=r(6540);function o(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function a(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function s(e){for(var t=1;t=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var l=n.createContext({}),p=function(e){var t=n.useContext(l),r=t;return e&&(r="function"==typeof e?e(t):s(s({},t),e)),r},c=function(e){var t=p(e.components);return n.createElement(l.Provider,{value:t},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},h=n.forwardRef((function(e,t){var r=e.components,o=e.mdxType,a=e.originalType,l=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),u=p(r),h=o,y=u["".concat(l,".").concat(h)]||u[h]||d[h]||a;return r?n.createElement(y,s(s({ref:t},c),{},{components:r})):n.createElement(y,s({ref:t},c))}));function y(e,t){var r=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var a=r.length,s=new Array(a);s[0]=h;var i={};for(var l in t)hasOwnProperty.call(t,l)&&(i[l]=t[l]);i.originalType=e,i[u]="string"==typeof e?e:o,s[1]=i;for(var p=2;p{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>u,frontMatter:()=>a,metadata:()=>i,toc:()=>p});var n=r(8168),o=(r(6540),r(5680));const a={id:"http-processor",title:"HTTP Processor",sidebar_label:"HTTP Processor",sidebar_position:6},s=void 0,i={unversionedId:"reference/http-processor",id:"reference/http-processor",title:"HTTP Processor",description:"The DefaultGraphQLHttpProcessor is mapped to a route for the target schema and accepts an HttpContext from the ASP.NET runtime. It inspects the received payload (the query text and variables) then packages an IQueryExecutionRequest and sends it to the GraphQL runtime. Once a result is generated the controller forwards that response to the response writer for serialization.",source:"@site/docs/reference/http-processor.md",sourceDirName:"reference",slug:"/reference/http-processor",permalink:"/docs/reference/http-processor",draft:!1,tags:[],version:"current",sidebarPosition:6,frontMatter:{id:"http-processor",title:"HTTP Processor",sidebar_label:"HTTP Processor",sidebar_position:6},sidebar:"tutorialSidebar",previous:{title:"GraphDirective",permalink:"/docs/reference/graph-directive"},next:{title:"Pipelines & Middleware",permalink:"/docs/reference/middleware"}},l={},p=[{value:"Extending the Http Processor",id:"extending-the-http-processor",level:2},{value:"Helpful Methods",id:"helpful-methods",level:2},{value:"CreateRequest(queryData)",id:"createrequestquerydata",level:3},{value:"HandleQueryException(exception)",id:"handlequeryexceptionexception",level:3},{value:"HandleQueryMetrics(metrics)",id:"handlequerymetricsmetrics",level:3},{value:"ExposeExceptions",id:"exposeexceptions",level:3},{value:"ExposeMetrics",id:"exposemetrics",level:3}],c={toc:p};function u(e){let{components:t,...r}=e;return(0,o.yg)("wrapper",(0,n.A)({},c,r,{components:t,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"DefaultGraphQLHttpProcessor")," is mapped to a route for the target schema and accepts an ",(0,o.yg)("inlineCode",{parentName:"p"},"HttpContext")," from the ASP.NET runtime. It inspects the received payload (the query text and variables) then packages an ",(0,o.yg)("inlineCode",{parentName:"p"},"IQueryExecutionRequest")," and sends it to the GraphQL runtime. Once a result is generated the controller forwards that response to the response writer for serialization."),(0,o.yg)("h2",{id:"extending-the-http-processor"},"Extending the Http Processor"),(0,o.yg)("p",null,"Extending the http processor allows you to add custom code and interject into a few places in the request processing flow. It most cases its easier to extend the default implementation than rolling your own or replacing the handler altogether."),(0,o.yg)("p",null,"First, extend from ",(0,o.yg)("inlineCode",{parentName:"p"},"DefaultGraphQLHttpProcessor"),' for your target schema. The processor is instantiated from your DI container on a "per request" basis. Any services referenced in your constructor will need to be servable from ',(0,o.yg)("inlineCode",{parentName:"p"},"IServiceProvider"),". Those required by the default processor are automatically injected during the call to ",(0,o.yg)("inlineCode",{parentName:"p"},"AddGraphQL()"),"."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Create a Custom HTTP Processor"',title:'"Create',a:!0,Custom:!0,HTTP:!0,'Processor"':!0},"public class MyHttpProcessor : DefaultGraphQLHttpProcessor\n{\n public MyHttpProcessor(\n MySchema schema,\n IGraphQLRuntime queryPipeline,\n IQueryResponseWriter writer,\n IGraphEventLogger logger = null)\n : base(schema, queryPipeline, writer, metricsFactory, logger)\n {\n }\n}\n")),(0,o.yg)("p",null,"Second, override the http processor reference in your ",(0,o.yg)("inlineCode",{parentName:"p"},"Startup.cs"),":"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Register Your Custom Processor"',title:'"Register',Your:!0,Custom:!0,'Processor"':!0},"services.AddGraphQL(options =>\n{\n options.QueryHandler.HttpProcessorType = typeof(MyHttpProcessor);\n});\n")),(0,o.yg)("p",null,"That's all there is. Your processor will now serve requests for your schema."),(0,o.yg)("h2",{id:"helpful-methods"},"Helpful Methods"),(0,o.yg)("p",null,"These methods can be overridden to provide custom logic at various points in the query operation."),(0,o.yg)("h3",{id:"createrequestquerydata"},"CreateRequest(queryData)"),(0,o.yg)("p",null,"Override this method to supply your own ",(0,o.yg)("inlineCode",{parentName:"p"},"IQueryExecutionRequest")," to the runtime."),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"queryData"),": The raw data package read from the HttpContext")),(0,o.yg)("h3",{id:"handlequeryexceptionexception"},"HandleQueryException(exception)"),(0,o.yg)("p",null,"Override this method to provide some custom processing to an unhandled exception. If this method returns an ",(0,o.yg)("inlineCode",{parentName:"p"},"IQueryExecutionResult")," it will be sent to the requestor, otherwise return null to allow a status 500 result to be generated."),(0,o.yg)("p",null,"It is exceedingly rare that this method will ever be called. The runtime will normally attach exceptions as messages to the graphql response. This method exists as a catch all ",(0,o.yg)("em",{parentName:"p"},"just in case")," something occurs beyond all expected constraints."),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"exception"),": The exception that was thrown and unhandled by the runtime.")),(0,o.yg)("h3",{id:"handlequerymetricsmetrics"},"HandleQueryMetrics(metrics)"),(0,o.yg)("p",null,"Override this method to perform some custom processing on a set of query metrics that were gathered for the executed query. This method will only be called if metrics were actually gathered."),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"metrics"),": the ",(0,o.yg)("inlineCode",{parentName:"li"},"IQueryExecutionMetrics")," package that was populated during the request.")),(0,o.yg)("h3",{id:"exposeexceptions"},"ExposeExceptions"),(0,o.yg)("p",null,"Override this property to conditionally expose exceptions on the outgoing response. This can be useful to return true for users authorized as administrators yet hide the data from others."),(0,o.yg)("h3",{id:"exposemetrics"},"ExposeMetrics"),(0,o.yg)("p",null,"Override this property to conditionally expose gathered metrics on the outgoing response."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/52b17a27.9f82953e.js b/assets/js/52b17a27.9f82953e.js new file mode 100644 index 0000000..2201e5b --- /dev/null +++ b/assets/js/52b17a27.9f82953e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[2980],{5680:(e,t,r)=>{r.d(t,{xA:()=>p,yg:()=>m});var n=r(6540);function i(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function a(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function o(e){for(var t=1;t=0||(i[r]=e[r]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(i[r]=e[r])}return i}var c=n.createContext({}),s=function(e){var t=n.useContext(c),r=t;return e&&(r="function"==typeof e?e(t):o(o({},t),e)),r},p=function(e){var t=s(e.components);return n.createElement(c.Provider,{value:t},e.children)},d="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},h=n.forwardRef((function(e,t){var r=e.components,i=e.mdxType,a=e.originalType,c=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),d=s(r),h=i,m=d["".concat(c,".").concat(h)]||d[h]||u[h]||a;return r?n.createElement(m,o(o({ref:t},p),{},{components:r})):n.createElement(m,o({ref:t},p))}));function m(e,t){var r=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=r.length,o=new Array(a);o[0]=h;var l={};for(var c in t)hasOwnProperty.call(t,c)&&(l[c]=t[c]);l.originalType=e,l[d]="string"==typeof e?e:i,o[1]=l;for(var s=2;s{r.r(t),r.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>d,frontMatter:()=>a,metadata:()=>l,toc:()=>s});var n=r(8168),i=(r(6540),r(5680));const a={id:"graph-directive",title:"Graph Directive",sidebar_label:"GraphDirective",sidebar_position:5},o=void 0,l={unversionedId:"reference/graph-directive",id:"reference/graph-directive",title:"Graph Directive",description:"\u2705 See the section on Directives for a detailed explination on how directive action methods work and how to declare them.",source:"@site/docs/reference/graph-directive.md",sourceDirName:"reference",slug:"/reference/graph-directive",permalink:"/docs/reference/graph-directive",draft:!1,tags:[],version:"current",sidebarPosition:5,frontMatter:{id:"graph-directive",title:"Graph Directive",sidebar_label:"GraphDirective",sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"GraphController",permalink:"/docs/reference/graph-controller"},next:{title:"HTTP Processor",permalink:"/docs/reference/http-processor"}},c={},s=[{value:"ModelState",id:"modelstate",level:2},{value:"Request",id:"request",level:2},{value:"Notable Items on the Request",id:"notable-items-on-the-request",level:3},{value:"User",id:"user",level:2},{value:"Schema",id:"schema",level:2}],p={toc:s};function d(e){let{components:t,...r}=e;return(0,i.yg)("wrapper",(0,n.A)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"\u2705 See the section on ",(0,i.yg)("a",{parentName:"p",href:"/docs/advanced/directives"},"Directives")," for a detailed explination on how directive action methods work and how to declare them."),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"GraphDirective"),", from which all of your directives inherit, is a core object used throughout graphql. This page details some lesser known and lesser used object referenced made available to each directive."),(0,i.yg)("h2",{id:"modelstate"},"ModelState"),(0,i.yg)("p",null,"The completed model state dictionary with an entry for each validated parameter ",(0,i.yg)("strong",{parentName:"p"},"of the directive"),". The model state for the field being resolved is not accessible by the directive."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},"public class AllowDirective : GraphDirective\n{\n public IGraphActionResult BeforeResolution(FilterModel model)\n {\n if(!this.ModelState.IsValid)\n return this.BadRequest(this.ModelState);\n\n //...\n }\n}\n")),(0,i.yg)("h2",{id:"request"},"Request"),(0,i.yg)("p",null,"The individual directive request spawned from the field pipeline."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},"public class AllowDirective : GraphDirective\n{\n public IGraphActionResult BeforeResolution(FilterModel model)\n {\n if(this.Request.SourceData is Human human)\n {\n // ...\n }\n }\n}\n")),(0,i.yg)("h3",{id:"notable-items-on-the-request"},"Notable Items on the Request"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"Request.Directive"),": Useful metadata related to the directive type being resolved."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"Request.LifeCycle"),": The enumeration value indicating which life cycle point is being executed."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"Request.DirectiveLocation"),": Indicates location in the query text this directive instance is currently being executed."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"Request.DataSource"),": The source data item being supplied to the field to be resolved."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"Request.Items"),": A collection of key/value pairs accessible to all fields and directives in this individual request pipeline.")),(0,i.yg)("h2",{id:"user"},"User"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"ClaimsPrincipal")," created by ASP.NET when this request was authorized."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'public class MyCustomDirective : GraphDirective\n{\n public IGraphActionResult BeforeResolution(FilterModel model)\n {\n if(this.User.Identity.Name == "DebbieEast")\n {\n // ...\n }\n }\n}\n')),(0,i.yg)("h2",{id:"schema"},"Schema"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"Schema")," property contains a reference to the singleton instance of the schema the current controller is resolving a field for. This object is considered read-only and should not be modified."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},"public class AllowDirective : GraphDirective\n{\n public IGraphActionResult BeforeResolution(FilterModel model)\n {\n // highlight-next-line\n IObjectGraphType droidType = this.Schema.KnownTypes.FindGraphType(typeof(Droid), TypeKind.OBJECT);\n // ...\n }\n}\n")),(0,i.yg)("admonition",{type:"caution"},(0,i.yg)("p",{parentName:"admonition"}," For type system directives, executed as part of schema construction, the schema object available may be incomplete or null. Avoid using and do not rely on the data in ",(0,i.yg)("inlineCode",{parentName:"p"},"this.Schema")," for type system directives.")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/575dc170.ade35cc6.js b/assets/js/575dc170.ade35cc6.js new file mode 100644 index 0000000..01f62b6 --- /dev/null +++ b/assets/js/575dc170.ade35cc6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[6593],{5680:(e,t,n)=>{n.d(t,{xA:()=>c,yg:()=>g});var r=n(6540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),u=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},c=function(e){var t=u(e.components);return r.createElement(s.Provider,{value:t},e.children)},d="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},h=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),d=u(n),h=a,g=d["".concat(s,".").concat(h)]||d[h]||p[h]||i;return n?r.createElement(g,o(o({ref:t},c),{},{components:n})):r.createElement(g,o({ref:t},c))}));function g(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,o=new Array(i);o[0]=h;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[d]="string"==typeof e?e:a,o[1]=l;for(var u=2;u{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>u});var r=n(8168),a=(n(6540),n(5680));const i={id:"graph-action-results",title:"Action Results",sidebar_label:"Action Results",sidebar_position:4},o=void 0,l={unversionedId:"advanced/graph-action-results",id:"advanced/graph-action-results",title:"Action Results",description:"What is an Action Result?",source:"@site/docs/advanced/graph-action-results.md",sourceDirName:"advanced",slug:"/advanced/graph-action-results",permalink:"/docs/advanced/graph-action-results",draft:!1,tags:[],version:"current",sidebarPosition:4,frontMatter:{id:"graph-action-results",title:"Action Results",sidebar_label:"Action Results",sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"Custom Scalars",permalink:"/docs/advanced/custom-scalars"},next:{title:"Multi-Schema Support",permalink:"/docs/advanced/multi-schema-support"}},s={},u=[{value:"What is an Action Result?",id:"what-is-an-action-result",level:2},{value:"Controller Action Results",id:"controller-action-results",level:2},{value:"Directive Action Results",id:"directive-action-results",level:3},{value:"Using an IGraphActionResult",id:"using-an-igraphactionresult",level:2},{value:"Custom Graph Action Results",id:"custom-graph-action-results",level:2}],c={toc:u};function d(e){let{components:t,...n}=e;return(0,a.yg)("wrapper",(0,r.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("h2",{id:"what-is-an-action-result"},"What is an Action Result?"),(0,a.yg)("p",null,"In ASP.NET you may do things like this:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Return an Action Result"',title:'"Return',an:!0,Action:!0,'Result"':!0},'public class BakeryController : Controller\n{\n [HttpGet("donuts/{id}")]\n public IActionResult RetrieveDonut(int id)\n {\n Donut donut = null;\n // ...\n\n // highlight-start\n // return the donut and indicate success\n return this.Ok(donut); \n // highlight-end\n }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Return an Object"',title:'"Return',an:!0,'Object"':!0},'public class BakeryController : Controller\n{\n [HttpGet("donuts/{id}")]\n public Donut RetrieveDonut(int id)\n {\n Donut donut = null;\n // ...\n\n // highlight-start\n // return the donut directly! \n return donut;\n // highlight-end\n }\n}\n')),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},"You can either return the data itself or some alternate ",(0,a.yg)("inlineCode",{parentName:"p"},"IActionResult")," to tell ASP.NET how to render a response.")),(0,a.yg)("p",null,"Some common ASP.NET action results:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.Ok()")," : Everything worked fine, return status 200."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.NotFound()")," : The item doesn't exist, return status 404."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.File()"),": Return status 200 and stream the file to the client."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.View()"),": Render a razor view and send the HTML to the client.")),(0,a.yg)("p",null,"This works the same way in GraphQL ASP.NET. The available actions are slightly different (e.g. GraphQL won't stream files) but the usage is the same. You can even write your own action results."),(0,a.yg)("h2",{id:"controller-action-results"},"Controller Action Results"),(0,a.yg)("p",null,"Instead of ",(0,a.yg)("inlineCode",{parentName:"p"},"IActionResult")," we use ",(0,a.yg)("inlineCode",{parentName:"p"},"IGraphActionResult")," from a controller action method. Both ",(0,a.yg)("a",{parentName:"p",href:"./directives"},"directives")," and controller ",(0,a.yg)("a",{parentName:"p",href:"../controllers/actions"},"action methods")," can return action results."),(0,a.yg)("p",null,"Built in Controller Action Methods:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.Ok(fieldValue)")," : Return ",(0,a.yg)("em",{parentName:"li"},"fieldValue")," as the resolved value of the field and indicate to the runtime that everything completed successfully."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.Error(message)"),": Indicates a problem. The field will resolve to a ",(0,a.yg)("inlineCode",{parentName:"li"},"null")," value automatically. Child fields are not processed and an error message with the given text and error code is added to the response payload."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.StartBatch()")," : Initiates the start a of a new batch. See ",(0,a.yg)("a",{parentName:"li",href:"/docs/controllers/batch-operations"},"batch operations")," for details."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.Unauthorized()"),": Indicate the user is not authorized to request the field. A message telling them as such will be added to the result and no child fields will be processed. The field will resolve to a ",(0,a.yg)("inlineCode",{parentName:"li"},"null")," value automatically. This is sometimes necessary for data-level validation that can't be readily determined from an ",(0,a.yg)("inlineCode",{parentName:"li"},"[Authorize]")," attribute or query level validation."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.BadRequest()"),": Commonly used in conjunction with ",(0,a.yg)("inlineCode",{parentName:"li"},"this.ModelState"),". This result indicates the data supplied to the method is not valid for the operation. If given a model state collection, an error for each validation error is rendered."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.InternalServerError()"),": Indicates an unintended error, such as an exception occurred. The supplied message will be added to the response and no child fields will be resolved.")),(0,a.yg)("h3",{id:"directive-action-results"},"Directive Action Results"),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"./directives"},"Directives")," have two built in Action Results:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.Ok()"),": Indicates that the directive completed its expected operation successfully and processing can continue."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"this.Cancel()"),": Indicates that the directive did NOT complete its operation successfully. ",(0,a.yg)("ul",{parentName:"li"},(0,a.yg)("li",{parentName:"ul"},"If this is a type system directive, the schema will fail to complete and the server will not start."),(0,a.yg)("li",{parentName:"ul"},"If this is an execution directive, the query will be abandoned and the user will receive an error message.")))),(0,a.yg)("h2",{id:"using-an-igraphactionresult"},"Using an IGraphActionResult"),(0,a.yg)("p",null,"Using a graph action result is straight forward. Use it like you would a regular action result with a REST query:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp"},'public class BakeryController : GraphController\n{\n [Query("donut", typeof(Donut))]\n public IGraphActionResult RetrieveDonut(int id)\n {\n if(id < 0)\n // highlight-next-line\n return this.Error("Invalid Id");\n\n Donut donut = new Donut(id);\n // highlight-next-line\n return this.Ok(donut);\n }\n}\n')),(0,a.yg)("p",null,"Notice, however; that we had to declare the return type of the donut field in the ",(0,a.yg)("inlineCode",{parentName:"p"},"[Query]")," attribute. This is because the actual return type is hidden by the use of ",(0,a.yg)("inlineCode",{parentName:"p"},"IGraphActionResult"),". This is the trade off to the extra functionality provided by action results. Since GraphQL is a statically typed language all field return types must be known at startup. "),(0,a.yg)("admonition",{type:"info"},(0,a.yg)("p",{parentName:"admonition"},"Using a graph action result requires you to declare the return type of your action method elsewhere, usually in the ",(0,a.yg)("inlineCode",{parentName:"p"},"[Query]")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"[Mutation]")," attribute.")),(0,a.yg)("h2",{id:"custom-graph-action-results"},"Custom Graph Action Results"),(0,a.yg)("p",null,"You can add your own custom action results. This can be particularly useful on larger teams where you want a uniform field response or error message contents for a given situation."),(0,a.yg)("p",null,"To create a custom result, implement ",(0,a.yg)("inlineCode",{parentName:"p"},"IGraphActionResult"),", which defines a single method:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="IGraphActionResult.cs"',title:'"IGraphActionResult.cs"'},"public interface IGraphActionResult\n{\n Task CompleteAsync(ResolutionContext context);\n}\n")),(0,a.yg)("p",null,(0,a.yg)("inlineCode",{parentName:"p"},"ResolutionContext")," is the data context used to resolve the field or directive. For controller actions this can be cast to ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldResolutionContext")," to obtain access to the ",(0,a.yg)("inlineCode",{parentName:"p"},"Result")," property. Setting this property sets the resolved value for the field."),(0,a.yg)("admonition",{type:"info"},(0,a.yg)("p",{parentName:"admonition"},(0,a.yg)("inlineCode",{parentName:"p"},"FieldResolutionContext")," contains a ",(0,a.yg)("inlineCode",{parentName:"p"},"Result")," property which indicates the final resolved value for the field.")),(0,a.yg)("p",null,"Looking at the ",(0,a.yg)("inlineCode",{parentName:"p"},"UnauthorizedGraphActionResult")," is a great example of how to implement your own:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="UnauthorizedGraphActionResult.cs"',title:'"UnauthorizedGraphActionResult.cs"'},' public class UnauthorizedGraphActionResult : IGraphActionResult\n {\n private readonly string _errorCode;\n private readonly string _errorMessage;\n\n public UnauthorizedGraphActionResult(\n string errorMessage = "",\n string errorCode = Constants.ErrorCodes.ACCESS_DENIED)\n {\n _errorMessage = errorMessage ?? "Unauthorized Access";\n _errorCode = errorCode ?? Constants.ErrorCodes.ACCESS_DENIED;\n }\n\n public Task CompleteAsync(ResolutionContext context)\n {\n // add an error message to the response\n context.Messages.Critical(\n _errorMessage,\n _errorCode,\n context.Request.Origin);\n\n // instruct graphql to stop processing this field \n // and its children\n context.Cancel();\n return Task.CompletedTask;\n }\n }\n')),(0,a.yg)("p",null,"The result takes in an optional error message and code, providing defaults if not supplied. Then on ",(0,a.yg)("inlineCode",{parentName:"p"},"CompleteAsync")," it adds the message to the context and cancels its execution."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5c71343a.0dc5b22e.js b/assets/js/5c71343a.0dc5b22e.js new file mode 100644 index 0000000..507af7b --- /dev/null +++ b/assets/js/5c71343a.0dc5b22e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[1294],{5680:(e,t,a)=>{a.d(t,{xA:()=>p,yg:()=>s});var n=a(6540);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function l(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function i(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var o=n.createContext({}),g=function(e){var t=n.useContext(o),a=t;return e&&(a="function"==typeof e?e(t):i(i({},t),e)),a},p=function(e){var t=g(e.components);return n.createElement(o.Provider,{value:t},e.children)},y="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},u=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,l=e.originalType,o=e.parentName,p=d(e,["components","mdxType","originalType","parentName"]),y=g(a),u=r,s=y["".concat(o,".").concat(u)]||y[u]||m[u]||l;return a?n.createElement(s,i(i({ref:t},p),{},{components:a})):n.createElement(s,i({ref:t},p))}));function s(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var l=a.length,i=new Array(l);i[0]=u;var d={};for(var o in t)hasOwnProperty.call(t,o)&&(d[o]=t[o]);d.originalType=e,d[y]="string"==typeof e?e:r,i[1]=d;for(var g=2;g{a.r(t),a.d(t,{assets:()=>o,contentTitle:()=>i,default:()=>y,frontMatter:()=>l,metadata:()=>d,toc:()=>g});var n=a(8168),r=(a(6540),a(5680));const l={id:"standard-events",title:"Standard Logging Events",sidebar_label:"Standard Events",sidebar_position:1},i=void 0,d={unversionedId:"logging/standard-events",id:"logging/standard-events",title:"Standard Logging Events",description:"GraphQL ASP.NET tracks many standard events. Most of these are recorded during the execution of a query. Some, such as those around field resolution, can be recorded many times in the course of a single request.",source:"@site/docs/logging/standard-events.md",sourceDirName:"logging",slug:"/logging/standard-events",permalink:"/docs/logging/standard-events",draft:!1,tags:[],version:"current",sidebarPosition:1,frontMatter:{id:"standard-events",title:"Standard Logging Events",sidebar_label:"Standard Events",sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Structured Logging",permalink:"/docs/logging/structured-logging"},next:{title:"Subscription Events",permalink:"/docs/logging/subscription-events"}},o={},g=[{value:"Common Event Properties",id:"common-event-properties",level:2},{value:"Schema Level Events",id:"schema-level-events",level:2},{value:"Schema Route Registered",id:"schema-route-registered",level:3},{value:"Schema Instance Created",id:"schema-instance-created",level:3},{value:"Schema Pipeline Registered",id:"schema-pipeline-registered",level:3},{value:"Request Level Events",id:"request-level-events",level:2},{value:"Request Received",id:"request-received",level:3},{value:"Query Plan Generated",id:"query-plan-generated",level:3},{value:"Query Cache Hit",id:"query-cache-hit",level:3},{value:"Query Cache Miss",id:"query-cache-miss",level:3},{value:"Query Cache Add",id:"query-cache-add",level:3},{value:"Request Completed",id:"request-completed",level:3},{value:"Request Cancelled",id:"request-cancelled",level:3},{value:"Request Timeout",id:"request-timeout",level:3},{value:"Directive Level Events",id:"directive-level-events",level:2},{value:"Execution Directive Applied",id:"execution-directive-applied",level:3},{value:"Type System Directive Applied",id:"type-system-directive-applied",level:3},{value:"Auth Events",id:"auth-events",level:2},{value:"Item Authentication Started",id:"item-authentication-started",level:3},{value:"Item Authentication Completed",id:"item-authentication-completed",level:3},{value:"Item Authorization Started",id:"item-authorization-started",level:3},{value:"Item Authorization Completed",id:"item-authorization-completed",level:3},{value:"Field Level Events",id:"field-level-events",level:2},{value:"Field Resolution Started",id:"field-resolution-started",level:3},{value:"Field Resolution Completed",id:"field-resolution-completed",level:3},{value:"Controller Level Events",id:"controller-level-events",level:2},{value:"Action Invocation Started",id:"action-invocation-started",level:3},{value:"Action Model State Validated",id:"action-model-state-validated",level:3},{value:"Action Invocation Completed",id:"action-invocation-completed",level:3},{value:"Action Invocation Exception",id:"action-invocation-exception",level:3},{value:"Action Unhandled Exception",id:"action-unhandled-exception",level:3},{value:"Other Events",id:"other-events",level:2},{value:"Unhandled Exception",id:"unhandled-exception",level:3}],p={toc:g};function y(e){let{components:t,...a}=e;return(0,r.yg)("wrapper",(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL ASP.NET tracks many standard events. Most of these are recorded during the execution of a query. Some, such as those around field resolution, can be recorded many times in the course of a single request."),(0,r.yg)("h2",{id:"common-event-properties"},"Common Event Properties"),(0,r.yg)("p",null,"All logging events share a common set of properties."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"EventId")),(0,r.yg)("td",{parentName:"tr",align:null},"The numeric constant assigned to the event.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"EventName")),(0,r.yg)("td",{parentName:"tr",align:null},"The human-friendly name of the event.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"LogEntryId")),(0,r.yg)("td",{parentName:"tr",align:null},"A guid unique to the given log entry.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"Message")),(0,r.yg)("td",{parentName:"tr",align:null},"A simple text message.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"DateTimeUTC")),(0,r.yg)("td",{parentName:"tr",align:null},"A date and time, in UTC-0, when the event was ",(0,r.yg)("em",{parentName:"td"},"created"),".")))),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},"Constants for ",(0,r.yg)("inlineCode",{parentName:"p"},"EventIds")," can be found at ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL.AspNet.Logging.LogEventIds"))),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},"Constants for all built in log entry properties can be found at ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL.AspNet.Logging.LogPropertyNames"))),(0,r.yg)("admonition",{title:"Scope Id",type:"tip"},(0,r.yg)("p",{parentName:"admonition"},"Another common, but not universal, property is ",(0,r.yg)("inlineCode",{parentName:"p"},"ScopeId"),". Any log entries related to the execution of a single query will have a common scope id. In general, this id is unique per HTTP request.")),(0,r.yg)("h2",{id:"schema-level-events"},"Schema Level Events"),(0,r.yg)("h3",{id:"schema-route-registered"},"Schema Route Registered"),(0,r.yg)("p",null,"This event is recorded when GraphQL successfully registers an entry in the ASP.NET route table to accept requests for a target schema. This event is recorded once per application instance."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full name of your your schema type. For most single schema applications this will be ",(0,r.yg)("inlineCode",{parentName:"td"},"GraphQL.AspNet.Schemas.GraphSchema"),".")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"RoutePath")),(0,r.yg)("td",{parentName:"tr",align:null},"The relative URL that was registered for the schema type, e.g. ",(0,r.yg)("inlineCode",{parentName:"td"},"'/graphql"))))),(0,r.yg)("h3",{id:"schema-instance-created"},"Schema Instance Created"),(0,r.yg)("p",null,"This event is recorded each time an instance of your schema is created. By default, schema's are stored as singleton instances so this event should be recorded once per application instance."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full name of your your schema type. For most single schema applications this will be ",(0,r.yg)("inlineCode",{parentName:"td"},"GraphQL.AspNet.Schemas.GraphSchema"),".")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SupportedOperationTypes")),(0,r.yg)("td",{parentName:"tr",align:null},"A comma separated list of the operations the schema is tracking (e.g. query, mutation and/or subscription)")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"GraphTypes")),(0,r.yg)("td",{parentName:"tr",align:null},"A collection of objects containing each registered graph type's name, type kind, .NET type association and number of fields (if any)")))),(0,r.yg)("h3",{id:"schema-pipeline-registered"},"Schema Pipeline Registered"),(0,r.yg)("p",null,"This event is recorded each time an instance of a schema pipeline is created by your DI container. Like schemas, field middleware pipelines are stored as singleton instances so this event should be recorded once per pipleline per application instance."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"PipelineName")),(0,r.yg)("td",{parentName:"tr",align:null},"The human friendly name of the pipeline that was created")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"MiddlewareComponents")),(0,r.yg)("td",{parentName:"tr",align:null},"An array of the registered names of the components in your schema's field pipeline. The names are ordered according to their position in the pipeline.")))),(0,r.yg)("h2",{id:"request-level-events"},"Request Level Events"),(0,r.yg)("h3",{id:"request-received"},"Request Received"),(0,r.yg)("p",null,"This is event is recorded when the query execution pipeline first receives a new query to process."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"Username")),(0,r.yg)("td",{parentName:"tr",align:null},"the value of ",(0,r.yg)("inlineCode",{parentName:"td"},"this.User.Identity.Name")," or null")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"QueryRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the overall request that was received.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"QueryText")),(0,r.yg)("td",{parentName:"tr",align:null},"The query provided by the user.")))),(0,r.yg)("h3",{id:"query-plan-generated"},"Query Plan Generated"),(0,r.yg)("p",null,"This is event is recorded when the runtime generates a new query plan. This event may or may not be recorded on each request if you are making use of the query cache."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"QueryPlanId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id assigned to the created query plan.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"the full .NET type name of the schema type this plan targets")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"IsValid")),(0,r.yg)("td",{parentName:"tr",align:null},"A boolean value indicating if the query document resulted in a valid plan")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"MaxDepth","*")),(0,r.yg)("td",{parentName:"tr",align:null},"The maximum nested depth of any given field in the plan")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"EstimatedComplexity","*")),(0,r.yg)("td",{parentName:"tr",align:null},"The complexity score assigned to the query plan by the runtime")))),(0,r.yg)("p",null,"*"," See the section on dealing with ",(0,r.yg)("a",{parentName:"p",href:"../execution/malicious-queries"},"malicious queries")," for more details on ",(0,r.yg)("inlineCode",{parentName:"p"},"MaxDepth")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"EstimatedComplexity"),"."),(0,r.yg)("h3",{id:"query-cache-hit"},"Query Cache Hit"),(0,r.yg)("p",null,"This event is recorded when the runtime is able to pull an existing instance of a Query Plan from the query cache for the received query document. This event is only recorded if the query cache is enabled."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"QueryPlanHashCode")),(0,r.yg)("td",{parentName:"tr",align:null},"The unique hash key generated from the query document and used to search for an existing plan in the query cache.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"the full .NET type name of the schema type this plan targets")))),(0,r.yg)("h3",{id:"query-cache-miss"},"Query Cache Miss"),(0,r.yg)("p",null,"This event is recorded when the runtime is unable to pull an existing instance of a Query Plan from the query cache for the received query document. This event is only recorded if the query cache is enabled."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"QueryPlanHashCode")),(0,r.yg)("td",{parentName:"tr",align:null},"The unique hash key generated from the query document and used to search for an existing plan in the query cache.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"the full .NET type name of the schema type this plan targets")))),(0,r.yg)("h3",{id:"query-cache-add"},"Query Cache Add"),(0,r.yg)("p",null,"This is event is recorded when the runtime successfully stores an instance of a Query Plan into the query cache. This event is only recorded if the query cache is enabled."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"QueryPlanHashCode")),(0,r.yg)("td",{parentName:"tr",align:null},"The unique hash key generated from the query document and used to search for an existing plan in the query cache.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"the full .NET type name of the schema type this plan targets")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"QueryPlanId")),(0,r.yg)("td",{parentName:"tr",align:null},"The unique query plan Id created when it was generated. Can be used to cross link this event to the ",(0,r.yg)("inlineCode",{parentName:"td"},"Query Plan Generated")," event for further details.")))),(0,r.yg)("h3",{id:"request-completed"},"Request Completed"),(0,r.yg)("p",null,"This is event is recorded when the final result for the request is generated and is returned from the runtime to be serialized. No actual data values are recorded to the logs to prevent leaks of potentially sensitive information."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"QueryRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the overall request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"HasData")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("inlineCode",{parentName:"td"},"true")," or ",(0,r.yg)("inlineCode",{parentName:"td"},"false")," indicating if at least one data value was included in the result")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"HasErrors")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("inlineCode",{parentName:"td"},"true")," or ",(0,r.yg)("inlineCode",{parentName:"td"},"false")," indicating if at least one error message was included in the result")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"TotalExecutionMs")),(0,r.yg)("td",{parentName:"tr",align:null},"A numerical value indicating the total runtime of the request, in milliseconds.")))),(0,r.yg)("h3",{id:"request-cancelled"},"Request Cancelled"),(0,r.yg)("p",null,"This is event is recorded when the a request is explicitly cancelled, usually by the underlying HTTP connection."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"QueryRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the overall request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"TotalExecutionMs")),(0,r.yg)("td",{parentName:"tr",align:null},"A numerical value indicating the total runtime of the request, in milliseconds.")))),(0,r.yg)("h3",{id:"request-timeout"},"Request Timeout"),(0,r.yg)("p",null,"This is event is recorded when the a request is is cancelled due to reaching a maximum timeout limit defined by the schema."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"QueryRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the overall request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"TotalExecutionMs")),(0,r.yg)("td",{parentName:"tr",align:null},"A numerical value indicating the total runtime of the request, in milliseconds.")))),(0,r.yg)("h2",{id:"directive-level-events"},"Directive Level Events"),(0,r.yg)("h3",{id:"execution-directive-applied"},"Execution Directive Applied"),(0,r.yg)("p",null,"This event is recorded when an execution directive is successfully executed against an ",(0,r.yg)("inlineCode",{parentName:"p"},"IDocumentPart")," on an incoming query."),(0,r.yg)("p",null,"This is event is recorded when the final result for the request is generated and is returned from the runtime to be serialized. No actual data values are recorded to the logs to prevent leaks of potentially sensitive information."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full .NET type name of the schema type this plan targets")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"DirectiveName")),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the directive as it exists in the target schema")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"DirectiveInternalName")),(0,r.yg)("td",{parentName:"tr",align:null},"The .NET class name of the directive")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"DirectiveLocation")),(0,r.yg)("td",{parentName:"tr",align:null},"The target location in the query document (e.g. FIELD, FRAGMENT_SPREAD etc.)")))),(0,r.yg)("h3",{id:"type-system-directive-applied"},"Type System Directive Applied"),(0,r.yg)("p",null,"This event is recorded when a schema is first generated and all known type system directives are applied to the schema items\nto which they are attached. An entry is recorded for each directive applied."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full .NET type name of the schema type this plan targets")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaItemPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The path of the item being resolved, e.g. ",(0,r.yg)("inlineCode",{parentName:"td"},"[type]/Donut/id"))),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"DirectiveName")),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the directive as it exists in the target schema")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"DirectiveInternalName")),(0,r.yg)("td",{parentName:"tr",align:null},"The .NET class name of the directive")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"DirectiveLocation")),(0,r.yg)("td",{parentName:"tr",align:null},"The target location in the query document (e.g. FIELD, FRAGMENT_SPREAD etc.)")))),(0,r.yg)("h2",{id:"auth-events"},"Auth Events"),(0,r.yg)("h3",{id:"item-authentication-started"},"Item Authentication Started"),(0,r.yg)("p",null,"This is event is record when a security context on a query is authenticated to determine an\nappropriate ClaimsPrincipal to use for authorization."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"PipelineRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the individual field request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaItemPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The path of the item being resolved, e.g. ",(0,r.yg)("inlineCode",{parentName:"td"},"[type]/Donut/id"))))),(0,r.yg)("h3",{id:"item-authentication-completed"},"Item Authentication Completed"),(0,r.yg)("p",null,"This is event is recorded after a security context is authenticated and a ClaimsPrincipal was generated (if required)."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"PipelineRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the individual field request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaItemPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The path of the item being resolved, e.g. ",(0,r.yg)("inlineCode",{parentName:"td"},"[type]/Donut/id"))),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"Username")),(0,r.yg)("td",{parentName:"tr",align:null},"The value of the ",(0,r.yg)("inlineCode",{parentName:"td"},"Name")," field on the active Identity or null")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"AuthenticationScheme")),(0,r.yg)("td",{parentName:"tr",align:null},"The key representing the chosen authentication schema (e.g. ",(0,r.yg)("inlineCode",{parentName:"td"},"Bearer"),", ",(0,r.yg)("inlineCode",{parentName:"td"},"Kerberos")," etc.)")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"AuthenticationSchemaSuccess")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("inlineCode",{parentName:"td"},"true")," if authentication against the scheme was successful")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaItemPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The path of the item being resolved, e.g. ",(0,r.yg)("inlineCode",{parentName:"td"},"[type]/Donut/id"))))),(0,r.yg)("h3",{id:"item-authorization-started"},"Item Authorization Started"),(0,r.yg)("p",null,"This is event is recorded when an authenticated user is authorized against schema item (typically a Field or Directive)."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"PipelineRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the individual field request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaItemPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The path of the item being resolved, e.g. ",(0,r.yg)("inlineCode",{parentName:"td"},"[type]/Donut/id"))),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"Username")),(0,r.yg)("td",{parentName:"tr",align:null},"The value of the ",(0,r.yg)("inlineCode",{parentName:"td"},"Name")," field on the active Identity or null")))),(0,r.yg)("h3",{id:"item-authorization-completed"},"Item Authorization Completed"),(0,r.yg)("p",null,"This is event is recorded after a schema item authorization has completed."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"PipelineRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the individual field request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SchemaItemPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The path of the item being resolved, e.g. ",(0,r.yg)("inlineCode",{parentName:"td"},"[type]/Donut/id"))),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"Username")),(0,r.yg)("td",{parentName:"tr",align:null},"The value of the ",(0,r.yg)("inlineCode",{parentName:"td"},"Name")," field on the active Identity or null")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"AuthorizationStatus")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("inlineCode",{parentName:"td"},"Skipped"),", ",(0,r.yg)("inlineCode",{parentName:"td"},"Authorized")," or ",(0,r.yg)("inlineCode",{parentName:"td"},"Unauthorized"))),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"LogMessage")),(0,r.yg)("td",{parentName:"tr",align:null},"An internal message containing an explanation of why authorization failed.")))),(0,r.yg)("h2",{id:"field-level-events"},"Field Level Events"),(0,r.yg)("p",null,"After a query plan has been created GraphQL ASP.NET begins resolving each field needed to fulfill the request. This group of events is recorded for each item of each field that is processed. Since all fields are executed asynchronously (even if the resolvers themselves are synchronous) the order in which the events are recorded can be unpredictable and overlap between fields can occur. Using the recorded date along with the ",(0,r.yg)("inlineCode",{parentName:"p"},"PipelineRequestId")," can help to filter the noise."),(0,r.yg)("h3",{id:"field-resolution-started"},"Field Resolution Started"),(0,r.yg)("p",null,"This event is recorded when a new field is queued for resolution."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"PipelineRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the individual field request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"FieldExecutionMode")),(0,r.yg)("td",{parentName:"tr",align:null},"Indicates if this pipeline is being executed for a ",(0,r.yg)("inlineCode",{parentName:"td"},"single source item")," or as a ",(0,r.yg)("inlineCode",{parentName:"td"},"batch"))),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"FieldPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The path of the field being resolved, e.g. ",(0,r.yg)("inlineCode",{parentName:"td"},"[type]/Donut/id"))))),(0,r.yg)("h3",{id:"field-resolution-completed"},"Field Resolution Completed"),(0,r.yg)("p",null,"This is event is recorded when a field completes its execution pipeline and a result is generated. No actual data values are recorded to the logs to prevent leaks of potentially sensitive information."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"PipelineRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the individual field request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"FieldPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The path of the field being resolved, e.g. ",(0,r.yg)("inlineCode",{parentName:"td"},"[type]/Donut/id"))),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"HasData")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("inlineCode",{parentName:"td"},"true")," or ",(0,r.yg)("inlineCode",{parentName:"td"},"false")," indicating if at least one data value was included in the result")))),(0,r.yg)("h2",{id:"controller-level-events"},"Controller Level Events"),(0,r.yg)("p",null,"After the security challenge has completed, but before field resolution is completed, if the pipeline executes a controller method to resolve the field these events will be recorded. If the target resolver of the field is a property or POCO method, these events are skipped."),(0,r.yg)("h3",{id:"action-invocation-started"},"Action Invocation Started"),(0,r.yg)("p",null,"This event is recorded when a controller begins processing a request to execute an action method."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"PipelineRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the individual field request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ControllerTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full .NET type of the controller being invoked")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ActionName")),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the method (not the field) that was being invoked when the exception occurred")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"FieldPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The schema field path that represents the action method")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"SourceObjectType")),(0,r.yg)("td",{parentName:"tr",align:null},"The .NET type name of the input source to the action.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"IsAsync")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("inlineCode",{parentName:"td"},"true")," or ",(0,r.yg)("inlineCode",{parentName:"td"},"false")," indicating if the controller is going to invoke the action asynchronously or not")))),(0,r.yg)("h3",{id:"action-model-state-validated"},"Action Model State Validated"),(0,r.yg)("p",null,"This event occurs after the controller has processed the input objects and validated the state of the model being passed to the action method."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"PipelineRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the individual field request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ControllerTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full .NET type of the controller being invoked")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ActionName")),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the method (not the field) that was being invoked when the exception occurred")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"FieldPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The schema field path that represents the action method")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ModelIsValid")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("inlineCode",{parentName:"td"},"true")," or ",(0,r.yg)("inlineCode",{parentName:"td"},"false")," indicating if the all model items completed validation successfully")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ModelItems")),(0,r.yg)("td",{parentName:"tr",align:null},"A list of items, one for each item that was invalid, indicating the parameter name and the string messages generated indicating the errors.")))),(0,r.yg)("h3",{id:"action-invocation-completed"},"Action Invocation Completed"),(0,r.yg)("p",null,"This event is recorded when a controller completes the invocation of an action method and a result is created."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"PipelineRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the individual field request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ControllerTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full .NET type of the controller being invoked")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ActionName")),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the method (not the field) that was being invoked when the exception occurred")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"FieldPath")),(0,r.yg)("td",{parentName:"tr",align:null},"The schema field path that represents the action method")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ResultTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The .NET type name of the data returned from the action. The may be an actual field value or an ",(0,r.yg)("inlineCode",{parentName:"td"},"IGraphActionResult")," type.")))),(0,r.yg)("h3",{id:"action-invocation-exception"},"Action Invocation Exception"),(0,r.yg)("p",null,"This event is recorded by the controller if it is unable to invoke the target action method. This usually indicates some sort of data corruption or failed conversion of source data to the requested parameter types of the target action method. This can happen if the query plan or variables collection is altered by a 3rd party outside of the normal pipeline. Should this event occur the field will be abandoned and a null value returned as the field result. Child fields to this instance will not be processed but the operation will continue to attempt to resolve other sibling fields and their children."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"PipelineRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the individual field request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ControllerTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full .NET type of the controller being invoked")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ActionName")),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the method (not the field) that was being invoked when the exception occurred")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ExceptionMessage")),(0,r.yg)("td",{parentName:"tr",align:null},"The message on the exception that was thrown")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ExceptionTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The .NET type name of the exception that was thrown")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"StackTrace")),(0,r.yg)("td",{parentName:"tr",align:null},"The complete stack trace text of the exception")))),(0,r.yg)("h3",{id:"action-unhandled-exception"},"Action Unhandled Exception"),(0,r.yg)("p",null,"This event is recorded if an unhandled exception occurs ",(0,r.yg)("inlineCode",{parentName:"p"},"within the controller action method body"),". Should this event occur the field will be abandoned and a null value returned as the result of the field. Child fields will not be processed but the operation will continue to attempt to resolve other sibling fields and their children."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"PipelineRequestId")),(0,r.yg)("td",{parentName:"tr",align:null},"A unique id identifying the individual field request.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ControllerTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The full .NET type of the controller being invoked")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ActionName")),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the method (not the field) that was being invoked when the exception occurred")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ExceptionMessage")),(0,r.yg)("td",{parentName:"tr",align:null},"The message on the exception that was thrown")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ExceptionTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The .NET type name of the exception that was thrown")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"StackTrace")),(0,r.yg)("td",{parentName:"tr",align:null},"The complete stack trace text of the exception")))),(0,r.yg)("h2",{id:"other-events"},"Other Events"),(0,r.yg)("h3",{id:"unhandled-exception"},"Unhandled Exception"),(0,r.yg)("p",null,"This event is recorded if any pipeline invocation is unable to recover from an error. If this is event is recorded the request is abandoned and an error status is returned to the requestor. This event is always recorded at a ",(0,r.yg)("inlineCode",{parentName:"p"},"Critical")," log level. This event will be immediately followed by a ",(0,r.yg)("inlineCode",{parentName:"p"},"Request Completed")," event."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Important Properties")),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Property"),(0,r.yg)("th",{parentName:"tr",align:null},"Description"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ExceptionMessage")),(0,r.yg)("td",{parentName:"tr",align:null},"The message on the exception that was thrown")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"ExceptionTypeName")),(0,r.yg)("td",{parentName:"tr",align:null},"The .NET type name of the exception that was thrown")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"StackTrace")),(0,r.yg)("td",{parentName:"tr",align:null},"The complete stack trace text of the exception")))))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/609dc67a.41b76dc6.js b/assets/js/609dc67a.41b76dc6.js new file mode 100644 index 0000000..ce27f9b --- /dev/null +++ b/assets/js/609dc67a.41b76dc6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[61],{5680:(e,t,n)=>{n.d(t,{xA:()=>d,yg:()=>y});var r=n(6540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),p=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},d=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},h="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},u=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,s=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),h=p(n),u=a,y=h["".concat(s,".").concat(u)]||h[u]||c[u]||i;return n?r.createElement(y,o(o({ref:t},d),{},{components:n})):r.createElement(y,o({ref:t},d))}));function y(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,o=new Array(i);o[0]=u;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[h]="string"==typeof e?e:a,o[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>h,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var r=n(8168),a=(n(6540),n(5680));const i={id:"batch-operations",title:"Batch Operations",sidebar_label:"Batch Operations",sidebar_position:5},o=void 0,l={unversionedId:"controllers/batch-operations",id:"controllers/batch-operations",title:"Batch Operations",description:"Read the section on type extensions before reading this document. Batch Operations expand on type extensions and understanding how they work is critical.",source:"@site/docs/controllers/batch-operations.md",sourceDirName:"controllers",slug:"/controllers/batch-operations",permalink:"/docs/controllers/batch-operations",draft:!1,tags:[],version:"current",sidebarPosition:5,frontMatter:{id:"batch-operations",title:"Batch Operations",sidebar_label:"Batch Operations",sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"Type Extensions",permalink:"/docs/controllers/type-extensions"},next:{title:"Objects",permalink:"/docs/types/objects"}},s={},p=[{value:"The N+1 Problem",id:"the-n1-problem",level:2},{value:"[BatchTypeExtension] Attribute",id:"batchtypeextension-attribute",level:2},{value:"Data Loaders",id:"data-loaders",level:2},{value:"Returning Data",id:"returning-data",level:2},{value:"Returning IDictionary<TSource, TValue>",id:"returning-idictionarytsource-tvalue",level:4},{value:"Other Resources",id:"other-resources",level:2}],d={toc:p};function h(e){let{components:t,...n}=e;return(0,a.yg)("wrapper",(0,r.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("admonition",{type:"caution"},(0,a.yg)("p",{parentName:"admonition"},"Read the section on ",(0,a.yg)("a",{parentName:"p",href:"./type-extensions"},"type extensions")," before reading this document. Batch Operations expand on type extensions and understanding how they work is critical.")),(0,a.yg)("h2",{id:"the-n1-problem"},"The N+1 Problem"),(0,a.yg)("p",null,"There are plenty of articles on the web discussing the theory behind the N+1 problem (",(0,a.yg)("a",{parentName:"p",href:"./batch-operations#other-resources"},"links below"),"). Instead, we'll jump into an example to illustrate the issue when it comes to GraphQL."),(0,a.yg)("p",null,"Let's build on our example from the discussion on type extensions where we created an extension to retrieve ",(0,a.yg)("inlineCode",{parentName:"p"},"Cake Orders")," for a ",(0,a.yg)("strong",{parentName:"p"},"single")," ",(0,a.yg)("inlineCode",{parentName:"p"},"Bakery"),". What if we're a national chain and need to see the last 50 orders for each of our stores in a region? This seems like a reasonable thing an auditor would do so lets alter our controller to fetch all our bakeries and then let our type extension fetch the cake orders."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Retrieving Multiple Bakeries"',title:'"Retrieving',Multiple:!0,'Bakeries"':!0},'public class Bakery\n{\n public int Id { get; set; }\n public string Name { get; set; }\n}\n\npublic class BakedGoodsCompanyController : GraphController\n{\n [QueryRoot("bakeries")]\n // highlight-next-line\n public async Task> RetrieveBakeries(Regions region = Regions.All)\n {/*...*/}\n\n // retrieve the cake orders for a single bakery\n [TypeExtension(typeof(Bakery), "orders")]\n public async Task> RetrieveCakeOrders(Bakery bakery, int limitTo = 15){\n\n return await _service.RetrieveCakeOrders(bakery.Id, limitTo);\n }\n}\n')),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query {\n bakeries(region: SOUTH_WEST){\n name\n orders(limitTo: 50) {\n id\n writtenPhrase\n }\n }\n}\n")),(0,a.yg)("p",null,"Well that was easy, right? Not so fast!"),(0,a.yg)("p",null," The ",(0,a.yg)("inlineCode",{parentName:"p"},"bakeries")," field returns a ",(0,a.yg)("inlineCode",{parentName:"p"},"List")," but the ",(0,a.yg)("inlineCode",{parentName:"p"},"RetrieveCakeOrders")," method takes in a single ",(0,a.yg)("inlineCode",{parentName:"p"},"Bakery"),". GraphQL will, ",(0,a.yg)("strong",{parentName:"p"},"for each bakery retrieved"),", execute the ",(0,a.yg)("inlineCode",{parentName:"p"},"orders")," field to retrieve its orders. If ",(0,a.yg)("inlineCode",{parentName:"p"},"bakeries")," retrieved 50 stores in the south west region, graphql will execute ",(0,a.yg)("inlineCode",{parentName:"p"},"RetrieveCakeOrders")," 50 times, which will execute 50 database queries."),(0,a.yg)("p",null,"This is the N+1 problem. ",(0,a.yg)("inlineCode",{parentName:"p"},"1 query")," for the bakeries + ",(0,a.yg)("inlineCode",{parentName:"p"},"N queries")," for the cake orders, where N is the number of bakeries first retrieved."),(0,a.yg)("p",null,"If only we could batch the request and fetch all the cake orders for all the bakeries at once, then assign the ",(0,a.yg)("inlineCode",{parentName:"p"},"Cake Orders")," back to their respective bakeries, we'd be a lot better off. No matter the number of bakeries retrieved, we'd execute 2 queries; 1 for ",(0,a.yg)("inlineCode",{parentName:"p"},"bakeries")," and 1 for ",(0,a.yg)("inlineCode",{parentName:"p"},"orders"),".This is where batch extensions come in to play."),(0,a.yg)("h2",{id:"batchtypeextension-attribute"},"[","BatchTypeExtension","]"," Attribute"),(0,a.yg)("p",null,"A batch operation is implemented as a type extension but with the word ",(0,a.yg)("inlineCode",{parentName:"p"},"Batch")," in it. Lets look at an example:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="A Batch Type Extension"',title:'"A',Batch:!0,Type:!0,'Extension"':!0},'public class BakedGoodsCompanyController : GraphController\n{\n [QueryRoot("bakeries")]\n public async Task> RetrieveBakeries(Region region){/*...*/}\n\n // declare the batch operation as an extension\n // highlight-next-line\n [BatchTypeExtension(typeof(Bakery), "orders", typeof(List))]\n public async Task RetrieveCakeOrders(\n IEnumerable bakeries,\n int limitTo = 15)\n {\n //fetch all the orders at once\n var allOrders = await _service.RetrieveCakeOrders(bakeries.Select(x => x.Id), limitTo);\n\n // return the batch of orders\n // highlight-start\n return this.StartBatch()\n .FromSource(bakeries, bakery => bakery.Id)\n .WithResults(allOrders, cakeOrder => cakeOrder.BakeryId)\n .Complete();\n // highlight-end\n }\n}\n')),(0,a.yg)("p",null,"Key things to notice:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"We've used ",(0,a.yg)("inlineCode",{parentName:"li"},"[BatchTypeExtension]")," instead of ",(0,a.yg)("inlineCode",{parentName:"li"},"[TypeExtension]")),(0,a.yg)("li",{parentName:"ul"},"The method takes in an ",(0,a.yg)("inlineCode",{parentName:"li"},"IEnumerable")," instead of a single ",(0,a.yg)("inlineCode",{parentName:"li"},"Bakery"),"."),(0,a.yg)("li",{parentName:"ul"},"The method returns an action result created from ",(0,a.yg)("inlineCode",{parentName:"li"},"this.StartBatch()"))),(0,a.yg)("p",null,"The contents of your extension method is going to vary widely from use case to use case. Here we've forwarded the ids of the bakeries to a service to fetch the orders. The important take away is that ",(0,a.yg)("inlineCode",{parentName:"p"},"RetrieveCakeOrders")," is now called only once, regardless of how many items are in the ",(0,a.yg)("inlineCode",{parentName:"p"},"IEnumerable")," parameter."),(0,a.yg)("h2",{id:"data-loaders"},"Data Loaders"),(0,a.yg)("p",null,"You'll often hear the term ",(0,a.yg)("inlineCode",{parentName:"p"},"Data Loaders")," when reading about GraphQL implementations. Methods that load the child data being requested as a single operation before assigning that data to each of the parents. There is no difference with GraphQL ASP.NET. You still have to write that method. But with the ability to capture action method parameters and clever use of an ",(0,a.yg)("inlineCode",{parentName:"p"},"IGraphActionResult")," we can combine the data load phase with the assignment phase into a single batch operation, at least on the surface. The aim is to make it easy to read and easier to write."),(0,a.yg)("h2",{id:"returning-data"},"Returning Data"),(0,a.yg)("p",null,(0,a.yg)("inlineCode",{parentName:"p"},"this.StartBatch()")," returns a builder to define how you want GraphQL to construct your batch. We need to tell it how each of the child items we fetched maps to the parents that were supplied (if at all)."),(0,a.yg)("p",null,"In the example, we matched on a bakery's primary key selecting ",(0,a.yg)("inlineCode",{parentName:"p"},"Bakery.Id")," from each of the source items and pairing it against ",(0,a.yg)("inlineCode",{parentName:"p"},"CakeOrder.BakeryId")," from each of the results. This is enough information for the builder to generate a valid result. Depending on the contents of your data and the type expression of your extension there are few scenarios that emerge:"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"1 Source -> 1 Result")),(0,a.yg)("p",null,"If you've defined your extension field to be a single item (i.e. ",(0,a.yg)("inlineCode",{parentName:"p"},"CakeOrder")," instead of ",(0,a.yg)("inlineCode",{parentName:"p"},"IEnumerable"),") GraphQL will enforce the type check and reject/fail the resolution for any parent item mapped to more than one child. This is useful for sibling relationships where two objects might be related but aren't otherwise aggregated together. For example, a person and their spouse."),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"1 Source -> N Results")),(0,a.yg)("p",null,"If you've defined your extension to return a collection of items, like in the example, then GraphQL will generate an array of 0 or more children for every parent included in the batch."),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"N Sources -> N Results")),(0,a.yg)("p",null,"To GraphQL, many to many relationships are treated the same as one to many. Internally, it doesn't care how you map your data, only that the type expression of the results are enforced. Each child can map to multiple parents and in the cases of overlap, GraphQL will resolve the requested fields of that child once then render it to each parent in the outgoing response package."),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"1 Source -> No Results")),(0,a.yg)("p",null,"For 1:1 results there are only two options; either the data exists and a value is returned or it doesn't and ",(0,a.yg)("inlineCode",{parentName:"p"},"null")," is returned. But with 1:N relationships, sometimes you want to indicate that no results were ",(0,a.yg)("em",{parentName:"p"},"included")," for a parent item and sometimes you want to indicate that no results ",(0,a.yg)("em",{parentName:"p"},"exist"),". This could be represented as being an empty array vs. ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),". When working with children, for every parent supplied to ",(0,a.yg)("inlineCode",{parentName:"p"},"this.StartBatch"),", GraphQL will generate a field result of ",(0,a.yg)("strong",{parentName:"p"},"at least")," an empty array. To indicate a parent item should receive ",(0,a.yg)("inlineCode",{parentName:"p"},"null")," instead of ",(0,a.yg)("inlineCode",{parentName:"p"},"[]")," exclude it from the batch."),(0,a.yg)("p",null,"Note that it is your method's responsibility to be compliant with the type expression of the field. If a field is marked as ",(0,a.yg)("inlineCode",{parentName:"p"},"NON_NULL")," and you exclude the parent item from the batch (resulting in a null result for the field for that item) the field will be marked invalid and register an error."),(0,a.yg)("admonition",{type:"caution"},(0,a.yg)("p",{parentName:"admonition"},"Excluding a source item from ",(0,a.yg)("inlineCode",{parentName:"p"},"this.StartBatch()")," will result in it receiving ",(0,a.yg)("inlineCode",{parentName:"p"},"null")," for its resolved field value. Be mindful of your extension's type expression. If you've made the field non-nullable an error will be generated.")),(0,a.yg)("h4",{id:"returning-idictionarytsource-tvalue"},"Returning ",(0,a.yg)("inlineCode",{parentName:"h4"},"IDictionary")),(0,a.yg)("p",null,"Using ",(0,a.yg)("inlineCode",{parentName:"p"},"this.StartBatch")," is the preferred way of returning data from a batch extension but there is a small amount of overhead to it. It has to join two separate lists of data on a common key, which could take a few extra cycles for large data sets."),(0,a.yg)("p",null,"Another option would be to generate the same result yourself while you're generating your data set. Once its all said and done, ",(0,a.yg)("inlineCode",{parentName:"p"},"this.StartBatch()")," creates an ",(0,a.yg)("inlineCode",{parentName:"p"},"IDictionary")," where ",(0,a.yg)("inlineCode",{parentName:"p"},"TSource")," is a parent object and ",(0,a.yg)("inlineCode",{parentName:"p"},"TValue")," is either a single child or an ",(0,a.yg)("inlineCode",{parentName:"p"},"IEnumerable")," depending on the type expression of your field. A batch extension is the only operation that will accept a return type of ",(0,a.yg)("inlineCode",{parentName:"p"},"IDictionary"),"."),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},"When returning ",(0,a.yg)("inlineCode",{parentName:"p"},"IDictionary"),", the key ",(0,a.yg)("strong",{parentName:"p"},"MUST BE")," the original object reference supplied to the the extension method, not a copy.")),(0,a.yg)("p",null,"This is the example above reconfigured to a custom dictionary. Note that when we use an ",(0,a.yg)("inlineCode",{parentName:"p"},"IDictionary")," return type, GraphQL is able to infer our field data type and an explicit declaration is no longer needed on the attribute."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using a Custom Dictionary"',title:'"Using',a:!0,Custom:!0,'Dictionary"':!0},'public class BakedGoodsCompanyController : GraphController\n{\n [QueryRoot("bakeries")]\n public async Task> RetrieveBakeries(Region region){/*...*/}\n\n // declare the batch operation as an extension\n // highlight-start\n [BatchTypeExtension(typeof(Bakery), "orders")]\n public async Task>> RetrieveCakeOrders(IEnumerable bakeries, int limitTo = 15)\n // highlight-end\n {\n //fetch all the orders at once\n Dictionary> allOrders = await _service\n .RetrieveCakeOrders(bakeries, limitTo);\n\n return allOrders;\n }\n}\n')),(0,a.yg)("h2",{id:"other-resources"},"Other Resources"),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://itnext.io/what-is-the-n-1-problem-in-graphql-dd4921cb3c1a"},"What is the N+1 Problem in GraphQL?")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/61e865ea.0f52cb2d.js b/assets/js/61e865ea.0f52cb2d.js new file mode 100644 index 0000000..90be2d4 --- /dev/null +++ b/assets/js/61e865ea.0f52cb2d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[7978],{4061:e=>{e.exports=JSON.parse('{"name":"docusaurus-plugin-content-pages","id":"default"}')}}]); \ No newline at end of file diff --git a/assets/js/6499ffa8.ba0c0a55.js b/assets/js/6499ffa8.ba0c0a55.js new file mode 100644 index 0000000..94f9a86 --- /dev/null +++ b/assets/js/6499ffa8.ba0c0a55.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[6792],{5680:(e,a,n)=>{n.d(a,{xA:()=>c,yg:()=>m});var t=n(6540);function i(e,a,n){return a in e?Object.defineProperty(e,a,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[a]=n,e}function r(e,a){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(e);a&&(t=t.filter((function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.push.apply(n,t)}return n}function l(e){for(var a=1;a=0||(i[n]=e[n]);return i}(e,a);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=t.createContext({}),p=function(e){var a=t.useContext(s),n=a;return e&&(n="function"==typeof e?e(a):l(l({},a),e)),n},c=function(e){var a=p(e.components);return t.createElement(s.Provider,{value:a},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var a=e.children;return t.createElement(t.Fragment,{},a)}},y=t.forwardRef((function(e,a){var n=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),u=p(n),y=i,m=u["".concat(s,".").concat(y)]||u[y]||d[y]||r;return n?t.createElement(m,l(l({ref:a},c),{},{components:n})):t.createElement(m,l({ref:a},c))}));function m(e,a){var n=arguments,i=a&&a.mdxType;if("string"==typeof e||i){var r=n.length,l=new Array(r);l[0]=y;var o={};for(var s in a)hasOwnProperty.call(a,s)&&(o[s]=a[s]);o.originalType=e,o[u]="string"==typeof e?e:i,l[1]=o;for(var p=2;p{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var t=n(8168),i=(n(6540),n(5680));const r={id:"custom-scalars",title:"Custom Scalars",sidebar_label:"Custom Scalars",sidebar_position:3},l=void 0,o={unversionedId:"advanced/custom-scalars",id:"advanced/custom-scalars",title:"Custom Scalars",description:"Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being enums).",source:"@site/docs/advanced/custom-scalars.md",sourceDirName:"advanced",slug:"/advanced/custom-scalars",permalink:"/docs/advanced/custom-scalars",draft:!1,tags:[],version:"current",sidebarPosition:3,frontMatter:{id:"custom-scalars",title:"Custom Scalars",sidebar_label:"Custom Scalars",sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Directives",permalink:"/docs/advanced/directives"},next:{title:"Action Results",permalink:"/docs/advanced/graph-action-results"}},s={},p=[{value:"Implement IScalarGraphType",id:"implement-iscalargraphtype",level:2},{value:"IScalarGraphType Members",id:"iscalargraphtype-members",level:3},{value:"ILeafValueResolver",id:"ileafvalueresolver",level:3},{value:"Dealing with Escaped Strings",id:"dealing-with-escaped-strings",level:4},{value:"Indicating an Error",id:"indicating-an-error",level:4},{value:"Example: Money Scalar",id:"example-money-scalar",level:3},{value:"Registering A Scalar",id:"registering-a-scalar",level:2},{value:"@specifiedBy Directive",id:"specifiedby-directive",level:2},{value:"Tips When Developing a Scalar",id:"tips-when-developing-a-scalar",level:2},{value:"Aim for Fewer Scalars",id:"aim-for-fewer-scalars",level:3}],c={toc:p};function u(e){let{components:a,...n}=e;return(0,i.yg)("wrapper",(0,t.A)({},c,n,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being ",(0,i.yg)("a",{parentName:"p",href:"../types/enums"},"enums"),"). "),(0,i.yg)("p",null,"Enums, being a type of their own, are very straight forward in .NET. Scalars, however; can be anything. For instance, the ",(0,i.yg)("inlineCode",{parentName:"p"},"Uri")," scalar is represented in GraphQL by a string. On the server though, we convert it into a ",(0,i.yg)("inlineCode",{parentName:"p"},"System.Uri")," object, with all the extra features that go along with it."),(0,i.yg)("p",null,"This can be done for any value that can be represented as a simple set of characters. When you create a scalar you declare its .NET type, provide a value resolver that accepts raw data from a query (a ",(0,i.yg)("inlineCode",{parentName:"p"},"ReadOnlySpan"),") and returns the instantiated scalar value."),(0,i.yg)("p",null,"Lets say we wanted to build a scalar called ",(0,i.yg)("inlineCode",{parentName:"p"},"Money"),' that can handle both an amount and currency symbol (e.g. "$23.45"). We might accept it in a query like this:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Declaring a Money Scalar"',title:'"Declaring',a:!0,Money:!0,'Scalar"':!0},'public class InventoryController : GraphController\n{\n [QueryRoot("search")]\n // highlight-next-line\n public IEnumerable Search(Money minPrice)\n {\n return _service.RetrieveProducts(\n minPrice.Symbol,\n minPrice.Price);\n\n }\n}\n\npublic class Money\n{\n public Money(char symbol, decimal price)\n {\n this.Symbol = symbol;\n this.Price = price;\n }\n\n public char Symbol { get; }\n public decimal Price { get; }\n}\n')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Using the Money Scalar"',title:'"Using',the:!0,Money:!0,'Scalar"':!0},'query {\n # highlight-next-line\n search(minPrice: "$18.45"){\n id\n name\n }\n}\n')),(0,i.yg)("p",null,"The query supplies the data as a quoted string, ",(0,i.yg)("inlineCode",{parentName:"p"},'"$18.45"'),", but our action method receives a ",(0,i.yg)("inlineCode",{parentName:"p"},"Money")," object. Internally, GraphQL senses that the supplied string value should be ",(0,i.yg)("inlineCode",{parentName:"p"},"Money")," from the schema definition and invokes the correct resolver to parse the value and generate the .NET object that can be passed to our action method."),(0,i.yg)("h2",{id:"implement-iscalargraphtype"},"Implement IScalarGraphType"),(0,i.yg)("p",null,"To create a scalar we need to implement ",(0,i.yg)("inlineCode",{parentName:"p"},"IScalarGraphType")," and register it with GraphQL. The methods and properties of ",(0,i.yg)("inlineCode",{parentName:"p"},"IScalarGraphType")," are as follows:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="IScalarGraphType.cs"',title:'"IScalarGraphType.cs"'},"public interface IScalarGraphType\n{\n string Name { get; }\n string InternalName { get; }\n string Description { get; }\n string SpecifiedByUrl { get; }\n TypeKind Kind { get; }\n bool Publish { get; }\n ScalarValueType ValueType { get; }\n Type ObjectType { get; }\n TypeCollection OtherKnownTypes { get; }\n ILeafValueResolver SourceResolver { get; }\n\n object Serialize(object item);\n string SerializeToQueryLanguage(object item);\n bool ValidateObject(object item);\n}\n\npublic interface ILeafValueResolver\n{\n object Resolve(ReadOnlySpan data);\n}\n")),(0,i.yg)("h3",{id:"iscalargraphtype-members"},"IScalarGraphType Members"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"Name"),": The unique name of this scalar. This name must be used when declaring a variable."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"InternalName"),": An alternate name representing the scalar in server side code. This name is commonly used in logging messages and exceptions to identify the scalar in terms of the server definitions. Its common to use the fully qualified name, i.e. ",(0,i.yg)("inlineCode",{parentName:"li"},'"MyNameSpace.Money"'),"."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"Description"),": The phrase that will used to describe the scalar in introspection queries."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"Kind"),": Scalars must always be declared as ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeKind.SCALAR"),"."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"Publish"),": Indicates if the scalar should be published for introspection queries. Unless there is a very strong reason not to, scalars should always be published. Set this value to ",(0,i.yg)("inlineCode",{parentName:"li"},"true"),"."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"ValueType"),": A set of flags indicating what type of source data, read from a query, this scalar is capable of processing (string, number or boolean). GraphQL will do a preemptive check and if the query document does not supply the data in the correct format it will not attempt to resolve the scalar. Most custom scalars will use ",(0,i.yg)("inlineCode",{parentName:"li"},"ScalarValueType.String"),"."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SpecifiedByUrl"),": A url, formatted as a string, pointing to information or the specification that defines this scalar. (optional, can be null)"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"ObjectType"),": The primary, internal type representing the scalar in .NET. In our example above we would set this to ",(0,i.yg)("inlineCode",{parentName:"li"},"typeof(Money)"),"."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"OtherKnownTypes"),": A collection of other potential types that could be used to represent the scalar in a controller class. For instance, integers can be expressed as ",(0,i.yg)("inlineCode",{parentName:"li"},"int")," or ",(0,i.yg)("inlineCode",{parentName:"li"},"int?"),". Most scalars will provide an empty list (e.g. ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeCollection.Empty"),")."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SourceResolver"),": An object that implements ",(0,i.yg)("inlineCode",{parentName:"li"},"ILeafValueResolver")," which can convert raw input data into the scalar's primary ",(0,i.yg)("inlineCode",{parentName:"li"},"ObjectType"),"."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"Serialize(object)"),": A method that converts an instance of your scalar to a leaf value that is serializable in a query response",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"This method ",(0,i.yg)("strong",{parentName:"li"},"must")," return a ",(0,i.yg)("inlineCode",{parentName:"li"},"number"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"string"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"bool")," or ",(0,i.yg)("inlineCode",{parentName:"li"},"null"),"."),(0,i.yg)("li",{parentName:"ul"},"When converting to a number this method can return any C# number value type (int, float, decimal etc.)."))),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SerializeToQueryLanguage(object)"),": A method that converts an instance of your scalar to a string representing it if it were declared as part of a schema language type definition. ",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"This method is used when generated default values for field arguments and input object fields via introspection queries."),(0,i.yg)("li",{parentName:"ul"},"This method must return a value exactly as it would appear in a schema type definition For example, strings must be surrounded by quotes."))),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"ValidateObject(object)"),": A method used when validating data received from a a field resolver. GraphQL will call this method and provide an object instance to determine if its acceptable and can be used in a query result.")),(0,i.yg)("admonition",{type:"note"},(0,i.yg)("p",{parentName:"admonition"}," ",(0,i.yg)("inlineCode",{parentName:"p"},"ValidateObject(object)"),' should not attempt to enforce nullability rules. In general, all scalars "could be null" depending on their usage in a schema. All scalars should return ',(0,i.yg)("inlineCode",{parentName:"p"},"true")," for a validation result if the provided object is ",(0,i.yg)("inlineCode",{parentName:"p"},"null"),". A field's type expression, enforced by graphql, will decide if null is acceptable on an individual field.")),(0,i.yg)("h3",{id:"ileafvalueresolver"},"ILeafValueResolver"),(0,i.yg)("p",null,"ILeafValueResolver contains a single method:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"Resolve(ReadOnlySpan)"),": A resolver function used for converting an array of characters into the internal representation of the scalar.")),(0,i.yg)("h4",{id:"dealing-with-escaped-strings"},"Dealing with Escaped Strings"),(0,i.yg)("p",null,"The span provided to ",(0,i.yg)("inlineCode",{parentName:"p"},"ILeafValueResolver.Resolve")," will be the raw data read from the query document. If the data represents a string, it will be provided in its delimited format. This means being surrounded by quotes as well as containing escaped characters (including escaped unicode characters):"),(0,i.yg)("p",null,"Example data:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},'"quoted string"')),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},'"""triple quoted string"""')),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},'"With \\"\\u03A3scaped ch\\u03B1racters\\""'),";")),(0,i.yg)("p",null,"The static method ",(0,i.yg)("inlineCode",{parentName:"p"},"GraphQLStrings.UnescapeAndTrimDelimiters")," provides a handy way for unescaping the data if you don't need to do anything special with it."),(0,i.yg)("p",null,"Calling ",(0,i.yg)("inlineCode",{parentName:"p"},"GraphQLStrings.UnescapeAndTrimDelimiters")," with the previous examples produces:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"quoted string")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"triple quoted string")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},'With "\u03a3scaped ch\u03b1racters"'))),(0,i.yg)("h4",{id:"indicating-an-error"},"Indicating an Error"),(0,i.yg)("p",null,"When resolving incoming values with ",(0,i.yg)("inlineCode",{parentName:"p"},"Resolve()"),", if the provided value is not usable and must be rejected then the entire query document must be rejected. For instance, if a document contained the value ",(0,i.yg)("inlineCode",{parentName:"p"},'"$15.R0"')," for our money scalar it would need to be rejected because ",(0,i.yg)("inlineCode",{parentName:"p"},"15.R0")," cannot be converted to a decimal. "),(0,i.yg)("p",null,"Throw an exception when this happens and GraphQL will automatically generate an appropriate response with the correct origin information indicating the line and column in the query document where the error occurred. However, like with any other encounterd exception, the library will obfuscate it to a generic message and only expose your exception details if allowed by the ",(0,i.yg)("a",{parentName:"p",href:"../reference/schema-configuration"},"schema configuration"),"."),(0,i.yg)("admonition",{title:"Pro Tip!",type:"tip"},(0,i.yg)("p",{parentName:"admonition"},"If you throw the special ",(0,i.yg)("inlineCode",{parentName:"p"},"UnresolvedValueException")," your error message will be delivered verbatim to the requestor as part of the response message instead of being obfuscated. ")),(0,i.yg)("h3",{id:"example-money-scalar"},"Example: Money Scalar"),(0,i.yg)("p",null,"The completed Money custom scalar type"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp"},'\n public class MoneyScalarType : IScalarGraphType\n {\n public string Name => "Money";\n\n public string InternalName => typeof(Money).FullName;\n\n public string Description => string.Empty;\n\n public TypeKind Kind => TypeKind.SCALAR;\n\n public bool Publish => true;\n\n public ScalarValueType ValueType => ScalarValueType.String;\n\n public Type ObjectType => typeof(Money);\n\n public TypeCollection OtherKnownTypes => TypeCollection.Empty;\n\n public ILeafValueResolver SourceResolver { get; } = new MoneyValueResolver();\n \n public object Serialize(object item)\n {\n if (item == null)\n return item;\n\n var money = (Money)item;\n return $"{money.Symbol}{money.Price}";\n }\n \n public string SerializeToQueryLanguage(object item)\n {\n // convert to a string first\n var serialized = this.Serialize(item);\n if (serialized == null)\n return "null";\n\n // return value as quoted\n return $"\\"{serialized}\\"";\n }\n\n public bool ValidateObject(object item)\n {\n if (item == null)\n return true;\n\n return item.GetType() == typeof(Money);\n }\n }\n\n public class MoneyValueResolver : ILeafValueResolver\n {\n public object Resolve(ReadOnlySpan data)\n {\n // example only, more validation code is needed to fully validate \n // the data\n var sanitizedMoney = GraphQLStrings.UnescapeAndTrimDelimiters(data);\n if(sanitizedMoney == null || sanitizedMoney.Length < 2)\n throw new UnresolvedValueException("Money must be at least 2 characters");\n\n return new Money(sanitizedMoney[0], Decimal.Parse(sanitizedMoney.Substring(1)));\n }\n }\n')),(0,i.yg)("h2",{id:"registering-a-scalar"},"Registering A Scalar"),(0,i.yg)("p",null,"The last step in declaring a scalar is to register it with the runtime. Scalars are schema agnostic. They sit outside of any dependency injection context and must be registered directly with GraphQL."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Register The Money Scalar "',title:'"Register',The:!0,Money:!0,Scalar:!0,'"':!0},"// register the scalar type to the global provider\n// BEFORE calling .AddGraphQL()\nGraphQLProviders.ScalarProvider.RegisterCustomScalar(typeof(MoneyScalarType));\n\nservices.AddGraphQL();\n")),(0,i.yg)("admonition",{type:"info"},(0,i.yg)("p",{parentName:"admonition"},"Since our scalar is represented by a .NET class, if we don't pre-register it, GraphQL will attempt to parse the ",(0,i.yg)("inlineCode",{parentName:"p"},"Money")," class as an input object graph type. Once registered as a scalar, any attempt to use ",(0,i.yg)("inlineCode",{parentName:"p"},"Money")," as an object graph type will cause an exception.")),(0,i.yg)("h2",{id:"specifiedby-directive"},"@specifiedBy Directive"),(0,i.yg)("p",null,"GraphQL provides a special, built-in directive called ",(0,i.yg)("inlineCode",{parentName:"p"},"@specifiedBy")," that allows you to supply a URL pointing to a the specification for your custom scalar. This url is used by various tools to link to additional data for you or your customers so they know how to interact with your scalar type. It is entirely optional."),(0,i.yg)("p",null,"The @specifiedBy directive can be applied to a scalar in all the same ways as other type system directives or by use of the special ",(0,i.yg)("inlineCode",{parentName:"p"},"[SpecifiedBy]")," attribute."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Applying the @specifiedBy"',title:'"Applying',the:!0,'@specifiedBy"':!0},'// apply the directive to a single schema at startup\nservices.AddGraphQL(o => {\n// highlight-start\n o.ApplyDirective("@specifiedBy")\n .WithArguments("https://myurl.com")\n .ToItems(item => item.Name == "Money");\n// highlight-end\n});\n\n// via the [ApplyDirective] attribute\n// for all schemas\n// highlight-next-line\n[ApplyDirective("@specifiedBy", "https://myurl.com")]\npublic class MoneyScalarType : IScalarGraphType\n{}\n\n// via the special [SpecifiedBy] attribute\n// for all schemas\n// highlight-next-line\n[SpecifiedBy("https://myurl.com")]\npublic class MoneyScalarType : IScalarGraphType\n{}\n\n// as part of the contructor\n// for all schemas\npublic class MoneyScalarType : IScalarGraphType\n{\n public MoneyScalarType()\n {\n // highlight-next-line\n this.SpecifiedByUrl = "https://myurl.com";\n }\n}\n')),(0,i.yg)("h2",{id:"tips-when-developing-a-scalar"},"Tips When Developing a Scalar"),(0,i.yg)("p",null,"A few points about designing your scalar:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Looking through the ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Schemas/TypeSystem/Scalars"},"built in scalars")," can be helpful when designing your own."),(0,i.yg)("li",{parentName:"ul"},"Scalar types are expected to be thread safe."),(0,i.yg)("li",{parentName:"ul"},"The runtime will pass a new instance of your scalar graph type to each registered schema. It must be declared with a public, parameterless constructor."),(0,i.yg)("li",{parentName:"ul"},"Scalar types should be simple and work in isolation."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"ReadOnlySpan")," provided to ",(0,i.yg)("inlineCode",{parentName:"li"},"ILeafValueResolver.Resolve")," should be all the data needed to generate a value, there should be no need to perform side effects or fetch additional data."),(0,i.yg)("li",{parentName:"ul"},"Scalar types should not track any state, depend on any stateful objects, or attempt to use any sort of dependency injection."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"ILeafValueResolver.Resolve")," must be ",(0,i.yg)("strong",{parentName:"li"},"FAST"),"! Since your resolver is used to construct an initial query plan from the raw query text, it'll be called many orders of magnitude more often than any other method. Taking the time and performing various micro-optimizations are appropriate for this method.")),(0,i.yg)("h3",{id:"aim-for-fewer-scalars"},"Aim for Fewer Scalars"),(0,i.yg)("p",null,"Avoid the urge to start declaring a lot of custom scalars. In fact, chances are that you'll never need to create one. In our example we could have represented our money scalar as an INPUT_OBJECT graph type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Money as an Input Object Graph Type"',title:'"Money',as:!0,an:!0,Input:!0,Object:!0,Graph:!0,'Type"':!0},'public class InventoryController : GraphController\n{\n [QueryRoot("search")]\n public IEnumerable Search(Money minPrice)\n {\n return _service.RetrieveProducts(\n minPrice.Symbol,\n minPrice.Price);\n }\n\n}\n\npublic class Money\n{\n public string Symbol { get; set; }\n public decimal Price { get; set; }\n}\n')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Using the Money Input Object"',title:'"Using',the:!0,Money:!0,Input:!0,'Object"':!0},'query {\n search(minPrice: {symbol: "$" price: 18.45}){\n id\n name\n }\n}\n')),(0,i.yg)("p",null,"This is a lot more flexible. We can add more properties to ",(0,i.yg)("inlineCode",{parentName:"p"},"Money")," when needed and not break existing queries. Whereas with a scalar if we change the acceptable format of the string data any existing applications using our graph may need to be updated. It is almost always better to represent your data as an input object rather than a custom scalar."),(0,i.yg)("admonition",{title:"Be Careful",type:"caution"},(0,i.yg)("p",{parentName:"admonition"},"Creating a custom scalar should be a last resort, not a first option.")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6a8cb708.912ce0ef.js b/assets/js/6a8cb708.912ce0ef.js new file mode 100644 index 0000000..0d0f6a0 --- /dev/null +++ b/assets/js/6a8cb708.912ce0ef.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[9694],{1966:e=>{e.exports=JSON.parse('{"name":"docusaurus-plugin-content-docs","id":"default"}')}}]); \ No newline at end of file diff --git a/assets/js/6c218552.ea014484.js b/assets/js/6c218552.ea014484.js new file mode 100644 index 0000000..edacae3 --- /dev/null +++ b/assets/js/6c218552.ea014484.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[7045],{5680:(e,t,a)=>{a.d(t,{xA:()=>h,yg:()=>c});var n=a(6540);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function o(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function i(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var p=n.createContext({}),s=function(e){var t=n.useContext(p),a=t;return e&&(a="function"==typeof e?e(t):i(i({},t),e)),a},h=function(e){var t=s(e.components);return n.createElement(p.Provider,{value:t},e.children)},d="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},g=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,o=e.originalType,p=e.parentName,h=l(e,["components","mdxType","originalType","parentName"]),d=s(a),g=r,c=d["".concat(p,".").concat(g)]||d[g]||u[g]||o;return a?n.createElement(c,i(i({ref:t},h),{},{components:a})):n.createElement(c,i({ref:t},h))}));function c(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=a.length,i=new Array(o);i[0]=g;var l={};for(var p in t)hasOwnProperty.call(t,p)&&(l[p]=t[p]);l.originalType=e,l[d]="string"==typeof e?e:r,i[1]=l;for(var s=2;s{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>d,frontMatter:()=>o,metadata:()=>l,toc:()=>s});var n=a(8168),r=(a(6540),a(5680));const o={id:"what-is-graphql",title:"What is GraphQL?",sidebar_label:"What is GraphQL?",sidebar_position:0},i=void 0,l={unversionedId:"introduction/what-is-graphql",id:"introduction/what-is-graphql",title:"What is GraphQL?",description:"GraphQL is a query language specification originally created by Meta for their own internal use. It was eventually open-sourced and moved to its own foundation, the GraphQL Foundation, and hosted by the Linux Foundation. The specification provides an alternative to traditional REST queries that we all know and love in giving the requestor more control over what data to return.",source:"@site/docs/introduction/what-is-graphql.md",sourceDirName:"introduction",slug:"/introduction/what-is-graphql",permalink:"/docs/introduction/what-is-graphql",draft:!1,tags:[],version:"current",sidebarPosition:0,frontMatter:{id:"what-is-graphql",title:"What is GraphQL?",sidebar_label:"What is GraphQL?",sidebar_position:0},sidebar:"tutorialSidebar",previous:{title:"Code Examples",permalink:"/docs/quick/code-examples"},next:{title:"Made for ASP.NET Developers",permalink:"/docs/introduction/made-for-aspnet-developers"}},p={},s=[{value:"Is it Better than REST?",id:"is-it-better-than-rest",level:2},{value:"GraphQL is not a .NET Technology",id:"graphql-is-not-a-net-technology",level:2},{value:"Other Popular GraphQL Implementations",id:"other-popular-graphql-implementations",level:4},{value:"Why Choose GraphQL?",id:"why-choose-graphql",level:2}],h={toc:s};function d(e){let{components:t,...a}=e;return(0,r.yg)("wrapper",(0,n.A)({},h,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("a",{parentName:"p",href:"https://graphql.org"},"GraphQL")," is a query language specification originally created by ",(0,r.yg)("a",{parentName:"p",href:"https://facebook.com"},"Meta")," for their own internal use. It was eventually open-sourced and moved to its own foundation, the ",(0,r.yg)("a",{parentName:"p",href:"https://foundation.graphql.org/"},"GraphQL Foundation"),", and hosted by the ",(0,r.yg)("a",{parentName:"p",href:"https://www.linuxfoundation.org/"},"Linux Foundation"),". The specification provides an alternative to traditional REST queries that we all know and love in giving the requestor more control over what data to return."),(0,r.yg)("p",null,"With REST someone may requst data by querying against a URL with the ",(0,r.yg)("inlineCode",{parentName:"p"},"GET")," HTTP verb. It's up to the server to decide what data to return."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-javascript",metastring:'title="Sample REST Query"',title:'"Sample',REST:!0,"":!0,'Query"':!0},'//REST Request:\nGET https://www.the-rebel-alliance.site/directory/persons/1\n\n// Response\n{\n "id": 1,\n "name": "Luke Skywalker",\n "department": "Tatooine",\n "favoriteSong": "Papa Was a Rollin\' Stone",\n}\n')),(0,r.yg)("p",null,"But with GraphQL a user ",(0,r.yg)("em",{parentName:"p"},"describes")," the data they want in their request to the GraphQL server."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample GraphQL Query"',title:'"Sample',GraphQL:!0,'Query"':!0},'# GraphQL Request\nquery {\n person(id: 1){\n name\n department\n favoriteSong\n }\n}\n\n# Response\n{\n "data": {\n "person": {\n "name": "Luke Skywalker",\n "department": "Tatooine",\n "favoriteSong": "Papa Was a Rollin\' Stone",\n }\n }\n}\n')),(0,r.yg)("h2",{id:"is-it-better-than-rest"},"Is it Better than REST?"),(0,r.yg)("p",null,"Nope! As with any choice in technology there are pros and cons. It comes down to what trade offs you're willing to accept. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"REST")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"gRPC")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"Carrier Pigeon")," for communicating between systens is never a cut and dry decision. Your team's needs are going to be different than others. To claim that GraphQL should replace REST would be short sighted."),(0,r.yg)("p",null,"One of the primary benefits of GraphQL is the requestor only gets the data they need. In the REST example above no choice was given for the JSON object that was generated All 4 fields were (and always will be) returned. In the second example notice that we specified only to return ",(0,r.yg)("inlineCode",{parentName:"p"},"name"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"department")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"favoriteSong"),". We already know the ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," in this instance and returning isn't necessary. For two characters of data that's not a big deal, but if the the REST response included a detailed biography of the person, returning that amount of data when its not needed (such as on a search page) could hamper performance. The issue compounds itself when we start dealing with parent-child relationships and hundreds of rows of data. Being able to ignore a field could mean the difference between a 50KB result and a 50MB result."),(0,r.yg)("h2",{id:"graphql-is-not-a-net-technology"},"GraphQL is not a .NET Technology"),(0,r.yg)("p",null,"GraphQL is a query ",(0,r.yg)("a",{parentName:"p",href:"https://spec.graphql.org/"},"language specification"),"; a contract describing a syntax and rule set for how to process requests for data. There are many implementations of GraphQL for various tech stacks from JavaScript, to Java, to Ruby and even other .NET implementations to choose from."),(0,r.yg)("h4",{id:"other-popular-graphql-implementations"},"Other Popular GraphQL Implementations"),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Library Name"),(0,r.yg)("th",{parentName:"tr",align:null},"Language"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"https://github.com/apollographql/apollo-server"},"Apollo Server")),(0,r.yg)("td",{parentName:"tr",align:null},"JavaScript")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"https://github.com/rmosolgo/graphql-ruby"},"GraphQL Ruby")),(0,r.yg)("td",{parentName:"tr",align:null},"Ruby")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"https://github.com/graphql-java/graphql-java"},"GraphQL Java")),(0,r.yg)("td",{parentName:"tr",align:null},"Java")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"https://github.com/graphql-dotnet/graphql-dotnet"},"GraphQL .NET")),(0,r.yg)("td",{parentName:"tr",align:null},".NET")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"https://github.com/ChilliCream/hotchocolate"},"Hot Chocolate")),(0,r.yg)("td",{parentName:"tr",align:null},".NET")))),(0,r.yg)("h2",{id:"why-choose-graphql"},"Why Choose GraphQL?"),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},'"Can we add one more thing?" - Product Managers')),(0,r.yg)("p",null,"Have you ever heard that in a stand-up meeting? Lets say your team is building a website to show a list of all the members of the rebel alliance. The original specs of a REST end point calls for the return of an object in the above example. Then, for the 2nd time this week, the product owner decided to make a change. \"Hey, lets add everybody's direct manager's name to their employee card!\", Karen said. The project manager sighs audibly, Dave rolls his eyes and now you have to make a code change to return the new field from the endpoint."),(0,r.yg)("p",null,"With GraphQL the ",(0,r.yg)("strong",{parentName:"p"},"requestor"),", that is the client application, adds the new field to their query and the data is automatically returned. Since they have to update the UI they are going to be impacted anyways so no additional harm done. From the back-end though, using GraphQL means there is no need for a server update; no change request, additional QA, no PR approvals and most importantly no new deployment. Server-side code tends to be scrutinized a lot more than client-side code and for good reason. The fewer changes we have to make to a production server the better! This is, of course, dependent on how you design your object graph but with a little forward thinking you can greatly minimize the required server changes once you setup your schema."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7964.c1ffc5a7.js b/assets/js/7964.c1ffc5a7.js new file mode 100644 index 0000000..8803a07 --- /dev/null +++ b/assets/js/7964.c1ffc5a7.js @@ -0,0 +1 @@ +(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[7964],{7964:(e,t,n)=>{"use strict";n.d(t,{A:()=>F});var o=n(8168),s=n(6540),c=n(2303),a=n(53),r=n(5293),l=n(6342);function i(){const{prism:e}=(0,l.p)(),{colorMode:t}=(0,r.G)(),n=e.theme,o=e.darkTheme||n;return"dark"===t?o:n}var u=n(7559),p=n(8426),d=n.n(p);const m=/title=(?["'])(?.*?)\1/,g=/\{(?<range>[\d,-]+)\}/,y={js:{start:"\\/\\/",end:""},jsBlock:{start:"\\/\\*",end:"\\*\\/"},jsx:{start:"\\{\\s*\\/\\*",end:"\\*\\/\\s*\\}"},bash:{start:"#",end:""},html:{start:"\x3c!--",end:"--\x3e"}};function h(e,t){const n=e.map((e=>{const{start:n,end:o}=y[e];return`(?:${n}\\s*(${t.flatMap((e=>[e.line,e.block?.start,e.block?.end].filter(Boolean))).join("|")})\\s*${o})`})).join("|");return new RegExp(`^\\s*(?:${n})\\s*$`)}function f(e,t){let n=e.replace(/\n$/,"");const{language:o,magicComments:s,metastring:c}=t;if(c&&g.test(c)){const e=c.match(g).groups.range;if(0===s.length)throw new Error(`A highlight range has been given in code block's metastring (\`\`\` ${c}), but no magic comment config is available. Docusaurus applies the first magic comment entry's className for metastring ranges.`);const t=s[0].className,o=d()(e).filter((e=>e>0)).map((e=>[e-1,[t]]));return{lineClassNames:Object.fromEntries(o),code:n}}if(void 0===o)return{lineClassNames:{},code:n};const a=function(e,t){switch(e){case"js":case"javascript":case"ts":case"typescript":return h(["js","jsBlock"],t);case"jsx":case"tsx":return h(["js","jsBlock","jsx"],t);case"html":return h(["js","jsBlock","html"],t);case"python":case"py":case"bash":return h(["bash"],t);case"markdown":case"md":return h(["html","jsx","bash"],t);default:return h(Object.keys(y),t)}}(o,s),r=n.split("\n"),l=Object.fromEntries(s.map((e=>[e.className,{start:0,range:""}]))),i=Object.fromEntries(s.filter((e=>e.line)).map((e=>{let{className:t,line:n}=e;return[n,t]}))),u=Object.fromEntries(s.filter((e=>e.block)).map((e=>{let{className:t,block:n}=e;return[n.start,t]}))),p=Object.fromEntries(s.filter((e=>e.block)).map((e=>{let{className:t,block:n}=e;return[n.end,t]})));for(let d=0;d<r.length;){const e=r[d].match(a);if(!e){d+=1;continue}const t=e.slice(1).find((e=>void 0!==e));i[t]?l[i[t]].range+=`${d},`:u[t]?l[u[t]].start=d:p[t]&&(l[p[t]].range+=`${l[p[t]].start}-${d-1},`),r.splice(d,1)}n=r.join("\n");const m={};return Object.entries(l).forEach((e=>{let[t,{range:n}]=e;d()(n).forEach((e=>{m[e]??=[],m[e].push(t)}))})),{lineClassNames:m,code:n}}const b="codeBlockContainer_Ckt0";function k(e){let{as:t,...n}=e;const c=function(e){const t={color:"--prism-color",backgroundColor:"--prism-background-color"},n={};return Object.entries(e.plain).forEach((e=>{let[o,s]=e;const c=t[o];c&&"string"==typeof s&&(n[c]=s)})),n}(i());return s.createElement(t,(0,o.A)({},n,{style:c,className:(0,a.A)(n.className,b,u.G.common.codeBlock)}))}const v={codeBlockContent:"codeBlockContent_biex",codeBlockTitle:"codeBlockTitle_Ktv7",codeBlock:"codeBlock_bY9V",codeBlockStandalone:"codeBlockStandalone_MEMb",codeBlockLines:"codeBlockLines_e6Vv",codeBlockLinesWithNumbering:"codeBlockLinesWithNumbering_o6Pm",buttonGroup:"buttonGroup__atx"};function E(e){let{children:t,className:n}=e;return s.createElement(k,{as:"pre",tabIndex:0,className:(0,a.A)(v.codeBlockStandalone,"thin-scrollbar",n)},s.createElement("code",{className:v.codeBlockLines},t))}var N=n(9532);const B={attributes:!0,characterData:!0,childList:!0,subtree:!0};function C(e,t){const[n,o]=(0,s.useState)(),c=(0,s.useCallback)((()=>{o(e.current?.closest("[role=tabpanel][hidden]"))}),[e,o]);(0,s.useEffect)((()=>{c()}),[c]),function(e,t,n){void 0===n&&(n=B);const o=(0,N._q)(t),c=(0,N.Be)(n);(0,s.useEffect)((()=>{const t=new MutationObserver(o);return e&&t.observe(e,c),()=>t.disconnect()}),[e,o,c])}(n,(e=>{e.forEach((e=>{"attributes"===e.type&&"hidden"===e.attributeName&&(t(),c())}))}),{attributes:!0,characterData:!1,childList:!1,subtree:!1})}const w={plain:{backgroundColor:"#2a2734",color:"#9a86fd"},styles:[{types:["comment","prolog","doctype","cdata","punctuation"],style:{color:"#6c6783"}},{types:["namespace"],style:{opacity:.7}},{types:["tag","operator","number"],style:{color:"#e09142"}},{types:["property","function"],style:{color:"#9a86fd"}},{types:["tag-id","selector","atrule-id"],style:{color:"#eeebff"}},{types:["attr-name"],style:{color:"#c4b9fe"}},{types:["boolean","string","entity","url","attr-value","keyword","control","directive","unit","statement","regex","atrule","placeholder","variable"],style:{color:"#ffcc99"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"#c4b9fe"}}]};var L={Prism:n(1258).A,theme:w};function j(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function A(){return A=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(e[o]=n[o])}return e},A.apply(this,arguments)}var T=/\r\n|\r|\n/,_=function(e){0===e.length?e.push({types:["plain"],content:"\n",empty:!0}):1===e.length&&""===e[0].content&&(e[0].content="\n",e[0].empty=!0)},S=function(e,t){var n=e.length;return n>0&&e[n-1]===t?e:e.concat(t)},x=function(e,t){var n=e.plain,o=Object.create(null),s=e.styles.reduce((function(e,n){var o=n.languages,s=n.style;return o&&!o.includes(t)||n.types.forEach((function(t){var n=A({},e[t],s);e[t]=n})),e}),o);return s.root=n,s.plain=A({},n,{backgroundColor:null}),s};function O(e,t){var n={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&-1===t.indexOf(o)&&(n[o]=e[o]);return n}const P=function(e){function t(){for(var t=this,n=[],o=arguments.length;o--;)n[o]=arguments[o];e.apply(this,n),j(this,"getThemeDict",(function(e){if(void 0!==t.themeDict&&e.theme===t.prevTheme&&e.language===t.prevLanguage)return t.themeDict;t.prevTheme=e.theme,t.prevLanguage=e.language;var n=e.theme?x(e.theme,e.language):void 0;return t.themeDict=n})),j(this,"getLineProps",(function(e){var n=e.key,o=e.className,s=e.style,c=A({},O(e,["key","className","style","line"]),{className:"token-line",style:void 0,key:void 0}),a=t.getThemeDict(t.props);return void 0!==a&&(c.style=a.plain),void 0!==s&&(c.style=void 0!==c.style?A({},c.style,s):s),void 0!==n&&(c.key=n),o&&(c.className+=" "+o),c})),j(this,"getStyleForToken",(function(e){var n=e.types,o=e.empty,s=n.length,c=t.getThemeDict(t.props);if(void 0!==c){if(1===s&&"plain"===n[0])return o?{display:"inline-block"}:void 0;if(1===s&&!o)return c[n[0]];var a=o?{display:"inline-block"}:{},r=n.map((function(e){return c[e]}));return Object.assign.apply(Object,[a].concat(r))}})),j(this,"getTokenProps",(function(e){var n=e.key,o=e.className,s=e.style,c=e.token,a=A({},O(e,["key","className","style","token"]),{className:"token "+c.types.join(" "),children:c.content,style:t.getStyleForToken(c),key:void 0});return void 0!==s&&(a.style=void 0!==a.style?A({},a.style,s):s),void 0!==n&&(a.key=n),o&&(a.className+=" "+o),a})),j(this,"tokenize",(function(e,t,n,o){var s={code:t,grammar:n,language:o,tokens:[]};e.hooks.run("before-tokenize",s);var c=s.tokens=e.tokenize(s.code,s.grammar,s.language);return e.hooks.run("after-tokenize",s),c}))}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.render=function(){var e=this.props,t=e.Prism,n=e.language,o=e.code,s=e.children,c=this.getThemeDict(this.props),a=t.languages[n];return s({tokens:function(e){for(var t=[[]],n=[e],o=[0],s=[e.length],c=0,a=0,r=[],l=[r];a>-1;){for(;(c=o[a]++)<s[a];){var i=void 0,u=t[a],p=n[a][c];if("string"==typeof p?(u=a>0?u:["plain"],i=p):(u=S(u,p.type),p.alias&&(u=S(u,p.alias)),i=p.content),"string"==typeof i){var d=i.split(T),m=d.length;r.push({types:u,content:d[0]});for(var g=1;g<m;g++)_(r),l.push(r=[]),r.push({types:u,content:d[g]})}else a++,t.push(u),n.push(i),o.push(0),s.push(i.length)}a--,t.pop(),n.pop(),o.pop(),s.pop()}return _(r),l}(void 0!==a?this.tokenize(t,o,a,n):[o]),className:"prism-code language-"+n,style:void 0!==c?c.root:{},getLineProps:this.getLineProps,getTokenProps:this.getTokenProps})},t}(s.Component),I="codeLine_lJS_",$="codeLineNumber_Tfdd",D="codeLineContent_feaV";function H(e){let{line:t,classNames:n,showLineNumbers:c,getLineProps:r,getTokenProps:l}=e;1===t.length&&"\n"===t[0].content&&(t[0].content="");const i=r({line:t,className:(0,a.A)(n,c&&I)}),u=t.map(((e,t)=>s.createElement("span",(0,o.A)({key:t},l({token:e,key:t})))));return s.createElement("span",i,c?s.createElement(s.Fragment,null,s.createElement("span",{className:$}),s.createElement("span",{className:D},u)):u,s.createElement("br",null))}var z=n(1312);const M={copyButtonCopied:"copyButtonCopied_obH4",copyButtonIcons:"copyButtonIcons_eSgA",copyButtonIcon:"copyButtonIcon_y97N",copyButtonSuccessIcon:"copyButtonSuccessIcon_LjdS"};function V(e){let{code:t,className:n}=e;const[o,c]=(0,s.useState)(!1),r=(0,s.useRef)(void 0),l=(0,s.useCallback)((()=>{!function(e,t){let{target:n=document.body}=void 0===t?{}:t;const o=document.createElement("textarea"),s=document.activeElement;o.value=e,o.setAttribute("readonly",""),o.style.contain="strict",o.style.position="absolute",o.style.left="-9999px",o.style.fontSize="12pt";const c=document.getSelection();let a=!1;c.rangeCount>0&&(a=c.getRangeAt(0)),n.append(o),o.select(),o.selectionStart=0,o.selectionEnd=e.length;let r=!1;try{r=document.execCommand("copy")}catch{}o.remove(),a&&(c.removeAllRanges(),c.addRange(a)),s&&s.focus()}(t),c(!0),r.current=window.setTimeout((()=>{c(!1)}),1e3)}),[t]);return(0,s.useEffect)((()=>()=>window.clearTimeout(r.current)),[]),s.createElement("button",{type:"button","aria-label":o?(0,z.T)({id:"theme.CodeBlock.copied",message:"Copied",description:"The copied button label on code blocks"}):(0,z.T)({id:"theme.CodeBlock.copyButtonAriaLabel",message:"Copy code to clipboard",description:"The ARIA label for copy code blocks button"}),title:(0,z.T)({id:"theme.CodeBlock.copy",message:"Copy",description:"The copy button label on code blocks"}),className:(0,a.A)("clean-btn",n,M.copyButton,o&&M.copyButtonCopied),onClick:l},s.createElement("span",{className:M.copyButtonIcons,"aria-hidden":"true"},s.createElement("svg",{className:M.copyButtonIcon,viewBox:"0 0 24 24"},s.createElement("path",{d:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"})),s.createElement("svg",{className:M.copyButtonSuccessIcon,viewBox:"0 0 24 24"},s.createElement("path",{d:"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"}))))}const W="wordWrapButtonIcon_Bwma",R="wordWrapButtonEnabled_EoeP";function G(e){let{className:t,onClick:n,isEnabled:o}=e;const c=(0,z.T)({id:"theme.CodeBlock.wordWrapToggle",message:"Toggle word wrap",description:"The title attribute for toggle word wrapping button of code block lines"});return s.createElement("button",{type:"button",onClick:n,className:(0,a.A)("clean-btn",t,o&&R),"aria-label":c,title:c},s.createElement("svg",{className:W,viewBox:"0 0 24 24","aria-hidden":"true"},s.createElement("path",{fill:"currentColor",d:"M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"})))}function q(e){let{children:t,className:n="",metastring:c,title:r,showLineNumbers:u,language:p}=e;const{prism:{defaultLanguage:d,magicComments:g}}=(0,l.p)(),y=p??n.split(" ").find((e=>e.startsWith("language-")))?.replace(/language-/,"")??d;const h=i(),b=function(){const[e,t]=(0,s.useState)(!1),[n,o]=(0,s.useState)(!1),c=(0,s.useRef)(null),a=(0,s.useCallback)((()=>{const n=c.current.querySelector("code");e?n.removeAttribute("style"):(n.style.whiteSpace="pre-wrap",n.style.overflowWrap="anywhere"),t((e=>!e))}),[c,e]),r=(0,s.useCallback)((()=>{const{scrollWidth:e,clientWidth:t}=c.current,n=e>t||c.current.querySelector("code").hasAttribute("style");o(n)}),[c]);return C(c,r),(0,s.useEffect)((()=>{r()}),[e,r]),(0,s.useEffect)((()=>(window.addEventListener("resize",r,{passive:!0}),()=>{window.removeEventListener("resize",r)})),[r]),{codeBlockRef:c,isEnabled:e,isCodeScrollable:n,toggle:a}}(),E=function(e){return e?.match(m)?.groups.title??""}(c)||r,{lineClassNames:N,code:B}=f(t,{metastring:c,language:y,magicComments:g}),w=u??function(e){return Boolean(e?.includes("showLineNumbers"))}(c);return s.createElement(k,{as:"div",className:(0,a.A)(n,y&&!n.includes(`language-${y}`)&&`language-${y}`)},E&&s.createElement("div",{className:v.codeBlockTitle},E),s.createElement("div",{className:v.codeBlockContent},s.createElement(P,(0,o.A)({},L,{theme:h,code:B,language:y??"text"}),(e=>{let{className:t,tokens:n,getLineProps:o,getTokenProps:c}=e;return s.createElement("pre",{tabIndex:0,ref:b.codeBlockRef,className:(0,a.A)(t,v.codeBlock,"thin-scrollbar")},s.createElement("code",{className:(0,a.A)(v.codeBlockLines,w&&v.codeBlockLinesWithNumbering)},n.map(((e,t)=>s.createElement(H,{key:t,line:e,getLineProps:o,getTokenProps:c,classNames:N[t],showLineNumbers:w})))))})),s.createElement("div",{className:v.buttonGroup},(b.isEnabled||b.isCodeScrollable)&&s.createElement(G,{className:v.codeButton,onClick:()=>b.toggle(),isEnabled:b.isEnabled}),s.createElement(V,{className:v.codeButton,code:B}))))}function F(e){let{children:t,...n}=e;const a=(0,c.A)(),r=function(e){return s.Children.toArray(e).some((e=>(0,s.isValidElement)(e)))?e:Array.isArray(e)?e.join(""):e}(t),l="string"==typeof r?q:E;return s.createElement(l,(0,o.A)({key:String(a)},n),r)}},8426:(e,t)=>{function n(e){let t,n=[];for(let o of e.split(",").map((e=>e.trim())))if(/^-?\d+$/.test(o))n.push(parseInt(o,10));else if(t=o.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/)){let[e,o,s,c]=t;if(o&&c){o=parseInt(o),c=parseInt(c);const e=o<c?1:-1;"-"!==s&&".."!==s&&"\u2025"!==s||(c+=e);for(let t=o;t!==c;t+=e)n.push(t)}}return n}t.default=n,e.exports=n}}]); \ No newline at end of file diff --git a/assets/js/7966db6c.cad9283f.js b/assets/js/7966db6c.cad9283f.js new file mode 100644 index 0000000..5bbe3c8 --- /dev/null +++ b/assets/js/7966db6c.cad9283f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[2892],{5680:(e,t,n)=>{n.d(t,{xA:()=>p,yg:()=>h});var r=n(6540);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function a(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?i(Object(n),!0).forEach((function(t){o(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):i(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function l(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=r.createContext({}),c=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):a(a({},t),e)),n},p=function(e){var t=c(e.components);return r.createElement(s.Provider,{value:t},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},g=r.forwardRef((function(e,t){var n=e.components,o=e.mdxType,i=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=c(n),g=o,h=u["".concat(s,".").concat(g)]||u[g]||d[g]||i;return n?r.createElement(h,a(a({ref:t},p),{},{components:n})):r.createElement(h,a({ref:t},p))}));function h(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var i=n.length,a=new Array(i);a[0]=g;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[u]="string"==typeof e?e:o,a[1]=l;for(var c=2;c<i;c++)a[c]=n[c];return r.createElement.apply(null,a)}return r.createElement.apply(null,n)}g.displayName="MDXCreateElement"},3644:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>a,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>c});var r=n(8168),o=(n(6540),n(5680));const i={id:"entity-framework",title:"Using Entity Framework",sidebar_label:"Entity Framework",sidebar_position:2},a=void 0,l={unversionedId:"development/entity-framework",id:"development/entity-framework",title:"Using Entity Framework",description:"DbContext and Parallel Query Operations",source:"@site/docs/development/entity-framework.md",sourceDirName:"development",slug:"/development/entity-framework",permalink:"/docs/development/entity-framework",draft:!1,tags:[],version:"current",sidebarPosition:2,frontMatter:{id:"entity-framework",title:"Using Entity Framework",sidebar_label:"Entity Framework",sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"Unit Testing",permalink:"/docs/development/unit-testing"},next:{title:"File Uploads & Batching",permalink:"/docs/server-extensions/multipart-requests"}},s={},c=[{value:"DbContext and Parallel Query Operations",id:"dbcontext-and-parallel-query-operations",level:2},{value:"Register DbContext as Transient",id:"register-dbcontext-as-transient",level:3},{value:"Execute Controller Actions in Isolation",id:"execute-controller-actions-in-isolation",level:3}],p={toc:c};function u(e){let{components:t,...i}=e;return(0,o.yg)("wrapper",(0,r.A)({},p,i,{components:t,mdxType:"MDXLayout"}),(0,o.yg)("h2",{id:"dbcontext-and-parallel-query-operations"},"DbContext and Parallel Query Operations"),(0,o.yg)("p",null,"In a standard REST application we would register our ",(0,o.yg)("inlineCode",{parentName:"p"},"DbContext")," like so:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Adding Entity Framework at Startup"',title:'"Adding',Entity:!0,Framework:!0,at:!0,'Startup"':!0},'// highlight-next-line\nservices.AddDbContext<AppDbContext>(o =>\n{\n o.UseSqlServer("<connectionString>");\n});\n')),(0,o.yg)("p",null,"This default registration adds the ",(0,o.yg)("inlineCode",{parentName:"p"},"DbContext")," to the DI container is as a ",(0,o.yg)("inlineCode",{parentName:"p"},"Scoped")," service. Meaning one instance is generated per Http request. However, consider the following graph controller and query:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="FoodController.cs"',title:'"FoodController.cs"'},"public class FoodController : GraphController\n{ \n private AppDbContext _context;\n public FoodController(AppDbContext context){/**/}\n\n [QueryRoot]\n // highlight-next-line\n public IFood SearchMeat(string name){/**/}\n\n [QueryRoot]\n // highlight-next-line\n public IFood SearchVeggies(string name){/**/}\n}\n")),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'query {\n # highlight-next-line\n searchMeat(name: "steak*") {\n name\n }\n # highlight-next-line\n searchVeggies(name: "green*") {\n name\n }\n}\n')),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"FoodController")," contains two action methods both of which are requested. Since this is a query and not a mutation, both top-level action methods are executed in parallel. This can result in an exception being thrown:"),(0,o.yg)("p",null,(0,o.yg)("img",{alt:"Ef Core Error",src:n(2634).A,width:"1350",height:"575"})),(0,o.yg)("p",null,"This is caused by graphql attempting to execute both controller actions simultaneously. EF Core will reject multiple active queries. There are a few ways to address this and each comes with its own trade offs:"),(0,o.yg)("h3",{id:"register-dbcontext-as-transient"},"Register DbContext as Transient"),(0,o.yg)("p",null,"One way to correct this problem is to register your DbContext as a transient object."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Option 1: Register DbContext as Transient"',title:'"Option',"1:":!0,Register:!0,DbContext:!0,as:!0,'Transient"':!0},'services.AddDbContext<AppDbContext>(\n options =>\n {\n options.UseSqlServer("<connectionString>");\n },\n // highlight-next-line\n ServiceLifetime.Transient);\n')),(0,o.yg)("p",null,"Now each invocation will get its own DbContext and the queries can execute in parallel without issue. "),(0,o.yg)("p",null,"The tradeoff here is that you lose the singular scoped unit-of-work for the whole request granted by a shared context. "),(0,o.yg)("p",null,"If you have services registered to the DI container that make use of the DbContext you would want to register them as ",(0,o.yg)("inlineCode",{parentName:"p"},"Transient")," as well lest one scoped service be created for the request trapping a single DbContext instance. Sometimes, however; this is unavoidable, especially with legacy code..."),(0,o.yg)("h3",{id:"execute-controller-actions-in-isolation"},"Execute Controller Actions in Isolation"),(0,o.yg)("p",null,"Another option is to instruct graphql to execute its controller actions in sequence, rather than in parallel. "),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Option 2: Isolate GraphQL Controller Actions"',title:'"Option',"2:":!0,Isolate:!0,GraphQL:!0,Controller:!0,'Actions"':!0},"services.AddGraphQL(o =>\n{\n // highlight-next-line\n o.ExecutionOptions.ResolverIsolation = ResolverIsolationOptions.ControllerActions;\n});\n")),(0,o.yg)("p",null,"This will instruct graphql to execute each encountered controller action one after the other. Your scoped ",(0,o.yg)("inlineCode",{parentName:"p"},"DbContext")," would then be able to process the queries without issue."),(0,o.yg)("p",null,"The tradeoff with this method is a slight increase in processing time since the methods are called in sequence. All other field resolutions would be executed in parallel."),(0,o.yg)("p",null,"If your application has other resources or services that may have similar restrictions, it can be beneficial to isolate the other resolver types as well. You can add them to the ResolverIsolation configuration option as needed."))}u.isMDXComponent=!0},2634:(e,t,n)=>{n.d(t,{A:()=>r});const r=n.p+"assets/images/ef-core-error-25cba617d4749a01fdb960bac7d221fb.png"}}]); \ No newline at end of file diff --git a/assets/js/7f4a28f4.9e54798a.js b/assets/js/7f4a28f4.9e54798a.js new file mode 100644 index 0000000..53c661e --- /dev/null +++ b/assets/js/7f4a28f4.9e54798a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[5749],{5680:(e,t,n)=>{n.d(t,{xA:()=>u,yg:()=>y});var a=n(6540);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?i(Object(n),!0).forEach((function(t){r(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):i(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function l(e,t){if(null==e)return{};var n,a,r=function(e,t){if(null==e)return{};var n,a,r={},i=Object.keys(e);for(a=0;a<i.length;a++)n=i[a],t.indexOf(n)>=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a<i.length;a++)n=i[a],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},u=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},h="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),h=p(n),d=r,y=h["".concat(s,".").concat(d)]||h[d]||c[d]||i;return n?a.createElement(y,o(o({ref:t},u),{},{components:n})):a.createElement(y,o({ref:t},u))}));function y(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,o=new Array(i);o[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[h]="string"==typeof e?e:r,o[1]=l;for(var p=2;p<i;p++)o[p]=n[p];return a.createElement.apply(null,o)}return a.createElement.apply(null,n)}d.displayName="MDXCreateElement"},5037:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>h,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var a=n(8168),r=(n(6540),n(5680));const i={id:"field-paths",title:"Field Paths",sidebar_label:"Field Paths",sidebar_position:2,hide_title:!0},o=void 0,l={unversionedId:"controllers/field-paths",id:"controllers/field-paths",title:"Field Paths",description:"What is a Field Path?",source:"@site/docs/controllers/field-paths.md",sourceDirName:"controllers",slug:"/controllers/field-paths",permalink:"/docs/controllers/field-paths",draft:!1,tags:[],version:"current",sidebarPosition:2,frontMatter:{id:"field-paths",title:"Field Paths",sidebar_label:"Field Paths",sidebar_position:2,hide_title:!0},sidebar:"tutorialSidebar",previous:{title:"Model State",permalink:"/docs/controllers/model-state"},next:{title:"Authorization",permalink:"/docs/controllers/authorization"}},s={},p=[{value:"What is a Field Path?",id:"what-is-a-field-path",level:2},{value:"Declaring Field Paths",id:"declaring-field-paths",level:2},{value:"Actions Must Have a Unique Path",id:"actions-must-have-a-unique-path",level:2},{value:"Declare Explicit Names",id:"declare-explicit-names",level:3},{value:"Change The Hierarchy",id:"change-the-hierarchy",level:3},{value:"Combine the Fields",id:"combine-the-fields",level:3},{value:"Field Path Names",id:"field-path-names",level:2},{value:"Field Naming Formats",id:"field-naming-formats",level:3}],u={toc:p};function h(e){let{components:t,...n}=e;return(0,r.yg)("wrapper",(0,a.A)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"what-is-a-field-path"},"What is a Field Path?"),(0,r.yg)("p",null,' GraphQL is statically typed. All possible fields on all possible objects must be pre-defined and well-known in advance. This is what defines the schema of your graph. Along with this, each field must be "resolvable" in a known and consistant manner. If a user requests the ',(0,r.yg)("inlineCode",{parentName:"p"},"name")," field of a donut, graphql must know what steps to take in order to generate a data value for that field. "),(0,r.yg)("p",null," In .NET terms that means each field must be represented by a method or property on some class or struct. Traditionally speaking, this can introduce a lot of overhead in defining intermediate types that do nothing but organize our data. "),(0,r.yg)("p",null,"Let's think about this query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query {\n groceryStore {\n bakery {\n pastries {\n donut(id: 15){\n name\n flavor\n }\n }\n }\n deli {\n meats {\n beef (id: 23) {\n name\n cut\n }\n }\n } \n }\n}\n")),(0,r.yg)("p",null,"Knowing what we know, you may think we need to create classes for the grocery store, the bakery, pastries, a donut, the deli counter, meats, beef etc. in order to create properties and methods for all those fields. Its a lot of setup for what basically boils down to two methods to retrieve a donut and a cut of beef by their respective ids. Some other GraphQL libraries take this approach and it provides an extreme amount of customization at the cost of being rather verbose."),(0,r.yg)("p",null,"GraphQL ASP.NET takes a different appraoch and uses a templating pattern similar to what we do with REST controllers we can create rich graphs with very little boiler plate. Adding a new branch to your graph is as simple as defining a path to it in a controller. "),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Sample Controller"',title:'"Sample','Controller"':!0},'// highlight-next-line\n[GraphRoute("groceryStore")]\npublic class GroceryStoreController : GraphController\n{\n // highlight-next-line\n [Query("bakery/pastries/donut")]\n public Donut RetrieveDonut(int id)\n {/* ...*/}\n\n // highlight-next-line\n [Query("deli/meats/beef")]\n public Meat RetrieveCutOfBeef(int id)\n {/* ...*/}\n}\n')),(0,r.yg)("p",null,"Internally, for each encountered path segment (e.g. ",(0,r.yg)("inlineCode",{parentName:"p"},"bakery"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"meats"),"), GraphQL generates a virutal, intermediate graph type to fulfill resolver requests for you and acts as a pass through to your real code. It does this in concert with your real code and performs a lot of checks at start up to ensure that the combination of your real types as well as virutal types can be put together to form a functional graph. If a collision occurs the server will fail to start."),(0,r.yg)("admonition",{title:"Intermediate Type Names",type:"info"},(0,r.yg)("p",{parentName:"admonition"},"You may notice some object types in your schema named as ",(0,r.yg)("inlineCode",{parentName:"p"},"Query_Bakery"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"Mutation_Deli"),". These are the virtual types generated at runtime to create a valid schema from your path segments.")),(0,r.yg)("h2",{id:"declaring-field-paths"},"Declaring Field Paths"),(0,r.yg)("p",null,"Declaring fields works just like it does with a REST query. You can nest fields as deep as you want and spread them across any number of controllers in order to create a rich organizational hierarchy to your data. This is best explained by code, take a look at these two controllers:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs"',title:'"BakeryController.cs"'},'// highlight-next-line\n[GraphRoute("groceryStore/bakery")]\npublic class BakeryController : GraphController\n{\n // highlight-next-line\n [Query("pastries/search")]\n public IEnumerable<IPastry> SearchPastries(string nameLike)\n {/* ... */}\n\n // highlight-next-line\n [Query("pastries/recipe")]\n public Task<Recipe> RetrieveRecipe(int id)\n {/* ... */}\n\n // highlight-next-line\n [Query("breadCounter/orders")]\n public IEnumerable<BreadOrder> FindOrders(int customerId)\n {/* ... */}\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="PharmacyController.cs"',title:'"PharmacyController.cs"'},'// highlight-next-line\n[GraphRoute("groceryStore/pharmacy")]\npublic class PharmacyController : GraphController\n{\n // highlight-next-line\n [Query("employees/search")]\n public IPastry SearchEmployees(string nameLike)\n {/* ... */}\n\n // highlight-next-line\n [QueryRoot("pharmacyHours")]\n public HoursOfOperation RetrievePharmacyHours(DayOfTheWeek day)\n {/* ... */}\n\n // highlight-next-line\n [Query("orders")]\n public IEnumerable<Prescription> FindOrders(int customerId)\n {/* ... */}\n}\n')),(0,r.yg)("p",null,"And this single query we can perform:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'query SearchGroceryStore {\n groceryStore {\n bakery {\n pastries {\n search(nameLike: "chocolate"){\n name\n type\n }\n recipe(id: 15) {\n name\n ingredients {\n name\n }\n }\n }\n }\n pharmacy {\n orders(customerId: 45123){\n dayOrdered\n type\n doctorsName\n }\n }\n }\n pharmacyHours(day: MONDAY){\n openAt\n closeAt\n }\n}\n')),(0,r.yg)("p",null,"With REST, this is probably 4 separate requests or one super contrived request but with GraphQL and a carefully thought out set of field paths we can model our data hierarchy quickly and without over complicating the code. There is no more code in this example than would be required by a REST API; we've just changed how its interpreted at runtime."),(0,r.yg)("h2",{id:"actions-must-have-a-unique-path"},"Actions Must Have a Unique Path"),(0,r.yg)("p",null,"Each field of each type in your schema must uniquely map to one method or property getter; commonly referred to as its resolver. We can't declare a field twice."),(0,r.yg)("p",null,"Take this example:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Overloaded Methods"',title:'"Overloaded','Methods"':!0},"[GraphRoute(\"bakery\")]\npublic class BakeryController : GraphController\n{\n // Both Methods represent the same 'orderDonuts' field on the graph\n\n [Mutation]\n // highlight-next-line\n public BoxOfDonuts OrderDonuts(int quantity){/*...*/}\n\n [Mutation]\n // highlight-next-line\n public BoxOfDonuts OrderDonuts(string type, int quantity){/*...*/}\n}\n")),(0,r.yg)("p",null,"From a GraphQL perspective this equivilant to trying to define a ",(0,r.yg)("inlineCode",{parentName:"p"},"Bakery")," type with two fields named ",(0,r.yg)("inlineCode",{parentName:"p"},"orderDonuts"),". Since both methods map to a field path this would cause a ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphTypeDeclarationException")," to be thrown when your application starts. "),(0,r.yg)("p",null,"With Web API, the ASP.NET runtime could inspect any combinations of parameters passed on the query string or the POST body to work out which overload to call. You might be thinking, why can't GraphQL inspect the passed input arguments and make the same determination?"),(0,r.yg)("p",null,"Putting aside that it ",(0,r.yg)("a",{parentName:"p",href:"http://spec.graphql.org/October2021/#sec-Objects"},"violates the specification"),", in some cases it probably could. But looking at this example we run into an issue:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp"},"[GraphRoute(\"bakery\")]\npublic class BakeryController : GraphController\n{\n // Both Methods represent the same 'orderDonuts' field on the object graph\n [Mutation]\n public Manager OrderDonuts(int quantity, string type){/*...*/}\n\n [Mutation]\n public Manager OrderDonuts(string type, int quantity){/*...*/}\n}\n")),(0,r.yg)("p",null,"GraphQL states that input arguments can be passed in any order [Spec \xa7 ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.github.io/graphql-spec/October2021/#sec-Language.Arguments"},"2.6"),"]. By definition, there is not enough information in the query syntax language to decide which overload to invoke. To combat the issue, the runtime will reject any field that it can't uniquely identify."),(0,r.yg)("p",null,"No problem through, there are a number of ways fix the conflict."),(0,r.yg)("h3",{id:"declare-explicit-names"},"Declare Explicit Names"),(0,r.yg)("p",null,"You can declare explicit names for each of your methods. Not only does this resolve the method overloading conflict but should an errant refactor of your code occur, your graph fields won't magically be renamed to their new method names and break your front-end."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Use Explicit Field Names"',title:'"Use',Explicit:!0,Field:!0,'Names"':!0},'[GraphRoute("bakery")]\npublic class BakeryController : GraphController\n{\n // GraphQL treats these fields differently!\n \n // highlight-next-line\n [Mutation("orderDonutsByQuantity")]\n public Manager OrderDonuts(int quantity){/*...*/}\n\n // highlight-next-line\n [Mutation("orderDonutsByType")]\n public Manager OrderDonuts(string type, int quantity){/*...*/}\n}\n')),(0,r.yg)("p",null,"But this can feel a bit awkward in some situations so instead..."),(0,r.yg)("h3",{id:"change-the-hierarchy"},"Change The Hierarchy"),(0,r.yg)("p",null,"Another alternative is to change where in the object graph the field sits. Here we've moved one field to the root mutation type and left the other under the controller's own virtual ",(0,r.yg)("inlineCode",{parentName:"p"},"Bakery")," type. This can be a good strategy if you have one primary way of interacting with your data and a few auxillary methods such as a quick dozen donuts at the drive thru window or going into the shop and selecting which ones you want."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Change the Field Path"',title:'"Change',the:!0,Field:!0,'Path"':!0},'[GraphRoute("bakery")]\npublic class BakeryController : GraphController\n{ \n // highlight-next-line\n [MutationRoot("orderDonuts")]\n public IEnumerable<Donut> OrderDonuts(int count)\n {/*...*/}\n\n // highlight-next-line\n [Mutation("orderDonuts")]\n public IEnumerable<Donut> OrderDonuts(\n string type,\n int count)\n {/*...*/}\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Queries"',title:'"Sample','Queries"':!0},'mutation {\n orderDonuts(count: 12) {\n name\n flavor\n }\n}\n\nmutation {\n bakery {\n orderDonuts(type: "Chocolate" count: 3) {\n name\n flavor\n }\n }\n}\n')),(0,r.yg)("h3",{id:"combine-the-fields"},"Combine the Fields"),(0,r.yg)("p",null,"Lastly, we can make use of input objects with optional fields and combine parameters into a more robust method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Use an Input Object"',title:'"Use',an:!0,Input:!0,'Object"':!0},'[GraphRoute("bakery")]\npublic class BakeryController : GraphController\n{\n [Mutation("orderDonuts")] \n // highlight-next-line\n public IEnumerable<Donut> OrderDonuts(DonutOrderModel order)\n {/*...*/}\n}\n\npublic class DonutOrderModel\n{\n public int? Quantity { get; set; }\n public string Type { get; set; }\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Queries"',title:'"Sample','Queries"':!0},'mutation byQuantity {\n bakery{\n orderDonuts (order: {quantity: 12}){\n id\n type\n }\n }\n}\n\nmutation byType {\n bakery{\n orderDonuts (order: {type: "Chocolate" quantity: 12}){\n id\n type\n }\n }\n}\n')),(0,r.yg)("p",null,"When you start thinking about large object graphs, 100s of controllers and 100s of types, you have to put some thought in to how you organize your data. Coming up with an intuitive structure to your hierarchy is going to be dependent on your audience and use cases. There is no one-size fits all approach, but with the ability to move graph fields by updating one string, its trivial to build as you iterate."),(0,r.yg)("h2",{id:"field-path-names"},"Field Path Names"),(0,r.yg)("admonition",{type:"info"},(0,r.yg)("p",{parentName:"admonition"}," Each segment of a virtual field path must individually conform to the required naming standards for fields and graph type names.")),(0,r.yg)("p",null,"In reality this primarily means don't start your fields with a double underscore, ",(0,r.yg)("inlineCode",{parentName:"p"},"__"),", as thats reserved by the introspection system. The complete regex is available in the source code at ",(0,r.yg)("inlineCode",{parentName:"p"},"Constants.RegExPatterns.NameRegex"),"."),(0,r.yg)("p",null,"These are some valid field paths:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Valid Field Fragments"',title:'"Valid',Field:!0,'Fragments"':!0},'[Mutation("store/bakery/deliCounter/sandwiches/order")]\n[Query("path1/path2/path3/path4/")]\n[Mutation("path1/path1/path1/path1/path1/path1/path1/path1/path1")]\n')),(0,r.yg)("p",null,"But if even one segment of the path is invalid GraphQL will reject it completely."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Invalid Field Fragments"',title:'"Invalid',Field:!0,'Fragments"':!0},'[Query("store/__bakery")] // can\'t start with "__"\n[Query("store/\u03b2akery")] // unicode characters are not allowed\n[Query("path1/path2/path 33")] // spaces are not allowed\n')),(0,r.yg)("h3",{id:"field-naming-formats"},"Field Naming Formats"),(0,r.yg)("p",null,"At runtime, when your schema is generated, the naming requirements it defines for fields will be enforced for each path segment individually. By default, this means ",(0,r.yg)("inlineCode",{parentName:"p"},"camelCasing"),":"),(0,r.yg)("p",null,"If you declare:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp"},'[Mutation("Store/Bakery/DeliCounter")]\n')),(0,r.yg)("p",null,"You would still query with :"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-javascript"},"mutation {\n store {\n bakery {\n deliCounter {\n ...\n\n }\n }\n}\n")),(0,r.yg)("p",null,"You can alter the naming formats for fields, enum values and graph types using the declaration options on your ",(0,r.yg)("a",{parentName:"p",href:"../reference/schema-configuration#graphnamingformatter"},"schema configuration"),"."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/878c0d65.6d2cf3cd.js b/assets/js/878c0d65.6d2cf3cd.js new file mode 100644 index 0000000..d7dbcd8 --- /dev/null +++ b/assets/js/878c0d65.6d2cf3cd.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[8700],{5680:(e,t,a)=>{a.d(t,{xA:()=>d,yg:()=>h});var n=a(6540);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t<arguments.length;t++){var a=null!=arguments[t]?arguments[t]:{};t%2?i(Object(a),!0).forEach((function(t){r(e,t,a[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(a)):i(Object(a)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(a,t))}))}return e}function l(e,t){if(null==e)return{};var a,n,r=function(e,t){if(null==e)return{};var a,n,r={},i=Object.keys(e);for(n=0;n<i.length;n++)a=i[n],t.indexOf(a)>=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n<i.length;n++)a=i[n],t.indexOf(a)>=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var s=n.createContext({}),p=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},d=function(e){var t=p(e.components);return n.createElement(s.Provider,{value:t},e.children)},y="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},c=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),y=p(a),c=r,h=y["".concat(s,".").concat(c)]||y[c]||u[c]||i;return a?n.createElement(h,o(o({ref:t},d),{},{components:a})):n.createElement(h,o({ref:t},d))}));function h(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=a.length,o=new Array(i);o[0]=c;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[y]="string"==typeof e?e:r,o[1]=l;for(var p=2;p<i;p++)o[p]=a[p];return n.createElement.apply(null,o)}return n.createElement.apply(null,a)}c.displayName="MDXCreateElement"},1577:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>y,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var n=a(8168),r=(a(6540),a(5680));const i={id:"type-extensions",title:"Type Extensions",sidebar_label:"Type Extensions",sidebar_position:4},o=void 0,l={unversionedId:"controllers/type-extensions",id:"controllers/type-extensions",title:"Type Extensions",description:"Working with Child Data",source:"@site/docs/controllers/type-extensions.md",sourceDirName:"controllers",slug:"/controllers/type-extensions",permalink:"/docs/controllers/type-extensions",draft:!1,tags:[],version:"current",sidebarPosition:4,frontMatter:{id:"type-extensions",title:"Type Extensions",sidebar_label:"Type Extensions",sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"Authorization",permalink:"/docs/controllers/authorization"},next:{title:"Batch Operations",permalink:"/docs/controllers/batch-operations"}},s={},p=[{value:"Working with Child Data",id:"working-with-child-data",level:2},{value:"The TypeExtension Attribute",id:"the-typeextension-attribute",level:2},{value:"\u2753 But what about the Bakery parameter?",id:"-but-what-about-the-bakery-parameter",level:4},{value:"Can Every Field be a Type Extension?",id:"can-every-field-be-a-type-extension",level:2}],d={toc:p};function y(e){let{components:t,...a}=e;return(0,r.yg)("wrapper",(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"working-with-child-data"},"Working with Child Data"),(0,r.yg)("p",null,(0,r.yg)("em",{parentName:"p"},"The Motiviation for using Type Extensions")),(0,r.yg)("p",null,"Before we dive into type extensions we have to talk about parent-child relationships. So far, the examples we've seen have used well defined fields in an object graph. Be that an action method on a controller or a property on an object. But when we think about real world data, there are scenarios where that poses a problem. Lets suppose for a moment we have a chain of bakery stores that let customers place orders for cakes at an individual store and customize the writing on the cake."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Sample Bakery Model"',title:'"Sample',Bakery:!0,'Model"':!0},"public class Bakery\n{\n public int Id { get; set; }\n // highlight-next-line\n public List<CakeOrder> Orders { get; set; }\n}\n\npublic class CakeOrder\n{\n public Customer Customer { get; set; }\n public string WrittenPhrase { get; set; }\n // highlight-next-line\n public Bakery Bakery { get; set; }\n}\n\n// ...Customer class excluded for brevity\n")),(0,r.yg)("p",null,"But consider the following scenarios:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"What happens when we retrieve a single ",(0,r.yg)("inlineCode",{parentName:"li"},"CakeOrder")," via a controller?"),(0,r.yg)("li",{parentName:"ul"},"Do we automatically have to populate the entire ",(0,r.yg)("inlineCode",{parentName:"li"},"Bakery")," and ",(0,r.yg)("inlineCode",{parentName:"li"},"Customer")," objects?",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},"Even if a caller didn't request any of that data?"))),(0,r.yg)("li",{parentName:"ul"},"What happens when retrieving a bakery that may have 1000s of cake orders?")),(0,r.yg)("p",null,"Our application is going to slow to a crawl very quickly doing all this extra data loading. In the case of a single Bakery, a timeout may occur trying to fetch many years of cake orders to populate the bakery instance from a database query only to discard them when a graphql query doesn't ask for it. If we're using something like Entity Framework how do we know when to use an Include statement to populate the child data? (Hint: you don't)"),(0,r.yg)("p",null,"One solution could be to use lazy loading on our model."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Lazy Loading Child Data (Terrible!)"',title:'"Lazy',Loading:!0,Child:!0,Data:!0,'(Terrible!)"':!0},"public class Bakery\n{\n\n private ICakeService _service;\n private Lazy<List<CakeOrder>> _orders;\n\n public Bakery(int id, ICakeService service)\n {\n this.Id = id;\n _service = service;\n _orders = new Lazy<List<CakeOrder>>(this.RetrieveCakeOrders);\n }\n\n private List<CakeOrder> RetrieveCakeOrders()\n {\n return _service.RetrieveCakeOrders(this.Id);\n }\n\n public int Id { get; }\n public List<CakeOrder> Orders => _orders.Value;\n}\n")),(0,r.yg)("p",null,"Well that's just plain awful. We've over complicated our bakery model and made it dependent on a service instance to exist. If this was a real world example, you'd need some sort of error handling in there too."),(0,r.yg)("h2",{id:"the-typeextension-attribute"},"The ","[TypeExtension]"," Attribute"),(0,r.yg)("p",null,"We've talked before about GraphQL maintaining a 1:1 mapping between a field in the graph and a method to retrieve data for it (i.e. its assigned resolver). What prevents us from creating a method to fetch a list of Cake Orders and saying, \"Hey, GraphQL! When someone asks for a set of bakery orders call a custom method instead of a property getter on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Bakery"),' class." As it turns out, that is exactly what a ',(0,r.yg)("inlineCode",{parentName:"p"},"Type Extension")," does."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using a Type Extension"',title:'"Using',a:!0,Type:!0,'Extension"':!0},'public class Bakery\n{\n public int Id { get; set; }\n public string Name { get; set; }\n}\n\npublic class BakedGoodsCompanyController : GraphController\n{\n [QueryRoot("bakery")]\n public Bakery RetrieveBakery(int id){/*...*/}\n\n // declare a extension to the Bakery object\n // highlight-next-line\n [TypeExtension(typeof(Bakery), "orders")]\n public async Task<List<CakeOrder>> RetrieveCakeOrders(Bakery bakery, int limitTo = 15)\n {\n return await _service.RetrieveCakeOrders(bakery.Id, limitTo);\n }\n}\n')),(0,r.yg)("p",null,"Much Cleaner!!"),(0,r.yg)("p",null,"There is a lot to unpack here, so lets step through it:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"We've declared the ",(0,r.yg)("inlineCode",{parentName:"li"},"RetrieveBakery")," method as a root field named ",(0,r.yg)("inlineCode",{parentName:"li"},"bakery")," that allows us to fetch a single bakery."),(0,r.yg)("li",{parentName:"ul"},"We've added a method named ",(0,r.yg)("inlineCode",{parentName:"li"},"RetrieveCakeOrders"),", declared it as an ",(0,r.yg)("em",{parentName:"li"},"extension")," to the ",(0,r.yg)("inlineCode",{parentName:"li"},"Bakery")," object and gave it a field name of ",(0,r.yg)("inlineCode",{parentName:"li"},"orders"),"."),(0,r.yg)("li",{parentName:"ul"},"The extension returns ",(0,r.yg)("inlineCode",{parentName:"li"},"List<CakeOrder>")," as the type of data it generates."),(0,r.yg)("li",{parentName:"ul"},"The method takes in a ",(0,r.yg)("inlineCode",{parentName:"li"},"Bakery")," instance (more on that in a second) as well as an integer, with a default value of ",(0,r.yg)("inlineCode",{parentName:"li"},"15"),", to limit the number of orders to retrieve.")),(0,r.yg)("p",null,"Now we can query the ",(0,r.yg)("inlineCode",{parentName:"p"},"orders")," field from anywhere a bakery is returned in the object graph and GraphQL will invoke our method:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query {\n bakery(id: 5){\n name\n orders(limitTo: 50) {\n id\n writtenPhrase\n }\n }\n}\n")),(0,r.yg)("admonition",{type:"tip"},(0,r.yg)("p",{parentName:"admonition"},"Type Extensions allow you to attach new fields to a graph type without altering the original ",(0,r.yg)("inlineCode",{parentName:"p"},"System.Type"),".")),(0,r.yg)("h4",{id:"-but-what-about-the-bakery-parameter"},"\u2753 But what about the Bakery parameter?"),(0,r.yg)("p",null,"When we return a value from a property, an instance of an object must exist in order to supply that value. That is to say if you want the ",(0,r.yg)("inlineCode",{parentName:"p"},"Name")," property of a bakery, you need a bakery instance to retrieve it from. The same is true for a ",(0,r.yg)("inlineCode",{parentName:"p"},"type extension")," except that instead of calling a property getter on the instance, graphql hands the entire object to your method and lets you figure out what needs to happen to resolve the field."),(0,r.yg)("p",null,"GraphQL inspects the type being extended and finds a parameter on the method to match it. It captures that parameter, hides it from the object graph, and fills it with the result of the parent field, in this case the resolution of field ",(0,r.yg)("inlineCode",{parentName:"p"},"bakery(id: 5)"),"."),(0,r.yg)("p",null,"This is immensely scalable:"),(0,r.yg)("p",null,"\u2705 There are no wasted cycles fetching ",(0,r.yg)("inlineCode",{parentName:"p"},"CakeOrders")," unless the requestor specifically asks for them.",(0,r.yg)("br",null),"\n\u2705 We have full access to ",(0,r.yg)("a",{parentName:"p",href:"../advanced/type-expressions"},"type expression validation")," and ",(0,r.yg)("a",{parentName:"p",href:"./model-state"},"model validation")," for our other method parameters.",(0,r.yg)("br",null),"\n\u2705Since its a controller action we have full access to graph action results and can return ",(0,r.yg)("inlineCode",{parentName:"p"},"this.Ok()"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"this.Error()")," etc. to give a rich experience.",(0,r.yg)("br",null),"\n\u2705",(0,r.yg)("a",{parentName:"p",href:"./authorization"},"Field Security")," and use of the ",(0,r.yg)("inlineCode",{parentName:"p"},"[Authorize]")," attribute is also wired up for us. ",(0,r.yg)("br",null),"\n\u2705The bakery model is greatly simplified."),(0,r.yg)("h2",{id:"can-every-field-be-a-type-extension"},"Can Every Field be a Type Extension?"),(0,r.yg)("p",null,"Theoretically, yes. But take a moment and think about performance. For basic objects with few dozen properties which is faster:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("em",{parentName:"li"},"Option 1:")," One database query to retrieve 24 columns of a single record then only use six in a data result"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("em",{parentName:"li"},"Option 2:")," Six separate database queries, one for each column requested.")),(0,r.yg)("p",null,"Type extensions shine in parent-child relationships when preloading lots of data is a concern. But be careful not to isolate every graph field just to avoid retrieving extra data at all. Fetching a few extra bytes from a database is negligible compared to querying a database 20 individual times. Your REST APIs were already querying extra data and they were likely transmitting that data to the client."),(0,r.yg)("p",null,"It comes down to your use case. There are times when it makes sense to seperate things using type extensions and times when preloading whole objects is better. For many applications, once you've deployed to production, the queries being executed are finite. Design your model objects and extensions to be performant in the ways your data is being requested, not in the ways it ",(0,r.yg)("em",{parentName:"p"},"could be")," requested."))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.d4075e24.js b/assets/js/935f2afb.d4075e24.js new file mode 100644 index 0000000..81c7897 --- /dev/null +++ b/assets/js/935f2afb.d4075e24.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[8581],{5610:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"tutorialSidebar":[{"type":"category","label":"Getting Started","collapsible":false,"collapsed":false,"items":[{"type":"link","label":"Overview","href":"/docs/quick/overview","docId":"quick/overview"},{"type":"link","label":"Your First App","href":"/docs/quick/create-app","docId":"quick/create-app"},{"type":"link","label":"Code Examples","href":"/docs/quick/code-examples","docId":"quick/code-examples"}]},{"type":"category","label":"Introduction","collapsible":false,"collapsed":false,"items":[{"type":"link","label":"What is GraphQL?","href":"/docs/introduction/what-is-graphql","docId":"introduction/what-is-graphql"},{"type":"link","label":"Made for ASP.NET Developers","href":"/docs/introduction/made-for-aspnet-developers","docId":"introduction/made-for-aspnet-developers"}]},{"type":"category","label":"Controllers & Actions","collapsible":false,"collapsed":false,"items":[{"type":"link","label":"Actions","href":"/docs/controllers/actions","docId":"controllers/actions"},{"type":"link","label":"Model State","href":"/docs/controllers/model-state","docId":"controllers/model-state"},{"type":"link","label":"Field Paths","href":"/docs/controllers/field-paths","docId":"controllers/field-paths"},{"type":"link","label":"Authorization","href":"/docs/controllers/authorization","docId":"controllers/authorization"},{"type":"link","label":"Type Extensions","href":"/docs/controllers/type-extensions","docId":"controllers/type-extensions"},{"type":"link","label":"Batch Operations","href":"/docs/controllers/batch-operations","docId":"controllers/batch-operations"}]},{"type":"category","label":"Graph Type Definitions","collapsible":false,"collapsed":false,"items":[{"type":"link","label":"Objects","href":"/docs/types/objects","docId":"types/objects"},{"type":"link","label":"Input Objects","href":"/docs/types/input-objects","docId":"types/input-objects"},{"type":"link","label":"Interfaces","href":"/docs/types/interfaces","docId":"types/interfaces"},{"type":"link","label":"Unions","href":"/docs/types/unions","docId":"types/unions"},{"type":"link","label":"Enums","href":"/docs/types/enums","docId":"types/enums"},{"type":"link","label":"Scalars","href":"/docs/types/scalars","docId":"types/scalars"},{"type":"link","label":"List & Non-Null","href":"/docs/types/list-non-null","docId":"types/list-non-null"}]},{"type":"category","label":"Advanced","collapsible":false,"collapsed":false,"items":[{"type":"link","label":"Subscriptions","href":"/docs/advanced/subscriptions","docId":"advanced/subscriptions"},{"type":"link","label":"Type Expressions","href":"/docs/advanced/type-expressions","docId":"advanced/type-expressions"},{"type":"link","label":"Directives","href":"/docs/advanced/directives","docId":"advanced/directives"},{"type":"link","label":"Custom Scalars","href":"/docs/advanced/custom-scalars","docId":"advanced/custom-scalars"},{"type":"link","label":"Action Results","href":"/docs/advanced/graph-action-results","docId":"advanced/graph-action-results"},{"type":"link","label":"Multi-Schema Support","href":"/docs/advanced/multi-schema-support","docId":"advanced/multi-schema-support"}]},{"type":"category","label":"Logging","collapsible":false,"collapsed":false,"items":[{"type":"link","label":"Structured Logging","href":"/docs/logging/structured-logging","docId":"logging/structured-logging"},{"type":"link","label":"Standard Events","href":"/docs/logging/standard-events","docId":"logging/standard-events"},{"type":"link","label":"Subscription Events","href":"/docs/logging/subscription-events","docId":"logging/subscription-events"}]},{"type":"category","label":"Query Execution","collapsible":false,"collapsed":false,"items":[{"type":"link","label":"Query Profiling","href":"/docs/execution/metrics","docId":"execution/metrics"},{"type":"link","label":"Malicious Queries","href":"/docs/execution/malicious-queries","docId":"execution/malicious-queries"}]},{"type":"category","label":"Development Concerns","collapsible":false,"collapsed":false,"items":[{"type":"link","label":"Debugging","href":"/docs/development/debugging","docId":"development/debugging"},{"type":"link","label":"Unit Testing","href":"/docs/development/unit-testing","docId":"development/unit-testing"},{"type":"link","label":"Entity Framework","href":"/docs/development/entity-framework","docId":"development/entity-framework"}]},{"type":"category","label":"Extensions","collapsible":false,"collapsed":false,"items":[{"type":"link","label":"File Uploads & Batching","href":"/docs/server-extensions/multipart-requests","docId":"server-extensions/multipart-requests"}]},{"type":"category","label":"References","collapsible":false,"collapsed":false,"items":[{"type":"link","label":"How it Works","href":"/docs/reference/how-it-works","docId":"reference/how-it-works"},{"type":"link","label":"Schema Configuration","href":"/docs/reference/schema-configuration","docId":"reference/schema-configuration"},{"type":"link","label":"Global Configuration","href":"/docs/reference/global-configuration","docId":"reference/global-configuration"},{"type":"link","label":"Attributes","href":"/docs/reference/attributes","docId":"reference/attributes"},{"type":"link","label":"GraphController","href":"/docs/reference/graph-controller","docId":"reference/graph-controller"},{"type":"link","label":"GraphDirective","href":"/docs/reference/graph-directive","docId":"reference/graph-directive"},{"type":"link","label":"HTTP Processor","href":"/docs/reference/http-processor","docId":"reference/http-processor"},{"type":"link","label":"Pipelines & Middleware","href":"/docs/reference/middleware","docId":"reference/middleware"},{"type":"link","label":"Query Caching","href":"/docs/reference/query-cache","docId":"reference/query-cache"},{"type":"link","label":"Demo Projects","href":"/docs/reference/demo-projects","docId":"reference/demo-projects"},{"type":"link","label":"Benchmarks","href":"/docs/reference/performance","docId":"reference/performance"},{"type":"link","label":"Vocabulary","href":"/docs/reference/vocabulary","docId":"reference/vocabulary"}]}]},"docs":{"advanced/custom-scalars":{"id":"advanced/custom-scalars","title":"Custom Scalars","description":"Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being enums).","sidebar":"tutorialSidebar"},"advanced/directives":{"id":"advanced/directives","title":"Directives","description":"What is a Directive?","sidebar":"tutorialSidebar"},"advanced/graph-action-results":{"id":"advanced/graph-action-results","title":"Action Results","description":"What is an Action Result?","sidebar":"tutorialSidebar"},"advanced/multi-schema-support":{"id":"advanced/multi-schema-support","title":"Multi-Schema Support","description":"GraphQL ASP.NET supports multiple schemas on the same server out of the box. Each schema is recognized by its concrete .NET type.","sidebar":"tutorialSidebar"},"advanced/subscriptions":{"id":"advanced/subscriptions","title":"Subscriptions","description":"Initial Setup","sidebar":"tutorialSidebar"},"advanced/type-expressions":{"id":"advanced/type-expressions","title":"Type Expressions","description":"The GraphQL specification states that when a field resolves a value that doesn\'t conform to the expected type expression of the field that the value is rejected, converted to null and an error added to the response.","sidebar":"tutorialSidebar"},"controllers/actions":{"id":"controllers/actions","title":"Controllers & Actions","description":"What is an Action?","sidebar":"tutorialSidebar"},"controllers/authorization":{"id":"controllers/authorization","title":"Authorization","description":"Quick Examples","sidebar":"tutorialSidebar"},"controllers/batch-operations":{"id":"controllers/batch-operations","title":"Batch Operations","description":"Read the section on type extensions before reading this document. Batch Operations expand on type extensions and understanding how they work is critical.","sidebar":"tutorialSidebar"},"controllers/field-paths":{"id":"controllers/field-paths","title":"Field Paths","description":"What is a Field Path?","sidebar":"tutorialSidebar"},"controllers/model-state":{"id":"controllers/model-state","title":"Model State","description":"What is Model State?","sidebar":"tutorialSidebar"},"controllers/type-extensions":{"id":"controllers/type-extensions","title":"Type Extensions","description":"Working with Child Data","sidebar":"tutorialSidebar"},"development/debugging":{"id":"development/debugging","title":"Debugging Your Schema","description":"Disable Field Asynchronousity","sidebar":"tutorialSidebar"},"development/entity-framework":{"id":"development/entity-framework","title":"Using Entity Framework","description":"DbContext and Parallel Query Operations","sidebar":"tutorialSidebar"},"development/unit-testing":{"id":"development/unit-testing","title":"Unit Testing","description":".NET 8+","sidebar":"tutorialSidebar"},"execution/malicious-queries":{"id":"execution/malicious-queries","title":"Dealing with Malicious Queries","description":"When GraphQL ASP.NET parses a query it creates two values that attempt to describe the query in terms of impact and server load; Max Depth and Estimated Complexity. There also exists limiters to these values that can be set in the schema configuration such that should any query plan exceed the limits you set, the plan will be rejected and the query not fulfilled.","sidebar":"tutorialSidebar"},"execution/metrics":{"id":"execution/metrics","title":"Profiling Your Queries","description":"GraphQL ASP.NET tracks query metrics through the IQueryExecutionMetrics interface attached to each query execution context as its processed by the runtime and allows for tracing and timing of individual fields as they are started and completed.","sidebar":"tutorialSidebar"},"introduction/made-for-aspnet-developers":{"id":"introduction/made-for-aspnet-developers","title":"Made for ASP.NET Developers","description":"This library is designed by people who use ASP.NET in their day to day activities and is built for similar minded developers. When you first started digging in to GraphQL you most likely came across the plethora of articles, documents, tutorials and groups centered around JavaScript. JavaScript certainly has the highest adoption rate and with the tools provided by Apollo its no surprise. Its amazing how well those tools fit in with the existing knowledge and coding paradigms of JavaScript developers on both sides of the fence (be that front end or back end).","sidebar":"tutorialSidebar"},"introduction/what-is-graphql":{"id":"introduction/what-is-graphql","title":"What is GraphQL?","description":"GraphQL is a query language specification originally created by Meta for their own internal use. It was eventually open-sourced and moved to its own foundation, the GraphQL Foundation, and hosted by the Linux Foundation. The specification provides an alternative to traditional REST queries that we all know and love in giving the requestor more control over what data to return.","sidebar":"tutorialSidebar"},"logging/standard-events":{"id":"logging/standard-events","title":"Standard Logging Events","description":"GraphQL ASP.NET tracks many standard events. Most of these are recorded during the execution of a query. Some, such as those around field resolution, can be recorded many times in the course of a single request.","sidebar":"tutorialSidebar"},"logging/structured-logging":{"id":"logging/structured-logging","title":"Structured Logging","description":"GraphQL ASP.NET utilizes structured logging for reporting runtime events. The log messages generated aren\'t just strings but actual objects. All internal log events are raised as objects that inherit from IGraphLogEntry.","sidebar":"tutorialSidebar"},"logging/subscription-events":{"id":"logging/subscription-events","title":"Subscription Logging Events","description":"GraphQL ASP.NET tracks some special events related to the management of subscriptions. They are outlined below.","sidebar":"tutorialSidebar"},"quick/code-examples":{"id":"quick/code-examples","title":"Code Examples","description":"Below is a quick introduction to some common scenarios and the C# code to support them.","sidebar":"tutorialSidebar"},"quick/create-app":{"id":"quick/create-app","title":"Building Your First App","description":"Step by Step instructions for creating a sample application","sidebar":"tutorialSidebar"},"quick/overview":{"id":"quick/overview","title":"Overview","description":"A quick overview of how to use the library","sidebar":"tutorialSidebar"},"reference/attributes":{"id":"reference/attributes","title":"Attributes","description":"This document contains an alphabetical reference of each of the class, property and method attributes used by GraphQL ASP.NET.","sidebar":"tutorialSidebar"},"reference/demo-projects":{"id":"reference/demo-projects","title":"Demo Projects","description":"General","sidebar":"tutorialSidebar"},"reference/global-configuration":{"id":"reference/global-configuration","title":"Global Configuration","description":"Global configuration settings affect the entire server instance, they are not restricted to a single schema registration. All global settings are optional and define resonable default values. Use these to fine tune your server environment. You should change any global settings BEFORE calling .AddGraphQL().","sidebar":"tutorialSidebar"},"reference/graph-controller":{"id":"reference/graph-controller","title":"Graph Controller","description":"\u2705 See the section on Controllers & Actions for a detailed explination on how action methods work and how to declare them.","sidebar":"tutorialSidebar"},"reference/graph-directive":{"id":"reference/graph-directive","title":"Graph Directive","description":"\u2705 See the section on Directives for a detailed explination on how directive action methods work and how to declare them.","sidebar":"tutorialSidebar"},"reference/how-it-works":{"id":"reference/how-it-works","title":"How it Works","description":"This document is a high level overview how GraphQL ASP.NET ultimately generates a response to a query with some insight into core details. Its assumes a working knowledge of both ASP.NET and the GraphQL specification. If you are only interested in the \\"how\\" and not the \\"why\\", feel free to skip this.","sidebar":"tutorialSidebar"},"reference/http-processor":{"id":"reference/http-processor","title":"HTTP Processor","description":"The DefaultGraphQLHttpProcessor is mapped to a route for the target schema and accepts an HttpContext from the ASP.NET runtime. It inspects the received payload (the query text and variables) then packages an IQueryExecutionRequest and sends it to the GraphQL runtime. Once a result is generated the controller forwards that response to the response writer for serialization.","sidebar":"tutorialSidebar"},"reference/middleware":{"id":"reference/middleware","title":"Pipelines and Custom Middleware","description":"At the heart of GraphQL ASP.NET are 4 middleware pipelines; chains of components executed in a specific order to produce a result.","sidebar":"tutorialSidebar"},"reference/performance":{"id":"reference/performance","title":"Benchmarks & Performance","description":"Query Benchmarking","sidebar":"tutorialSidebar"},"reference/query-cache":{"id":"reference/query-cache","title":"Query Caching","description":"When GraphQL ASP.NET parses a query, it generates a query plan that contains all the required data needed to execute the requested operation. For most queries this process is near instantaneous but in some particularly large queries it may take an extra moment to generate a full query plan. The query cache will help alleviate this bottleneck by caching a plan for a set period of time to skip the parsing and generation phases when completing a request.","sidebar":"tutorialSidebar"},"reference/schema-configuration":{"id":"reference/schema-configuration","title":"Schema Configuration","description":"This document contains a list of various configuration settings available during schema configuration. All options are added as part of the .AddGraphQL() method used at startup.","sidebar":"tutorialSidebar"},"reference/vocabulary":{"id":"reference/vocabulary","title":"Vocabulary","description":"Fields & Resolvers","sidebar":"tutorialSidebar"},"server-extensions/multipart-requests":{"id":"server-extensions/multipart-requests","title":"Multipart Form Request Extension","description":".NET 8+","sidebar":"tutorialSidebar"},"types/enums":{"id":"types/enums","title":"Enums","description":"The ENUM graph type is represented by an enum type in .NET. The naming and exclusion rules used with object types apply in the same manner to enums.","sidebar":"tutorialSidebar"},"types/input-objects":{"id":"types/input-objects","title":"Input Objects","description":"INPUTOBJECT graph types (a.k.a. input objects) represent complex data supplied to arguments on fields or directives. Anytime you want to pass more data than a single string or a number, perhaps an Address or a new Employee record, you use an INPUTOBJECT to represent that entity in GraphQL. When the system scans your controllers, if it comes across a class or struct used as a parameter to a method it will attempt to generate the appropriate input type definition to represent that class.","sidebar":"tutorialSidebar"},"types/interfaces":{"id":"types/interfaces","title":"Interfaces","description":"Interfaces in GraphQL work like interfaces in C#, for the most part. They provide a contract for a set of common fields amongst different objects. When it comes to declaring them, the INTERFACE graph type works exactly like object types.","sidebar":"tutorialSidebar"},"types/list-non-null":{"id":"types/list-non-null","title":"List & Non-Null","description":"In addition to the six fundamental graph types, GraphQL contains two meta graph types: LIST and NON_NULL.","sidebar":"tutorialSidebar"},"types/objects":{"id":"types/objects","title":"The Object Graph Type","description":"The OBJECT graph type is one of six fundamental types defined by GraphQL. We can think of a graph query like a tree and if scalar values, such as string and int, are the leafs then objects are the branches.","sidebar":"tutorialSidebar"},"types/scalars":{"id":"types/scalars","title":"Scalars","description":"Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being enums). You can extend GraphQL with your own custom scalars when needed.","sidebar":"tutorialSidebar"},"types/unions":{"id":"types/unions","title":"Unions","description":"Unions are an aggregate graph type representing multiple, different OBJECT types with no guaranteed fields or interfaces in common; for instance, Salad or House. Because of this, unions define no fields themselves but provide a common way to query the fields of the union members when one is encountered.","sidebar":"tutorialSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/99037cd0.ed44f196.js b/assets/js/99037cd0.ed44f196.js new file mode 100644 index 0000000..56b6c6c --- /dev/null +++ b/assets/js/99037cd0.ed44f196.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[7776],{5680:(e,t,n)=>{n.d(t,{xA:()=>u,yg:()=>g});var r=n(6540);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?a(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):a(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function l(e,t){if(null==e)return{};var n,r,i=function(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r<a.length;r++)n=a[r],t.indexOf(n)>=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r<a.length;r++)n=a[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=r.createContext({}),p=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},u=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},c="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),c=p(n),m=i,g=c["".concat(s,".").concat(m)]||c[m]||d[m]||a;return n?r.createElement(g,o(o({ref:t},u),{},{components:n})):r.createElement(g,o({ref:t},u))}));function g(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,o=new Array(a);o[0]=m;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[c]="string"==typeof e?e:i,o[1]=l;for(var p=2;p<a;p++)o[p]=n[p];return r.createElement.apply(null,o)}return r.createElement.apply(null,n)}m.displayName="MDXCreateElement"},8682:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>c,frontMatter:()=>a,metadata:()=>l,toc:()=>p});var r=n(8168),i=(n(6540),n(5680));const a={id:"metrics",title:"Profiling Your Queries",sidebar_label:"Query Profiling",sidebar_position:0},o=void 0,l={unversionedId:"execution/metrics",id:"execution/metrics",title:"Profiling Your Queries",description:"GraphQL ASP.NET tracks query metrics through the IQueryExecutionMetrics interface attached to each query execution context as its processed by the runtime and allows for tracing and timing of individual fields as they are started and completed.",source:"@site/docs/execution/metrics.md",sourceDirName:"execution",slug:"/execution/metrics",permalink:"/docs/execution/metrics",draft:!1,tags:[],version:"current",sidebarPosition:0,frontMatter:{id:"metrics",title:"Profiling Your Queries",sidebar_label:"Query Profiling",sidebar_position:0},sidebar:"tutorialSidebar",previous:{title:"Subscription Events",permalink:"/docs/logging/subscription-events"},next:{title:"Malicious Queries",permalink:"/docs/execution/malicious-queries"}},s={},p=[{value:"A sample query profile, serialized to json",id:"a-sample-query-profile-serialized-to-json",level:4},{value:"Enable Query Profiling",id:"enable-query-profiling",level:2},{value:"Delivering Profiling Results",id:"delivering-profiling-results",level:2},{value:"Performance Costs",id:"performance-costs",level:2},{value:"Implementing a Custom Profiling Scheme",id:"implementing-a-custom-profiling-scheme",level:2}],u={toc:p};function c(e){let{components:t,...n}=e;return(0,i.yg)("wrapper",(0,r.A)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQL ASP.NET tracks query metrics through the ",(0,i.yg)("inlineCode",{parentName:"p"},"IQueryExecutionMetrics")," interface attached to each query execution context as its processed by the runtime and allows for tracing and timing of individual fields as they are started and completed."),(0,i.yg)("p",null,"The metrics themselves enable 3 levels of tracing:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The start time and duration of a query as a whole."),(0,i.yg)("li",{parentName:"ul"},'The start time and duration of an arbitrary "phase" of the query.'),(0,i.yg)("li",{parentName:"ul"},"The start and duration of an in individual field resolution.")),(0,i.yg)("p",null,"Out of the box, the GraphQL ASP.NET implements the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/apollographql/apollo-tracing"},"Apollo Tracing")," specification for tracking query performance and uses three phases: ",(0,i.yg)("inlineCode",{parentName:"p"},"Parsing"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"Validation")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"Execution"),"."),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"GraphQL ASP.NET implements the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/apollographql/apollo-tracing"},"Apollo Tracing")," format for capturing query profile information.")),(0,i.yg)("h4",{id:"a-sample-query-profile-serialized-to-json"},"A sample query profile, serialized to json"),(0,i.yg)("p",null,"Query metrics are appended to the ",(0,i.yg)("inlineCode",{parentName:"p"},"extensions")," node of the standard query response. This query of 3 fields will generate a ",(0,i.yg)("inlineCode",{parentName:"p"},"tracing")," object similar to this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"{\n hero(episode: EMPIRE) {\n id\n name\n __typename\n }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="Profile Results"',title:'"Profile','Results"':!0},'{\n // data and error nodes omitted\n "extensions": {\n "tracing": {\n "version": 1,\n "startTime": "2019-09-29T18:21:35.903+00:00",\n "endTime": "2019-09-29T18:21:35.904+00:00",\n "duration": 4862,\n "execution": {\n "resolvers": [\n {\n "path": [\n "hero"\n ],\n "parentType": "Query",\n "fieldName": "hero",\n "returnType": "Character!",\n "startOffset": 78200,\n "duration": 140500\n },\n {\n "path": [\n "hero",\n "id"\n ],\n "parentType": "Human",\n "fieldName": "id",\n "returnType": "ID!",\n "startOffset": 297900,\n "duration": 24100\n },\n {\n "path": [\n "hero",\n "name"\n ],\n "parentType": "Human",\n "fieldName": "name",\n "returnType": "String",\n "startOffset": 352100,\n "duration": 18200\n },\n {\n "path": [\n "hero",\n "__typename"\n ],\n "parentType": "Human",\n "fieldName": "__typename",\n "returnType": "String!",\n "startOffset": 404300,\n "duration": 18400\n }\n ]\n }\n }\n}\n')),(0,i.yg)("h2",{id:"enable-query-profiling"},"Enable Query Profiling"),(0,i.yg)("p",null,"Metrics can be turned on for all requests during configuration in ",(0,i.yg)("inlineCode",{parentName:"p"},"Startup.cs"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Enable Metrics at Startup"',title:'"Enable',Metrics:!0,at:!0,'Startup"':!0},"services.AddGraphQL(options =>\n{\n options.ExecutionOptions.EnableMetrics = true;\n});\n")),(0,i.yg)("p",null,'If you choose to override the default processor that accepts HTTP requests you can also enable metrics on a "per request" basis by overriding the ',(0,i.yg)("inlineCode",{parentName:"p"},"EnableMetrics")," property and/or the ",(0,i.yg)("inlineCode",{parentName:"p"},"CreateRequest()")," method to handle any conditional logic. See the section on the ",(0,i.yg)("a",{parentName:"p",href:"../reference/http-processor"},(0,i.yg)("inlineCode",{parentName:"a"},"GraphQL Http Processor"))," for more details."),(0,i.yg)("h2",{id:"delivering-profiling-results"},"Delivering Profiling Results"),(0,i.yg)("p",null,"Options to profile a query vs. sending the results to a requestor are separate flags. Since the metrics package is attached to the query's primary context the results can be easily captured either by overriding the default http processor or in the event logger (during the ",(0,i.yg)("inlineCode",{parentName:"p"},"Request Completed")," event) and perform an operation without sending them to the requestor. This allows you to perform silent profiling when necessary and can be a useful tool for random sampling and quality control in many scenarios."),(0,i.yg)("p",null,"To enable delivery of the metrics results to the requestor, set the appropriate schema configuration property at startup:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Expose Metrics at Startup"',title:'"Expose',Metrics:!0,at:!0,'Startup"':!0},"services.AddGraphQL(options =>\n{\n options.ResponseOptions.ExposeMetrics = true;\n});\n")),(0,i.yg)("p",null,"As with enabling metrics, additional control can be gained by overriding ",(0,i.yg)("inlineCode",{parentName:"p"},"HandleQueryMetrics()")," on the http processor."),(0,i.yg)("h2",{id:"performance-costs"},"Performance Costs"),(0,i.yg)("p",null,"Just as with ",(0,i.yg)("a",{parentName:"p",href:"../logging/structured-logging"},"logging"),", profiling your queries to this level of detail is not free. There is a performance cost that increases as your queries get larger. Care should be taken on deciding when to enable query profiling. It is recommended to keep profiling turned off in production during normal use."),(0,i.yg)("h2",{id:"implementing-a-custom-profiling-scheme"},"Implementing a Custom Profiling Scheme"),(0,i.yg)("p",null,"Customizing the way metrics are captured is not a trivial task but can be done:"),(0,i.yg)("ol",null,(0,i.yg)("li",{parentName:"ol"},"Implement ",(0,i.yg)("inlineCode",{parentName:"li"},"IQueryExecutionMetricsFactory<TSchema>")," and register it to your DI container before calling ",(0,i.yg)("inlineCode",{parentName:"li"},".AddGraphQL()"),". This will override the internal factory and use your implementation to generate metrics packages for any received requests."),(0,i.yg)("li",{parentName:"ol"},"Implement ",(0,i.yg)("inlineCode",{parentName:"li"},"IQueryExecutionMetrics")," and have your factory return transient instances of this class when requested.")),(0,i.yg)("p",null,"The runtime will now send metrics events to your objects and you can proceed with handling the data. However, the default pipeline structure is still only going to deliver 3 named phases to your metrics package (Parsing, Validation, Execution). If you want to alter the phase sequence or add new ones, you'll need to implement your own core pipeline components, which is beyond the scope of this documentation."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9f241a4b.939c1f9b.js b/assets/js/9f241a4b.939c1f9b.js new file mode 100644 index 0000000..4df3388 --- /dev/null +++ b/assets/js/9f241a4b.939c1f9b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[8956],{5680:(e,t,n)=>{n.d(t,{xA:()=>c,yg:()=>y});var a=n(6540);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?i(Object(n),!0).forEach((function(t){r(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):i(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function s(e,t){if(null==e)return{};var n,a,r=function(e,t){if(null==e)return{};var n,a,r={},i=Object.keys(e);for(a=0;a<i.length;a++)n=i[a],t.indexOf(n)>=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a<i.length;a++)n=i[a],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var o=a.createContext({}),p=function(e){var t=a.useContext(o),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},c=function(e){var t=p(e.components);return a.createElement(o.Provider,{value:t},e.children)},u="mdxType",g={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,o=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),u=p(n),d=r,y=u["".concat(o,".").concat(d)]||u[d]||g[d]||i;return n?a.createElement(y,l(l({ref:t},c),{},{components:n})):a.createElement(y,l({ref:t},c))}));function y(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,l=new Array(i);l[0]=d;var s={};for(var o in t)hasOwnProperty.call(t,o)&&(s[o]=t[o]);s.originalType=e,s[u]="string"==typeof e?e:r,l[1]=s;for(var p=2;p<i;p++)l[p]=n[p];return a.createElement.apply(null,l)}return a.createElement.apply(null,n)}d.displayName="MDXCreateElement"},9774:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>u,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var a=n(8168),r=(n(6540),n(5680));const i={id:"interfaces",title:"Interfaces",sidebar_label:"Interfaces",sidebar_position:2},l=void 0,s={unversionedId:"types/interfaces",id:"types/interfaces",title:"Interfaces",description:"Interfaces in GraphQL work like interfaces in C#, for the most part. They provide a contract for a set of common fields amongst different objects. When it comes to declaring them, the INTERFACE graph type works exactly like object types.",source:"@site/docs/types/interfaces.md",sourceDirName:"types",slug:"/types/interfaces",permalink:"/docs/types/interfaces",draft:!1,tags:[],version:"current",sidebarPosition:2,frontMatter:{id:"interfaces",title:"Interfaces",sidebar_label:"Interfaces",sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"Input Objects",permalink:"/docs/types/input-objects"},next:{title:"Unions",permalink:"/docs/types/unions"}},o={},p=[{value:"Inheritance and Implmentations",id:"inheritance-and-implmentations",level:2},{value:"Use it to Include it",id:"use-it-to-include-it",level:3},{value:"Implmenting Other Interfaces",id:"implmenting-other-interfaces",level:3},{value:"Interfaces are not Input Objects",id:"interfaces-are-not-input-objects",level:2},{value:"Interface Names",id:"interface-names",level:2},{value:"Methods as Fields",id:"methods-as-fields",level:2},{value:"Excluding Fields",id:"excluding-fields",level:2},{value:"Forced Interface Exclusions",id:"forced-interface-exclusions",level:2}],c={toc:p};function u(e){let{components:t,...n}=e;return(0,r.yg)("wrapper",(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Interfaces in GraphQL work like interfaces in C#, for the most part. They provide a contract for a set of common fields amongst different objects. When it comes to declaring them, the ",(0,r.yg)("inlineCode",{parentName:"p"},"INTERFACE")," graph type works exactly like ",(0,r.yg)("a",{parentName:"p",href:"./objects"},"object types"),"."),(0,r.yg)("p",null,"By default, when creating an interface graph type, the library:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Will name the interface the same as its C# type name."),(0,r.yg)("li",{parentName:"ul"},"Will include all properties that have a getter."),(0,r.yg)("li",{parentName:"ul"},"Will ignore any methods.")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="IPastry.cs"',title:'"IPastry.cs"'},"public interface IPastry\n{\n int Id { get; set; }\n string Name { get; set; }\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="IPastry Type Definition"',title:'"IPastry',Type:!0,'Definition"':!0},"interface IPastry {\n id: Int!\n name: String\n}\n")),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},"You can override the default settings in your ",(0,r.yg)("a",{parentName:"p",href:"/docs/reference/schema-configuration#fielddeclarationrequirements"},"schema configuration")," or by use of the ",(0,r.yg)("a",{parentName:"p",href:"/docs/reference/attributes#graphtype"},"GraphType")," and ",(0,r.yg)("a",{parentName:"p",href:"/docs/reference/attributes#graphfield"},"GraphField")," attributes.")),(0,r.yg)("h2",{id:"inheritance-and-implmentations"},"Inheritance and Implmentations"),(0,r.yg)("p",null,"The section on working with interfaces with ",(0,r.yg)("a",{parentName:"p",href:"../controllers/actions#working-with-interfaces"},"action methods")," provides a great discussion on proper usage but its worth pointing out here as well."),(0,r.yg)("p",null,"You must let GraphQL know of the possible object types which implement your interface. If your action method returns ",(0,r.yg)("inlineCode",{parentName:"p"},"IPastry")," and you return a ",(0,r.yg)("inlineCode",{parentName:"p"},"Donut"),", but didn't let GraphQL know about the ",(0,r.yg)("inlineCode",{parentName:"p"},"Donut")," class, it won't be able to continue to resolve the requested fields as it won't know which resolvers to call. This is especially true if you use type restricted fragments or spreads."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs"',title:'"BakeryController.cs"'},"public class BakeryController : GraphController\n{\n // highlight-next-line\n [QueryRoot(typeof(Donut), typeof(Cake))]\n public IPastry SearchPastries(string name)\n {/* ... */}\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'query {\n searchPastries(name: "chocolate*") {\n id\n name\n\n ...on Donut {\n isFilled\n }\n\n ...on Cake {\n icingFlavor\n }\n }\n}\n')),(0,r.yg)("h3",{id:"use-it-to-include-it"},"Use it to Include it"),(0,r.yg)("p",null,"Unless an interface is actually referenced as a return value of a field, be it from an action method or a model property, it won't be added to your schema and won't be visible to introspection queries. "),(0,r.yg)("p",null,"That is to say that when you register ",(0,r.yg)("inlineCode",{parentName:"p"},"Donut"),", unless you specifically return ",(0,r.yg)("inlineCode",{parentName:"p"},"IPastry")," from your application, GraphQL will leave it out of the schema. This goes a long ways in preventing security vulnerabilities and reducing clutter in your schema with all the interfaces you may declare internally. For instance, while common in .NET, its doubtful that you ever want to expose ",(0,r.yg)("inlineCode",{parentName:"p"},"IEnumerable")," to your graph."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="IPastry is never used"',title:'"IPastry',is:!0,never:!0,'used"':!0},"public class BakeryController : GraphController\n{\n [QueryRoot]\n public Donut FindDonut(string name)\n {/* ... */}\n}\n\npublic class Donut : IPastry\n{/*...*/}\n\n// IPastry will be excluded from the schema since\n// its not referenced in any controllers or other object properties.\npublic interface IPastry\n{/*...*/}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Type Definitions"',title:'"Type','Definitions"':!0},"# Donut is published on the schema\n# but IPastry is not included\ntype Donut {\n id: Int!\n name: String\n ...\n}\n")),(0,r.yg)("admonition",{type:"tip"},(0,r.yg)("p",{parentName:"admonition"}," Use ",(0,r.yg)("inlineCode",{parentName:"p"},"schemaOptions.AddGraphType<IPastry>()")," during ",(0,r.yg)("a",{parentName:"p",href:"../reference/schema-configuration"},"schema configuration")," at startup to force GraphQL to publish the interface, even if its never used in the graph. This is true for any graph type.")),(0,r.yg)("h3",{id:"implmenting-other-interfaces"},"Implmenting Other Interfaces"),(0,r.yg)("p",null,"Interfaces implementing other interfaces worksa bit differently than it does in .NET. Take for example, these two interfaces:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="C# Interface Inheritance"',title:'"C#',Interface:!0,'Inheritance"':!0},"public interface IPastry\n{\n int Id { get; set; }\n string Name { get; set; }\n}\n\npublic interface IDonut : IPastry\n{\n string Flavor{ get; set; }\n}\n")),(0,r.yg)("p",null,"In .NET ",(0,r.yg)("inlineCode",{parentName:"p"},"IDonut"),", by virtue of implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"IPastry"),', grants "access" to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Id")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"Name")," fields for any object that implements IDonut since said object must implement both interfaces to compile correctly. However, this is not the case with interfaces in your graphql schema. As said above, since interfaces are not automatically parsed the fields they define are also not automatically included in child interfaces."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Startup Code"',title:'"Startup','Code"':!0},"services.AddGraphQL(o => \n{\n // only include IDonut in the schema\n // highlight-next-line\n o.AddGraphType<IDonut>();\n});\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="IDonut Type Definition"',title:'"IDonut',Type:!0,'Definition"':!0},"# IDonut DOES NOT contain name or id\n# because IPastry is not part of the schema\ninterface IDonut {\n flavor: String\n}\n")),(0,r.yg)("p",null,"However, GraphQL does support interface inheritance. As long as both interfaces are included as part of the schema then the fields will wire up as you'd expect."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Startup Code"',title:'"Startup','Code"':!0},"services.AddGraphQL(o => \n{\n // Include both interfaces\n // highlight-start\n o.AddGraphType<IPastry>(); \n o.AddGraphType<IDonut>();\n // highlight-end\n});\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Type Definitions"',title:'"Type','Definitions"':!0},"interface IPastry { \n id: Int!\n name: String\n}\n\n# IDonut DOES contain all the expected fields\n# Since IPastry is included\n// highlight-next-line\ninterface IDonut implements IPastry { \n id: Int!\n name: String\n flavor: String\n}\n")),(0,r.yg)("admonition",{type:"info"},(0,r.yg)("p",{parentName:"admonition"},"GraphQL will NOT attempt to include inherited fields unless the interface they are declared on is part the schema.")),(0,r.yg)("h2",{id:"interfaces-are-not-input-objects"},"Interfaces are not Input Objects"),(0,r.yg)("p",null,'The GraphQL specification states that "interfaces are never valid inputs" [',(0,r.yg)("a",{parentName:"p",href:"https://graphql.github.io/graphql-spec/October2021/#sec-Interfaces"},"Spec \xa7 3.7"),"]. The runtime will reject any attempts to use an interface as a parameter to a method (i.e. a field argument) that is exposed on the graph."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Interfaces cannot be used as input arguments"',title:'"Interfaces',cannot:!0,be:!0,used:!0,as:!0,input:!0,'arguments"':!0},"public class BakeryController : GraphController\n{\n // ERROR!\n // A GraphTypeDeclarationException will be thrown\n [Mutation]\n // highlight-next-line\n public Donut AddNewDonut(IPastry newPastry)\n {/* ... */}\n}\n")),(0,r.yg)("h2",{id:"interface-names"},"Interface Names"),(0,r.yg)("p",null,"Like with other graph types use the ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphType]")," attribute to indicate a custom name for the interface in the object graph."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Interface Custom Name"',title:'"Interface',Custom:!0,'Name"':!0},'// highlight-next-line\n[GraphType("Pastry")]\npublic interface IPastry\n{\n int Id { get; set; }\n string Name { get; set; }\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Graph Type Definition"',title:'"Graph',Type:!0,'Definition"':!0},"interface Pastry {\n id: Int!\n name: String\n}\n")),(0,r.yg)("h2",{id:"methods-as-fields"},"Methods as Fields"),(0,r.yg)("p",null,"By default, interface methods are excluded from being fields on the graph but can be added by tagging the method with ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphField]"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Including a POCO method as a field"',title:'"Including',a:!0,POCO:!0,method:!0,as:!0,'field"':!0},'public interface IPastry\n{\n // highlight-next-line\n [GraphField("salesTax")]\n decimal CalculateSalesTax(decimal taxPercentage);\n\n int Id { get; set; }\n string Name { get; set; }\n decimal Price { get; set; }\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="IPastry Type Definition"',title:'"IPastry',Type:!0,'Definition"':!0},"inteface IPastry {\n // highlight-next-line\n salesTax (taxPercentage: Decimal!): Decimal!\n id: Int!\n name: String\n price: Decimal!\n}\n")),(0,r.yg)("p",null,"Just as with ",(0,r.yg)("a",{parentName:"p",href:"../controllers/actions"},"controller actions"),", GraphQL will analyze the signature of the method to determine its return type, expression requirements and input arguments."),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},"Methods on interfaces lack many of the features of controllers such as being able to perform ",(0,r.yg)("a",{parentName:"p",href:"../controllers/model-state"},"model state")," validation or provide access to ",(0,r.yg)("inlineCode",{parentName:"p"},"this.User")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"this.Request"),". ")),(0,r.yg)("h2",{id:"excluding-fields"},"Excluding Fields"),(0,r.yg)("p",null,"To exclude a single property that you don't want to expose to GraphQL add the ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphSkip]")," attribute to it:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Excluding a property"',title:'"Excluding',a:!0,'property"':!0},"public interface IPastry\n{\n int Id { get; set; }\n string Name { get; set; }\n\n // highlight-next-line\n [GraphSkip]\n decimal Price { get; set; }\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="IPastry Type Definition"',title:'"IPastry',Type:!0,'Definition"':!0},"interface IPastry {\n id: Int!\n name: String\n # price is not included\n}\n")),(0,r.yg)("p",null,"Or force GraphQL to skip all fields except those you explicitly define with a ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphField]")," attribute:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Require explicit declarations for this type"',title:'"Require',explicit:!0,declarations:!0,for:!0,this:!0,'type"':!0},"// highlight-next-line\n[GraphType(FieldDeclarationRequirements = TemplateDeclarationRequirements.RequireAll)]\npublic interface IPastry\n{\n [GraphField]\n int Id { get; set; }\n\n [GraphField]\n string Name { get; set; }\n\n decimal Price { get; set; }\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="IPastry Type Definition"',title:'"IPastry',Type:!0,'Definition"':!0},"# only id and name are included\ninterface IPastry {\n id: Int!\n name: String\n}\n")),(0,r.yg)("p",null,"Or set a schema-wide option during startup:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Set Field Declaration Requirements at Startup"',title:'"Set',Field:!0,Declaration:!0,Requirements:!0,at:!0,'Startup"':!0},"services.AddGraphQL(options =>\n {\n options.DeclarationOptions.FieldDeclarationRequirements = TemplateDeclarationRequirements.RequireAll;\n });\n")),(0,r.yg)("p",null,"Your schema will follow a cascading model of inclusion rules in order of increasing priority from ",(0,r.yg)("inlineCode",{parentName:"p"},"schema -> class -> field")," level declarations. This can be useful in multi-schema setups where a class may be shared but you don't want the exposed fields to be different or if there is a secure field that you want to guarantee is not exposed regardless of the schema."),(0,r.yg)("h2",{id:"forced-interface-exclusions"},"Forced Interface Exclusions"),(0,r.yg)("p",null,"Perhaps there exists an interface in a shared assembly used amongst multiple work teams that contains some utility classes that absolutely, positively CANNOT be exposed to GraphQL at any cost. "),(0,r.yg)("p",null,"In these cases, add ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphSkip]")," to an interface and GraphQL will throw a ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphTypeDeclarationException")," if its ever asked to include it in a schema. "),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Prevent a Type from EVER Being Included in the Graph"',title:'"Prevent',a:!0,Type:!0,from:!0,EVER:!0,Being:!0,Included:!0,in:!0,the:!0,'Graph"':!0},"// ERROR, GraphTypeDeclarationException will be thrown!\n[GraphSkip]\npublic interface IPastry\n{\n int Id { get; set; }\n string Name { get; set; }\n decimal Price { get; set; }\n}\n")),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},"This rule is enforced at the template level and is applied to the ",(0,r.yg)("inlineCode",{parentName:"p"},"System.Type"),". Its not specific to the ",(0,r.yg)("inlineCode",{parentName:"p"},"INTERFACE")," graph type. Any class, interface, enum etc. with the ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphSkip]")," attribute will be permanantly skipped.")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a79310a1.76f167be.js b/assets/js/a79310a1.76f167be.js new file mode 100644 index 0000000..a9e181d --- /dev/null +++ b/assets/js/a79310a1.76f167be.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[1442],{5680:(e,t,a)=>{a.d(t,{xA:()=>u,yg:()=>m});var n=a(6540);function l(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function r(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function i(e){for(var t=1;t<arguments.length;t++){var a=null!=arguments[t]?arguments[t]:{};t%2?r(Object(a),!0).forEach((function(t){l(e,t,a[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(a)):r(Object(a)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(a,t))}))}return e}function o(e,t){if(null==e)return{};var a,n,l=function(e,t){if(null==e)return{};var a,n,l={},r=Object.keys(e);for(n=0;n<r.length;n++)a=r[n],t.indexOf(a)>=0||(l[a]=e[a]);return l}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(n=0;n<r.length;n++)a=r[n],t.indexOf(a)>=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(l[a]=e[a])}return l}var s=n.createContext({}),p=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):i(i({},t),e)),a},u=function(e){var t=p(e.components);return n.createElement(s.Provider,{value:t},e.children)},d="mdxType",g={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},y=n.forwardRef((function(e,t){var a=e.components,l=e.mdxType,r=e.originalType,s=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),d=p(a),y=l,m=d["".concat(s,".").concat(y)]||d[y]||g[y]||r;return a?n.createElement(m,i(i({ref:t},u),{},{components:a})):n.createElement(m,i({ref:t},u))}));function m(e,t){var a=arguments,l=t&&t.mdxType;if("string"==typeof e||l){var r=a.length,i=new Array(r);i[0]=y;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[d]="string"==typeof e?e:l,i[1]=o;for(var p=2;p<r;p++)i[p]=a[p];return n.createElement.apply(null,i)}return n.createElement.apply(null,a)}y.displayName="MDXCreateElement"},2190:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>d,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var n=a(8168),l=(a(6540),a(5680));const r={id:"schema-configuration",title:"Schema Configuration",sidebar_label:"Schema Configuration",sidebar_position:1},i=void 0,o={unversionedId:"reference/schema-configuration",id:"reference/schema-configuration",title:"Schema Configuration",description:"This document contains a list of various configuration settings available during schema configuration. All options are added as part of the .AddGraphQL() method used at startup.",source:"@site/docs/reference/schema-configuration.md",sourceDirName:"reference",slug:"/reference/schema-configuration",permalink:"/docs/reference/schema-configuration",draft:!1,tags:[],version:"current",sidebarPosition:1,frontMatter:{id:"schema-configuration",title:"Schema Configuration",sidebar_label:"Schema Configuration",sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"How it Works",permalink:"/docs/reference/how-it-works"},next:{title:"Global Configuration",permalink:"/docs/reference/global-configuration"}},s={},p=[{value:"Builder Options",id:"builder-options",level:2},{value:"AddAssembly",id:"addassembly",level:3},{value:"AddSchemaAssembly",id:"addschemaassembly",level:3},{value:"AddType*",id:"addtype",level:3},{value:"ApplyDirective",id:"applydirective",level:3},{value:"AutoRegisterLocalEntities",id:"autoregisterlocalentities",level:3},{value:"Authorization Options",id:"authorization-options",level:2},{value:"Method",id:"method",level:3},{value:"Declaration Options",id:"declaration-options",level:2},{value:"AllowedOperations",id:"allowedoperations",level:3},{value:"DisableIntrospection",id:"disableintrospection",level:3},{value:"FieldDeclarationRequirements",id:"fielddeclarationrequirements",level:3},{value:"GraphNamingFormatter",id:"graphnamingformatter",level:3},{value:"Execution Options",id:"execution-options",level:2},{value:"DebugMode",id:"debugmode",level:3},{value:"EnableMetrics",id:"enablemetrics",level:3},{value:"MaxQueryComplexity",id:"maxquerycomplexity",level:3},{value:"MaxQueryDepth",id:"maxquerydepth",level:3},{value:"QueryTimeout",id:"querytimeout",level:3},{value:"ResolverIsolation",id:"resolverisolation",level:3},{value:"Response Options",id:"response-options",level:2},{value:"AppendServerHeader",id:"appendserverheader",level:3},{value:"ExposeExceptions",id:"exposeexceptions",level:3},{value:"ExposeMetrics",id:"exposemetrics",level:3},{value:"IndentDocument",id:"indentdocument",level:3},{value:"MessageSeverityLevel",id:"messageseveritylevel",level:3},{value:"Message Severity Levels",id:"message-severity-levels",level:4},{value:"TimeStampLocalizer",id:"timestamplocalizer",level:3},{value:"QueryHandler Options",id:"queryhandler-options",level:2},{value:"AuthenticatedRequestsOnly",id:"authenticatedrequestsonly",level:3},{value:"DisableDefaultRoute",id:"disabledefaultroute",level:3},{value:"HttpProcessorType",id:"httpprocessortype",level:3},{value:"Route",id:"route",level:3},{value:"Subscription Server Options",id:"subscription-server-options",level:2},{value:"AuthenticatedRequestsOnly",id:"authenticatedrequestsonly-1",level:3},{value:"ConnectionKeepAliveInterval",id:"connectionkeepaliveinterval",level:3},{value:"ConnectionInitializationTimeout",id:"connectioninitializationtimeout",level:3},{value:"DefaultMessageProtocol",id:"defaultmessageprotocol",level:3},{value:"DisableDefaultRoute",id:"disabledefaultroute-1",level:3},{value:"HttpMiddlewareComponentType",id:"httpmiddlewarecomponenttype",level:3},{value:"RequireAuthenticatedConnection",id:"requireauthenticatedconnection",level:3},{value:"Route",id:"route-1",level:3},{value:"SupportedMessageProtocols",id:"supportedmessageprotocols",level:3}],u={toc:p};function d(e){let{components:t,...a}=e;return(0,l.yg)("wrapper",(0,n.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"This document contains a list of various configuration settings available during schema configuration. All options are added as part of the ",(0,l.yg)("inlineCode",{parentName:"p"},".AddGraphQL()")," method used at startup."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Adding Schema Configuration Options"',title:'"Adding',Schema:!0,Configuration:!0,'Options"':!0},"services.AddGraphQL(schemaOptions =>\n{\n // *************************\n // CONFIGURE YOUR SCHEMA HERE\n // *************************\n});\n\n\n// Be sure to add graphql to the ASP.NET pipeline builder\nappBuilder.UseGraphQL();\n")),(0,l.yg)("h2",{id:"builder-options"},"Builder Options"),(0,l.yg)("h3",{id:"addassembly"},"AddAssembly"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.AddAssembly(assembly);\n")),(0,l.yg)("p",null,"The runtime will scan the referenced assembly and auto-add any found required entities (controllers, types, enums, directives etc.) to the schema."),(0,l.yg)("h3",{id:"addschemaassembly"},"AddSchemaAssembly"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.AddSchemaAssembly();\n")),(0,l.yg)("p",null,"When declaring a new schema with .",(0,l.yg)("inlineCode",{parentName:"p"},"AddGraphQL<TSchema>()"),", the runtime will scan the assembly where ",(0,l.yg)("inlineCode",{parentName:"p"},"TSchema")," is declared and auto-add any found required entities (controllers, types, enums, directives etc.) to the schema. "),(0,l.yg)("p",null,"This method has no effect when using ",(0,l.yg)("inlineCode",{parentName:"p"},"AddGraphQL()"),"."),(0,l.yg)("h3",{id:"addtype"},"AddType*"),(0,l.yg)("p",null,"Multiple Options: ",(0,l.yg)("inlineCode",{parentName:"p"},"AddGraphType"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"AddController"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"AddDirective"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"AddType")),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.AddGraphType<Donut>();\nschemaOptions.AddController<BakeryController>();\n")),(0,l.yg)("p",null,"Adds a single entity of a given type the schema. Use these methods to add individual graph types, directives or controllers. ",(0,l.yg)("inlineCode",{parentName:"p"},"AddType")," acts a catch all and will try to infer the expected action to take against the supplied type. The other entity-specific methods will throw an exception should an unqualified type be supplied. For example, trying to supply a controller to ",(0,l.yg)("inlineCode",{parentName:"p"},".AddGraphType()")," will result in an exception."),(0,l.yg)("h3",{id:"applydirective"},"ApplyDirective"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},'schemaOptions.ApplyDirective("@deprecated")\n .WithArguments("The name field is deprecated.")\n .ToItems(schemaItem => schemaItem.IsGraphField<Person>("name"));\n')),(0,l.yg)("p",null,"Allows for the runtime registration of a type system directive to a given schema item. "),(0,l.yg)("blockquote",null,(0,l.yg)("p",{parentName:"blockquote"},"See the section on ",(0,l.yg)("a",{parentName:"p",href:"/docs/advanced/directives#using-schema-options"},"directives")," for complete details on how to use this method. ")),(0,l.yg)("h3",{id:"autoregisterlocalentities"},"AutoRegisterLocalEntities"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.AutoRegisterLocalEntities = true;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"When true, the graph entities (controllers, types, enums etc.) that are declared in the startup assembly for the application are automatically registered to the schema. Typically this is your API project where ",(0,l.yg)("inlineCode",{parentName:"p"},"Startup.cs")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"Program.cs")," is declared."),(0,l.yg)("h2",{id:"authorization-options"},"Authorization Options"),(0,l.yg)("h3",{id:"method"},"Method"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.AuthorizationOptions.Method = AuthorizationMethod.PerField;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"null")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"PerField"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"PerRequest"))))),(0,l.yg)("p",null,"Controls how the graphql execution pipeline will authorize a request."),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("p",{parentName:"li"},(0,l.yg)("inlineCode",{parentName:"p"},"PerField"),": Each field of a query is evaluated individually allowing a data response to be generated that includes data the user can access and ",(0,l.yg)("inlineCode",{parentName:"p"},"null")," values for those fields the user cannot access. Any unauthorized fields will also register an error in the response.")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("p",{parentName:"li"},(0,l.yg)("inlineCode",{parentName:"p"},"PerRequest"),": All fields of a query are validated BEFORE execution. Each field is validated individually, using its own authorization and authentication requirements. If the current user does not have access to 1 or more requested fields the entire request is denied and an error message generated."))),(0,l.yg)("blockquote",null,(0,l.yg)("p",{parentName:"blockquote"},"See ",(0,l.yg)("a",{parentName:"p",href:"../advanced/subscriptions#security--query-authorization"},"Subscription Security")," for additional considerations regarding authorization and subscriptions.")),(0,l.yg)("h2",{id:"declaration-options"},"Declaration Options"),(0,l.yg)("h3",{id:"allowedoperations"},"AllowedOperations"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.DeclarationOptions.AllowedOperations.Remove(GraphOperationType.Mutation);\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"Query, Mutation")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"Query"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"Mutatation"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"Subscription"))))),(0,l.yg)("p",null,"Controls which top level operations are available on your schema. In general, this property is managed internally and you do not need to alter it. An operation not in the list will not be configured at start up."),(0,l.yg)("blockquote",null,(0,l.yg)("p",{parentName:"blockquote"},"Subscriptions are automatically added when the subscription library is added via ",(0,l.yg)("inlineCode",{parentName:"p"},".AddSubscriptions()"),".")),(0,l.yg)("h3",{id:"disableintrospection"},"DisableIntrospection"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.DeclarationOptions.DisableIntrospection = false;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"false")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"When ",(0,l.yg)("inlineCode",{parentName:"p"},"true"),", any attempts to perform an introspection query will fail, preventing exposure of type meta data. "),(0,l.yg)("blockquote",null,(0,l.yg)("p",{parentName:"blockquote"},"Note: Many tools, IDEs and client libraries not work if you disable introspection data.")),(0,l.yg)("h3",{id:"fielddeclarationrequirements"},"FieldDeclarationRequirements"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.DeclarationOptions.FieldDeclarationRequirements = TemplateDeclarationRequirements.Default;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"TemplateDeclarationRequirements.Default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"all enum values"))))),(0,l.yg)("p",null,"Indicates to the runtime which fields and values of POCO classes must be explicitly declared for them to be added to a schema. "),(0,l.yg)("p",null,"By default:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"All values declared on an ",(0,l.yg)("inlineCode",{parentName:"li"},"enum")," ",(0,l.yg)("strong",{parentName:"li"},"will be")," included."),(0,l.yg)("li",{parentName:"ul"},"All properties of POCOs and interfaces ",(0,l.yg)("strong",{parentName:"li"},"will be")," included."),(0,l.yg)("li",{parentName:"ul"},"All methods of POCOs and interfaces ",(0,l.yg)("strong",{parentName:"li"},"will NOT be")," included.")),(0,l.yg)("blockquote",null,(0,l.yg)("p",{parentName:"blockquote"},"NOTE: Controller and Directive action methods are not effected by this setting.")),(0,l.yg)("h3",{id:"graphnamingformatter"},"GraphNamingFormatter"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.DeclarationOptions.GraphNamingFormatter = new GraphNameFormatter(...);\n")),(0,l.yg)("p",null,"An object that will format any string to an acceptable name for use in the graph."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Entity Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Default Format"),(0,l.yg)("th",{parentName:"tr",align:null},"Examples"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"Graph Type Names"),(0,l.yg)("td",{parentName:"tr",align:null},"Pascal Casing"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"Donut"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"BigHorse"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"SpeakerSystem"))),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"Field Names"),(0,l.yg)("td",{parentName:"tr",align:null},"Camel Casing"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"flavor"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"minWidth"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"firstName"))),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"Enum Values"),(0,l.yg)("td",{parentName:"tr",align:null},"All Caps"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"CHOCOLATE"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"FEET"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"PHONE_NUMBER"))))),(0,l.yg)("p",null,(0,l.yg)("em",{parentName:"p"},"Default formats for the three different entity types")),(0,l.yg)("admonition",{type:"tip"},(0,l.yg)("p",{parentName:"admonition"}," To make radical changes to your name formats, beyond the available options, inherit from ",(0,l.yg)("inlineCode",{parentName:"p"},"GraphNameFormatter")," and override the different formatting methods.")),(0,l.yg)("h2",{id:"execution-options"},"Execution Options"),(0,l.yg)("h3",{id:"debugmode"},"DebugMode"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.ExecutionOptions.DebugMode = false;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"false")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"When true, each field and each list member of each field will be executed sequentially with no parallelization. All asynchronous methods will be individually awaited and allowed to throw immediately. A single encountered exception will halt the entire query process. This can be very helpful in preventing a jumping debug cursor. This option will greatly impact performance and can cause inconsistent query results if used in production. It should only be enabled for ",(0,l.yg)("a",{parentName:"p",href:"../development/debugging"},"debugging"),"."),(0,l.yg)("h3",{id:"enablemetrics"},"EnableMetrics"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.ExecutionOptions.EnableMetrics = false;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"false")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"When true, metrics and query profiling will be enabled for all queries processed for a given schema."),(0,l.yg)("blockquote",null,(0,l.yg)("p",{parentName:"blockquote"},"Note: This option DOES NOT control if those metrics are sent to the query requestor, just that they are generated. See ",(0,l.yg)("a",{parentName:"p",href:"./schema-configuration#exposemetrics"},"ExposeMetrics")," in the response options for that switch.")),(0,l.yg)("h3",{id:"maxquerycomplexity"},"MaxQueryComplexity"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.ExecutionOptions.MaxQueryComplexity = 50.0f;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"-",(0,l.yg)("em",{parentName:"td"},"not set"),"-"),(0,l.yg)("td",{parentName:"tr",align:null},"Float Greater Than 0")))),(0,l.yg)("p",null,"The maximum allowed ",(0,l.yg)("a",{parentName:"p",href:"../execution/malicious-queries#query-complexity"},"complexity")," value of a query. If a query is scored higher than this value it will be rejected."),(0,l.yg)("h3",{id:"maxquerydepth"},"MaxQueryDepth"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.ExecutionOptions.MaxQueryDepth = 15;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"-",(0,l.yg)("em",{parentName:"td"},"not set"),"-"),(0,l.yg)("td",{parentName:"tr",align:null},"Integer Greater than 0")))),(0,l.yg)("p",null,"The maximum allowed ",(0,l.yg)("a",{parentName:"p",href:"../execution/malicious-queries#maximum-allowed-field-depth"},"field depth")," of any child field within a given query. If a query contains a child that is nested deeper than this value the query will be rejected."),(0,l.yg)("h3",{id:"querytimeout"},"QueryTimeout"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.ExecutionOptions.QueryTimeout = TimeSpan.FromMinutes(2);\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"-",(0,l.yg)("em",{parentName:"td"},"not set"),"-"),(0,l.yg)("td",{parentName:"tr",align:null},"> 10 milliseconds")))),(0,l.yg)("p",null,"The amount of time an individual query will be given to run before being abandoned and canceled by the runtime. By default, the timeout is disabled and a query will continue to execute as long as the underlying HTTP request is also executing. The minimum allowed amount of time for a query to run is 10ms."),(0,l.yg)("h3",{id:"resolverisolation"},"ResolverIsolation"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.ExecutionOptions.ResolverIsolation = ResolverIsolationOptions.ControllerActions | ResolverIsolation.Properties;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"ResolverIsolationOptions.None"))))),(0,l.yg)("p",null,"Resolver types identified in ",(0,l.yg)("inlineCode",{parentName:"p"},"ResolverIsolation")," are guaranteed to be executed independently. This is different than ",(0,l.yg)("inlineCode",{parentName:"p"},"DebugMode"),". In debug mode a single encountered error will end the request whereas errors encountered in isolated resolvers will still be aggregated. This allows the returning partial results which can be useful in some use cases. "),(0,l.yg)("h2",{id:"response-options"},"Response Options"),(0,l.yg)("h3",{id:"appendserverheader"},"AppendServerHeader"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.ResponseOptions.AppendServerHeader = true;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"When true, an ",(0,l.yg)("inlineCode",{parentName:"p"},"X-GraphQL-AspNet-Server")," header with the current library version (e.g. ",(0,l.yg)("inlineCode",{parentName:"p"},"v1.0.1"),") is added to the outgoing response. This option has no effect when a custom ",(0,l.yg)("inlineCode",{parentName:"p"},"HttpProcessorType")," is declared."),(0,l.yg)("h3",{id:"exposeexceptions"},"ExposeExceptions"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.ResponseOptions.ExposeExceptions = false;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"false")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"When true, exception details including message, type and stack trace will be sent to the requestor as part of any error messages. "),(0,l.yg)("admonition",{title:"WARNING",type:"caution"},(0,l.yg)("p",{parentName:"admonition"},"Setting this value to true can expose sensitive server details and may be considered a security risk.")),(0,l.yg)("h3",{id:"exposemetrics"},"ExposeMetrics"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.ResponseOptions.ExposeMetrics = false;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"false")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"When true, the full set of metrics gathered when a query is executed is sent to the requestor. This value is disregarded unless ",(0,l.yg)("inlineCode",{parentName:"p"},"ExecutionOptions.EnableMetrics")," is set to true."),(0,l.yg)("blockquote",null,(0,l.yg)("p",{parentName:"blockquote"},"Note: Metrics data for large queries can be quite expansive; double or tripling the size of the json data returned.")),(0,l.yg)("h3",{id:"indentdocument"},"IndentDocument"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.ResponseOptions.IndentDocument = true;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,'When true, the default json response writer will indent and "pretty up" the output response to make it more human-readable. Turning off this setting can result in a smaller output response.'),(0,l.yg)("h3",{id:"messageseveritylevel"},"MessageSeverityLevel"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.ResponseOptions.MessageSeverityLevel = GraphMessageSeverity.Information;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"Information")),(0,l.yg)("td",{parentName:"tr",align:null},"-",(0,l.yg)("em",{parentName:"td"},"any ",(0,l.yg)("inlineCode",{parentName:"em"},"GraphMessageSeverity")," value"),"-")))),(0,l.yg)("p",null,"Indicates which messages generated during a query should be sent to the requestor. Any message with a ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet/Execution/GraphMessageSeverity.cs"},"severity level")," equal to or greater than the provided level will be delivered."),(0,l.yg)("h4",{id:"message-severity-levels"},"Message Severity Levels"),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Rank")))),(0,l.yg)("p",null,"|"),(0,l.yg)("h3",{id:"timestamplocalizer"},"TimeStampLocalizer"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.ResponseOptions.TimeStampLocalizer = (dtos) => dtos.DateTime;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Value"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},(0,l.yg)("inlineCode",{parentName:"em"},"null"))),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"Func<DateTimeOffset, DateTime>"))))),(0,l.yg)("p",null,"A function to convert any system-provided timestamp values present in the output into a value of a given timezone. By default, no localization occurs and all times are delivered in their native ",(0,l.yg)("inlineCode",{parentName:"p"},"UTC-0")," format. This localizer does not effect any query field date values. Only those related to internal messaging (e.g. message creation dates, start and stop times for query metrics etc.) are effected."),(0,l.yg)("h2",{id:"queryhandler-options"},"QueryHandler Options"),(0,l.yg)("h3",{id:"authenticatedrequestsonly"},"AuthenticatedRequestsOnly"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.QueryHandler.AuthenticatedRequestsOnly = false;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"false")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"When true, only those requests that are successfully authenticated by the ASP.NET runtime will be passed to GraphQL. Should an unauthenticated request make it to the graphql query processor it will be immediately rejected. "),(0,l.yg)("admonition",{type:"note"},(0,l.yg)("p",{parentName:"admonition"}," This setting acts as a short cut to assigning custom HttpProcessorType. If you provide your own custom ",(0,l.yg)("a",{parentName:"p",href:"#httpprocessortype"},(0,l.yg)("inlineCode",{parentName:"a"},"HttpProcessorType"))," this setting has no effect.")),(0,l.yg)("h3",{id:"disabledefaultroute"},"DisableDefaultRoute"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.QueryHandler.DisableDefaultRoute = false;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"false")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"When set to true the default route and http query processor will ",(0,l.yg)("strong",{parentName:"p"},"NOT")," be registered with the ASP.NET runtime when the application starts. GraphQL queries will not be processed unless manually invoked."),(0,l.yg)("h3",{id:"httpprocessortype"},"HttpProcessorType"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nschemaOptions.QueryHandler.HttpProcessorType = typeof(MyProcessorType);\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"null"))))),(0,l.yg)("p",null,"When set to a ",(0,l.yg)("inlineCode",{parentName:"p"},"System.Type"),", GraphQL will attempt to load the provided type from the configured DI container in order to handle graphql requests. Any class wishing to act as an Http Processor must implement ",(0,l.yg)("inlineCode",{parentName:"p"},"IGraphQLHttpProcessor<TSchema>"),". "),(0,l.yg)("admonition",{type:"tip"},(0,l.yg)("p",{parentName:"admonition"},"It can be easier to extend ",(0,l.yg)("inlineCode",{parentName:"p"},"DefaultGraphQLHttpProcessor<TSchema>")," instead of implementing the interface from scratch if you only need to make minor changes.")),(0,l.yg)("h3",{id:"route"},"Route"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},'// usage examples\nschemaOptions.QueryHandler.Route = "/graphql";\n')),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"/graphql"))))),(0,l.yg)("p",null,"Represents the REST end point where GraphQL will listen for new POST and GET requests. In multi-schema configurations this value will need to be unique per schema type."),(0,l.yg)("h2",{id:"subscription-server-options"},"Subscription Server Options"),(0,l.yg)("p",null,"These options are available to configure a subscription server for a given schema via ",(0,l.yg)("inlineCode",{parentName:"p"},".AddSubscriptions(subscriptionOptions)")),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Adding Subscription Configuration Options"',title:'"Adding',Subscription:!0,Configuration:!0,'Options"':!0},"services.AddGraphQL()\n .AddSubscriptions(subscriptionOptions =>\n {\n // *************************\n // CONFIGURE YOUR SUBSCRIPTION\n // OPTIONS HERE\n // *************************\n });\n\n\n// Be sure to add graphql to the ASP.NET pipeline builder\nappBuilder.UseGraphQL();\n")),(0,l.yg)("h3",{id:"authenticatedrequestsonly-1"},"AuthenticatedRequestsOnly"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nsubscriptionOptions.AuthenticatedRequestsOnly = false;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"false")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"When true, only requests that are successfully authenticated by the ASP.NET runtime will be passed to GraphQL and registered as a subscription client. Connections with unauthenticated sources are immediately closed."),(0,l.yg)("h3",{id:"connectionkeepaliveinterval"},"ConnectionKeepAliveInterval"),(0,l.yg)("p",null,"The interval at which the subscription server will send a protocol-specific message to a connected graphql client informing it the connection is still open."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nsubscriptionOptions.ConnectionKeepAliveInterval = TimeSpan.FromMinutes(2);\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"2 minutes"))))),(0,l.yg)("admonition",{type:"tip"},(0,l.yg)("p",{parentName:"admonition"},"This is an application level keep-alive supported by most graphql messaging protocols. This is a different keep-alive than the web socket specific keep alive provided by ASP.NET")),(0,l.yg)("h3",{id:"connectioninitializationtimeout"},"ConnectionInitializationTimeout"),(0,l.yg)("p",null,"When supported by a messaging protocol, represents a timeframe after the connection is initiated in which a successful initialization handshake must occur. "),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nsubscriptionOptions.ConnectionInitializationTimeout = TimeSpan.FromSeconds(30);\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"30 seconds"))))),(0,l.yg)("blockquote",null,(0,l.yg)("p",{parentName:"blockquote"},"Note: Not all messaging protocols require an explicit timeframe or support an inititalization handshake.")),(0,l.yg)("h3",{id:"defaultmessageprotocol"},"DefaultMessageProtocol"),(0,l.yg)("p",null,"When set, represents a valid and supported messaging protocol that a client should use if it does not specify which protocols it can communicate in."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},'// usage examples\nsubscriptionOptions.DefaultMessageProtocol = "my-custom-protocol";\n')),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"null"))))),(0,l.yg)("blockquote",null,(0,l.yg)("p",{parentName:"blockquote"},"Note: By default, this value is not set and connected clients ",(0,l.yg)("strong",{parentName:"p"},"MUST")," supply a prioritized protocol list.")),(0,l.yg)("h3",{id:"disabledefaultroute-1"},"DisableDefaultRoute"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nsubscriptionOptions.DisableDefaultRoute = false;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"false ")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"When true, GraphQL will not register a component to listen for web socket requests. You must handle the acceptance of web sockets yourself and provision client proxies that can interact with the runtime. If you wish to implement your own web socket middleware handler, viewing ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet-subscriptions/Engine/DefaultGraphQLHttpSubscriptionMiddleware.cs"},"DefaultGraphQLHttpSubscriptionMiddleware","<","TSchema",">")," may help."),(0,l.yg)("h3",{id:"httpmiddlewarecomponenttype"},"HttpMiddlewareComponentType"),(0,l.yg)("p",null,"When set, represents the custom middleware component GraphQL will inject into the ASP.NET pipeline to intercept new web socket connection requests. "),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nsubscriptionOptions.HttpMiddlewareComponentType = typeof(MyMiddleware);\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"null"))))),(0,l.yg)("p",null,"When null, ",(0,l.yg)("inlineCode",{parentName:"p"},"DefaultGraphQLHttpSubscriptionMiddleware<TSchema>")," is used."),(0,l.yg)("h3",{id:"requireauthenticatedconnection"},"RequireAuthenticatedConnection"),(0,l.yg)("p",null,"Deteremines if a web socket request will be accepted in an unauthenticated state or not."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},"// usage examples\nsubscriptionOptions.RequiredAuthenticatedConnection = false;\n")),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"),(0,l.yg)("th",{parentName:"tr",align:null},"Acceptable Values"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"false ")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"true"),", ",(0,l.yg)("inlineCode",{parentName:"td"},"false"))))),(0,l.yg)("p",null,"When set to true, the subscription middleware will immediately reject any websocket requests from un-authenticated sources. This option does not include query authorization (i.e. can the user access the fields they are requesting). That occurs after the websocket is established."),(0,l.yg)("p",null,"When set to false, the subscription middleware will initially accept all web socket requests."),(0,l.yg)("h3",{id:"route-1"},"Route"),(0,l.yg)("p",null,"Similar to the query/mutation query handler route this represents the path the default subscription middleware will look for when accepting new web sockets."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},'// usage examples\nsubscriptionOptions.Route = "/graphql";\n')),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"/graphql"))))),(0,l.yg)("p",null,"Represents the http end point where GraphQL will listen for new web socket requests. In multi-schema configurations this value will need to be unique per schema type."),(0,l.yg)("admonition",{type:"info"},(0,l.yg)("p",{parentName:"admonition"},"Your subscriptions can share the same route as your general queries for a schema or be different, its up to you.")),(0,l.yg)("h3",{id:"supportedmessageprotocols"},"SupportedMessageProtocols"),(0,l.yg)("p",null,"When populated, represents a list of messaging protocol keys supported by this schema. A connected client MUST be able to communicate in one of the approved\nvalues or it will be dropped."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-csharp"},'// usage examples\nvar myProtocols = new Hashset<string>();\nmyProtocols.Add("protocol1");\nmyProtocols.Add("protocol2");\nserverOptions.SupportedMessageProtocols = myProtocols;\n')),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Default Value"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("inlineCode",{parentName:"td"},"null"))))),(0,l.yg)("blockquote",null,(0,l.yg)("p",{parentName:"blockquote"},"By default, ",(0,l.yg)("inlineCode",{parentName:"p"},"SupportedMessageProtocols")," is null; meaning any server supported protocol will be usable by the target schema. If set to an empty set, then the schema is effectively disabled as no supported protocols will be matched.")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b19420f8.a3de76e7.js b/assets/js/b19420f8.a3de76e7.js new file mode 100644 index 0000000..54bedaf --- /dev/null +++ b/assets/js/b19420f8.a3de76e7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[4231],{5680:(e,t,r)=>{r.d(t,{xA:()=>c,yg:()=>f});var n=r(6540);function o(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function a(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function i(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?a(Object(r),!0).forEach((function(t){o(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):a(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}function l(e,t){if(null==e)return{};var r,n,o=function(e,t){if(null==e)return{};var r,n,o={},a=Object.keys(e);for(n=0;n<a.length;n++)r=a[n],t.indexOf(r)>=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n<a.length;n++)r=a[n],t.indexOf(r)>=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var s=n.createContext({}),p=function(e){var t=n.useContext(s),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},c=function(e){var t=p(e.components);return n.createElement(s.Provider,{value:t},e.children)},u="mdxType",g={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var r=e.components,o=e.mdxType,a=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),u=p(r),d=o,f=u["".concat(s,".").concat(d)]||u[d]||g[d]||a;return r?n.createElement(f,i(i({ref:t},c),{},{components:r})):n.createElement(f,i({ref:t},c))}));function f(e,t){var r=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var a=r.length,i=new Array(a);i[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[u]="string"==typeof e?e:o,i[1]=l;for(var p=2;p<a;p++)i[p]=r[p];return n.createElement.apply(null,i)}return n.createElement.apply(null,r)}d.displayName="MDXCreateElement"},9075:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>a,metadata:()=>l,toc:()=>p});var n=r(8168),o=(r(6540),r(5680));const a={id:"overview",title:"Overview",sidebar_label:"Overview",sidebar_position:0,description:"A quick overview of how to use the library"},i=void 0,l={unversionedId:"quick/overview",id:"quick/overview",title:"Overview",description:"A quick overview of how to use the library",source:"@site/docs/quick/overview.md",sourceDirName:"quick",slug:"/quick/overview",permalink:"/docs/quick/overview",draft:!1,tags:[],version:"current",sidebarPosition:0,frontMatter:{id:"overview",title:"Overview",sidebar_label:"Overview",sidebar_position:0,description:"A quick overview of how to use the library"},sidebar:"tutorialSidebar",next:{title:"Your First App",permalink:"/docs/quick/create-app"}},s={},p=[{value:"Nuget & Installation",id:"nuget--installation",level:2},{value:"Other Helpful Pages",id:"other-helpful-pages",level:2}],c={toc:p};function u(e){let{components:t,...r}=e;return(0,o.yg)("wrapper",(0,n.A)({},c,r,{components:t,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"Use the menus on the left to navigate through the documentation. You do not need to read the various sections in order, feel free to use this as a reference guide as you dig deeper."),(0,o.yg)("h2",{id:"nuget--installation"},"Nuget & Installation"),(0,o.yg)("span",{className:"pill"},".NET Standard 2.0")," ",(0,o.yg)("span",{className:"pill"},".NET 8")," ",(0,o.yg)("span",{className:"pill"},".NET 9")," ",(0,o.yg)("br",null),(0,o.yg)("br",null),(0,o.yg)("p",null,"The library is available on ",(0,o.yg)("a",{parentName:"p",href:"https://www.nuget.org/packages/GraphQL.AspNet/"},"nuget")," and can be added to your project via the conventional means."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-powershell",metastring:'title="How to Install The Library"',title:'"How',to:!0,Install:!0,The:!0,'Library"':!0},"# Using the dotnet CLI\n> dotnet add package GraphQL.AspNet\n\n# Using Package Manager Console\n> Install-Package GraphQL.AspNet\n")),(0,o.yg)("span",{style:{fontSize:"1.1em"}},(0,o.yg)("p",null,"\ud83d\udc49 ",(0,o.yg)("a",{parentName:"p",href:"/docs/quick/create-app"},"Your First App"),": Step by step instructions for configuring app services and writing your first controller."),(0,o.yg)("p",null,"\ud83d\udc49 ",(0,o.yg)("a",{parentName:"p",href:"/docs/quick/code-examples"},"Code Examples"),": A few code snippets if you just want the gist of things.")),(0,o.yg)("h2",{id:"other-helpful-pages"},"Other Helpful Pages"),(0,o.yg)("p",null,"These pages may be helpful in getting started with the library:"),(0,o.yg)("span",{style:{fontSize:"1.1em"}},(0,o.yg)("p",null,"\ud83d\udca1 ",(0,o.yg)("a",{parentName:"p",href:"/docs/controllers/actions"},"Controllers & Actions")," : Everything you need to know about creating a ",(0,o.yg)("inlineCode",{parentName:"p"},"GraphController")," and defining action methods."),(0,o.yg)("p",null,"\ud83d\udcdc ",(0,o.yg)("a",{parentName:"p",href:"/docs/reference/attributes"},"Attributes")," : A reference list of the various ",(0,o.yg)("inlineCode",{parentName:"p"},"[Attributes]")," used to configure your controllers and models."),(0,o.yg)("p",null,"\ud83d\udcd0 ",(0,o.yg)("a",{parentName:"p",href:"/docs/reference/schema-configuration"},"Schema Configuration")," : A reference list of the various configuration options."),(0,o.yg)("p",null,"\ud83d\udccc ",(0,o.yg)("a",{parentName:"p",href:"/docs/reference/demo-projects"},"Demo Projects")," : A number of downloadable sample projects covering a wide range of topics")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b540f618.230cbcd1.js b/assets/js/b540f618.230cbcd1.js new file mode 100644 index 0000000..ec8735a --- /dev/null +++ b/assets/js/b540f618.230cbcd1.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[3715],{5680:(e,n,t)=>{t.d(n,{xA:()=>c,yg:()=>m});var r=t(6540);function i(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function a(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[n]?arguments[n]:{};n%2?o(Object(t),!0).forEach((function(n){i(e,n,t[n])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):o(Object(t)).forEach((function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))}))}return e}function l(e,n){if(null==e)return{};var t,r,i=function(e,n){if(null==e)return{};var t,r,i={},o=Object.keys(e);for(r=0;r<o.length;r++)t=o[r],n.indexOf(t)>=0||(i[t]=e[t]);return i}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r<o.length;r++)t=o[r],n.indexOf(t)>=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}var s=r.createContext({}),u=function(e){var n=r.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):a(a({},n),e)),t},c=function(e){var n=u(e.components);return r.createElement(s.Provider,{value:n},e.children)},d="mdxType",p={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},g=r.forwardRef((function(e,n){var t=e.components,i=e.mdxType,o=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),d=u(t),g=i,m=d["".concat(s,".").concat(g)]||d[g]||p[g]||o;return t?r.createElement(m,a(a({ref:n},c),{},{components:t})):r.createElement(m,a({ref:n},c))}));function m(e,n){var t=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var o=t.length,a=new Array(o);a[0]=g;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[d]="string"==typeof e?e:i,a[1]=l;for(var u=2;u<o;u++)a[u]=t[u];return r.createElement.apply(null,a)}return r.createElement.apply(null,t)}g.displayName="MDXCreateElement"},3984:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>a,default:()=>d,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var r=t(8168),i=(t(6540),t(5680));const o={id:"debugging",title:"Debugging Your Schema",sidebar_label:"Debugging",sidebar_position:0},a=void 0,l={unversionedId:"development/debugging",id:"development/debugging",title:"Debugging Your Schema",description:"Disable Field Asynchronousity",source:"@site/docs/development/debugging.md",sourceDirName:"development",slug:"/development/debugging",permalink:"/docs/development/debugging",draft:!1,tags:[],version:"current",sidebarPosition:0,frontMatter:{id:"debugging",title:"Debugging Your Schema",sidebar_label:"Debugging",sidebar_position:0},sidebar:"tutorialSidebar",previous:{title:"Malicious Queries",permalink:"/docs/execution/malicious-queries"},next:{title:"Unit Testing",permalink:"/docs/development/unit-testing"}},s={},u=[{value:"Disable Field Asynchronousity",id:"disable-field-asynchronousity",level:2}],c={toc:u};function d(e){let{components:n,...t}=e;return(0,i.yg)("wrapper",(0,r.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"disable-field-asynchronousity"},"Disable Field Asynchronousity"),(0,i.yg)("p",null,"GraphQL will execute sibling fields asynchronously during normal operation. This includes multiple top-level controller action calls. However, during a debugging session, having multiple fields trying to resolve themselves can play havoc with your debug cursor. If you've ever encountered a situation where the yellow line in Visual Studio seemly jumps around to random lines of code then you've experienced this issue."),(0,i.yg)("p",null,"At startup, it can help to disable asynchronous field resolution and instead force each field to execute in sequential order awaiting its completion before beginning the next one. "),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Configure Debug Mode"',title:'"Configure',Debug:!0,'Mode"':!0},"services.AddGraphQL(options =>\n{\n // Enable debug mode for the schema\n options.ExecutionOptions.DebugMode = true;\n});\n")),(0,i.yg)("admonition",{title:"Performance Killer",type:"danger"},(0,i.yg)("p",{parentName:"admonition"},"Don't forget to disable debug mode in production though. Awaiting fields individually will ",(0,i.yg)("em",{parentName:"p"},(0,i.yg)("strong",{parentName:"em"},"significantly"))," impact performance.")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b8ee5ddb.89a51733.js b/assets/js/b8ee5ddb.89a51733.js new file mode 100644 index 0000000..cdc68ff --- /dev/null +++ b/assets/js/b8ee5ddb.89a51733.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[2286],{5680:(e,r,t)=>{t.d(r,{xA:()=>c,yg:()=>h});var a=t(6540);function n(e,r,t){return r in e?Object.defineProperty(e,r,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[r]=t,e}function o(e,r){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);r&&(a=a.filter((function(r){return Object.getOwnPropertyDescriptor(e,r).enumerable}))),t.push.apply(t,a)}return t}function i(e){for(var r=1;r<arguments.length;r++){var t=null!=arguments[r]?arguments[r]:{};r%2?o(Object(t),!0).forEach((function(r){n(e,r,t[r])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):o(Object(t)).forEach((function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(t,r))}))}return e}function l(e,r){if(null==e)return{};var t,a,n=function(e,r){if(null==e)return{};var t,a,n={},o=Object.keys(e);for(a=0;a<o.length;a++)t=o[a],r.indexOf(t)>=0||(n[t]=e[t]);return n}(e,r);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a<o.length;a++)t=o[a],r.indexOf(t)>=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(n[t]=e[t])}return n}var s=a.createContext({}),u=function(e){var r=a.useContext(s),t=r;return e&&(t="function"==typeof e?e(r):i(i({},r),e)),t},c=function(e){var r=u(e.components);return a.createElement(s.Provider,{value:r},e.children)},p="mdxType",y={inlineCode:"code",wrapper:function(e){var r=e.children;return a.createElement(a.Fragment,{},r)}},d=a.forwardRef((function(e,r){var t=e.components,n=e.mdxType,o=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),p=u(t),d=n,h=p["".concat(s,".").concat(d)]||p[d]||y[d]||o;return t?a.createElement(h,i(i({ref:r},c),{},{components:t})):a.createElement(h,i({ref:r},c))}));function h(e,r){var t=arguments,n=r&&r.mdxType;if("string"==typeof e||n){var o=t.length,i=new Array(o);i[0]=d;var l={};for(var s in r)hasOwnProperty.call(r,s)&&(l[s]=r[s]);l.originalType=e,l[p]="string"==typeof e?e:n,i[1]=l;for(var u=2;u<o;u++)i[u]=t[u];return a.createElement.apply(null,i)}return a.createElement.apply(null,t)}d.displayName="MDXCreateElement"},3134:(e,r,t)=>{t.r(r),t.d(r,{assets:()=>s,contentTitle:()=>i,default:()=>p,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var a=t(8168),n=(t(6540),t(5680));const o={id:"vocabulary",title:"Vocabulary",sidebar_label:"Vocabulary",sidebar_position:11},i=void 0,l={unversionedId:"reference/vocabulary",id:"reference/vocabulary",title:"Vocabulary",description:"Fields & Resolvers",source:"@site/docs/reference/vocabulary.md",sourceDirName:"reference",slug:"/reference/vocabulary",permalink:"/docs/reference/vocabulary",draft:!1,tags:[],version:"current",sidebarPosition:11,frontMatter:{id:"vocabulary",title:"Vocabulary",sidebar_label:"Vocabulary",sidebar_position:11},sidebar:"tutorialSidebar",previous:{title:"Benchmarks",permalink:"/docs/reference/performance"}},s={},u=[{value:"Fields & Resolvers",id:"fields--resolvers",level:3},{value:"Graph Type",id:"graph-type",level:3},{value:"Query Document",id:"query-document",level:3},{value:"Root Graph Types",id:"root-graph-types",level:3},{value:"Schema",id:"schema",level:3}],c={toc:u};function p(e){let{components:r,...t}=e;return(0,n.yg)("wrapper",(0,a.A)({},c,t,{components:r,mdxType:"MDXLayout"}),(0,n.yg)("h3",{id:"fields--resolvers"},"Fields & Resolvers"),(0,n.yg)("p",null,"In GraphQL terms, a field is any requested piece of data (such as an id or name). A resolver fulfills the request for data from a schema field. It takes in a set of input arguments and produces a piece of data that is returned to the client. In GraphQL ASP.NET your controller methods act as resolvers for top level fields in any query."),(0,n.yg)("h3",{id:"graph-type"},"Graph Type"),(0,n.yg)("p",null,"A graph type is an entity on your object graph; a droid, a donut, a string, a number etc. In GraphQL ASP.NET your model classes, interfaces, enums, controllers etc. are compiled into the various graph types required by the runtime."),(0,n.yg)("h3",{id:"query-document"},"Query Document"),(0,n.yg)("p",null,"This is the raw query text submitted by a client. When GraphQL accepts a query it is converted from a string to an internal document format that is parsed and used to fulfill the request. "),(0,n.yg)("blockquote",null,(0,n.yg)("p",{parentName:"blockquote"},"Queries, Mutations and Subscriptions are all types of query documents.")),(0,n.yg)("h3",{id:"root-graph-types"},"Root Graph Types"),(0,n.yg)("p",null,'There are three root graph types in GraphQL: Query, Mutation, Subscription. Whenever you make a graphql request, you always specify which query root you are targeting. This documentation will usually refer to all operations as "queries" but this includes mutations and subscriptions as well.'),(0,n.yg)("h3",{id:"schema"},"Schema"),(0,n.yg)("p",null,"This is the set of public data types, their fields, input arguments etc. that are exposed on an object graph. When you write a graphql query to return data, the fields you request must all be defined on a schema that graphql will validate your query against. "),(0,n.yg)("p",null,'Your schema is "generated" at runtime by analyzing your model classes, controllers and action methods then populating a ',(0,n.yg)("inlineCode",{parentName:"p"},"GraphSchema")," container with the appropriate graph types to map graphql requests to your controllers. "),(0,n.yg)("admonition",{type:"note"},(0,n.yg)("p",{parentName:"admonition"}," In GraphQL ASP.NET the schema is generated at runtime directly from your C# controllers and POCOs; there is no additional boilerplate code necessary to define a schema.")))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c4f5d8e4.c84b1214.js b/assets/js/c4f5d8e4.c84b1214.js new file mode 100644 index 0000000..1d4426c --- /dev/null +++ b/assets/js/c4f5d8e4.c84b1214.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[2634],{1459:(e,n,t)=>{t.r(n),t.d(n,{default:()=>p});var r=t(6540),a=t(53),l=t(5489),s=t(4586),c=t(9408),i=t(7964);const o={heroBanner:"heroBanner_qdFl","main-buttons":"main-buttons_U_dc",buttons:"buttons_AeoN"};function m(){const{siteConfig:e}=(0,s.A)();return r.createElement("header",{className:(0,a.A)(o.heroBanner)},r.createElement("div",{className:"container"},r.createElement("h1",{className:"hero__title"},e.title," ",r.createElement("span",{className:"pill-header pill-small"},".NET 8+")),r.createElement("div",{className:o.buttons},r.createElement("div",{className:(0,a.A)("row",o["main-buttons"])},r.createElement("div",null,r.createElement(l.A,{className:"button button--primary button--lg",to:"/docs/quick/overview"},"Documentation"))))))}function u(){return r.createElement(r.Fragment,null,r.createElement("h2",{style:{fontSize:"2em"}},"Write a Controller"),r.createElement(i.A,{className:"language-csharp"},'// C# Controller \n[GraphRoute("groceryStore/bakery")]\npublic class BakeryController : GraphController \n{ \n [Query("pastries/search")]\n public IEnumerable<IPastry> SearchPastries(string text)\n { /* ... */ }\n\n [Query("pastries/recipe")]\n public async Task<Recipe> RetrieveRecipe(int id)\n { /* ... */ }\n\n [Query("breadCounter/orders")]\n public IEnumerable<BreadOrder> FindOrders(int id)\n { /* ... */ }\n}'))}function d(){return r.createElement(r.Fragment,null,r.createElement("h2",{style:{fontSize:"2em"}},"Execute a Query"),r.createElement(i.A,{className:"language-graphql"},"# GraphQL Query \nquery SearchGroceryStore($pastryName: String!) { \n groceryStore {\n bakery {\n pastries {\n search(text: $pastryName) {\n name\n type\n }\n recipe(id: 15) {\n name\n ingredients {\n name\n }\n } \n } \n } \n breadCounter {\n orders(id:36) {\n id \n items { \n id \n quantity \n } \n } \n } \n } \n}"))}function p(){const{siteConfig:e}=(0,s.A)();return r.createElement(c.A,{title:"",description:"A GraphQL library for ASP.NET developers."},r.createElement(m,null),r.createElement("main",null,r.createElement("section",{className:o.features},r.createElement("div",{className:"container"},r.createElement("div",{className:"row",style:{justifyContent:"space-evenly"}},r.createElement("div",null,r.createElement(u,null)),r.createElement("div",null,r.createElement(d,null)))))))}}}]); \ No newline at end of file diff --git a/assets/js/c8b5e9da.d94a376d.js b/assets/js/c8b5e9da.d94a376d.js new file mode 100644 index 0000000..b5828e7 --- /dev/null +++ b/assets/js/c8b5e9da.d94a376d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[4206],{5680:(e,n,t)=>{t.d(n,{xA:()=>y,yg:()=>m});var a=t(6540);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function l(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function i(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[n]?arguments[n]:{};n%2?l(Object(t),!0).forEach((function(n){r(e,n,t[n])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):l(Object(t)).forEach((function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))}))}return e}function o(e,n){if(null==e)return{};var t,a,r=function(e,n){if(null==e)return{};var t,a,r={},l=Object.keys(e);for(a=0;a<l.length;a++)t=l[a],n.indexOf(t)>=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(a=0;a<l.length;a++)t=l[a],n.indexOf(t)>=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var p=a.createContext({}),s=function(e){var n=a.useContext(p),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},y=function(e){var n=s(e.components);return a.createElement(p.Provider,{value:n},e.children)},u="mdxType",g={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},d=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,l=e.originalType,p=e.parentName,y=o(e,["components","mdxType","originalType","parentName"]),u=s(t),d=r,m=u["".concat(p,".").concat(d)]||u[d]||g[d]||l;return t?a.createElement(m,i(i({ref:n},y),{},{components:t})):a.createElement(m,i({ref:n},y))}));function m(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var l=t.length,i=new Array(l);i[0]=d;var o={};for(var p in n)hasOwnProperty.call(n,p)&&(o[p]=n[p]);o.originalType=e,o[u]="string"==typeof e?e:r,i[1]=o;for(var s=2;s<l;s++)i[s]=t[s];return a.createElement.apply(null,i)}return a.createElement.apply(null,t)}d.displayName="MDXCreateElement"},1748:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>i,default:()=>u,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var a=t(8168),r=(t(6540),t(5680));const l={id:"list-non-null",title:"List & Non-Null",sidebar_label:"List & Non-Null",sidebar_position:6},i=void 0,o={unversionedId:"types/list-non-null",id:"types/list-non-null",title:"List & Non-Null",description:"In addition to the six fundamental graph types, GraphQL contains two meta graph types: LIST and NON_NULL.",source:"@site/docs/types/list-non-null.md",sourceDirName:"types",slug:"/types/list-non-null",permalink:"/docs/types/list-non-null",draft:!1,tags:[],version:"current",sidebarPosition:6,frontMatter:{id:"list-non-null",title:"List & Non-Null",sidebar_label:"List & Non-Null",sidebar_position:6},sidebar:"tutorialSidebar",previous:{title:"Scalars",permalink:"/docs/types/scalars"},next:{title:"Subscriptions",permalink:"/docs/advanced/subscriptions"}},p={},s=[{value:"Type Expressions",id:"type-expressions",level:2},{value:"Overriding Type Expressions",id:"overriding-type-expressions",level:3}],y={toc:s};function u(e){let{components:n,...t}=e;return(0,r.yg)("wrapper",(0,a.A)({},y,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In addition to the six fundamental graph types, GraphQL contains two meta graph types: ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#lists-and-non-null"},"LIST and NON_NULL"),"."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NON_NULL")," : Indicates that the Graph Type its describing must not be a null value, be that as an input argument or returned from a field"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"LIST"),": Indicates that GraphQL should expect a collection of objects instead of just a single item.")),(0,r.yg)("p",null,'These meta types aren\'t anything concrete like a scalar or an enum. Instead they "wrap" another graph type (such as ',(0,r.yg)("inlineCode",{parentName:"p"},"int")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"Donut"),"). They are used to describe the usage of a graph type in a field or input argument:"),(0,r.yg)("p",null,"For example, we would say:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'"A field that returns a ',(0,r.yg)("inlineCode",{parentName:"li"},"Float"),' number."'),(0,r.yg)("li",{parentName:"ul"},'"A field that must return a ',(0,r.yg)("inlineCode",{parentName:"li"},"Person"),'."'),(0,r.yg)("li",{parentName:"ul"},'"An input argument that must be a ',(0,r.yg)("inlineCode",{parentName:"li"},"Date"),'."')),(0,r.yg)("p",null,"We can even describe complex scenarios:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'"A field that ',(0,r.yg)("strong",{parentName:"li"},"might")," return a collection of ",(0,r.yg)("inlineCode",{parentName:"li"},"persons")," but when returned, each person ",(0,r.yg)("strong",{parentName:"li"},"must"),' be a valid reference."'),(0,r.yg)("li",{parentName:"ul"},'"An input argument that ',(0,r.yg)("strong",{parentName:"li"},"must")," be a list that contains lists of ",(0,r.yg)("inlineCode",{parentName:"li"},"integers"),'." (e.g. ',(0,r.yg)("inlineCode",{parentName:"li"},"[[1, 2], [5, 15]]"),")")),(0,r.yg)("h2",{id:"type-expressions"},"Type Expressions"),(0,r.yg)("p",null,'Together these "wrappers" make up a field\'s ',(0,r.yg)("inlineCode",{parentName:"p"},"Type Expression"),". GraphQL ASP.NET will automatically infer a type expression for every field and every input argument when generating your schema."),(0,r.yg)("p",null,"The following assumptions about your data are made when creating type expressions:"),(0,r.yg)("p",null,"\u2705 Reference types ",(0,r.yg)("strong",{parentName:"p"},"can be")," null ",(0,r.yg)("br",null),"\n\u2705 Value types ",(0,r.yg)("strong",{parentName:"p"},"cannot be")," null ",(0,r.yg)("br",null),"\n\u2705 Nullable value types (e.g. ",(0,r.yg)("inlineCode",{parentName:"p"},"int?"),") ",(0,r.yg)("strong",{parentName:"p"},"can be")," null ",(0,r.yg)("br",null),"\n\u2705 When a reference type implements ",(0,r.yg)("inlineCode",{parentName:"p"},"IEnumerable<TType>"),' it will be expressed as a "list of ',(0,r.yg)("inlineCode",{parentName:"p"},"TType"),'"'),(0,r.yg)("p",null,"Type Expressions are commonly shown in the GraphQL schema syntax for field definitions. Here are a few examples of a .NET type and its equivalent type expression in schema syntax."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},".NET Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Type Expression"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"int"),(0,r.yg)("td",{parentName:"tr",align:null},"Int!")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"float?"),(0,r.yg)("td",{parentName:"tr",align:null},"Float")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"IEnumerable","<","Person",">"),(0,r.yg)("td",{parentName:"tr",align:null},"[Person]")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"Person[]"),(0,r.yg)("td",{parentName:"tr",align:null},"[Person]")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"List","<","bool",">"),(0,r.yg)("td",{parentName:"tr",align:null},"[Boolean!]")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"IReadOnlyList","<","long",">"),(0,r.yg)("td",{parentName:"tr",align:null},"[Long!]")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"IReadOnlyList","<","long?",">"),(0,r.yg)("td",{parentName:"tr",align:null},"[Long]")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"IEnumerable","<","List","<","ICollection","<","Donut",">",">",">"),(0,r.yg)("td",{parentName:"tr",align:null},"[[","[Donut]","]]")))),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},"The ",(0,r.yg)("inlineCode",{parentName:"p"},"!")," indicates NON_NULL and ",(0,r.yg)("inlineCode",{parentName:"p"},"[]")," for a LIST.")),(0,r.yg)("h3",{id:"overriding-type-expressions"},"Overriding Type Expressions"),(0,r.yg)("p",null,"You may need to override the default behavior from time to time. For instance, a ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),", which is a reference type, is nullable by default but you may want to enforce non-nullability at the query level and declare that null is not valid for a given argument. Or, perhaps, an object implements ",(0,r.yg)("inlineCode",{parentName:"p"},"IEnumerable")," but you don't want graphql to treat it as a list."),(0,r.yg)("p",null,"You can override the default type expression of any field or argument by defining a ",(0,r.yg)("a",{parentName:"p",href:"../advanced/type-expressions"},"custom type expression")," when needed."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c8cba33e.40089fc5.js b/assets/js/c8cba33e.40089fc5.js new file mode 100644 index 0000000..ceafe63 --- /dev/null +++ b/assets/js/c8cba33e.40089fc5.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[2053],{5680:(e,t,a)=>{a.d(t,{xA:()=>u,yg:()=>g});var n=a(6540);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t<arguments.length;t++){var a=null!=arguments[t]?arguments[t]:{};t%2?i(Object(a),!0).forEach((function(t){r(e,t,a[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(a)):i(Object(a)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(a,t))}))}return e}function l(e,t){if(null==e)return{};var a,n,r=function(e,t){if(null==e)return{};var a,n,r={},i=Object.keys(e);for(n=0;n<i.length;n++)a=i[n],t.indexOf(a)>=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n<i.length;n++)a=i[n],t.indexOf(a)>=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var s=n.createContext({}),p=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},u=function(e){var t=p(e.components);return n.createElement(s.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},h=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),d=p(a),h=r,g=d["".concat(s,".").concat(h)]||d[h]||c[h]||i;return a?n.createElement(g,o(o({ref:t},u),{},{components:a})):n.createElement(g,o({ref:t},u))}));function g(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=a.length,o=new Array(i);o[0]=h;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[d]="string"==typeof e?e:r,o[1]=l;for(var p=2;p<i;p++)o[p]=a[p];return n.createElement.apply(null,o)}return n.createElement.apply(null,a)}h.displayName="MDXCreateElement"},8153:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var n=a(8168),r=(a(6540),a(5680));const i={id:"how-it-works",title:"How it Works",sidebar_label:"How it Works",sidebar_position:0},o=void 0,l={unversionedId:"reference/how-it-works",id:"reference/how-it-works",title:"How it Works",description:'This document is a high level overview how GraphQL ASP.NET ultimately generates a response to a query with some insight into core details. Its assumes a working knowledge of both ASP.NET and the GraphQL specification. If you are only interested in the "how" and not the "why", feel free to skip this.',source:"@site/docs/reference/how-it-works.md",sourceDirName:"reference",slug:"/reference/how-it-works",permalink:"/docs/reference/how-it-works",draft:!1,tags:[],version:"current",sidebarPosition:0,frontMatter:{id:"how-it-works",title:"How it Works",sidebar_label:"How it Works",sidebar_position:0},sidebar:"tutorialSidebar",previous:{title:"File Uploads & Batching",permalink:"/docs/server-extensions/multipart-requests"},next:{title:"Schema Configuration",permalink:"/docs/reference/schema-configuration"}},s={},p=[{value:"Schema Generation",id:"schema-generation",level:2},{value:"Object Templating",id:"object-templating",level:4},{value:"Middleware Pipelines",id:"middleware-pipelines",level:4},{value:"Query Execution",id:"query-execution",level:2},{value:"Phase 1: Parsing & Validation",id:"phase-1-parsing--validation",level:3},{value:"Phase 2: Execution",id:"phase-2-execution",level:3},{value:"Resolving a Field",id:"resolving-a-field",level:4},{value:"Phase 3: Response Generation",id:"phase-3-response-generation",level:3},{value:"Other Points of Interest",id:"other-points-of-interest",level:2},{value:"Architectural Diagrams",id:"architectural-diagrams",level:2}],u={toc:p};function d(e){let{components:t,...i}=e;return(0,r.yg)("wrapper",(0,n.A)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},'This document is a high level overview how GraphQL ASP.NET ultimately generates a response to a query with some insight into core details. Its assumes a working knowledge of both ASP.NET and the GraphQL specification. If you are only interested in the "how" and not the "why", feel free to skip this.')),(0,r.yg)("h2",{id:"schema-generation"},"Schema Generation"),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"How it Works",src:a(5389).A,width:"1321",height:"399"})),(0,r.yg)("h4",{id:"object-templating"},"Object Templating"),(0,r.yg)("p",null,"When your application starts the runtime begins by inspecting the registered schemas declared in your ",(0,r.yg)("inlineCode",{parentName:"p"},"Startup.cs")," for the different options you've declared and sets off gathering a collection of the possible graph types that may be required."),(0,r.yg)("p",null,"For each type it discovers, it generates a template that describes ",(0,r.yg)("em",{parentName:"p"},"how")," you've asked GraphQL to use your classes. By inspecting declared attributes and the ",(0,r.yg)("inlineCode",{parentName:"p"},"System.Type")," metadata it generates the appropriate information to create everything GraphQL ASP.NET will need to fulfill a query. Information such as input and output parameters for methods, property types, custom type naming, implemented interfaces, union declarations, field path definitions, validation requirements and enforced authorization policies are all gathered and stored at the application level under the globally configured ",(0,r.yg)("inlineCode",{parentName:"p"},"IGraphTypeTemplateProvider"),"."),(0,r.yg)("p",null,"From this collection of metadata, GraphQL then generates the appropriate ",(0,r.yg)("inlineCode",{parentName:"p"},"IGraphType")," objects for each of your schemas based on their individual configurations. By default, this completed a ",(0,r.yg)("inlineCode",{parentName:"p"},"ISchema")," is stored as a singleton in your DI container."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"How does it know what objects to include?")),(0,r.yg)("p",null,"GraphQL ASP.NET has a few methods of determining what objects to include in your schema. By default, it will inspect your application (the entry assembly) for any public classes that inherit from ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphController")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphDirective")," and work from there. It checks every tagged query and mutation method, looks at every return value and every method parameter to find relevant scalars, enums and object types then inspects each one in turn, deeper and deeper down your object chain, to create a full map. It will even inspect the arbitrary C# interfaces implemented on each of your consumed objects. If that interface is ever used as a return type on an action method or a property, its automatically promoted to a graph type and included in the schema."),(0,r.yg)("p",null,"You have complete control of what to include. Be that including additional assemblies, preventing the inclusion of the startup assembly, manually specifying each model class and controller etc. Attributes exist such as ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphSkip]")," to exclude certain properties, methods or entire classes and limit the scope of the inclusion. On the other side of the fence, you can configure it to only accept classes with an explicitly declared ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphType]")," attribute, ignoring everything else. And for the most control, disable everything and manually call ",(0,r.yg)("inlineCode",{parentName:"p"},".AddType<T>()")," at startup for each class you want to have in your schema (controllers included). GraphQL will then happily generate declaration errors when it can't find a reference declared in your controllers. This can be an effective technique in spotting data leaks or rogue methods that slipped through a code review. Configure a unit test to generate a schema with different inclusion rules per environment and you now have an automatic CI/CD check in place to give your developers more freedom to write code during a sprint and only have to worry about configurations when submitting a PR."),(0,r.yg)("p",null,"You can even go so far as to add a class to the schema but prevent its publication in introspection queries which can provide some helpful obfuscation. Alternatively, just disable introspection queries altogether. While this does cause client tooling to complain endlessly and makes front-end development much harder; if you and your consumers (like your UI) can agree ahead of time on the query syntax then there is no issue."),(0,r.yg)("h4",{id:"middleware-pipelines"},"Middleware Pipelines"),(0,r.yg)("p",null,"Similar to how ASP.NET utilizes a middleware pipeline to fulfill an HTTP request, GraphQL ASP.NET follows suit to fulfill a graphQL request. Major tasks like validation, parsing, field resolution and result packaging are just ",(0,r.yg)("a",{parentName:"p",href:"../reference/middleware"},"middleware components")," added to a chain of tasks and executed to complete the operation."),(0,r.yg)("p",null,"At the same time as its constructing your schema, GraphQL sets up the 4 primary pipelines and stores them in the DI container as an ",(0,r.yg)("inlineCode",{parentName:"p"},"ISchemaPipeline<TSchema, TContext>"),". Each pipeline can be extended, reworked or completely replaced as needed for your use case."),(0,r.yg)("h2",{id:"query-execution"},"Query Execution"),(0,r.yg)("p",null,"Query execution can be broken down into a series of phaes. For the sake of brevity we've left out the HTTP request steps required to invoke the GraphQL runtime but you can inspect the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet/Defaults/DefaultGraphQLHttpProcessor%7BTSchema%7D.cs"},"DefaultGraphQLHttpProcessor")," and read through the code. "),(0,r.yg)("p",null,'Note: The concept of a phase here is just for organizing the information, there is no concrete "phase" value managed by the pipelines.'),(0,r.yg)("h3",{id:"phase-1-parsing--validation"},"Phase 1: Parsing & Validation"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="HeroController.cs"',title:'"HeroController.cs"'},'public class HeroController : GraphController\n{\n [QueryRoot("hero")]\n public Human RetrieveHero(Episode episode)\n {\n if(episode == Episode.Empire)\n {\n return new Human()\n {\n Id = 1000,\n Name = "Han Solo",\n HomePlanet = "Corellia",\n }\n }\n else\n {\n return new Human()\n {\n Id = 1001,\n Name = "Luke SkyWalker",\n HomePlanet = "Tatooine",\n }\n }\n }\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query {\n hero(episode: EMPIRE) {\n name\n homePlanet\n }\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="Response JSON"',title:'"Response','JSON"':!0},'{\n "data" : {\n "hero": {\n "name" : "Han Solo",\n "homePlanet" : "Corellia"\n }\n }\n}\n')),(0,r.yg)("p",null,(0,r.yg)("em",{parentName:"p"},"Sample query used as a reference example in this section")),(0,r.yg)("p",null,"The supplied query document (top right in the example) is ran through a compilation cycle to ultimately generate an ",(0,r.yg)("inlineCode",{parentName:"p"},"IQueryExecutionPlan"),". It is first lexed into a series of tokens representing the various parts; things like curly braces, colons, strings etc. Then it parses those tokens into a collection of ",(0,r.yg)("inlineCode",{parentName:"p"},"SyntaxNodes")," (creating an Abstract Syntax Tree) representing concepts like ",(0,r.yg)("inlineCode",{parentName:"p"},"FieldNode"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"InputValueNode"),", and ",(0,r.yg)("inlineCode",{parentName:"p"},"OperationTypeNode")," following the ",(0,r.yg)("a",{parentName:"p",href:"https://spec.graphql.org/October2021/#sec-Source-Text"},"graphql specification rules for source text documents"),"."),(0,r.yg)("p",null,"Once parsed, the runtime will execute its internal rules engine against the generated ",(0,r.yg)("inlineCode",{parentName:"p"},"ISyntaxTree"),", using the targeted ",(0,r.yg)("inlineCode",{parentName:"p"},"ISchema"),", to create a query plan where it marries the nodes AST with concrete structures such as controllers, action methods and POCOs. It is at this stage where the ",(0,r.yg)("inlineCode",{parentName:"p"},"hero")," field in the example is matched to the ",(0,r.yg)("inlineCode",{parentName:"p"},"HeroController")," with its appropriate ",(0,r.yg)("inlineCode",{parentName:"p"},"IGraphFieldResolver")," to invoke the ",(0,r.yg)("inlineCode",{parentName:"p"},"RetrieveHero")," action method."),(0,r.yg)("p",null,"While generating a query plan the rules engine will do its best to complete an analysis of the entire document and return to the requestor every error it finds. Depending on the errors though, it may or may not be able to catch them all. For instance, a syntax error, like a missing ",(0,r.yg)("inlineCode",{parentName:"p"},"}"),", will preclude generating a query plan so errors centered around invalid field names or a type expression mismatch won't be caught until the syntax error is fixed (just like any other compiler)."),(0,r.yg)("h3",{id:"phase-2-execution"},"Phase 2: Execution"),(0,r.yg)("p",null,"The engine now has a completed query plan that describes:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The named operation in the document to be executed (or the single anonymous operation in the example above)"),(0,r.yg)("li",{parentName:"ul"},"The top level fields and every child field on the chosen operation.")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Its successfully validated that:")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"All the referenced fields for the graph types exist and are valid where requested"),(0,r.yg)("li",{parentName:"ul"},'Required input arguments have been supplied and their data is "resolvable"',(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},"This just means that we've validated that a number is a number, named fields on input objects exist etc.")))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"For each field, the runtime will:")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Substitute any deferred input arguments with referenced variable data"),(0,r.yg)("li",{parentName:"ul"},"Generate a field execution context containing the necessary data about the source data, arguments and resolver."),(0,r.yg)("li",{parentName:"ul"},"Authenticate the user to the field."),(0,r.yg)("li",{parentName:"ul"},"Execute the resolver to fetch a data value."),(0,r.yg)("li",{parentName:"ul"},"Invoke any child fields requested.")),(0,r.yg)("h4",{id:"resolving-a-field"},"Resolving a Field"),(0,r.yg)("p",null,'GraphQL uses the phrase "resolver" to describe a method that, for a given field, takes in parameters and generates a result.'),(0,r.yg)("p",null,"At startup, GraphQL ASP.NET automatically creates resolver references for your controller methods, POCO properties and tagged POCO methods. These are nothing more than delegates for method invocations; for properties it uses the getter registered with ",(0,r.yg)("inlineCode",{parentName:"p"},"PropertyInfo"),"."),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},(0,r.yg)("strong",{parentName:"p"},(0,r.yg)("em",{parentName:"strong"},"Performance Note")),": The library makes heavy use of compiled Expression Trees to call its resolvers and for instantiating input objects. As a result, its many orders of magnitude faster than baseline reflected calls and nearly as performant as precompiled code.")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Concerning Proxy Libraries (e.g. EF Core Proxies)")),(0,r.yg)("p",null,"GraphQL ASP.NET natively supports Liskov substitutions for all graph types opening up the possibility for using libraries such as EF Proxies that can provide a tremendously powerful and easy to setup graph structure. By lazy loading any child collections you can expose access to your entire domain model with very little work."),(0,r.yg)("p",null,"Be careful though, ",(0,r.yg)("a",{parentName:"p",href:"https://docs.microsoft.com/en-us/ef/core/querying/related-data/lazy"},"EF Core Proxies")," and like libraries suffer from the same N + 1 problem as GraphQL libraries do. By choosing to use such lazy loading techniques you are circumventing GraphQL ASP.NET's provided mechanisms for handling such situations and need to be exceptionally careful to manage the issue yourself."),(0,r.yg)("h3",{id:"phase-3-response-generation"},"Phase 3: Response Generation"),(0,r.yg)("p",null,"Once all fields have been processed the runtime makes a final pass to propagate any nullability errors up the field chain resulting in a final data set. This data set is then passed to the ",(0,r.yg)("inlineCode",{parentName:"p"},"IGraphResponseWriter")," registered for the schema and the result is serialized to the HttpResponse."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Writing a response includes:")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Serializing the ",(0,r.yg)("inlineCode",{parentName:"p"},"errors")," generated during execution"),(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},"All error messages have a severity level. Your schema configuration controls which are sent to the client."),(0,r.yg)("li",{parentName:"ul"},"You can add your own error messages through your controller actions by returning ",(0,r.yg)("inlineCode",{parentName:"li"},"this.Error()"),"."),(0,r.yg)("li",{parentName:"ul"},"Error messages may or may not have associated exception data which may or may not be exposed to the client depending on who they are or the schema configuration settings."))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Serializing the ",(0,r.yg)("inlineCode",{parentName:"p"},"data")," field to the response stream")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Serializing the ",(0,r.yg)("inlineCode",{parentName:"p"},"extensions")," field (such as metrics data) to the response stream"))),(0,r.yg)("h2",{id:"other-points-of-interest"},"Other Points of Interest"),(0,r.yg)("p",null,"Hopefully we've given you a bit of insight into how the library works under the hood. The other documents on this site go into exhaustive detail of the different features and how to use them but since you're here:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The library targets ",(0,r.yg)("a",{parentName:"li",href:"https://docs.microsoft.com/en-us/dotnet/standard/net-standard"},(0,r.yg)("inlineCode",{parentName:"a"},"netstandard2.0"))," and ",(0,r.yg)("inlineCode",{parentName:"li"},"net6.0"),".",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},"Out of the box there are no external dependencies beyond official Microsoft packages.")))),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Every core component and all ",(0,r.yg)("a",{parentName:"p",href:"/docs/reference/middleware"},"middleware components")," required to complete the tasks outlined in this document are referenced through dependency injection. Any one of them (or all of them) can be overridden and extended to do whatever you want as long as you register them prior to calling ",(0,r.yg)("inlineCode",{parentName:"p"},".AddGraphQL()")," at startup."),(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},"Inject your own ",(0,r.yg)("inlineCode",{parentName:"li"},"IGraphResponseWriter")," to serialize your results to XML or CSV."),(0,r.yg)("li",{parentName:"ul"},"Build your own ",(0,r.yg)("inlineCode",{parentName:"li"},"IOperationComplexityCalculator")," to intercept and alter how a query plan generates its ",(0,r.yg)("a",{parentName:"li",href:"../execution/malicious-queries"},"complexity values")," to be more suitable to your needs."),(0,r.yg)("li",{parentName:"ul"},"On and on and on...")))),(0,r.yg)("h2",{id:"architectural-diagrams"},"Architectural Diagrams"),(0,r.yg)("p",null,"\ud83d\udccc ",(0,r.yg)("a",{target:"_blank",href:a(1220).A},"Structural Diagrams")),(0,r.yg)("p",null,"A set of diagrams outlining the major interfaces and classes that make up GraphQL Asp.Net."),(0,r.yg)("p",null,"\ud83d\udccc ",(0,r.yg)("a",{target:"_blank",href:a(9581).A},"Execution Diagrams")),(0,r.yg)("p",null,"A set of flowcharts and relational diagrams showing how various aspects of the library fit together at run time, including the query execution and field execution pipelines."))}d.isMDXComponent=!0},9581:(e,t,a)=>{a.d(t,{A:()=>n});const n=a.p+"assets/files/2022-10-graphql-aspnet-execution-diagrams-82ed1c157484c18822b05a009f20768d.pdf"},1220:(e,t,a)=>{a.d(t,{A:()=>n});const n=a.p+"assets/files/2022-10-graphql-aspnet-structural-diagrams-2ce89a3328b83ac31eb4dac13986550a.pdf"},5389:(e,t,a)=>{a.d(t,{A:()=>n});const n=a.p+"assets/images/how-it-works-1-8cf26d6348f0ddb7a1e9d22117fa3045.png"}}]); \ No newline at end of file diff --git a/assets/js/cc10add0.ae40f07c.js b/assets/js/cc10add0.ae40f07c.js new file mode 100644 index 0000000..65b7fac --- /dev/null +++ b/assets/js/cc10add0.ae40f07c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[7937],{5680:(e,t,n)=>{n.d(t,{xA:()=>c,yg:()=>g});var i=n(6540);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function o(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?a(Object(n),!0).forEach((function(t){r(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):a(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function s(e,t){if(null==e)return{};var n,i,r=function(e,t){if(null==e)return{};var n,i,r={},a=Object.keys(e);for(i=0;i<a.length;i++)n=a[i],t.indexOf(n)>=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(i=0;i<a.length;i++)n=a[i],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=i.createContext({}),u=function(e){var t=i.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},c=function(e){var t=u(e.components);return i.createElement(l.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},h=i.forwardRef((function(e,t){var n=e.components,r=e.mdxType,a=e.originalType,l=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),p=u(n),h=r,g=p["".concat(l,".").concat(h)]||p[h]||d[h]||a;return n?i.createElement(g,o(o({ref:t},c),{},{components:n})):i.createElement(g,o({ref:t},c))}));function g(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=n.length,o=new Array(a);o[0]=h;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[p]="string"==typeof e?e:r,o[1]=s;for(var u=2;u<a;u++)o[u]=n[u];return i.createElement.apply(null,o)}return i.createElement.apply(null,n)}h.displayName="MDXCreateElement"},1839:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>p,frontMatter:()=>a,metadata:()=>s,toc:()=>u});var i=n(8168),r=(n(6540),n(5680));const a={id:"subscriptions",title:"Subscriptions",sidebar_label:"Subscriptions",sidebar_position:0},o=void 0,s={unversionedId:"advanced/subscriptions",id:"advanced/subscriptions",title:"Subscriptions",description:"Initial Setup",source:"@site/docs/advanced/subscriptions.md",sourceDirName:"advanced",slug:"/advanced/subscriptions",permalink:"/docs/advanced/subscriptions",draft:!1,tags:[],version:"current",sidebarPosition:0,frontMatter:{id:"subscriptions",title:"Subscriptions",sidebar_label:"Subscriptions",sidebar_position:0},sidebar:"tutorialSidebar",previous:{title:"List & Non-Null",permalink:"/docs/types/list-non-null"},next:{title:"Type Expressions",permalink:"/docs/advanced/type-expressions"}},l={},u=[{value:"Initial Setup",id:"initial-setup",level:2},{value:"Install the Subscriptions Package",id:"install-the-subscriptions-package",level:3},{value:"Configure the Server Instance",id:"configure-the-server-instance",level:3},{value:"Create a Subscription",id:"create-a-subscription",level:3},{value:"Publish a Subscription Event",id:"publish-a-subscription-event",level:3},{value:"Subscription Event Data Source",id:"subscription-event-data-source",level:3},{value:"Summary",id:"summary",level:3},{value:"Apollo Client Example",id:"apollo-client-example",level:3},{value:"Subscription Action Results",id:"subscription-action-results",level:2},{value:"Scaling Subscription Servers",id:"scaling-subscription-servers",level:2},{value:"Custom Event Publishing",id:"custom-event-publishing",level:3},{value:"Implement <code>ISubscriptionEventPublisher</code>",id:"implement-isubscriptioneventpublisher",level:4},{value:"Consuming Published Events",id:"consuming-published-events",level:3},{value:"Azure Service Bus Example",id:"azure-service-bus-example",level:3},{value:"Partial Server Configuration",id:"partial-server-configuration",level:2},{value:"Security & Query Authorization",id:"security--query-authorization",level:2},{value:"Query Timeouts",id:"query-timeouts",level:2},{value:"Websocket Protocols",id:"websocket-protocols",level:2},{value:"Supported Protocols",id:"supported-protocols",level:3},{value:"Creating Custom Protocols",id:"creating-custom-protocols",level:3},{value:"Other Communication Options",id:"other-communication-options",level:2},{value:"Performance Considerations",id:"performance-considerations",level:2},{value:"Event Multiplication",id:"event-multiplication",level:3},{value:"Dispatch Queue Monitoring",id:"dispatch-queue-monitoring",level:3},{value:"Default Event Alert Threshold",id:"default-event-alert-threshold",level:4},{value:"Custom Event Alert Thresholds",id:"custom-event-alert-thresholds",level:4},{value:"General Tips",id:"general-tips",level:2}],c={toc:u};function p(e){let{components:t,...a}=e;return(0,r.yg)("wrapper",(0,i.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"initial-setup"},"Initial Setup"),(0,r.yg)("p",null,"Successfully handling subscriptions in your GraphQL server can be straight forward for single server environments or very complicated for multi-server and scalable solutions. First we'll look at adding subscriptions for a single server."),(0,r.yg)("h3",{id:"install-the-subscriptions-package"},"Install the Subscriptions Package"),(0,r.yg)("p",null,"The first step to using subscriptions is to install the subscription server package."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-powershell",metastring:'title="Install The Library"',title:'"Install',The:!0,'Library"':!0},"# Using the dotnet CLI\n> dotnet add package GraphQL.AspNet.Subscriptions\n\n# using Package Manager Console\n> Install-Package GraphQL.AspNet.Subscriptions\n")),(0,r.yg)("p",null,"This adds the necessary components to create a subscription server for a given schema such as communicating with web sockets, parsing subscription queries and responding to events."),(0,r.yg)("h3",{id:"configure-the-server-instance"},"Configure the Server Instance"),(0,r.yg)("p",null,"You must configure web socket support for your Asp.Net server instance separately. The ways in which you perform this configuration will vary widely depending on your CORS requirements, keep-alive support and other needs."),(0,r.yg)("p",null,"After web sockets are added to your server, add subscription support to the graphql registration."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Add Subscription Support at Startup"',title:'"Add',Subscription:!0,Support:!0,at:!0,'Startup"':!0},"// configuring services at startup\n// ---------------\nservices.AddWebSockets(/*...*/);\n\nservices.AddGraphQL()\n// highlight-next-line\n .AddSubscriptions();\n\n// building the application pipeline\n// ---------------\napp.UseWebSockets();\n// highlight-next-line\napp.UseGraphQL(); \n")),(0,r.yg)("admonition",{type:"tip"},(0,r.yg)("p",{parentName:"admonition"}," Don't forget to call ",(0,r.yg)("inlineCode",{parentName:"p"},".UseWebsockets()")," before calling ",(0,r.yg)("inlineCode",{parentName:"p"},".UseGraphQL()"),". ")),(0,r.yg)("h3",{id:"create-a-subscription"},"Create a Subscription"),(0,r.yg)("p",null,"Declaring a subscription is the same as declaring a query or mutation on a controller but with ",(0,r.yg)("inlineCode",{parentName:"p"},"[Subscription]")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"[SubscriptionRoot]")," attributes. Feel free to mix subscriptions with your queries and mutations. They do not need to be kept seperate."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="SubscriptionController.cs"',title:'"SubscriptionController.cs"'},'public class SubscriptionController : GraphController\n{\n // other code not shown for brevity\n\n // highlight-next-line\n [SubscriptionRoot("onWidgetChanged", typeof(Widget), EventName = "WIDGET_CHANGED")]\n public IGraphActionResult OnWidgetChanged(Widget eventData, string filter)\n {\n if(eventData.Name.StartsWith(filter))\n {\n // send the data down to the listening client\n return this.Ok(eventData);\n }\n else\n {\n // use SkipSubscriptionEvent() to disregard the data\n // and not communicate anything to the listening client \n return this.SkipSubscriptionEvent();\n }\n }\n}\n')),(0,r.yg)("p",null,"Here we've declared a new subscription, one that takes in a ",(0,r.yg)("inlineCode",{parentName:"p"},"filter")," parameter to restrict the data that any subscribers receive."),(0,r.yg)("p",null,"A query to invoke this subscription may look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Subscription Query"',title:'"Sample',Subscription:!0,'Query"':!0},'subscription {\n onWidgetChanged(filter: "Big"){\n id\n name\n description\n }\n}\n')),(0,r.yg)("p",null,'Any updated widgets that start with the phrase "Big" will then be sent to the requestor as they are changed on the server. Any other changed widgets will be skipped/dropped and no data will be sent to the client.'),(0,r.yg)("h3",{id:"publish-a-subscription-event"},"Publish a Subscription Event"),(0,r.yg)("p",null,"In order for the subscription server to send data to any subscribers it has to be notified when its time to do so. It does this via named Subscription Events. These are internal, schema-unique keys (strings) that identify when something happened, usually via a mutation. Once the mutation publishes an event, the subscription server will execute the appropriate action method for any subscribers, using the supplied data, and deliver the results to the client."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="MutationController.cs"',title:'"MutationController.cs"'},'public class MutationController : GraphController\n{\n // other code not shown for brevity\n\n [MutationRoot("updateWidget", typeof(Widget))]\n public async Task<IGraphActionResult> OnWidgetChanged(int id, string name){\n var widget = _service.RetrieveWidget(id);\n widget.Name = name;\n\n await _service.UpdateWidget(widget);\n\n // publish a new event to let any subscribers know\n // something changed\n // highlight-next-line\n this.PublishSubscriptionEvent("WIDGET_CHANGED", widget);\n return this.Ok(widget);\n }\n}\n')),(0,r.yg)("admonition",{title:"Event Names Must Match",type:"info"},(0,r.yg)("p",{parentName:"admonition"}," Notice that the event name used in ",(0,r.yg)("inlineCode",{parentName:"p"},"PublishSubscriptionEvent()")," is the same as the ",(0,r.yg)("inlineCode",{parentName:"p"},"EventName")," property on the ",(0,r.yg)("inlineCode",{parentName:"p"},"[SubscriptionRoot]")," attribute above. This is how the subscription server knows what published event is tied to which subscription. This value is case-sensitive.")),(0,r.yg)("h3",{id:"subscription-event-data-source"},"Subscription Event Data Source"),(0,r.yg)("p",null,"In the example above, the data sent with ",(0,r.yg)("inlineCode",{parentName:"p"},"PublishSubscriptionEvent()")," is the same as the first input parameter, called ",(0,r.yg)("inlineCode",{parentName:"p"},"eventData"),", on the subscription method, which is the same as the return type of the subscription method. By default, GraphQL will look for a parameter with the same data type as the method's own return type and use that as the event data source. It will automatically populate this field with the data from ",(0,r.yg)("inlineCode",{parentName:"p"},"PublishSubscriptionEvent()"),"; this argument is not exposed in the object graph."),(0,r.yg)("p",null,"You can explicitly flag a different parameter, or a parameter of a different data type to be the expected event source with the ",(0,r.yg)("inlineCode",{parentName:"p"},"[SubscriptionSource]")," attribute."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Custom Event Data Source"',title:'"Custom',Event:!0,Data:!0,'Source"':!0},'public class SubscriptionController : GraphController\n{\n [SubscriptionRoot("onWidgetChanged", typeof(Widget), EventName = "WIDGET_CHANGED")]\n public IGraphActionResult OnWidgetChanged(\n // highlight-next-line\n [SubscriptionSource] WidgetInternal eventData,\n string filter)\n {\n if(eventData.Name.StartsWith(filter))\n return this.Ok(eventData.ToWidget());\n return this.SkipSubscriptionEvent();\n }\n}\n')),(0,r.yg)("p",null,"Here the subscription expects that an event is published using a ",(0,r.yg)("inlineCode",{parentName:"p"},"WidgetInternal")," object that it will convert to a ",(0,r.yg)("inlineCode",{parentName:"p"},"Widget")," and send to any subscribers. This can be useful if you wish to share internal objects between your mutations and subscriptions that you don't want publicly exposed."),(0,r.yg)("admonition",{title:"Event Data Objects Must Match",type:"info"},(0,r.yg)("p",{parentName:"admonition"}," The data object published with ",(0,r.yg)("inlineCode",{parentName:"p"},"PublishSubscriptionEvent()")," must have the same type as the ",(0,r.yg)("inlineCode",{parentName:"p"},"[SubscriptionSource]")," on the subscription field.")),(0,r.yg)("h3",{id:"summary"},"Summary"),(0,r.yg)("p",null,"That's all there is for a basic subscription server setup:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Add the package reference and update your startup code."),(0,r.yg)("li",{parentName:"ol"},"Declare a new subscription on a controller using ",(0,r.yg)("inlineCode",{parentName:"li"},"[Subscription]")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"[SubscriptionRoot]"),"."),(0,r.yg)("li",{parentName:"ol"},"Publish an event (usually from a mutation).")),(0,r.yg)("h3",{id:"apollo-client-example"},"Apollo Client Example"),(0,r.yg)("p",null,"\ud83d\udccc A complete example of single instance subscription server including a react app that utilizes the Apollo Client is available in the ",(0,r.yg)("a",{parentName:"p",href:"../reference/demo-projects"},"demo projects")," section."),(0,r.yg)("h2",{id:"subscription-action-results"},"Subscription Action Results"),(0,r.yg)("p",null,"You saw above the special action result ",(0,r.yg)("inlineCode",{parentName:"p"},"SkipSubscriptionEvent()")," used to instruct graphql to skip the received event and not tell the client about it; this can be very useful in scenarios where the subscription supplies filter data to only receive some very specific data and not all items published via a specific event."),(0,r.yg)("p",null,'Here is a complete list of the various "subscription specific" action results:'),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"SkipSubscriptionEvent()")," - Instructs the server to skip the raised event, the client will not receive any data."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"OkAndComplete(data)")," - Works just like ",(0,r.yg)("inlineCode",{parentName:"li"},"this.Ok()")," but ends the subscription after the event is completed. The client is informed that no additional data will be sent and that the server is closing the subscription permanently. This, however; does not close the underlying websocket connection.")),(0,r.yg)("admonition",{title:"Be Careful With Sensitive Data",type:"danger"},(0,r.yg)("p",{parentName:"admonition"},"All active subscriptions, from all connected users, have an opportunity to handle data published via ",(0,r.yg)("inlineCode",{parentName:"p"},"PublishSubscriptionEvent()"),"."),(0,r.yg)("p",{parentName:"admonition"},"If there are scenarios where an event payload should not be shared with a user, be sure to enforce that business logic in your subscription method and use ",(0,r.yg)("inlineCode",{parentName:"p"},"SkipSubscriptionEvent()")," for a given payload. ")),(0,r.yg)("h2",{id:"scaling-subscription-servers"},"Scaling Subscription Servers"),(0,r.yg)("p",null,"Using web sockets has a natural limitation in that any single server instance has a maximum number of socket connections that it can realistically handle before being overloaded. Additionally, all cloud providers impose an artifical limit for many of their pricing tiers. Once that limit is reached no additional clients can connect, even if the server has additional capacity."),(0,r.yg)("p",null,"Ok no problem, just scale horizontally, right? Spin up additional server instances, add a load balancer and have the new requests open a web socket connection to these additional server instances...Not so fast!"),(0,r.yg)("p",null,"With the examples above, events published by any mutation using ",(0,r.yg)("inlineCode",{parentName:"p"},"PublishSubscriptionEvent()")," are routed internally, directly to the local subscription server meaning only those clients connected to the server instance where the event was raised will receive it. Clients connected to other server instances will never know the event occured. This represents a big problem for large scale websites, so what do we do?"),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},(0,r.yg)("a",{target:"_blank",href:n(1662).A},"This diagram")," shows a high level difference between the default, single server configuration and a custom scalable solution.")),(0,r.yg)("h3",{id:"custom-event-publishing"},"Custom Event Publishing"),(0,r.yg)("p",null,"Instead of publishing events internally, within the server instance, we need to publish our events to some intermediate source such that any server can be notified of the change. There are a variety of technologies to handle this scenario; be it a common database or messaging technologies like RabbitMQ, Azure Service Bus etc."),(0,r.yg)("h4",{id:"implement-isubscriptioneventpublisher"},"Implement ",(0,r.yg)("inlineCode",{parentName:"h4"},"ISubscriptionEventPublisher")),(0,r.yg)("p",null,"Whatever your technology of choice the first step is to create and register a custom publisher that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"ISubscriptionEventPublisher"),". How your publisher class functions will vary widely depending on your implementation."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="ISubscriptionEventPublisher.cs"',title:'"ISubscriptionEventPublisher.cs"'},"public interface ISubscriptionEventPublisher\n{\n Task PublishEventAsync(SubscriptionEvent eventData);\n}\n")),(0,r.yg)("p",null,"Register your publisher with the DI container BEFORE calling ",(0,r.yg)("inlineCode",{parentName:"p"},".AddGraphQL()")," and GraphQL ASP.NET will use your registered publisher instead of its default, internal publisher."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Register Your Event Publisher"',title:'"Register',Your:!0,Event:!0,'Publisher"':!0},"// highlight-next-line\nservices.AddSingleton<ISubscriptionEventPublisher, MyEventPublisher>();\n\nservices.AddGraphQL()\n .AddSubscriptions();\n")),(0,r.yg)("admonition",{type:"caution"},(0,r.yg)("p",{parentName:"admonition"},"Publishing Subscription Events externally is not trivial. You'll have to deal with concerns like data serialization, package size etc. All of which are specific to your application and environment.")),(0,r.yg)("h3",{id:"consuming-published-events"},"Consuming Published Events"),(0,r.yg)("p",null,"At this point, we've successfully published our events to some external data source. Now we need to consume them. How that occurs is, again, implementation specific. Perhaps you run a background hosted service to watch for messages on an Azure Service Bus topic or perhaps you periodically pole a database table to look for new events. The ways in which data may be shared is endless."),(0,r.yg)("p",null,"Once you rematerialize a ",(0,r.yg)("inlineCode",{parentName:"p"},"SubscriptionEvent")," you need to let GraphQL know that it occurred. this is done using the ",(0,r.yg)("inlineCode",{parentName:"p"},"ISubscriptionEventRouter"),". In general, you won't need to implement your own router, just inject it into your listener then call ",(0,r.yg)("inlineCode",{parentName:"p"},"RaisePublishedEvent")," and GraphQL will take it from there."),(0,r.yg)("p",null,"The router will take care of the details in figuring out which schema the event is destined for, which clients have active subscriptions etc. and forward it accordingly."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Example Custom Event Listener Service"',title:'"Example',Custom:!0,Event:!0,Listener:!0,'Service"':!0}," public class MyListenerService : BackgroundService\n {\n private readonly ISubscriptionEventRouter _router;\n private bool _notStopped = true;\n\n public MyListenerService(ISubscriptionEventRouter router)\n {\n _router = router;\n }\n\n protected override async Task ExecuteAsync(CancellationToken cancelToken)\n {\n while (_notStopped)\n {\n SubscriptionEvent eventData = /* fetch next event */;\n // highlight-next-line\n _router.RaisePublishedEvent(eventData);\n }\n }\n }\n")),(0,r.yg)("admonition",{type:"info"},(0,r.yg)("p",{parentName:"admonition"},"The above example is made using ",(0,r.yg)("a",{parentName:"p",href:"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-7.0&tabs=visual-studio"},(0,r.yg)("inlineCode",{parentName:"a"},"Background Services")),". A feature of the modern ASP.NET stack.")),(0,r.yg)("h3",{id:"azure-service-bus-example"},"Azure Service Bus Example"),(0,r.yg)("p",null,"\ud83d\udccc A functional example, including serialization and deserialization using the Azure Service Bus is available in the ",(0,r.yg)("a",{parentName:"p",href:"../reference/demo-projects"},"demo projects")," section."),(0,r.yg)("admonition",{type:"note"},(0,r.yg)("p",{parentName:"admonition"}," The Azure Service Bus demo project represents a functional starting point and lacks a lot of the error handling and resilency needs of a production environment.")),(0,r.yg)("h2",{id:"partial-server-configuration"},"Partial Server Configuration"),(0,r.yg)("p",null,"When using the ",(0,r.yg)("inlineCode",{parentName:"p"},".AddSubscriptions()")," extension method two seperate operations occur:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"The subscription server components are registered to the DI container, the graphql execution pipeline is modified to support clients registering subscriptions and a middleware component is appended to the ASP.NET pipeline to intercept and communicate with web socket connections.")),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"A middleware component is appended to the end of the graphql execution pipeline to formally publish any events staged via ",(0,r.yg)("inlineCode",{parentName:"p"},"PublishSubscriptionEvent()")," "))),(0,r.yg)("p",null,"Some applications may wish to split these operations in different server instances for managing load or just splitting different types of traffic. For example, having one set of servers dedicated to query/mutation operations (stateless requests) and others dedicated to handling subscriptions and websockets (stateful requests)."),(0,r.yg)("p",null,"The following granular configuration options may be useful:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},(0,r.yg)("inlineCode",{parentName:"p"},".AddSubscriptionServer()")," :: Only configures the ASP.NET pipeline to intercept websockets and adds the subscription server components to the DI container. Mutations found on this server instance will ",(0,r.yg)("strong",{parentName:"p"},"NOT")," be successful in publishing events.")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},(0,r.yg)("inlineCode",{parentName:"p"},".AddSubscriptionPublishing()")," :: Only configures the graphql execution pipeline to publish events. Subscription creation and websocket support is ",(0,r.yg)("strong",{parentName:"p"},"NOT")," enabled."))),(0,r.yg)("h2",{id:"security--query-authorization"},"Security & Query Authorization"),(0,r.yg)("p",null,"Because subscriptions are long running, consume server resources and registered before any data is processed, the subscription server requires a ",(0,r.yg)("a",{parentName:"p",href:"../reference/schema-configuration#authorization-options"},"query authorization method")," of ",(0,r.yg)("inlineCode",{parentName:"p"},"PerRequest"),". This allows the subscription to be fully validated before its registered with the server and ensure that any queries can actually be processed when needed. This authorization method is set at startup and will apply to queries and mutations as well. It is because of this that it may be useful to split your mutations and subscriptions into different server instances for larger applications, as mentioned above, depending on your reliance on partial query resolution."),(0,r.yg)("p",null,"This is different than the default behavior when subscriptions are not enabled. Queries and mutations, by default, will follow a ",(0,r.yg)("inlineCode",{parentName:"p"},"PerField")," method allowing for partial query resolutions. "),(0,r.yg)("admonition",{type:"caution"},(0,r.yg)("p",{parentName:"admonition"}," Adding Subscriptions to your server will force the use of ",(0,r.yg)("inlineCode",{parentName:"p"},"PerRequest")," Authorization")),(0,r.yg)("h2",{id:"query-timeouts"},"Query Timeouts"),(0,r.yg)("p",null,"By default, the library does not define a timeout for an executed query. The query will run as long as the underlying connection is open. This is true for subscriptions as well. Given that the websocket connection is never closed while the end user is connected, any query executed through the websocket will be allowed to run for an infinite amount of time which can have some unintended side effects and consume resources unecessarily."),(0,r.yg)("p",null,"Optionally, you can define a query timeout for a given schema, which the subscription server will obey:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Adding a Query Timeout"',title:'"Adding',a:!0,Query:!0,'Timeout"':!0},"// startup code\nservices.AddGraphQL(o =>\n{\n // define a 2 minute timeout per query or subscription event executed.\n o.ExecutionOptions.QueryTimeout = TimeSpan.FromMinutes(2);\n})\n")),(0,r.yg)("h2",{id:"websocket-protocols"},"Websocket Protocols"),(0,r.yg)("p",null,"Out of the box, the library supports subscriptions over websockets using ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql-transport-ws"),", the modern protocol used by many client libraries. It also provides support for the legacy protocol ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql-ws")," (originally maintained by Apollo). A client requesting either protocol over a websocket will work with no additional configuration."),(0,r.yg)("h3",{id:"supported-protocols"},"Supported Protocols"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md"},"graphql-transport-ws")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md"},"graphql-ws")," (",(0,r.yg)("em",{parentName:"li"},"legacy"),")")),(0,r.yg)("h3",{id:"creating-custom-protocols"},"Creating Custom Protocols"),(0,r.yg)("p",null,"If you wish to add support for your own websocket messaging protocol you need to implement two interfaces:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"ISubscriptionClientProxy<TSchema>")," wraps a client connection, processes events and performs all necessary communications."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"ISubscriptionClientProxyFactory")," which is used create client proxy instances for your protocol.")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="ISubscriptionClientProxyFactory.cs"',title:'"ISubscriptionClientProxyFactory.cs"'},"public interface ISubscriptionClientProxyFactory\n{\n // Create client proxy instances that\n // act as an intermediary to communicate server-side events\n // to the client connection\n Task<ISubscriptionClientProxy<TSchema>> CreateClient<TSchema>(IClientConnection connection)\n where TSchema : class, ISchema;\n\n // The unique name of your sub-protocol. A client connection requesting your\n // protocol, by name, will be handed to this\n // factory to create the appropriate client proxy.\n string Protocol { get; }\n}\n")),(0,r.yg)("p",null,"Inject the factory into your DI container before calling AddGraphQL:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Register Your Protocol Client Factory"',title:'"Register',Your:!0,Protocol:!0,Client:!0,'Factory"':!0},"// startup\nservices.AddSingleton<ISubscriptionClientProxyFactory, MyClientProxyFactory>();\n\nservices.AddGraphQL()\n .AddSubscriptions();\n")),(0,r.yg)("admonition",{title:"Create a Singleton Factory",type:"caution"},(0,r.yg)("p",{parentName:"admonition"}," ",(0,r.yg)("inlineCode",{parentName:"p"},"ISubscriptionClientProxyFactory")," is expected to be a singleton; it is instantiated once when the server first comes online. The ",(0,r.yg)("inlineCode",{parentName:"p"},"ISubscriptionClientProxy<TSchema>"),"instances it creates should be unique per ",(0,r.yg)("inlineCode",{parentName:"p"},"IClientConnection")," instance.")),(0,r.yg)("p",null,"The server will listen for subscription registrations from your client proxy and send back published events when new data is available. It is up to your proxy to interprete these events, generate an appropriate result (including executing queries against the runtime), serialize the data and send it to the connected client on the other end."),(0,r.yg)("admonition",{title:"Custom Protocols are Difficult",type:"info"},(0,r.yg)("p",{parentName:"admonition"},"The complete details of implementing a custom graphql client proxy are beyond the scope of this documentation. Take a peek at the subscription library source code for some clues on how to get started.")),(0,r.yg)("h2",{id:"other-communication-options"},"Other Communication Options"),(0,r.yg)("p",null,"While websockets is the primary medium for persistant connections its not the only option. Internally, the library supplies an ",(0,r.yg)("inlineCode",{parentName:"p"},"IClientConnection")," interface which encapsulates a raw websocket received from ASP.NET. This interface is internally implemented as a ",(0,r.yg)("inlineCode",{parentName:"p"},"WebSocketClientConnection")," which is responsible for reading and writing raw bytes to the socket. Its not a stretch of the imagination to implement your own custom client connection, invent a way to capture said connections and basically rewrite the entire communications layer of the subscriptions module."),(0,r.yg)("p",null,"Please do a deep dive into the subscription code base to learn about all the intricacies of building your own communications layer and how you might go about registering it with the runtime. If you do try to tackle this very large effort don't hesitate to reach out. We're happy to partner with you and meet you half way on a solution if it makes sense for the rest of the community."),(0,r.yg)("h2",{id:"performance-considerations"},"Performance Considerations"),(0,r.yg)("p",null,"In a production app, its very possible that you may have lots of subscription events fired and communicated to a lot of connected clients in short succession. Its important to understand how the server will process those events and plan accordingly. "),(0,r.yg)("p",null,"When the router receives an event it looks to see which clients are subscribed to that event and queues up a work item for each one. If there are 5 clients registered to the single event, then 5 work items are queued. Internally, this work is processed asyncronously to a server-configured maximum. Once this maximum is reached, new work will only begin as other work finishes up."),(0,r.yg)("p",null,"Each work item is, for the most part, a single query execution. Though, if a client registers to a subscription multiple times each registration is executed as its own query. With lots of events being delivered on a server saturated with clients, each potentially having multiple subscriptions, along with regular queries and mutations executing...limits must be imposed otherwise CPU utilization could unreasonably spike...and it may spike regardless in some use cases. "),(0,r.yg)("p",null,"By default, the max number of work items the router will deliver simultaniously is ",(0,r.yg)("inlineCode",{parentName:"p"},"500"),". This is a global, server-wide pool, shared amongst all registered schemas. You can control this value by changing it prior to calling ",(0,r.yg)("inlineCode",{parentName:"p"},".AddGraphQL()"),". This value defaults to a low number on purpose, use it as a starting point to dial up the max concurrency to a level you feel comfortable with in terms of performance and cost. The only limit here is server resources and other environment limitations outside the control of graphql. "),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Set A Receiver Throttle During Startup"',title:'"Set',A:!0,Receiver:!0,Throttle:!0,During:!0,'Startup"':!0},"// Adjust the max concurrent communications value\n// BEFORE calling .AddGraphQL()\n// highlight-next-line\nGraphQLSubscriptionServerSettings.MaxConcurrentReceiverCount = 500;\n\nservices.AddGraphQL()\n .AddSubscriptions();\n")),(0,r.yg)("admonition",{type:"note"},(0,r.yg)("p",{parentName:"admonition"},"The max receiver count can easily be set in the 1000s or higher. There is no magic bullet in choosing an appropriate value. It all depends on the number of events you are expecting, the number of subscribers, the workload of each event and the hardware available to your application.")),(0,r.yg)("h3",{id:"event-multiplication"},"Event Multiplication"),(0,r.yg)("p",null,"Think carefully about your production scenarios when you introduce subscriptions into your application. As mentioned above, for each event raised, each subscription monitoring that event must execute a standard graphql query, with the supplied event data, to generate a result and send it to its connected client. "),(0,r.yg)("p",null,"If, for instance, you have ",(0,r.yg)("inlineCode",{parentName:"p"},"200 clients")," connected to a single server, each with a subscription against the same field, thats ",(0,r.yg)("inlineCode",{parentName:"p"},"200 individual queries")," that must be executed to process a ",(0,r.yg)("em",{parentName:"p"},"single event")," completely. Even if you call ",(0,r.yg)("inlineCode",{parentName:"p"},"SkipSubscriptionEvent")," to drop the event and send no send data to a client, the query must still be executed to determine if the subscriber is not interested in the data. If you execute any database operations in your ",(0,r.yg)("inlineCode",{parentName:"p"},"[Subcription]")," method, its going to be run 200 times. Suppose your server receives 5 mutations in rapid succession, all of which raise the event, thats a spike of ",(0,r.yg)("inlineCode",{parentName:"p"},"1,000 queries"),", instantaneously, that the server must process. "),(0,r.yg)("p",null,"Balancing the load can be difficult. Luckily there are some ",(0,r.yg)("a",{parentName:"p",href:"/docs/reference/global-configuration#subscriptions"},"throttling levers")," you can adjust."),(0,r.yg)("admonition",{title:"Know Your User Traffic",type:"danger"},(0,r.yg)("p",{parentName:"admonition"}," Raising subscription events can exponentially increase the load on each of your servers if not planned correctly. Think carefully when you deploy subscriptions to your application.")),(0,r.yg)("h3",{id:"dispatch-queue-monitoring"},"Dispatch Queue Monitoring"),(0,r.yg)("p",null,"Internally, whenever a subscription server instance receives an event, the router checks to see which of the currently connected clients need to process that event. The client/event combination is then put into a dispatch queue that is continually processed via an internal, background service according to the throttling limits you've specified. If events are received faster than they can be dispatched they are queued, in memory, until resources are freed up. "),(0,r.yg)("p",null,"There is a built-in monitoring of this queue that will automatically ",(0,r.yg)("a",{parentName:"p",href:"/docs/logging/subscription-events#subscription-event-dispatch-queue-alert"},"record a log event")," when a given threshold is reached. "),(0,r.yg)("h4",{id:"default-event-alert-threshold"},"Default Event Alert Threshold"),(0,r.yg)("p",null,"This log event is recorded at a ",(0,r.yg)("inlineCode",{parentName:"p"},"Critical")," level when the queue reaches ",(0,r.yg)("inlineCode",{parentName:"p"},"10,000 events"),". This alert is then re-recorded once every 5 minutes if the\nqueue remains above 10,000 events."),(0,r.yg)("h4",{id:"custom-event-alert-thresholds"},"Custom Event Alert Thresholds"),(0,r.yg)("p",null,"In some high volume scenarios, its not uncommon or unexpected for the dispatch queue to spike beyond the default monitoring levels from time to time. If you need more granular control of the notifications, register an instance of ",(0,r.yg)("inlineCode",{parentName:"p"},"ISubscriptionClientDispatchQueueAlertSettings")," to your DI container before adding GraphQL and your settings will be used instead."),(0,r.yg)("p",null,"In the example below, if the queue reaches 1,000 events, the debug level alert will be recorded. If 30 seconds pass and the queue is still above 1000 events, the debug level alert will be recorded again. However, if the queue crosses 10,000 events then the warning level alert will be recorded (the debug alert is then ignored). If the queue reaches 100k events then a critical level alert will be recorded every 15 seconds until it drops below 100k."),(0,r.yg)("p",null," Lower level thresholds (as determined by number of queued events) will not be triggered if a higher level is on active cool down."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Custom Dispatch Queue Monitoring"',title:'"Custom',Dispatch:!0,Queue:!0,'Monitoring"':!0},"// startup configuration\n\nvar thresholds = new SubscriptionClientDispatchQueueAlertSettings();\nthresholds.AddThreshold(\n LogLevel.Debug,\n 1000,\n TimeSpan.FromSeconds(30));\n\nthresholds.AddThreshold(\n LogLevel.Warning,\n 10000,\n TimeSpan.FromSeconds(120));\n\nthresholds.AddThreshold(\n LogLevel.Critical,\n 100000,\n TimeSpan.FromSeconds(15));\n \n// register as a singleton\n// highlight-next-line\nservices.AddSingleton<ISubscriptionClientDispatchQueueAlertSettings>(alerts);\n\n// normal graphql configuration\nservices.AddGraphQL()\n .AddSubscriptions();\n")),(0,r.yg)("admonition",{type:"tip"},(0,r.yg)("p",{parentName:"admonition"}," Consider using the built in ",(0,r.yg)("inlineCode",{parentName:"p"},"SubscriptionClientDispatchQueueAlertSettings")," object for a standard implementation of the required interface.")),(0,r.yg)("h2",{id:"general-tips"},"General Tips"),(0,r.yg)("p",null,"\u2705 ",(0,r.yg)("strong",{parentName:"p"},"DO")," exit a subscription with ",(0,r.yg)("inlineCode",{parentName:"p"},"this.SkipSubscriptionEvent()")," as soon as possible. ",(0,r.yg)("br",null)),(0,r.yg)("p",null,"\u2705 ",(0,r.yg)("strong",{parentName:"p"},"DO")," secure your business objects. Subscriptions will receive every event raised against the field they are subscribed to, regardless of the data. ",(0,r.yg)("br",null)),(0,r.yg)("p",null,"\ud83e\udde8 ",(0,r.yg)("strong",{parentName:"p"},"DON'T")," rely on database queries or other IO to determine if an event should be skipped."))}p.isMDXComponent=!0},1662:(e,t,n)=>{n.d(t,{A:()=>i});const i=n.p+"assets/files/2023-01-subscription-server-faaa899feed85da5e9e9e8625af3336f.pdf"}}]); \ No newline at end of file diff --git a/assets/js/cfce05e4.30782168.js b/assets/js/cfce05e4.30782168.js new file mode 100644 index 0000000..a985acb --- /dev/null +++ b/assets/js/cfce05e4.30782168.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[4338],{5680:(e,t,n)=>{n.d(t,{xA:()=>u,yg:()=>y});var a=n(6540);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?o(Object(n),!0).forEach((function(t){r(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):o(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function i(e,t){if(null==e)return{};var n,a,r=function(e,t){if(null==e)return{};var n,a,r={},o=Object.keys(e);for(a=0;a<o.length;a++)n=o[a],t.indexOf(n)>=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a<o.length;a++)n=o[a],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),d=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},u=function(e){var t=d(e.components);return a.createElement(s.Provider,{value:t},e.children)},c="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,s=e.parentName,u=i(e,["components","mdxType","originalType","parentName"]),c=d(n),m=r,y=c["".concat(s,".").concat(m)]||c[m]||p[m]||o;return n?a.createElement(y,l(l({ref:t},u),{},{components:n})):a.createElement(y,l({ref:t},u))}));function y(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,l=new Array(o);l[0]=m;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[c]="string"==typeof e?e:r,l[1]=i;for(var d=2;d<o;d++)l[d]=n[d];return a.createElement.apply(null,l)}return a.createElement.apply(null,n)}m.displayName="MDXCreateElement"},753:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>c,frontMatter:()=>o,metadata:()=>i,toc:()=>d});var a=n(8168),r=(n(6540),n(5680));const o={id:"model-state",title:"Model State",sidebar_label:"Model State",sidebar_position:1},l=void 0,i={unversionedId:"controllers/model-state",id:"controllers/model-state",title:"Model State",description:"What is Model State?",source:"@site/docs/controllers/model-state.md",sourceDirName:"controllers",slug:"/controllers/model-state",permalink:"/docs/controllers/model-state",draft:!1,tags:[],version:"current",sidebarPosition:1,frontMatter:{id:"model-state",title:"Model State",sidebar_label:"Model State",sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Actions",permalink:"/docs/controllers/actions"},next:{title:"Field Paths",permalink:"/docs/controllers/field-paths"}},s={},d=[{value:"What is Model State?",id:"what-is-model-state",level:2},{value:"Using Model Validation",id:"using-model-validation",level:2}],u={toc:d};function c(e){let{components:t,...n}=e;return(0,r.yg)("wrapper",(0,a.A)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"what-is-model-state"},"What is Model State?"),(0,r.yg)("p",null,"GraphQL, as a language, can easily enforce query level requirements like :"),(0,r.yg)("p",null,"\u2705 The data must a collection.",(0,r.yg)("br",null),"\n\u2705 The data value cannot be null.",(0,r.yg)("br",null),"\n\u2705 The argument 'zipCode' must be supplied as a string."),(0,r.yg)("br",null),(0,r.yg)("p",null,"But it fails to enforce the individual business level requirements:"),(0,r.yg)("p",null,"\ud83e\udde8 The employee's last name must be less than 70 characters.",(0,r.yg)("br",null),"\n\ud83e\udde8 A customer's phone number should be 7 or 10 digits.",(0,r.yg)("br",null),"\n\ud83e\udde8 A customer must order at least 1 donut."),(0,r.yg)("p",null,"This is where model state can come in handy. Its completely optional, but if you choose to make use of it, it provides a handy way to inforce business level requirements in your action methods."),(0,r.yg)("h2",{id:"using-model-validation"},"Using Model Validation"),(0,r.yg)("p",null,"When your controller action is invoked the runtime will analyze the input parameters and execute the validation attributes attached to each object to determine its validation state. This works in the same way as it does with a Web API controller."),(0,r.yg)("p",null,"In this example we use the ",(0,r.yg)("inlineCode",{parentName:"p"},"[Range]")," attribute to limit the quantity of donuts that can be ordered to three dozen."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="DonutOrderModel.cs"',title:'"DonutOrderModel.cs"'},"public class DonutOrderModel\n{\n // highlight-next-line\n [Range(1, 36)]\n public int Quantity { get; set; }\n public string Type { get; set; }\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs"',title:'"BakeryController.cs"'},'public class BakeryController : GraphController\n{\n //constructor with service injection omitted for brevity...\n\n [MutationRoot("orderDonuts", typeof(CompletedDonutOrder))]\n public async Task<IGraphActionResult> OrderDonuts(DonutOrderModel order)\n {\n // highlight-start\n if (!this.ModelState.IsValid)\n return this.BadRequest(this.ModelState);\n // highlight-end\n\n var result = await _service.PlaceDonutOrder(order);\n return this.Ok(result);\n }\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'# A valid query\n# that breaks a required business rule\nmutation {\n orderDonuts(order: {quantity: 60, type: "glazed"}) {\n id\n name\n }\n}\n')),(0,r.yg)("p",null,"Just like with ASP.NET, ",(0,r.yg)("inlineCode",{parentName:"p"},"this.ModelState"),' contains an entry for each "validatiable" object passed to the method and its current validation state (valid, invalid, skipped etc.) along with all the rules that did not pass. Also, just like with ASP.NET you can define custom attributes that inherit from ',(0,r.yg)("inlineCode",{parentName:"p"},"ValidationAttribute")," and GraphQL will execute them as well."),(0,r.yg)("p",null,"In the example, we returned a IGraphActionResult to make use of ",(0,r.yg)("inlineCode",{parentName:"p"},"this.BadRequest()")," which will add the friendly error messages to the outgoing response automatically. But we could have easily just returned null, thrown an exception or generated a generic custom error message. However you choose to deal with ",(0,r.yg)("inlineCode",{parentName:"p"},"ModelState")," is up to you. "),(0,r.yg)("admonition",{type:"tip"},(0,r.yg)("p",{parentName:"admonition"},"GraphQL will validate the data but it doesn't take action when model validation fails. That's up to you. If a valid query is provided your action method will be invoked and executed.")),(0,r.yg)("br",null),(0,r.yg)("p",null,"\u26a0\ufe0f ",(0,r.yg)("strong",{parentName:"p"},"Implementation Note")),(0,r.yg)("p",null,"The library makes use of the same ",(0,r.yg)("inlineCode",{parentName:"p"},"System.ComponentModel.DataAnnotations.Validator")," that ASP.NET does to validate its input objects. ",(0,r.yg)("a",{parentName:"p",href:"https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-7.0"},"All the applicable rules")," that apply to Web API model validation also apply to your controllers and models."),(0,r.yg)("p",null,"However, where Web API will validate model binding rules and represent binding errors it its ModelState object (such as invalid or missing property names) the GraphQL implementation will not. GraphQL binding issues such as invalid type expressions, nullability and missing arguments are taken care of at the query level, long before a query plan is finalized and the action method is invoked. In fact, your action methods won't even be invoked unless all the correct data was supplied and the query was properly structured. "),(0,r.yg)("p",null,"The model state of GraphQL ASP.NET is a close approximation of Web API's model state object, but it is not an exact match."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/d1dae560.52f085f0.js b/assets/js/d1dae560.52f085f0.js new file mode 100644 index 0000000..c732763 --- /dev/null +++ b/assets/js/d1dae560.52f085f0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[3603],{5680:(e,t,n)=>{n.d(t,{xA:()=>u,yg:()=>g});var a=n(6540);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function r(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?l(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):l(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function o(e,t){if(null==e)return{};var n,a,i=function(e,t){if(null==e)return{};var n,a,i={},l=Object.keys(e);for(a=0;a<l.length;a++)n=l[a],t.indexOf(n)>=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(a=0;a<l.length;a++)n=l[a],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},u=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},c="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},y=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,l=e.originalType,s=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),c=p(n),y=i,g=c["".concat(s,".").concat(y)]||c[y]||d[y]||l;return n?a.createElement(g,r(r({ref:t},u),{},{components:n})):a.createElement(g,r({ref:t},u))}));function g(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var l=n.length,r=new Array(l);r[0]=y;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[c]="string"==typeof e?e:i,r[1]=o;for(var p=2;p<l;p++)r[p]=n[p];return a.createElement.apply(null,r)}return a.createElement.apply(null,n)}y.displayName="MDXCreateElement"},7605:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>p});var a=n(8168),i=(n(6540),n(5680));const l={id:"input-objects",title:"Input Objects",sidebar_label:"Input Objects",sidebar_position:1},r=void 0,o={unversionedId:"types/input-objects",id:"types/input-objects",title:"Input Objects",description:"INPUTOBJECT graph types (a.k.a. input objects) represent complex data supplied to arguments on fields or directives. Anytime you want to pass more data than a single string or a number, perhaps an Address or a new Employee record, you use an INPUTOBJECT to represent that entity in GraphQL. When the system scans your controllers, if it comes across a class or struct used as a parameter to a method it will attempt to generate the appropriate input type definition to represent that class.",source:"@site/docs/types/input-objects.md",sourceDirName:"types",slug:"/types/input-objects",permalink:"/docs/types/input-objects",draft:!1,tags:[],version:"current",sidebarPosition:1,frontMatter:{id:"input-objects",title:"Input Objects",sidebar_label:"Input Objects",sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Objects",permalink:"/docs/types/objects"},next:{title:"Interfaces",permalink:"/docs/types/interfaces"}},s={},p=[{value:"Customized Type Names",id:"customized-type-names",level:2},{value:"Use an Empty Constructor",id:"use-an-empty-constructor",level:2},{value:"Properties Must Have a Public Setter",id:"properties-must-have-a-public-setter",level:2},{value:"Methods are Ignored",id:"methods-are-ignored",level:2},{value:"Non-Nullability",id:"non-nullability",level:2},{value:"Required Fields And Default Values",id:"required-fields-and-default-values",level:2},{value:"Nullable Fields are Never Required",id:"nullable-fields-are-never-required",level:3},{value:"Required Reference Types",id:"required-reference-types",level:3},{value:"Enum Fields and Coercability",id:"enum-fields-and-coercability",level:2}],u={toc:p};function c(e){let{components:t,...n}=e;return(0,i.yg)("wrapper",(0,a.A)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,(0,i.yg)("inlineCode",{parentName:"p"},"INPUT_OBJECT")," graph types (a.k.a. input objects) represent complex data supplied to arguments on fields or directives. Anytime you want to pass more data than a single string or a number, perhaps an Address or a new Employee record, you use an INPUT_OBJECT to represent that entity in GraphQL. When the system scans your controllers, if it comes across a class or struct used as a parameter to a method it will attempt to generate the appropriate input type definition to represent that class."),(0,i.yg)("p",null,"The rules surrounding naming, field declarations, exclusions, use of ",(0,i.yg)("inlineCode",{parentName:"p"},"[GraphSkip]")," etc. apply to input objects but with a few key differences:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Unless overridden, an input object is named the same as its class name, prefixed with ",(0,i.yg)("inlineCode",{parentName:"li"},"Input_")," (e.g. ",(0,i.yg)("inlineCode",{parentName:"li"},"Input_Address"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"Input_Employee"),")"),(0,i.yg)("li",{parentName:"ul"},"Only public properties with a ",(0,i.yg)("inlineCode",{parentName:"li"},"get")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"set")," will be included.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Property return types cannot be ",(0,i.yg)("inlineCode",{parentName:"li"},"Task<T>"),", an ",(0,i.yg)("inlineCode",{parentName:"li"},"interface")," and cannot implement ",(0,i.yg)("inlineCode",{parentName:"li"},"IGraphUnionProxy")," or ",(0,i.yg)("inlineCode",{parentName:"li"},"IGraphActionResult"),". Such properties are always skipped."))),(0,i.yg)("li",{parentName:"ul"},"Methods are always skipped.")),(0,i.yg)("h2",{id:"customized-type-names"},"Customized Type Names"),(0,i.yg)("p",null,"Input objects can be given customized names, just like with object types, using the ",(0,i.yg)("inlineCode",{parentName:"p"},"[GraphType]")," attribute. "),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Customized Input Object Type Name"',title:'"Customized',Input:!0,Object:!0,Type:!0,'Name"':!0},'[GraphType(InputName = "NewDonutModel")]\npublic class Donut\n{\n public int Id { get; set; }\n public string Name { get; set; }\n public DonutType Type { get; set; }\n public decimal Price { get; set; }\n}\n')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Donut Type Definition"',title:'"Donut',Type:!0,'Definition"':!0},"input NewDonutModel {\n id: Int! = 0\n name: String = null\n type: DonutType! = FROSTED\n price: Decimal! = 0\n}\n")),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Note the specific callout to ",(0,i.yg)("inlineCode",{parentName:"p"},"InputName")," in the attribution.")),(0,i.yg)("h2",{id:"use-an-empty-constructor"},"Use an Empty Constructor"),(0,i.yg)("p",null,"When GraphQL executes a query it will attempt to create an instance of your input object then assign the key/value pairs received on the query to the properties. In order to do the initial instantiation it requires a public parameterless constructor to do so."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Input Objects MUST have a Public, Parameterless Constructor',title:'"Input',Objects:!0,MUST:!0,have:!0,a:!0,"Public,":!0,Parameterless:!0,Constructor:!0},'public class BakeryController : GraphController\n{\n [Mutation("createDonut")]\n public bool CreateNewDonut(DonutModel donut)\n {/*....*/}\n}\n\n// DonutModel.cs\npublic class DonutModel\n{\n //Use a public parameterless constructor\n public DonutModel()\n {\n }\n\n public int Id { get; set; }\n public string Name { get; set; }\n}\n')),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},'Because of this consturctor restriction it can be helpful to separate your classes between "input" and "output" types much is the same way we do with ',(0,i.yg)("inlineCode",{parentName:"p"},"ViewModel")," vs. ",(0,i.yg)("inlineCode",{parentName:"p"},"BindingModel")," objects with REST queries in ASP.NET. This is optional, mix and match as needed by your use case.")),(0,i.yg)("h2",{id:"properties-must-have-a-public-setter"},"Properties Must Have a Public Setter"),(0,i.yg)("p",null,"Properties without a setter are ignored. At runtime, GraphQL compiles an expression tree with the set assignments declared on the graph type, it won't attempt to sneakily reflect and invoke a private or protected setter."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Properties must Have a Public Setter"',title:'"Properties',must:!0,Have:!0,a:!0,Public:!0,'Setter"':!0},"public class Donut\n{\n public int Id { get; }\n public string Name { get; set; }\n public DonutType Type { get; set; }\n public decimal Price { get; set; }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Donut Type Definition"',title:'"Donut',Type:!0,'Definition"':!0},"# Id field is not included \n\ninput Input_Donut {\n name: String = null\n type: DonutType! = FROSTED\n price: Decimal! = 0\n}\n")),(0,i.yg)("h2",{id:"methods-are-ignored"},"Methods are Ignored"),(0,i.yg)("p",null,"While its possible to have methods be exposed as resolvable fields on regular ",(0,i.yg)("inlineCode",{parentName:"p"},"OBJECT")," types, they are ignored for input types regardless of the declaration rules applied to the type."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Methods Are Ignored on Input Objects"',title:'"Methods',Are:!0,Ignored:!0,on:!0,Input:!0,'Objects"':!0},'public class Donut\n{\n [GraphField("salesTax")]\n public decimal CalculateSalesTax(decimal taxPercentage)\n {\n return this.Price * taxPercentage;\n }\n\n public int Id { get; set; }\n public string Name { get; set; }\n public DonutType Type { get; set; }\n public decimal Price { get; set; }\n}\n')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Donut Type Definition"',title:'"Donut',Type:!0,'Definition"':!0},"# CalculateSalesTax is not included\ninput Input_Donut {\n id: Int! = 0\n name: String = null\n type: DonutType! = FROSTED\n price: Decimal! = 0\n}\n")),(0,i.yg)("h2",{id:"non-nullability"},"Non-Nullability"),(0,i.yg)("p",null,"By default, all properties that are reference types (i.e. classes) are nullable and all value types (primatives, structs etc.) are non-nullable"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Recipe can be null, it is a reference type"',title:'"Recipe',can:!0,be:!0,"null,":!0,it:!0,is:!0,a:!0,reference:!0,'type"':!0},"public class Donut\n{\n public Recipe Recipe { get; set; } // reference type\n public int Quantity { get; set; } // value type\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Input Donut Definition"',title:'"Input',Donut:!0,'Definition"':!0},"input Input_Donut {\n recipe: Input_Recipe = null # nullable\n quantity: Int! = 0 # not nullable\n}\n")),(0,i.yg)("p",null,"If you want to force a reference type to be non-null you can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"[GraphField]")," attribute to augment the field's type expression."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Force Recipe to be non-null"',title:'"Force',Recipe:!0,to:!0,be:!0,'non-null"':!0},'public class Donut\n{\n public Donut()\n {\n // we must supply a non-null default value \n // for a non-nullable field\n this.Recipe = new Recipe("Flour, Sugar, Salt");\n }\n\n [GraphField(TypeExpression = "Type!")]\n public Recipe Recipe { get; set; } // reference type\n public int Quantity { get; set; } // value type\n}\n')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Input Donut Definition"',title:'"Input',Donut:!0,'Definition"':!0},'input Input_Donut {\n recipe: Input_Recipe! = {ingredients : "Flour, Sugar, Salt" } \n quantity: Int! = 0 \n}\n')),(0,i.yg)("admonition",{title:"Did You Notice?",type:"info"},(0,i.yg)("p",{parentName:"admonition"}," We assigned a recipe in the class's constructor to use as the default value."),(0,i.yg)("p",{parentName:"admonition"}," Any non-nullable field, that does not have the ",(0,i.yg)("inlineCode",{parentName:"p"},"[Required]")," attribute (see below), MUST have a default value assigned to it that is not ",(0,i.yg)("inlineCode",{parentName:"p"},"null"),". A ",(0,i.yg)("inlineCode",{parentName:"p"},"GraphTypeDeclarationException")," will be thrown at startup if this is not the case.")),(0,i.yg)("h2",{id:"required-fields-and-default-values"},"Required Fields And Default Values"),(0,i.yg)("p",null,"Add ",(0,i.yg)("inlineCode",{parentName:"p"},"[Required]")," (from System.ComponentModel) to any property to force a user to supply the field in a query document."),(0,i.yg)("p",null,"Any non-required field will automatically be assigned a default value if not supplied on a query. This default value is equivilant to the property value of the object when its instantiated via its public, parameterless constructor."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Add the Required attribute for force a query to define a value"',title:'"Add',the:!0,Required:!0,attribute:!0,for:!0,force:!0,a:!0,query:!0,to:!0,define:!0,'value"':!0},"public class Donut\n{\n public Donut()\n {\n // set custom defaults if needed\n this.Type = DonutType.Frosted;\n this.IsAvailable = true;\n } \n \n [Required]\n public int Id { get; set; }\n\n public string Name { get; set; }\n public DonutType Type { get; set; }\n public Bakery Bakery { get;set; } \n public bool IsAvailable { get; set; }\n public int SkuNumber { get; set; }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Donut Type Definition"',title:'"Donut',Type:!0,'Definition"':!0},"input Input_Donut {\n id: Int! # No Default Value on Id\n name: String = null \n type: DonutType! = FROSTED\n bakery: Input_Bakery = null\n isAvailable: Boolean! = true\n skuNumber: Int! = 0\n}\n")),(0,i.yg)("h3",{id:"nullable-fields-are-never-required"},"Nullable Fields are Never Required"),(0,i.yg)("p",null,"By Definition (spec \xa7 ",(0,i.yg)("a",{parentName:"p",href:"https://spec.graphql.org/October2021/#sec-Input-Object-Required-Fields"},"5.6.4"),"), a nullable field without a\ndeclared default value is still considered optional and does not need to be supplied. That is to say that if a query does not include a field that is nullable, it will default to ",(0,i.yg)("inlineCode",{parentName:"p"},"null")," regardless of the use of the ",(0,i.yg)("inlineCode",{parentName:"p"},"[Required]")," attribute."),(0,i.yg)("p",null,"These two property declarations for an input object are identical as far as graphql is concerned:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:"title='Example Input object Fields'",title:"'Example",Input:!0,object:!0,"Fields'":!0},"public InputEmployee\n{\n public string FirstName { get; set; }\n\n [Required]\n public string LastName { get; set; }\n}\n")),(0,i.yg)("p",null,"Both ",(0,i.yg)("inlineCode",{parentName:"p"},"FirstName")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"LastName")," are of type ",(0,i.yg)("inlineCode",{parentName:"p"},"string"),", which is nullable. GraphQL will ignore the required attribute and still allow a query to process even if neither value is supplied on a query."),(0,i.yg)("h3",{id:"required-reference-types"},"Required Reference Types"),(0,i.yg)("p",null,"Combine the ","[Required]"," attribute with a non-nullable type expression to force an otherwise nullable field to be required."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Force Owner to be non-null And Required"',title:'"Force',Owner:!0,to:!0,be:!0,"non-null":!0,And:!0,'Required"':!0},'public class Bakery\n{\n [Required]\n [GraphField(TypeExpression = "Type!")]\n public Person Owner { get; set; }\n}\n')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Donut Type Definition"',title:'"Donut',Type:!0,'Definition"':!0},"# No Default Value is supplied. \n# the 'owner' must be supplied on a query\ninput Input_Bakery {\n owner: Input_Person!\n}\n")),(0,i.yg)("p",null,(0,i.yg)("inlineCode",{parentName:"p"},"Owner")," is of type Person, which is a reference type, which is nullable by default. By augmenting its type expression to be non-null and adding the ",(0,i.yg)("inlineCode",{parentName:"p"},"[Required]")," attribute graphql will not supply a default value require it to be supplied on a query."),(0,i.yg)("h2",{id:"enum-fields-and-coercability"},"Enum Fields and Coercability"),(0,i.yg)("p",null,"Any default value declared for an input field must be coercible by its target graph type in the target schema. Because of this there is a small got'cha situation with enum values. "),(0,i.yg)("p",null,"Take a look at this example of an enum and input object:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using an Enum as a field type"',title:'"Using',an:!0,Enum:!0,as:!0,a:!0,field:!0,'type"':!0},"public class Donut \n{\n public string Name { get; set; }\n public DonutFlavor Flavor { get; set; }\n}\n\npublic enum DonutFlavor\n{\n [GraphSkip]\n Vanilla = 0,\n Chocolate = 1,\n}\n")),(0,i.yg)("p",null,"When ",(0,i.yg)("inlineCode",{parentName:"p"},"Donut")," is instantiated the value of Flavor will be ",(0,i.yg)("inlineCode",{parentName:"p"},"Vanilla")," because\nthats the default value (0) of the enum. However, the enum value ",(0,i.yg)("inlineCode",{parentName:"p"},"Vanilla")," is marked as being skipped in the schema. "),(0,i.yg)("p",null,"Because of this mismatch, a ",(0,i.yg)("inlineCode",{parentName:"p"},"GraphTypeDeclarationException")," will be thrown when the introspection data for your schema is built. As a result, the server will fail to start until the problem is corrected. "),(0,i.yg)("p",null,"You can get around this by setting an included enum value in the consturctor:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using an Enum as a field type"',title:'"Using',an:!0,Enum:!0,as:!0,a:!0,field:!0,'type"':!0},"public class Donut \n{\n public Donut()\n {\n // set the value of flavor to an enum value \n // included in the graph\n this.Flavor = DonutFlavor.Chocolate;\n }\n\n public string Name { get; set; }\n public DonutFlavor Flavor { get; set; }\n}\n\n")),(0,i.yg)("admonition",{type:"caution"},(0,i.yg)("p",{parentName:"admonition"}," Enum values used for the default value of input object properties MUST also exist as values in the schema or an exception will be thrown.")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/d99d9121.2e6f91bb.js b/assets/js/d99d9121.2e6f91bb.js new file mode 100644 index 0000000..38c59f8 --- /dev/null +++ b/assets/js/d99d9121.2e6f91bb.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[5292],{5680:(e,t,i)=>{i.d(t,{xA:()=>s,yg:()=>h});var n=i(6540);function a(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function r(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function o(e){for(var t=1;t<arguments.length;t++){var i=null!=arguments[t]?arguments[t]:{};t%2?r(Object(i),!0).forEach((function(t){a(e,t,i[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):r(Object(i)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))}))}return e}function l(e,t){if(null==e)return{};var i,n,a=function(e,t){if(null==e)return{};var i,n,a={},r=Object.keys(e);for(n=0;n<r.length;n++)i=r[n],t.indexOf(i)>=0||(a[i]=e[i]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(n=0;n<r.length;n++)i=r[n],t.indexOf(i)>=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(a[i]=e[i])}return a}var c=n.createContext({}),d=function(e){var t=n.useContext(c),i=t;return e&&(i="function"==typeof e?e(t):o(o({},t),e)),i},s=function(e){var t=d(e.components);return n.createElement(c.Provider,{value:t},e.children)},p="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var i=e.components,a=e.mdxType,r=e.originalType,c=e.parentName,s=l(e,["components","mdxType","originalType","parentName"]),p=d(i),m=a,h=p["".concat(c,".").concat(m)]||p[m]||u[m]||r;return i?n.createElement(h,o(o({ref:t},s),{},{components:i})):n.createElement(h,o({ref:t},s))}));function h(e,t){var i=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=i.length,o=new Array(r);o[0]=m;var l={};for(var c in t)hasOwnProperty.call(t,c)&&(l[c]=t[c]);l.originalType=e,l[p]="string"==typeof e?e:a,o[1]=l;for(var d=2;d<r;d++)o[d]=i[d];return n.createElement.apply(null,o)}return n.createElement.apply(null,i)}m.displayName="MDXCreateElement"},3230:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>p,frontMatter:()=>r,metadata:()=>l,toc:()=>d});var n=i(8168),a=(i(6540),i(5680));const r={id:"middleware",title:"Pipelines and Custom Middleware",sidebar_label:"Pipelines & Middleware",sidebar_position:7},o=void 0,l={unversionedId:"reference/middleware",id:"reference/middleware",title:"Pipelines and Custom Middleware",description:"At the heart of GraphQL ASP.NET are 4 middleware pipelines; chains of components executed in a specific order to produce a result.",source:"@site/docs/reference/middleware.md",sourceDirName:"reference",slug:"/reference/middleware",permalink:"/docs/reference/middleware",draft:!1,tags:[],version:"current",sidebarPosition:7,frontMatter:{id:"middleware",title:"Pipelines and Custom Middleware",sidebar_label:"Pipelines & Middleware",sidebar_position:7},sidebar:"tutorialSidebar",previous:{title:"HTTP Processor",permalink:"/docs/reference/http-processor"},next:{title:"Query Caching",permalink:"/docs/reference/query-cache"}},c={},d=[{value:"Creating New Middleware",id:"creating-new-middleware",level:2},{value:"Registering New Middleware",id:"registering-new-middleware",level:2},{value:"The Context Object",id:"the-context-object",level:2},{value:"Middleware is served from the DI Container",id:"middleware-is-served-from-the-di-container",level:2},{value:"Query Execution Pipeline",id:"query-execution-pipeline",level:2},{value:"QueryExecutionContext",id:"queryexecutioncontext",level:4},{value:"Field Execution Pipeline",id:"field-execution-pipeline",level:2},{value:"GraphFieldExecutionContext",id:"graphfieldexecutioncontext",level:4},{value:"Schema Item Authorization Pipeline",id:"schema-item-authorization-pipeline",level:2},{value:"GraphSchemaItemSecurityChallengeContext",id:"graphschemaitemsecuritychallengecontext",level:4},{value:"Directive Execution Pipeline",id:"directive-execution-pipeline",level:2},{value:"GraphDirectiveExecutionContext",id:"graphdirectiveexecutioncontext",level:4},{value:"A Note on Schema Instances",id:"a-note-on-schema-instances",level:3}],s={toc:d};function p(e){let{components:t,...i}=e;return(0,a.yg)("wrapper",(0,n.A)({},s,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"At the heart of GraphQL ASP.NET are 4 middleware pipelines; chains of components executed in a specific order to produce a result."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Query Execution Pipeline")," : Invoked once per request this pipeline is responsible for validating the incoming package on the POST or GET request, parsing the data and generating a query plan."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Field Execution Pipeline")," : Invoked once per requested field, this pipeline processes a single field resolver."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Schema Item Security Pipeline"),": Ensures the user on the request can access a given schema item (field, directive etc.)."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Directive Execution Pipeline"),": Executes directives for various phases of schema and query document lifetimes.")),(0,a.yg)("p",null,"All four pipelines can be extended or reworked to include custom components and perform additional work. A call to ",(0,a.yg)("inlineCode",{parentName:"p"},".AddGraphQL()")," returns a builder that can be used to restructure the pipelines when necessary. "),(0,a.yg)("h2",{id:"creating-new-middleware"},"Creating New Middleware"),(0,a.yg)("p",null,"Each new middleware component must implement one of the four middleware interfaces depending on the type of component you are creating; much in the way you'd define a middleware component for ASP.NET. The four middleware interfaces are:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"IQueryExecutionMiddleware")),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"IFieldExecutionMiddleware")),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"ISchemaItemSecurityMiddleware")),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"IDirectiveExecutionMiddleware"))),(0,a.yg)("p",null,"The interfaces define one method, ",(0,a.yg)("inlineCode",{parentName:"p"},"InvokeAsync"),", with identical signatures save for the type of data context accepted by each."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Query Execution Middleware Component definition."',title:'"Query',Execution:!0,Middleware:!0,Component:!0,'definition."':!0},"public interface IQueryExecutionMiddleware\n{\n Task InvokeAsync(\n QueryExecutionContext context,\n GraphMiddlewareInvocationDelegate next,\n CancellationToken cancelToken);\n}\n")),(0,a.yg)("p",null,"The library will invoke your component at the appropriate time and pass to it the active data context. Once you have performed any necessary work involving the context invoke the ",(0,a.yg)("inlineCode",{parentName:"p"},"next")," delegate (the second parameter) to pass the context to the next component in the chain."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Example Custom Middleware Component"',title:'"Example',Custom:!0,Middleware:!0,'Component"':!0},'public class MyQueryMiddleware : IQueryExecutionMiddleware\n{\n public async Task InvokeAsync(\n QueryExecutionContext context,\n GraphMiddlewareInvocationDelegate next,\n CancellationToken cancelToken)\n {\n // Do any necessary work with the context\n if(context.Request.QueryText == null)\n {\n context.Messages.Critical("No Query Text Provided");\n context.Cancel();\n }\n\n // invoke the next component in the chain\n await next(context, cancelToken);\n }\n}\n')),(0,a.yg)("h2",{id:"registering-new-middleware"},"Registering New Middleware"),(0,a.yg)("p",null,"Each pipeline can be extended using the ",(0,a.yg)("inlineCode",{parentName:"p"},"SchemaBuilder")," returned from calling ",(0,a.yg)("inlineCode",{parentName:"p"},".AddGraphQL()")," at startup. Each schema that is added to GraphQL will generate its own builder with its own set of pipelines and components. They can be configured independently as needed."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Register Your Middleware During Startup"',title:'"Register',Your:!0,Middleware:!0,During:!0,'Startup"':!0},"// obtain a reference to the builder after adding\n// graphql for your schema\nvar schemaBuilder = services.AddGraphQL(options =>\n{\n options.ExecutionOptions.MaxQueryDepth = 15;\n});\n\n// add new middleware components to any pipeline\nschemaBuilder.QueryExecutionPipeline.AddMiddleware<MyQueryMiddleware>();\n\n")),(0,a.yg)("p",null,"Instead of adding to the end of the existing pipeline you can also call ",(0,a.yg)("inlineCode",{parentName:"p"},".Clear()")," to remove the default components and rebuild the pipeline from scratch. See below for the list of default middleware components and their order of execution. This can be handy when needing to inject a new component into the middle of the execution chain."),(0,a.yg)("admonition",{title:"Component order Matters",type:"caution"},(0,a.yg)("p",{parentName:"admonition"}," Modifying the component order of a pipeline can cause unwanted side effects, including breaking the library such that it no longer functions. Take care when adding or removing middleware components.")),(0,a.yg)("h2",{id:"the-context-object"},"The Context Object"),(0,a.yg)("p",null,"Each context object has specific data fields required for it to perform its work (detailed below). However, all contexts share a common set of items to govern the flow of work."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"QueryRequest"),": The original request being executed. Contains the query text, variables etc."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Messages"),": A collection of messages that will be added to the query result."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Cancel()"),": Marks the context as cancelled and sets the ",(0,a.yg)("inlineCode",{parentName:"li"},"IsCancelled")," property to true. It is up to each middleware component to interpret the meaning of cancelled for its own purposes. A canceled field execution context, for instance, will be discarded and not rendered to the output whereas a canceled query context may or may not generate a result depending on when its cancelled."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"IsValid"),": Determines if the context is in a valid and runnable state. Most middleware components will not attempt to process the context if its not in a valid state and will simply forward the request on. By default, a context is automatically invalidated if an error message is added with the ",(0,a.yg)("inlineCode",{parentName:"li"},"Critical")," severity."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"SecurityContext"),": The information received from ASP.NET containing the credentials of the active user. May be null if user authentication is not setup for your application."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Metrics"),": The metrics package performing any profiling of the query. Various middleware components will stop/start phases of execution using this object. If metrics are not enabled this object will be null."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Items"),": A key/value collection of items available to every context on every pipeline related to a single request. This field is developer driven and not used by the runtime."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Logger"),": An ",(0,a.yg)("inlineCode",{parentName:"li"},"IGraphEventLogger")," instance scoped to the the current query.")),(0,a.yg)("h2",{id:"middleware-is-served-from-the-di-container"},"Middleware is served from the DI Container"),(0,a.yg)("p",null,"Each pipeline is registered as a singleton instance in your service provider but the components within the pipeline are invoked according to the service lifetime you supply when you register them, allowing you to manage dependencies as you see fit."),(0,a.yg)("admonition",{title:"Register Middleware as Singletons",type:"tip"},(0,a.yg)("p",{parentName:"admonition"}," Register your middleware components with the ",(0,a.yg)("inlineCode",{parentName:"p"},"Singleton")," lifetime scope whenever possible to boost performance.")),(0,a.yg)("p",null,"It is recommended that your middleware components be singleton in nature if possible. The field execution and item authorization pipelines can be invoked many dozens of times per request and fetching new middleware instances for each invocation can impact performance. Most default components are registered as a singletons."),(0,a.yg)("h2",{id:"query-execution-pipeline"},"Query Execution Pipeline"),(0,a.yg)("p",null,"The query execution pipeline is invoked once per request. It is supplied with the raw query text from the user and orchestrates the necessary calls to generate a a valid GraphQL result than can be returned to the client. It contains 9 components, in order of execution they are:"),(0,a.yg)("ol",null,(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"ValidateQueryRequestMiddleware")," : Ensures that the data request recieved is valid and runnable (i.e. was a request provided, is query text defined etc.)."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"RecordQueryMetricsMiddleware"),": Governs the query profiling for the context. It will start the recording and terminate it after all other components have completed their operations."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"QueryExecutionPlanCacheMiddleware")," : When the query cache is enabled for the schema, this component will analyze the incoming query text and attempt to fetch a pre-cached query plan from storage."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"ParseQueryPlanMiddleware"),": When required, this component will lex/parse the query text into a usable document from which a query plan can be created."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"ValidateQueryDocumentMiddleware"),": Performs a first pass validation of the query document, before any directives are applied."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"AssignQueryOperationMiddleware")," : Marries the operation name requested with the matching operation in the query document."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"ValidateOperationVariableDataMiddleware"),": Validates the supplied variables values against those required by the chosen operation."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"AuthorizeQueryOperationMiddleware"),": If the schema is configured for ",(0,a.yg)("inlineCode",{parentName:"li"},"PerRequest")," authorization this component will invoke the authorization pipeline for each field of the selected operation that has security requirements and assign authorization results as appropriate."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"ApplyExecutionDirectivesMiddleware"),": Applies all execution directives, if any, to the chosen operation."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"GenerateQueryPlanMiddleware"),": When required, this component will attempt to generate a fully qualified query plan for its target schema using the chosen operation. "),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"ExecuteQueryOperationMiddleware")," : Uses the query plan to dispatches field execution contexts to resolve needed each field."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"PackageQueryResultMiddleware"),": Performs a final set of checks on the resolved field data and generates an ",(0,a.yg)("inlineCode",{parentName:"li"},"IQueryExecutionResult")," for the query.\ndocument on the context.")),(0,a.yg)("h4",{id:"queryexecutioncontext"},"QueryExecutionContext"),(0,a.yg)("p",null,"In addition to the common properties defined above, the query execution context defines a number of useful fields:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="QueryExecutionContext.cs"',title:'"QueryExecutionContext.cs"'},"public class QueryExecutionContext\n{\n public IQueryExecutionResult Result { get; set; }\n public IQueryExecutionPlan QueryPlan { get; set; }\n public IList<FieldDataItem> FieldResults { get; }\n\n // other properties omitted for brevity\n}\n")),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Result"),": The created ",(0,a.yg)("inlineCode",{parentName:"li"},"IQueryExecutionResult"),". This property will be null until the result is created."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"QueryPlan"),": the created (or retrieved from cache) query plan for the current query."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"FieldResults"),": The individual, top-level data fields resolved for the selected operation. These fields are eventually packaged into the result object.")),(0,a.yg)("h2",{id:"field-execution-pipeline"},"Field Execution Pipeline"),(0,a.yg)("p",null,"The field execution pipeline is executed once for each field of data that needs to be resolved. Its primary job is to turn a request for a field into a data value that can be returned to the client. It contains 5 components, in order of execution they are:"),(0,a.yg)("ol",null,(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"ValidateFieldExecutionMiddleware")," : Validates that the context and required invocation data has been correctly supplied."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"AuthorizeFieldMiddleware")," : If the schema is configured for ",(0,a.yg)("inlineCode",{parentName:"li"},"PerField")," authorization this component will invoke the item authorization pipeline for the current field and assign authorization results as appropriate."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"InvokeFieldResolverMiddleware")," : The field resolver is called and a data value is created for the active context. This middleware component is ultimately responsible for invoking your controller actions."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"ProcessChildFieldsMiddleware")," : If any child fields are registered for this field they are executing using the context's field result as the new source object.")),(0,a.yg)("h4",{id:"graphfieldexecutioncontext"},"GraphFieldExecutionContext"),(0,a.yg)("p",null,"In addition to the common properties defined above the field execution context defines a number of useful properties:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="GraphFieldExecutionContext.cs"',title:'"GraphFieldExecutionContext.cs"'},"public class GraphFieldExecutionContext\n{\n public IGraphFieldRequest Request { get; }\n public object Result { get; set; }\n\n // other properties omitted for brevity\n}\n")),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Request"),": The field request containing any source data, a reference to the metadata for the field as defined by the schema and a reference to the invocation requirements determined by the query plan."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Result"),": The raw data object produced from the field resolver. This value is passed as the source value to any child fields.")),(0,a.yg)("h2",{id:"schema-item-authorization-pipeline"},"Schema Item Authorization Pipeline"),(0,a.yg)("p",null,"The field authorization pipeline can be invoked as part of query execution or field execution depending on your schema's configuration. It contains 1 component:"),(0,a.yg)("ol",null,(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"SchemItemSecurityRequirementsMiddleware")," : Gathers the authentication and authorization requirements for the given schema item and ensures that the item ",(0,a.yg)("em",{parentName:"li"},"can")," be authorized. "),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"SchemaItemAuthenticationMiddleware")," : Authenticates the request to the field. This generates a ClaimsPrincipal to be authorized against if one is not already assigned."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"SchemaItemAuthorizationMiddleware"),": Inspects the active ",(0,a.yg)("inlineCode",{parentName:"li"},"ClaimsPrincipal")," against the security requirements of the schema item and generates a ",(0,a.yg)("inlineCode",{parentName:"li"},"SchemaItemSecurityChallengeResult")," indicating if the user is authorized or not. This component makes no decisions against the authorization state. It is up to the other pipelines to act on the authorization results in an appropriate manner.")),(0,a.yg)("h4",{id:"graphschemaitemsecuritychallengecontext"},"GraphSchemaItemSecurityChallengeContext"),(0,a.yg)("p",null,"In addition to the common properties defined above the field security context defines a number of useful properties:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="GraphSchemaItemSecurityChallengeContext.cs"',title:'"GraphSchemaItemSecurityChallengeContext.cs"'}," public class GraphSchemaItemSecurityChallengeContext\n{\n public SchemaItemSecurityRequirements SecurityRequirements {get; set;}\n public IGraphSchemaItemSecurityRequest Request { get; }\n public SchemaItemSecurityChallengeResult Result { get; set; }\n\n // common properties omitted for brevity\n}\n")),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"SecurityRequirements"),": The security rules that need to be checked to authorize a user."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Request"),": Contains details about the item currently being authorized."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Result"),": The generated challenge result indicating if the user is authorized to the item. This result will contain additional detailed information as to why a request was not authorized. This information is automatically added to any generated log events.")),(0,a.yg)("h2",{id:"directive-execution-pipeline"},"Directive Execution Pipeline"),(0,a.yg)("p",null,"The directive execution pipeline will be invoked for each directive applied to each schema item during schema generation and each time the query engine encounters a directive on a query document. The directive pipeline contains four components by default:"),(0,a.yg)("ol",null,(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"ValidateDirectiveExecutionMiddleware"),": Inspects the context against the validation requirements for directives and applies appropriate error messages as necessary."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"AuthorizeDirectiveMiddleware"),": If the schema is configured for ",(0,a.yg)("inlineCode",{parentName:"li"},"PerField")," authorization this component will invoke the item authorization pipeline for the current directive and assign authorization results as appropriate."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"InvokeDirectiveResolverMiddleware"),": Generates a ",(0,a.yg)("inlineCode",{parentName:"li"},"DirectiveResolutionContext")," and invokes the directive's resolver, calling the correct action methods."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("inlineCode",{parentName:"li"},"LogDirectiveExecutionMiddleware"),": Generates appropriate log messages depending on the directive invoked.")),(0,a.yg)("h4",{id:"graphdirectiveexecutioncontext"},"GraphDirectiveExecutionContext"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="GraphDirectiveExecutionContext"',title:'"GraphDirectiveExecutionContext"'},"public class GraphDirectiveExecutionContext\n{\n public IGraphDirectiveRequest Request { get; }\n public IDirective Directive {get;}\n public ISchema Schema {get; }\n\n // common properties omitted for brevity\n}\n")),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Request"),": Contains the directive metadata for this context, including the DirectiveTarget, execution phase and executing location."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Directive"),": The specific ",(0,a.yg)("inlineCode",{parentName:"li"},"IDirective"),", registered to the schema, that is being processed."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"Schema"),": the schema instance where the directive is declared.")),(0,a.yg)("h3",{id:"a-note-on-schema-instances"},"A Note on Schema Instances"),(0,a.yg)("p",null," Since the directive execution pipeline is used to construct a schema and apply type system directives, middleware components within it cannot inject a schema instance from the DI container. To do so would cause a circular reference in the DI container. "),(0,a.yg)("p",null,"Instead use the schema instance attached to the ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphDirectiveExecutionContext"),"."),(0,a.yg)("admonition",{type:"note"},(0,a.yg)("p",{parentName:"admonition"}," You can inject a schema instance into components of every pipeline EXCEPT the Directive Execution Pipeline.")))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/de26b084.ad69191d.js b/assets/js/de26b084.ad69191d.js new file mode 100644 index 0000000..7e905a7 --- /dev/null +++ b/assets/js/de26b084.ad69191d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[5436],{5680:(e,n,t)=>{t.d(n,{xA:()=>p,yg:()=>y});var r=t(6540);function o(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function a(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function i(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[n]?arguments[n]:{};n%2?a(Object(t),!0).forEach((function(n){o(e,n,t[n])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):a(Object(t)).forEach((function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))}))}return e}function g(e,n){if(null==e)return{};var t,r,o=function(e,n){if(null==e)return{};var t,r,o={},a=Object.keys(e);for(r=0;r<a.length;r++)t=a[r],n.indexOf(t)>=0||(o[t]=e[t]);return o}(e,n);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r<a.length;r++)t=a[r],n.indexOf(t)>=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(o[t]=e[t])}return o}var l=r.createContext({}),s=function(e){var n=r.useContext(l),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},p=function(e){var n=s(e.components);return r.createElement(l.Provider,{value:n},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},u=r.forwardRef((function(e,n){var t=e.components,o=e.mdxType,a=e.originalType,l=e.parentName,p=g(e,["components","mdxType","originalType","parentName"]),d=s(t),u=o,y=d["".concat(l,".").concat(u)]||d[u]||c[u]||a;return t?r.createElement(y,i(i({ref:n},p),{},{components:t})):r.createElement(y,i({ref:n},p))}));function y(e,n){var t=arguments,o=n&&n.mdxType;if("string"==typeof e||o){var a=t.length,i=new Array(a);i[0]=u;var g={};for(var l in n)hasOwnProperty.call(n,l)&&(g[l]=n[l]);g.originalType=e,g[d]="string"==typeof e?e:o,i[1]=g;for(var s=2;s<a;s++)i[s]=t[s];return r.createElement.apply(null,i)}return r.createElement.apply(null,t)}u.displayName="MDXCreateElement"},141:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>i,default:()=>d,frontMatter:()=>a,metadata:()=>g,toc:()=>s});var r=t(8168),o=(t(6540),t(5680));const a={id:"structured-logging",title:"Structured Logging",sidebar_label:"Structured Logging",sidebar_position:0},i=void 0,g={unversionedId:"logging/structured-logging",id:"logging/structured-logging",title:"Structured Logging",description:"GraphQL ASP.NET utilizes structured logging for reporting runtime events. The log messages generated aren't just strings but actual objects. All internal log events are raised as objects that inherit from IGraphLogEntry.",source:"@site/docs/logging/structured-logging.md",sourceDirName:"logging",slug:"/logging/structured-logging",permalink:"/docs/logging/structured-logging",draft:!1,tags:[],version:"current",sidebarPosition:0,frontMatter:{id:"structured-logging",title:"Structured Logging",sidebar_label:"Structured Logging",sidebar_position:0},sidebar:"tutorialSidebar",previous:{title:"Multi-Schema Support",permalink:"/docs/advanced/multi-schema-support"},next:{title:"Standard Events",permalink:"/docs/logging/standard-events"}},l={},s=[{value:"IServiceCollection.AddLogging()",id:"iservicecollectionaddlogging",level:2},{value:"Using IGraphLogger",id:"using-igraphlogger",level:2},{value:"Custom ILoggers",id:"custom-iloggers",level:2},{value:"Log Entries are KeyValuePair Collections",id:"log-entries-are-keyvaluepair-collections",level:2},{value:"Logging Category",id:"logging-category",level:2},{value:"Scoped Log Entries",id:"scoped-log-entries",level:3}],p={toc:s};function d(e){let{components:n,...a}=e;return(0,o.yg)("wrapper",(0,r.A)({},p,a,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"GraphQL ASP.NET utilizes structured logging for reporting runtime events. The log messages generated aren't just strings but actual objects. All internal log events are raised as objects that inherit from ",(0,o.yg)("inlineCode",{parentName:"p"},"IGraphLogEntry"),"."),(0,o.yg)("h2",{id:"iservicecollectionaddlogging"},"IServiceCollection.AddLogging()"),(0,o.yg)("p",null,"GraphQL's logging extends off of .NET's built in logging framework. To enable it, you need to register logging to your application at startup. By doing so, GraphQL will automatically wire up its own logging as well."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Register Standard Logging"',title:'"Register',Standard:!0,'Logging"':!0},"// Adding Logging before calling AddGraphQL\nservices.AddLogging();\n\nservice.AddGraphQL(/*...*/);\n")),(0,o.yg)("h2",{id:"using-igraphlogger"},"Using IGraphLogger"),(0,o.yg)("p",null,"Its common practice to inject an instance of ",(0,o.yg)("inlineCode",{parentName:"p"},"ILogger")," or ",(0,o.yg)("inlineCode",{parentName:"p"},"ILoggerFactory")," into a controller in order to log various events of your controller methods."),(0,o.yg)("p",null,"This is fully supported but the library can also generate an instance of ",(0,o.yg)("inlineCode",{parentName:"p"},"IGraphLogger"),' with a few helpful methods for raising "on the fly" log entries if you wish to make use of it. ',(0,o.yg)("inlineCode",{parentName:"p"},"IGraphLogger")," implements ",(0,o.yg)("inlineCode",{parentName:"p"},"ILogger"),", the two can be used interchangeably as needed."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using IGraphLogger"',title:'"Using','IGraphLogger"':!0},'public class BakeryController : GraphController\n{\n private IGraphLogger _graphLogger;\n private IDonutService _service;\n // highlight-next-line\n public BakeryController(IDonutService service, IGraphLogger graphLogger)\n {\n _service = service;\n _graphLogger = graphLogger;\n }\n\n [MutationRoot]\n public Donut CreateDonut(string name)\n {\n Donut donut = _service.CreateDonut(name);\n\n // highlight-start\n var donutEvent = new GraphLogEntry("New Donut Created!");\n donutEvent["Name"] = name;\n donutEvent["Id"] = donut.Id;\n\n _graphLogger.Log(LogLevel.Information, donutEvent);\n // highlight-end\n\n return donut;\n }\n}\n')),(0,o.yg)("admonition",{type:"tip"},(0,o.yg)("p",{parentName:"admonition"}," ",(0,o.yg)("inlineCode",{parentName:"p"},"GraphLogEntry")," is an untyped implementation of ",(0,o.yg)("inlineCode",{parentName:"p"},"IGraphLogEntry")," and can be used on the fly for quick operations or as a basis for custom log entries.")),(0,o.yg)("h2",{id:"custom-iloggers"},"Custom ILoggers"),(0,o.yg)("p",null,"By default, the log events will return a contextual message on ",(0,o.yg)("inlineCode",{parentName:"p"},".ToString()")," with the important data related to the event. This is very handy when logging to the console during development."),(0,o.yg)("p",null,(0,o.yg)("img",{alt:"console logger",src:t(346).A,width:"1326",height:"767"})),(0,o.yg)("p",null,"But given the extra data the log entries contain, it makes more sense to create a custom ",(0,o.yg)("inlineCode",{parentName:"p"},"ILogger")," to take advantage of the full object."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Custom ILogger"',title:'"Custom','ILogger"':!0},"public class MyCustomLogger : ILogger\n{\n // other code ommited for brevity\n\n public void Log<TState>(LogLevel logLevel, EventId eventId,\n TState state, Exception exception, Func<TState, Exception, string> formatter)\n {\n // highlight-next-line\n if (state is IGraphLogEntry logEntry)\n {\n // handle the log entry here\n }\n }\n}\n")),(0,o.yg)("admonition",{type:"info"},(0,o.yg)("p",{parentName:"admonition"}," The state parameter of ",(0,o.yg)("inlineCode",{parentName:"p"},"ILogger.Log()")," will be an instance of ",(0,o.yg)("inlineCode",{parentName:"p"},"IGraphLogEntry")," whenever a GraphQL ASP.NET log event is recorded.")),(0,o.yg)("h2",{id:"log-entries-are-keyvaluepair-collections"},"Log Entries are KeyValuePair Collections"),(0,o.yg)("p",null,"While the various ",(0,o.yg)("a",{parentName:"p",href:"./standard-events"},"standard log events")," declare explicit properties for the data they return, every log entry is just a collection of key/value pairs that can be iterated through for quick serialization."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-csharp"},"public interface IGraphLogEntry : IEnumerable<KeyValuePair<string, object>>\n{ /*...*/ }\n")),(0,o.yg)("h2",{id:"logging-category"},"Logging Category"),(0,o.yg)("p",null,"All log events are registered under the the ",(0,o.yg)("inlineCode",{parentName:"p"},"GraphQL.AspNet")," category. With the exception of field authorization fails, all log events are recorded at ",(0,o.yg)("inlineCode",{parentName:"p"},"Debug")," or ",(0,o.yg)("inlineCode",{parentName:"p"},"Trace"),"."),(0,o.yg)("p",null,"Here we've enabled the log events through ",(0,o.yg)("inlineCode",{parentName:"p"},"appsettings.json")),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="appsettings.json"',title:'"appsettings.json"'},'{\n "Logging" : {\n "IncludeScopes" : false,\n "LogLevel": {\n "Default" : "Information",\n "System": "Debug",\n "Microsoft": "Information",\n // highlight-next-line\n "GraphQL.AspNet" : "Debug"\n }\n }\n}\n')),(0,o.yg)("p",null,"Log Entries are not allocated unless their respective log levels are enabled. It is not uncommon for real world queries to generate 100s of log entries per request. Take care to ensure you have setup your logging appropriately in a given environment as it can greatly impact performance if left on by accident. "),(0,o.yg)("admonition",{type:"caution"},(0,o.yg)("p",{parentName:"admonition"},"It is never a good idea to enable trace level logging outside of development.")),(0,o.yg)("h3",{id:"scoped-log-entries"},"Scoped Log Entries"),(0,o.yg)("p",null,(0,o.yg)("inlineCode",{parentName:"p"},"IGraphLogger"),' is generated from your service provider on a "per scope" basis. By default, this is at the HTTP Request level. Any log entries created through it using ',(0,o.yg)("inlineCode",{parentName:"p"},".Log(LogLevel, IGraphLogEntry)")," will be injected with a ",(0,o.yg)("inlineCode",{parentName:"p"},"scopeId")," and along with the date can produce a complete record of a request from when it was received through query plan generation, authorization and field resolution."),(0,o.yg)("p",null,"Here we've used a custom ",(0,o.yg)("inlineCode",{parentName:"p"},"ILogProvider")," and written out a small sample of events to a json array. Note the shared ",(0,o.yg)("inlineCode",{parentName:"p"},"scopeId")," on each entry. See the ",(0,o.yg)("a",{parentName:"p",href:"/docs/reference/demo-projects"},"example projects")," to download the code."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-json"},'[{\n "eventId": 86000,\n "eventName": "GraphQL Request Received",\n "dateTimeUTC": "2022-09-23T22:05:39.6023597+00:00",\n "logEntryId": "6afdf3d1f9464679becfbd2b96aa594f",\n "operationRequestId": "a13f5f7232dc475783c4e4798cfb50d2",\n "userName": "john-doe",\n "queryOperationName": null,\n "queryText": "query {\\n hero(episode: EMPIRE){\\n id\\n name \\n }\\n}",\n "scopeId": "3c3c858b1b344d0b9c0f8ac6b95469d1"\n},\n{\n "eventId": 86400,\n "eventName": "GraphQL Query Plan Generated",\n "dateTimeUTC": "2022-09-23T22:05:39.7214431+00:00",\n "logEntryId": "d133e032e2fc42a98a639ee8c72d2497",\n "schemaType": "GraphQL.AspNet.Schemas.GraphSchema",\n "isValid": true,\n "operationCount": 1,\n "estimatedComplexity": 9.5076,\n "maxDepth": 2,\n "queryPlanId": "57caffc5d5124f2fb08419ae99724974",\n "scopeId": "3c3c858b1b344d0b9c0f8ac6b95469d1"\n},\n{\n "eventId": 86599,\n "eventName": "GraphQL Field Resolution Completed",\n "path": "[type]/Human/Id",\n "dateTimeUTC": "2022-09-23T22:05:39.8528847+00:00",\n "logEntryId": "750ee155bf7e4eea895e5eec01d3fb6d",\n "pipelineRequestId": "aa670d3e2d6a41e1b314711acf6bc51c",\n "typeExpression": "ID!",\n "hasData": true,\n "resultIsValid": true,\n "scopeId": "3c3c858b1b344d0b9c0f8ac6b95469d1"\n}]\n')))}d.isMDXComponent=!0},346:(e,n,t)=>{t.d(n,{A:()=>r});const r=t.p+"assets/images/console-logger-292925d3a6989984be2dcf13e62efdcb.png"}}]); \ No newline at end of file diff --git a/assets/js/e844e6f9.f4adb037.js b/assets/js/e844e6f9.f4adb037.js new file mode 100644 index 0000000..f636ee0 --- /dev/null +++ b/assets/js/e844e6f9.f4adb037.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[9182],{5680:(e,n,t)=>{t.d(n,{xA:()=>u,yg:()=>h});var a=t(6540);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function i(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[n]?arguments[n]:{};n%2?o(Object(t),!0).forEach((function(n){r(e,n,t[n])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):o(Object(t)).forEach((function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))}))}return e}function l(e,n){if(null==e)return{};var t,a,r=function(e,n){if(null==e)return{};var t,a,r={},o=Object.keys(e);for(a=0;a<o.length;a++)t=o[a],n.indexOf(t)>=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a<o.length;a++)t=o[a],n.indexOf(t)>=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var s=a.createContext({}),p=function(e){var n=a.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},u=function(e){var n=p(e.components);return a.createElement(s.Provider,{value:n},e.children)},c="mdxType",d={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},y=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,o=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),c=p(t),y=r,h=c["".concat(s,".").concat(y)]||c[y]||d[y]||o;return t?a.createElement(h,i(i({ref:n},u),{},{components:t})):a.createElement(h,i({ref:n},u))}));function h(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var o=t.length,i=new Array(o);i[0]=y;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[c]="string"==typeof e?e:r,i[1]=l;for(var p=2;p<o;p++)i[p]=t[p];return a.createElement.apply(null,i)}return a.createElement.apply(null,t)}y.displayName="MDXCreateElement"},6291:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var a=t(8168),r=(t(6540),t(5680));const o={id:"actions",title:"Controllers & Actions",sidebar_label:"Actions",sidebar_position:0},i=void 0,l={unversionedId:"controllers/actions",id:"controllers/actions",title:"Controllers & Actions",description:"What is an Action?",source:"@site/docs/controllers/actions.md",sourceDirName:"controllers",slug:"/controllers/actions",permalink:"/docs/controllers/actions",draft:!1,tags:[],version:"current",sidebarPosition:0,frontMatter:{id:"actions",title:"Controllers & Actions",sidebar_label:"Actions",sidebar_position:0},sidebar:"tutorialSidebar",previous:{title:"Made for ASP.NET Developers",permalink:"/docs/introduction/made-for-aspnet-developers"},next:{title:"Model State",permalink:"/docs/controllers/model-state"}},s={},p=[{value:"What is an Action?",id:"what-is-an-action",level:2},{value:"Declaring An Operation Type",id:"declaring-an-operation-type",level:2},{value:"Returning Data",id:"returning-data",level:2},{value:"Working with Dictionaries",id:"working-with-dictionaries",level:3},{value:"Lists and Nulls",id:"lists-and-nulls",level:3},{value:"Working with Interfaces",id:"working-with-interfaces",level:3},{value:"Graph Action Results",id:"graph-action-results",level:3},{value:"Method Parameters",id:"method-parameters",level:2},{value:"Naming your Input Arguments",id:"naming-your-input-arguments",level:3},{value:"Default Argument Values",id:"default-argument-values",level:3},{value:"Working With Lists",id:"working-with-lists",level:3},{value:"Don't Use Dictionaries",id:"dont-use-dictionaries",level:3},{value:"Cancellation Tokens",id:"cancellation-tokens",level:2},{value:"Defining a Query Timeout",id:"defining-a-query-timeout",level:3}],u={toc:p};function c(e){let{components:n,...t}=e;return(0,r.yg)("wrapper",(0,a.A)({},u,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"what-is-an-action"},"What is an Action?"),(0,r.yg)("admonition",{type:"info"},(0,r.yg)("p",{parentName:"admonition"}," An action is a method on a controller, marked as being a query or mutation field, that is part of your graph schema.")),(0,r.yg)("p",null,"Controllers and actions are the bread and butter of GraphQL ASP.NET. Just like with Web API, they serve as an entry point into your business logic."),(0,r.yg)("p",null,"In this graphql query we have two top level query fields: ",(0,r.yg)("inlineCode",{parentName:"p"},"hero")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"droid"),". We declare action methods for each to handle the data request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query {\n hero(episode: EMPIRE){\n id\n name\n }\n droid(id: 2001){\n id\n name\n primaryFunction\n }\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Controllers.cs"',title:'"Controllers.cs"'},"// HeroController.cs\npublic class HeroController : GraphController\n{\n [QueryRoot]\n public Human Hero(Episode episode)\n {\n return new Human(...);\n }\n}\n\n// DroidController.cs\npublic class DroidController : GraphController\n{\n [QueryRoot]\n public Droid Droid(int id)\n {\n return new Droid(...);\n }\n}\n")),(0,r.yg)("p",null,"In the above example, it makes sense these these methods would exist on different controllers, ",(0,r.yg)("inlineCode",{parentName:"p"},"HeroController")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"DroidController"),". However, unlike with a REST API request, which will usually invoke one action method and returns the data generated, GraphQL will invoke every action method requested and aggregates the results. If one action fails, the other may not and the results of both the errors and the data retrieved would be returned."),(0,r.yg)("p",null,"The data returned by your action methods then return their requested child fields, those data items to their children and so on. In many cases this is just a selection of the appropriate properties on a model object, but more complex scenarios involving child objects, ",(0,r.yg)("a",{parentName:"p",href:"./type-extensions"},"type extensions")," or directly executing POCO methods can also occur."),(0,r.yg)("admonition",{type:"note"},(0,r.yg)("p",{parentName:"admonition"},"Since mutations may have a shared state or could otherwise produce race conditions in a data store, top level fields in a mutation operation are executed sequentially, in the order they are declared. All child requests there after are dispatched and executed asynchronously [",(0,r.yg)("a",{parentName:"p",href:"https://graphql.github.io/graphql-spec/October2021/#sec-Mutation"},"Spec \xa7 6.2.2"),"]",".")),(0,r.yg)("hr",null),(0,r.yg)("p",null,"To declare an action you first declare a controller that inherits from ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL.AspNet.Controllers.GraphController"),", then create a method with the the appropriate attribute to indicate it as a query or mutation method."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"An Action Method:")),(0,r.yg)("p",null," \u2705 ",(0,r.yg)("strong",{parentName:"p"},"Must")," declare an ",(0,r.yg)("a",{parentName:"p",href:"./actions#declaring-an-operation-type"},"operation type")," or a ",(0,r.yg)("a",{parentName:"p",href:"../controllers/type-extensions"},"type extension")," attribute. ",(0,r.yg)("br",null),"\n\u2705 ",(0,r.yg)("strong",{parentName:"p"},"Must")," declare a return type. ",(0,r.yg)("br",null),"\n\u2705 ",(0,r.yg)("strong",{parentName:"p"},"May")," be synchronous or asynchronous. ",(0,r.yg)("br",null),"\n\ud83e\udde8 ",(0,r.yg)("strong",{parentName:"p"},"Must not")," return ",(0,r.yg)("inlineCode",{parentName:"p"},"System.Object"),". ",(0,r.yg)("br",null),"\n\ud83e\udde8 ",(0,r.yg)("strong",{parentName:"p"},"Must not")," declare, as an input parameter, an object that implements any variation of ",(0,r.yg)("inlineCode",{parentName:"p"},"IDictionary"),". ",(0,r.yg)("br",null)),(0,r.yg)("p",null,"See below for more detail of the ",(0,r.yg)("a",{parentName:"p",href:"./actions#method-parameters"},"input")," and ",(0,r.yg)("a",{parentName:"p",href:"./actions#returning-data"},"return")," parameter restrictions."),(0,r.yg)("h2",{id:"declaring-an-operation-type"},"Declaring An Operation Type"),(0,r.yg)("p",null,"Attributes ",(0,r.yg)("inlineCode",{parentName:"p"},"[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"[QueryRoot]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"[Mutation]")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"[MutationRoot]")," are used to declare your action methods. Usage of the mutation and query attributes are exactly the same, they differ only in which of the root graph types to place a field reference. Lets look at the most common ways to use them:"),(0,r.yg)("br",null),(0,r.yg)("p",null,"\ud83d\udcbb ",(0,r.yg)("inlineCode",{parentName:"p"},"[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"[Mutation]")),(0,r.yg)("p",null,"When used alone, without any parameters, the field name in the schema is the same as the method name."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using [Query] & [Mutation]"',title:'"Using',"[Query]":!0,"&":!0,'[Mutation]"':!0},"public class BakeryController : GraphController\n{\n // highlight-next-line\n [Query]\n public Donut FindDonut(int id)\n {/* ... */ }\n\n // highlight-next-line\n [Mutation]\n public CakeModel UpdateCake(CakeModel cake)\n {/* ... */ }\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'query {\n bakery {\n findDonut(id: 5){\n name\n flavor\n }\n }\n} \n\nmutation {\n bakery {\n updateCake(cake: {id: 5, name: "Birthday Cake"}){\n id\n name\n }\n }\n}\n')),(0,r.yg)("br",null),(0,r.yg)("br",null),(0,r.yg)("p",null,"\ud83d\udcbb ",(0,r.yg)("inlineCode",{parentName:"p"},'[Query("donut")]'),",",(0,r.yg)("inlineCode",{parentName:"p"},'[Mutation("alterCake")]')),(0,r.yg)("p",null,"You can supply the name of the field you want to use in the schema allowing for different naming schemes in your C# code vs. your object graph. "),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using [Query] & [Mutation]"',title:'"Using',"[Query]":!0,"&":!0,'[Mutation]"':!0},'public class BakeryController : GraphController\n{\n // highlight-next-line\n [Query("donut")]\n public Donut FindDonut(int id)\n {/* ... */ }\n\n // highlight-next-line\n [Mutation("alterCake")]\n public CakeModel UpdateCake(CakeModel cake)\n {/* ... */ }\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'query {\n bakery {\n donut(id: 5){\n name\n flavor\n }\n }\n} \n\nmutation {\n bakery {\n alterCake(cake: {id: 5, name: "Birthday Cake"}) {\n id\n name\n }\n }\n}\n')),(0,r.yg)("br",null),(0,r.yg)("br",null),(0,r.yg)("p",null,"\ud83d\udcbb ",(0,r.yg)("inlineCode",{parentName:"p"},'[Query("donut", typeof(Donut))]'),", ",(0,r.yg)("inlineCode",{parentName:"p"},'[Mutation("donut", typeof(CakeModel))]')),(0,r.yg)("p",null," Sometimes, especially when you return action results, you may need to explicitly declare what data type you are returning from the method. This is because returning ",(0,r.yg)("inlineCode",{parentName:"p"},"IGraphActionResult")," obfuscates the results from the templating engine and it won't be able to infer the underlying type expression of the field. "),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using [Query] & [Mutation]"',title:'"Using',"[Query]":!0,"&":!0,'[Mutation]"':!0},'public class BakeryController : GraphController\n{\n [Query("donut")]\n public Donut FindDonut(int id)\n {/* ... */ }\n\n // highlight-next-line\n [Mutation("alterCake",typeof(CakeModel))]\n public async Task<IGraphActionResult> UpdateCake(CakeModel cake)\n {\n await _service.UpdateCake(cake);\n return this.Ok(cake);\n }\n}\n')),(0,r.yg)("br",null),(0,r.yg)("br",null),(0,r.yg)("p",null,"\ud83d\udcbb ",(0,r.yg)("inlineCode",{parentName:"p"},"[QueryRoot]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"[MutationRoot]")),(0,r.yg)("p",null,"These are special overloads to ",(0,r.yg)("inlineCode",{parentName:"p"},"[Query]")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"[Mutation]"),". They are declared in the same way but instruct GraphQL to ignore any inherited field fragments from the controller."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using [QueryRoot]"',title:'"Using','[QueryRoot]"':!0},'public class BakeryController : GraphController\n{\n // highlight-next-line\n [QueryRoot("donut")]\n public Donut FindDonut(int id)\n {/* ... */ }\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"# Notice the bakery field is gone!\nquery {\n donut(id: 5){\n name\n flavor\n }\n} \n")),(0,r.yg)("br",null),(0,r.yg)("br",null),(0,r.yg)("p",null,"\ud83d\udcbb ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphRoute]")),(0,r.yg)("p",null,"If you'll recall, Web API uses ",(0,r.yg)("inlineCode",{parentName:"p"},'[Route("somePathSegment")]')," declared on a controller to nest all the REST end points under that url piece. The same holds true here. Graph controllers can declare ",(0,r.yg)("inlineCode",{parentName:"p"},'[GraphRoute("someFieldName")]')," under which all the controller actions will be nested as child fields. This is the default behavior even if you don't declare a custom name (your controller name is used). Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"QueryRoot")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"MutationRoot")," attributes negates this and appends the action directly to the root graph type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using [GraphRoute]"',title:'"Using','[GraphRoute]"':!0},'// highlight-next-line\n[GraphRoute("BakedGoods")]\npublic class BakeryController : GraphController\n{\n [Query("donut")]\n public Donut FindDonut(int id)\n {/* ... */ }\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query {\n bakedGoods {\n donut(id: 5){\n name\n flavor\n }\n }\n} \n")),(0,r.yg)("p",null,"A complete explanation of the constructors for these attributes is available in the ",(0,r.yg)("a",{parentName:"p",href:"../reference/attributes"},"attributes reference")," and a detailed explanation of the nesting rules is available under the ",(0,r.yg)("a",{parentName:"p",href:"./field-paths"},"field paths")," section."),(0,r.yg)("h2",{id:"returning-data"},"Returning Data"),(0,r.yg)("p",null,"GraphQL creates your schema by first looking your controller action methods for the objects they return. It then inspects the properties/methods on those objects for other objects, then the properties of those child objects and so on. In GraphQL, there are no unknown or variable fields. For the library to determine your schema it MUST know what each action method returns. "),(0,r.yg)("p",null,"Unlike rest, the data you return is restricted to formats acceptable by graphql."),(0,r.yg)("h3",{id:"working-with-dictionaries"},"Working with Dictionaries"),(0,r.yg)("p",null,"Dictionary types, such as ",(0,r.yg)("inlineCode",{parentName:"p"},"Dictionary<TKey,TValue>"),", are generally not useful in GraphQL. They are forbidden as input parameters, since its not possible to validate arbitrary key/value pairs, and GraphQL makes no use of their lookup abilities as output objects. "),(0,r.yg)("p",null,"This isn't to say that dictionaries should be ignored. On the contrary, use them as needed to generate your data and perform your business logic. Just don't return a dictionary from your action method. "),(0,r.yg)("p",null,"However, ",(0,r.yg)("a",{parentName:"p",href:"../controllers/batch-operations"},"Batch operations"),", also called ",(0,r.yg)("inlineCode",{parentName:"p"},"Data Loaders"),", are a special type of extension method that uses dictionaries to map child data to multiple parents. Batch operations are incredibly important to the performance of your queries when you start working with large quantities of deeply nested parent/child relationships."),(0,r.yg)("h3",{id:"lists-and-nulls"},"Lists and Nulls"),(0,r.yg)("p",null,"Rules concerning GraphQL's two meta graph types, ",(0,r.yg)("a",{parentName:"p",href:"../types/list-non-null"},"LIST and NON_NULL")," apply to action methods as well. Like all fields, the runtime will attempt to determine the complete ",(0,r.yg)("a",{parentName:"p",href:"../advanced/type-expressions"},"type expression")," for your action method and you have the ability to override it as needed."),(0,r.yg)("h3",{id:"working-with-interfaces"},"Working with Interfaces"),(0,r.yg)("p",null,"Returning an ",(0,r.yg)("a",{parentName:"p",href:"../types/interfaces"},"interface graph type")," is a great way to deliver heterogeneous data results, especially in search operations. One got'cha is that the runtime must know the possible concrete object types that implement that interface in case a query uses a fragment with a type specification. That is to say that if we return ",(0,r.yg)("inlineCode",{parentName:"p"},"IPastry"),", we must let GraphQL know that ",(0,r.yg)("inlineCode",{parentName:"p"},"Cake")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"Donut")," exist and should be a part of our schema. Just like with C#, interfaces in graphql contain no logic. If i return an ",(0,r.yg)("inlineCode",{parentName:"p"},"IPastry"),", GraphQL still needs to know if the actual object is a ",(0,r.yg)("inlineCode",{parentName:"p"},"Cake")," or a ",(0,r.yg)("inlineCode",{parentName:"p"},"Donut")," and invoke the correct resolver for any child fields."),(0,r.yg)("admonition",{type:"info"},(0,r.yg)("p",{parentName:"admonition"},"When your action method returns an interface you must declare OBJECT types that implement that interface in some other way. "),(0,r.yg)("p",{parentName:"admonition"},"e.g. If your schema contains ",(0,r.yg)("inlineCode",{parentName:"p"},"IPastry"),", it must also contain ",(0,r.yg)("inlineCode",{parentName:"p"},"Cake")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"Donut"),".")),(0,r.yg)("p",null,"Take this example:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs"',title:'"BakeryController.cs"'},"public class BakeryController : GraphController\n{\n [QueryRoot]\n // highlight-next-line\n public IPastry SearchPastries(string name)\n {/* ... */}\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Query"',title:'"Query"'},'query {\n searchPastries(name: "chocolate*") {\n id\n name\n\n ...on Donut {\n isFilled\n }\n\n ...on Cake {\n icingFlavor\n }\n }\n}\n')),(0,r.yg)("p",null,"No where in our code have we told GraphQL about ",(0,r.yg)("inlineCode",{parentName:"p"},"Cake")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"Donut"),". When it goes to parse the fragments declared in the query it will try to validate that graph types exist named ",(0,r.yg)("inlineCode",{parentName:"p"},"Cake")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"Donut")," to ensure the fields in the fragments are valid, since we've never declared those graph types it won't be able to."),(0,r.yg)("p",null,"There are a number of ways to indicate these required relationships in your code in order to generate your schema correctly."),(0,r.yg)("br",null),(0,r.yg)("p",null," \ud83d\udcc3 ",(0,r.yg)("strong",{parentName:"p"},"Add OBJECT Types Directly to the Action Method")),(0,r.yg)("p",null,"If you have just two or three possible types, add them directly to the query attribute. You can safely add your types across multiple methods as needed, it will only be included in the schema once."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp"},"public class BakeryController : GraphController\n{\n // highlight-next-line\n [QueryRoot(typeof(Cake), typeof(Donut))]\n public IPastry SearchPastries(string name)\n {/* ... */}\n}\n")),(0,r.yg)("br",null),(0,r.yg)("p",null,"\ud83d\udcdc ",(0,r.yg)("strong",{parentName:"p"},"Using the PossibleTypes attribute")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"[Query]")," attribute can get a bit hard to read with a ton of data in it (especially with ",(0,r.yg)("a",{parentName:"p",href:"../types/unions"},"Unions"),"). Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"[PossibleTypes]")," attribute to help with readability."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp"},"public class BakeryController : GraphController\n{\n [QueryRoot]\n // highlight-next-line\n [PossibleTypes(typeof(Cake), typeof(Donut), typeof(Scone), typeof(Croissant))]\n public IPastry SearchPastries(string name)\n {/* ... */}\n}\n")),(0,r.yg)("br",null),(0,r.yg)("p",null,"\ud83e\uddee ",(0,r.yg)("strong",{parentName:"p"},"Declare Types at Startup")),(0,r.yg)("p",null,"The ",(0,r.yg)("a",{parentName:"p",href:"../reference/schema-configuration"},"schema configuration")," contains a host of options for auto-loading graph types. Here we've added our 100s and 1000s of types of pastries at our bakery to a shared assembly, obtained a reference to it through one of the types it contains, then added the whole assembly to our schema. GraphQL will automatically scan the assembly and ingest all the graph types mentioned in any controllers it finds as well as any objects marked with the ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphType]")," attribute."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="startup code"',title:'"startup','code"':!0},"// we can define all our objects in a single assembly, then load it\nAssembly pastryAssembly = Assembly.GetAssembly(typeof(Cake));\n\nservices.AddGraphQL(options =>\n{\n // highlight-next-line\n options.AddAssembly(pastryAssembly);\n});\n")),(0,r.yg)("br",null),(0,r.yg)("p",null,"\u26a0\ufe0f ",(0,r.yg)("strong",{parentName:"p"},"A Note On Type Ingestion")),(0,r.yg)("p",null,'You might be wondering, "if I just define ',(0,r.yg)("inlineCode",{parentName:"p"},"Cake")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"Donut")," in my application, why can't GraphQL just include them like it does the controller?\"."),(0,r.yg)("p",null,"It certainly can, but there are risks to arbitrarily grabbing class references not exposed on a schema. With introspection queries, all of those classes and their method/property names could be exposed and pose a security risk. It might not be able to query the data, but imagine if a enum named ",(0,r.yg)("inlineCode",{parentName:"p"},"EmployeeDiscountCodes")," was accidentally added to your graph. All the values of that enum would be publically exposed via introspection."),(0,r.yg)("p",null,"To combat this GraphQL will only ingest types that are:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Referenced in a ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphController")," action method ",(0,r.yg)("strong",{parentName:"li"},"OR")),(0,r.yg)("li",{parentName:"ul"},"Attributed with at least once instance of a ",(0,r.yg)("inlineCode",{parentName:"li"},"[GraphType]")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"[GraphField]")," attribute somewhere within the class ",(0,r.yg)("strong",{parentName:"li"},"OR")),(0,r.yg)("li",{parentName:"ul"},"Added explicitly at startup during ",(0,r.yg)("inlineCode",{parentName:"li"},".AddGraphQL()"),".")),(0,r.yg)("p",null,"This behavior is controlled with your schema's declaration configuration to make it more or less restrictive based on your needs. Ultimately you are in control of how aggressive or restrictive GraphQL should be; even going so far as declaring that every type be declared with ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphType]")," and every field with ",(0,r.yg)("inlineCode",{parentName:"p"},"[GraphField]")," lest it be ignored completely. The amount of automatic vs. manual wire up will vary from use case to use case but you should be able to achieve the result you desire."),(0,r.yg)("h3",{id:"graph-action-results"},"Graph Action Results"),(0,r.yg)("p",null,"Action Results provide a clean way to standardize your responses to different conditions across your application. In a Web API controller, if you've ever used ",(0,r.yg)("inlineCode",{parentName:"p"},"this.OK()")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"this.NotFound()")," you've used the concept of an action result before. "),(0,r.yg)("p",null,"Using action results can make your code a lot more readable and provide helpful, customizable messaging to the requestor. "),(0,r.yg)("p",null,"For Example, using ",(0,r.yg)("inlineCode",{parentName:"p"},"this.Error()")," injects a custom error message into the response providing some additional information other than just a null result. "),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs"',title:'"BakeryController.cs"'},'// BakeryController.cs\npublic class BakeryController : GraphController\n{\n [QueryRoot(typeof(IPastry))]\n public async Task<IGraphActionResult> SearchPastries(string name)\n {\n if(name == null || name.Length < 3)\n {\n // highlight-next-line\n return this.Error(GraphMessageSeverity.Warning, "At least 3 characters is required");\n }\n else\n {\n var results = await _service.SearchPastries(name); \n return this.Ok(results);\n }\n }\n}\n')),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},"The full list of graph action results can be found in the ",(0,r.yg)("a",{parentName:"p",href:"../advanced/graph-action-results"},"reference section")," .")),(0,r.yg)("p",null,"Create a class that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"IGraphActionResult")," and create your own."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="IGraphActionResult.cs"',title:'"IGraphActionResult.cs"'},"public interface IGraphActionResult\n{\n Task Complete(BaseResolutionContext context);\n}\n")),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"IGraphActionResult")," has one method. It accepts the raw resolution context (either a ",(0,r.yg)("inlineCode",{parentName:"p"},"FieldResolutionContext")," or a ",(0,r.yg)("inlineCode",{parentName:"p"},"DirectiveResolutionContext"),") that you can manipulate as needed. Combine this with any data you supply to your action result when you instantiate it and you have the ability to generate any response with any data value or any number and type of error messages etc. Take a look at the source code for built in ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Controllers/ActionResults"},"graph action results")," for some more detailed examples."),(0,r.yg)("admonition",{title:"Its Not REST",type:"caution"},(0,r.yg)("p",{parentName:"admonition"},"Action results for graph fields are not the same as REST action results. For Example, ",(0,r.yg)("inlineCode",{parentName:"p"},"BadRequest()")," does not return an HTTP status 400 for the request. An errored field is usually just one of many in the query and graphql supports partial query resolution. We use the ",(0,r.yg)("inlineCode",{parentName:"p"},"errors")," collection on a graphql response to provide details on what happened with any given field. The overall query will almost always return an HTTP status 200.")),(0,r.yg)("h2",{id:"method-parameters"},"Method Parameters"),(0,r.yg)("p",null,"Parameters on your action methods are interpreted as field arguments in your graph. GraphQL will inspect your method parameters and add the appropriate ",(0,r.yg)("a",{parentName:"p",href:"../types/scalars"},(0,r.yg)("inlineCode",{parentName:"a"},"SCALAR")),", ",(0,r.yg)("a",{parentName:"p",href:"../types/enums"},(0,r.yg)("inlineCode",{parentName:"a"},"ENUM"))," and ",(0,r.yg)("a",{parentName:"p",href:"../types/input-objects"},(0,r.yg)("inlineCode",{parentName:"a"},"INPUT_OBJECT"))," graph types to your schema automatically."),(0,r.yg)("h3",{id:"naming-your-input-arguments"},"Naming your Input Arguments"),(0,r.yg)("p",null,"By default, GraphQL will name a field's arguments the same as the parameter names in your method. Sometimes you'll want to override this, like when needing to use a C# keyword as an argument name. Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"[FromGraphQL]")," attribute on the parameter to accomplish this."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Overriding a Default Argument Name"',title:'"Overriding',a:!0,Default:!0,Argument:!0,'Name"':!0},'public class BakeryController : GraphController\n{\n [QueryRoot] \n // highlight-next-line\n public IEnumerable<Donut> SearchDonuts([FromGraphQL("name")] string searchText)\n {/* ... */}\n}\n')),(0,r.yg)("p",null,"We can then execute the query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'// GraphQL Query\nquery {\n searchPastries(name: "chocolate*") {\n id\n name\n flavor\n }\n}\n')),(0,r.yg)("h3",{id:"default-argument-values"},"Default Argument Values"),(0,r.yg)("p",null,"In GraphQL, not all field arguments are required. Add a default value to your method parameters to mark them as optional:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using an Optional Field Argument"',title:'"Using',an:!0,Optional:!0,Field:!0,'Argument"':!0},'public class BakeryController : GraphController\n{\n [QueryRoot] \n // highlight-next-line\n public Donut SearchDonuts(string name = "*")\n {/* ... */}\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},'# Pass a value\nquery {\n searchDonuts(name: "Chocolate*"){\n id\n flavor\n }\n}\n\n# The default value for name will be used (e.g. "*")\nquery {\n searchDonuts {\n id\n flavor\n }\n}\n')),(0,r.yg)("br",null),(0,r.yg)("p",null,"\u26a0\ufe0f ",(0,r.yg)("strong",{parentName:"p"},"Nullable vs. Not Required")),(0,r.yg)("p",null,'Note that there is a difference between "nullable" and "not required" for field arguments. If we have a nullable int as an input parameter, without a default value we still have to pass it to the field, even if we pass it as ',(0,r.yg)("inlineCode",{parentName:"p"},"null"),"; just like if we were to invoke the method from our C# code."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="NumberController.cs"',title:'"NumberController.cs"'},'public class NumberController : GraphController\n{\n // "seed" is still required, but you can supply null\n [QueryRoot] \n // highlight-next-line\n public int CreateRandomInt(int? seed)\n {/* ... */}\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Queries"',title:'"Sample','Queries"':!0},"# The argument must be passed\n# but it can passed as null\nquery {\n createRandomInt(seed: null)\n}\n\n## *** \n## ERROR, argument not supplied\n## *** \nquery {\n createRandomInt\n}\n")),(0,r.yg)("p",null,"By also defining a default value we can achieve the flexibility we are looking for."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="NumberController.cs"',title:'"NumberController.cs"'},"public class NumberController : GraphController\n{ \n [QueryRoot]\n // highlight-next-line\n public int CreateRandomInt(int? seed = null)\n {/* ... */}\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Queries"',title:'"Sample','Queries"':!0},"# Pass a value\nquery {\n createRandomInt(seed: 5)\n}\n\n# Pass null\nquery {\n createRandomInt(seed: null)\n}\n\n# Or omit the parameter (value received: null)\nquery {\n createRandomInt\n}\n")),(0,r.yg)("h3",{id:"working-with-lists"},"Working With Lists"),(0,r.yg)("p",null,"When constructing a set of items as an argument to an action method, GraphQL will instantiate a ",(0,r.yg)("inlineCode",{parentName:"p"},"List<T>")," internally and fill it with the appropriate data; be that another list, another input object, a scalar etc. While you can declare an array (e.g. ",(0,r.yg)("inlineCode",{parentName:"p"},"Donut[]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"int[]")," etc.) as your list structure for an input argument, graphql has to rebuild its internal representation to meet the requirements of your method. In some cases, especially with nested lists, or combinations of lists and arrays, this results in an ",(0,r.yg)("inlineCode",{parentName:"p"},"O(N)")," increase in processing time. "),(0,r.yg)("admonition",{type:"tip"},(0,r.yg)("p",{parentName:"admonition"},"Use ",(0,r.yg)("inlineCode",{parentName:"p"},"IEnumerable<T>")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"IList<T>")," as your argument types to avoid a performance bottleneck when sending lots of items as input data.")),(0,r.yg)("p",null,"This example shows various ways of accepting collections of data as inputs to controller actions."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs"',title:'"BakeryController.cs"'},'public class BakeryController : GraphController\n{\n // a list of donuts\n // schema syntax: [Donut]\n [Mutation("createDonuts")]\n public bool CreateDonuts(IEnumerable<Donut> donuts)\n {/*....*/}\n\n // when used as a "list of list"\n // schema syntax: [[Donut]]\n [Mutation("createDonutsBySet")]\n public bool CreateDonuts(List<List<Donut>> donuts)\n {/*....*/}\n\n // when supplied as a regular array\n // schema syntax: [Donut]\n [Mutation("donutsAsAnArray")]\n public bool DonutsAsAnArray(Donut[] donuts)\n {/*....*/}\n\n // This is a valid nested list believe it or not\n // schema syntax: [[[Donut]]]\n [Mutation("mixedDonuts")]\n public bool MixedDonuts(List<IEnumerable<Donut[]>> donuts)\n {/*....*/}\n}\n')),(0,r.yg)("h3",{id:"dont-use-dictionaries"},"Don't Use Dictionaries"),(0,r.yg)("p",null,"You might be tempted to use a dictionary as a parameter to accept arbitrary key value pairs into your methods. GraphQL will reject it and throw a declaration exception when your schema is created:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs"',title:'"BakeryController.cs"'},"public class BakeryController : GraphController\n{\n // ERROR, a GraphTypeDeclarationException\n // will be thrown.\n [QueryRoot]\n public IEnumerable<Donut>\n SearchDonuts(IDictionary searchParams)\n {/* ... */}\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Invalid Arguments"',title:'"Invalid','Arguments"':!0},'# ERROR, Unknown arguments on searchDonuts\nquery {\n searchDonuts(\n name: "jelly*"\n filled: true\n dayOld: false){\n id\n name\n }\n}\n')),(0,r.yg)("p",null,"At runtime, GraphQL will try to validate every argument on every field passed on a query. No where have we declared an argument ",(0,r.yg)("inlineCode",{parentName:"p"},"filled")," to be a boolean or ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," to be a string."),(0,r.yg)("p",null,"Well, lets just pass it as an input object to a declared argument, right?"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Invalid Input Object"',title:'"Invalid',Input:!0,'Object"':!0},'# ERROR, Unknown fields on searchParams\nquery {\n searchDonuts( searchParams : {name: "jelly*" filled: true dayOld: false }){\n id\n name\n }\n}\n')),(0,r.yg)("p",null,"But this is also not allowed. All we've done is pushed the problem down one level. No where on our ",(0,r.yg)("inlineCode",{parentName:"p"},"IDictionary")," type is there a ",(0,r.yg)("inlineCode",{parentName:"p"},"Name")," property declared as a string or a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filled")," property declared as a boolean. Since GraphQL can't fully validate the query against the schema before executing it, it's rejected."),(0,r.yg)("p",null,"Instead declare a search object with the parameters you need and use it as the input:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs"',title:'"BakeryController.cs"'},"public class DonutSearchParams\n{\n public string Name { get; set; }\n public bool? Filled { get; set; }\n public bool? DayOld { get; set; }\n}\n\npublic class BakeryController : GraphController\n{\n [QueryRoot]\n public IEnumerable<Donut> SearchDonuts(DonutSearchParams searchParams)\n {/* ... */}\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Valid Query"',title:'"Valid','Query"':!0},"query {\n searchDonuts( searchParams : {filled: true}){\n id\n name\n }\n}\n")),(0,r.yg)("h2",{id:"cancellation-tokens"},"Cancellation Tokens"),(0,r.yg)("p",null,"As with REST based action methods, your graph controller action methods can accept an optional ",(0,r.yg)("inlineCode",{parentName:"p"},"CancellationToken"),". This is useful when doing some long running activities such as IO, database queries, API orchestration etc. To make use of a cancellation token simply add it as a parameter to your method. GraphQL will automatically capture the token, wire it up for you and hide it from your schema."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs | Adding a CancellationToken"',title:'"BakeryController.cs',"|":!0,Adding:!0,a:!0,'CancellationToken"':!0},"public class BakeryController : GraphController\n{\n // Add a CancellationToken to your controller method\n [QueryRoot(typeof(IEnumerable<Donut>))]\n // highlight-next-line\n public async Task<IGraphActionResult> SearchDonuts(string name, CancellationToken cancelToken)\n {/* ... */}\n}\n")),(0,r.yg)("admonition",{type:"caution"},(0,r.yg)("p",{parentName:"admonition"}," Depending on your usage of the cancellation token a ",(0,r.yg)("inlineCode",{parentName:"p"},"TaskCanceledException")," may be thrown. GraphQL will not attempt to intercept this exception and will log it as an error-level, unhandled exception event if allowed to propegate. The query will still be cancelled as expected.")),(0,r.yg)("h3",{id:"defining-a-query-timeout"},"Defining a Query Timeout"),(0,r.yg)("p",null,"By default, the library does not define a timeout for an executed query. The query will run as long as the underlying HTTP connection is open. In fact, the ",(0,r.yg)("inlineCode",{parentName:"p"},"CancellationToken")," passed to your action methods is the same Cancellation Token offered on the HttpContext when it receives the initial request."),(0,r.yg)("p",null,"Optionally, you can define a query timeout for a given schema:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Startup Code"',title:'"Startup','Code"':!0},"services.AddGraphQL(o =>\n{\n // define a 2 minute timeout for every query.\n // highlight-next-line\n o.ExecutionOptions.QueryTimeout = TimeSpan.FromMinutes(2);\n})\n")),(0,r.yg)("p",null,"When a timeout is defined, the token passed to your action methods is a combined token representing the HttpContext as well as the timeout operation. That is to say the token will indicate a cancellation if the allotted query time expires or the http connection is closed, which ever comes first. When the timeout expires the caller will receive a response indicating the timeout. However, if the its the HTTP connection that is closed, the operation is simply halted and no result is produced."),(0,r.yg)("admonition",{title:"Timeouts and Subscriptions",type:"danger"},(0,r.yg)("p",{parentName:"admonition"},"The same rules for cancellation tokens apply to subscriptions as well. Since the websocket connection is a long running operation it will never be closed until the connection is closed. To prevent some processes from spinning out of control its a good idea to define a query timeout when implementing a subscription server. This way, even though the connection remains open the query will terminate and release resources if something goes awry.")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/e9cf564a.17ce7feb.js b/assets/js/e9cf564a.17ce7feb.js new file mode 100644 index 0000000..33a35ab --- /dev/null +++ b/assets/js/e9cf564a.17ce7feb.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[7678],{5680:(e,t,r)=>{r.d(t,{xA:()=>u,yg:()=>d});var n=r(6540);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function o(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?i(Object(r),!0).forEach((function(t){a(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):i(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}function c(e,t){if(null==e)return{};var r,n,a=function(e,t){if(null==e)return{};var r,n,a={},i=Object.keys(e);for(n=0;n<i.length;n++)r=i[n],t.indexOf(r)>=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n<i.length;n++)r=i[n],t.indexOf(r)>=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var l=n.createContext({}),s=function(e){var t=n.useContext(l),r=t;return e&&(r="function"==typeof e?e(t):o(o({},t),e)),r},u=function(e){var t=s(e.components);return n.createElement(l.Provider,{value:t},e.children)},p="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},y=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,i=e.originalType,l=e.parentName,u=c(e,["components","mdxType","originalType","parentName"]),p=s(r),y=a,d=p["".concat(l,".").concat(y)]||p[y]||h[y]||i;return r?n.createElement(d,o(o({ref:t},u),{},{components:r})):n.createElement(d,o({ref:t},u))}));function d(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=r.length,o=new Array(i);o[0]=y;var c={};for(var l in t)hasOwnProperty.call(t,l)&&(c[l]=t[l]);c.originalType=e,c[p]="string"==typeof e?e:a,o[1]=c;for(var s=2;s<i;s++)o[s]=r[s];return n.createElement.apply(null,o)}return n.createElement.apply(null,r)}y.displayName="MDXCreateElement"},456:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>p,frontMatter:()=>i,metadata:()=>c,toc:()=>s});var n=r(8168),a=(r(6540),r(5680));const i={id:"query-cache",title:"Query Caching",sidebar_label:"Query Caching",sidebar_position:8},o=void 0,c={unversionedId:"reference/query-cache",id:"reference/query-cache",title:"Query Caching",description:"When GraphQL ASP.NET parses a query, it generates a query plan that contains all the required data needed to execute the requested operation. For most queries this process is near instantaneous but in some particularly large queries it may take an extra moment to generate a full query plan. The query cache will help alleviate this bottleneck by caching a plan for a set period of time to skip the parsing and generation phases when completing a request.",source:"@site/docs/reference/query-caching.md",sourceDirName:"reference",slug:"/reference/query-cache",permalink:"/docs/reference/query-cache",draft:!1,tags:[],version:"current",sidebarPosition:8,frontMatter:{id:"query-cache",title:"Query Caching",sidebar_label:"Query Caching",sidebar_position:8},sidebar:"tutorialSidebar",previous:{title:"Pipelines & Middleware",permalink:"/docs/reference/middleware"},next:{title:"Demo Projects",permalink:"/docs/reference/demo-projects"}},l={},s=[{value:"Enabling the Query Cache",id:"enabling-the-query-cache",level:2}],u={toc:s};function p(e){let{components:t,...r}=e;return(0,a.yg)("wrapper",(0,n.A)({},u,r,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"When GraphQL ASP.NET parses a query, it generates a query plan that contains all the required data needed to execute the requested operation. For most queries this process is near instantaneous but in some particularly large queries it may take an extra moment to generate a full query plan. The query cache will help alleviate this bottleneck by caching a plan for a set period of time to skip the parsing and generation phases when completing a request."),(0,a.yg)("p",null,"The query cache makes a concerted effort to only cache plans that are truly unique and thus it will take a moment to analyze the incoming query to see if it identical to one that is already cached. For small queries the amount of time it takes to scrub the query text and look up a plan in the cache could be ",(0,a.yg)("em",{parentName:"p"},"as long")," as reparsing the query (200-300\u03bcs)."),(0,a.yg)("p",null,"Consider using the Query Cache only if:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Your application's individual query size is regularly more than 1000 characters in length"),(0,a.yg)("li",{parentName:"ul"},"You make use of a lot of interface graph types and a lot of object graph types for each of those interfaces."),(0,a.yg)("li",{parentName:"ul"},"When ",(0,a.yg)("a",{parentName:"li",href:"../execution/metrics"},"Profiling")," reveals a bottleneck in the ",(0,a.yg)("inlineCode",{parentName:"li"},"parsing")," phase of any given query.")),(0,a.yg)("h2",{id:"enabling-the-query-cache"},"Enabling the Query Cache"),(0,a.yg)("p",null,"At startup, inject the query cache into the service collection. The cache itself is schema agnostic."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Startup Code"',title:'"Startup','Code"':!0},"// Register the query cache BEFORE calling .AddGraphQL\n// highlight-next-line\nservices.AddGraphQLLocalQueryCache();\n\nservices.AddGraphQL();\n")),(0,a.yg)("admonition",{type:"note"},(0,a.yg)("p",{parentName:"admonition"},'Because a query plan contains function pointers and references to local graph types, the default query cache is restricted to being "in process" for a single server instance and does not scale out to redis or other similar technologies. ')))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ec78a8f8.e83fc98d.js b/assets/js/ec78a8f8.e83fc98d.js new file mode 100644 index 0000000..6005180 --- /dev/null +++ b/assets/js/ec78a8f8.e83fc98d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[5731],{5680:(e,t,o)=>{o.d(t,{xA:()=>d,yg:()=>y});var n=o(6540);function r(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}function a(e,t){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),o.push.apply(o,n)}return o}function i(e){for(var t=1;t<arguments.length;t++){var o=null!=arguments[t]?arguments[t]:{};t%2?a(Object(o),!0).forEach((function(t){r(e,t,o[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(o)):a(Object(o)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(o,t))}))}return e}function l(e,t){if(null==e)return{};var o,n,r=function(e,t){if(null==e)return{};var o,n,r={},a=Object.keys(e);for(n=0;n<a.length;n++)o=a[n],t.indexOf(o)>=0||(r[o]=e[o]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n<a.length;n++)o=a[n],t.indexOf(o)>=0||Object.prototype.propertyIsEnumerable.call(e,o)&&(r[o]=e[o])}return r}var s=n.createContext({}),p=function(e){var t=n.useContext(s),o=t;return e&&(o="function"==typeof e?e(t):i(i({},t),e)),o},d=function(e){var t=p(e.components);return n.createElement(s.Provider,{value:t},e.children)},c="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},h=n.forwardRef((function(e,t){var o=e.components,r=e.mdxType,a=e.originalType,s=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),c=p(o),h=r,y=c["".concat(s,".").concat(h)]||c[h]||u[h]||a;return o?n.createElement(y,i(i({ref:t},d),{},{components:o})):n.createElement(y,i({ref:t},d))}));function y(e,t){var o=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=o.length,i=new Array(a);i[0]=h;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[c]="string"==typeof e?e:r,i[1]=l;for(var p=2;p<a;p++)i[p]=o[p];return n.createElement.apply(null,i)}return n.createElement.apply(null,o)}h.displayName="MDXCreateElement"},1512:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>c,frontMatter:()=>a,metadata:()=>l,toc:()=>p});var n=o(8168),r=(o(6540),o(5680));const a={id:"made-for-aspnet-developers",title:"Made for ASP.NET Developers",sidebar_label:"Made for ASP.NET Developers",sidebar_position:1},i=void 0,l={unversionedId:"introduction/made-for-aspnet-developers",id:"introduction/made-for-aspnet-developers",title:"Made for ASP.NET Developers",description:"This library is designed by people who use ASP.NET in their day to day activities and is built for similar minded developers. When you first started digging in to GraphQL you most likely came across the plethora of articles, documents, tutorials and groups centered around JavaScript. JavaScript certainly has the highest adoption rate and with the tools provided by Apollo its no surprise. Its amazing how well those tools fit in with the existing knowledge and coding paradigms of JavaScript developers on both sides of the fence (be that front end or back end).",source:"@site/docs/introduction/made-for-aspnet-developers.md",sourceDirName:"introduction",slug:"/introduction/made-for-aspnet-developers",permalink:"/docs/introduction/made-for-aspnet-developers",draft:!1,tags:[],version:"current",sidebarPosition:1,frontMatter:{id:"made-for-aspnet-developers",title:"Made for ASP.NET Developers",sidebar_label:"Made for ASP.NET Developers",sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"What is GraphQL?",permalink:"/docs/introduction/what-is-graphql"},next:{title:"Actions",permalink:"/docs/controllers/actions"}},s={},p=[{value:"Plays Nice with Web API Controllers, Razor Views and Razor Pages",id:"plays-nice-with-web-api-controllers-razor-views-and-razor-pages",level:2},{value:"Scoped Dependency Injection",id:"scoped-dependency-injection",level:2},{value:"User Authorization",id:"user-authorization",level:2},{value:"Custom Action Results",id:"custom-action-results",level:2}],d={toc:p};function c(e){let{components:t,...o}=e;return(0,r.yg)("wrapper",(0,n.A)({},d,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"This library is designed by people who use ",(0,r.yg)("a",{parentName:"p",href:"https://dotnet.microsoft.com/en-us/apps/aspnet"},"ASP.NET")," in their day to day activities and is built for similar minded developers. When you first started digging in to GraphQL you most likely came across the plethora of ",(0,r.yg)("a",{parentName:"p",href:"https://www.graphqlweekly.com/"},"articles"),", ",(0,r.yg)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/GraphQL"},"documents"),", ",(0,r.yg)("a",{parentName:"p",href:"https://www.howtographql.com/"},"tutorials")," and ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/"},"groups")," centered around JavaScript. JavaScript certainly has the highest adoption rate and with the tools provided by ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/"},"Apollo")," its no surprise. Its amazing how well those tools fit in with the existing knowledge and coding paradigms of JavaScript developers on both sides of the fence (be that front end or back end)."),(0,r.yg)("p",null,"We believe that tooling and workflow is everything when it comes to picking up a technology. Its much more difficult for you (or your team) to adopt something new if there is no connection to what you already know. Migrating your personal development efforts or an entire team from .NET to NodeJS to leverage Apollo Server, for instance, is hard. The learning curve and even the monetary cost of bringing a team up to speed is high. But if you can leverage existing skills you reduce that cost significantly."),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},"GraphQL ASP.NET aims to reuse your existing knowledge of ASP.NET")),(0,r.yg)("p",null,"This is a core, guiding principle for the development of this library. We aim to reuse what you know. Or if you are still learning, make what you learn transferable to other .NET technologies. When coming from a .NET background, being able to reason about your graph queries in terms of ",(0,r.yg)("inlineCode",{parentName:"p"},"Controllers")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"Actions")," eases the cognitive load as you transition to thinking in terms of Fields and object graphs."),(0,r.yg)("p",null,"Using familiar concepts like ",(0,r.yg)("em",{parentName:"p"},"Binding Models")," and ",(0,r.yg)("em",{parentName:"p"},"View Models"),"; commonly used attributes like ",(0,r.yg)("inlineCode",{parentName:"p"},"[Authorize]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"[Required]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"[StringLength]"),"; modern ASP.NET's abstraction concepts like ",(0,r.yg)("inlineCode",{parentName:"p"},"IServiceCollection")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"ILogger")," all play a part in hopes of giving you a familiar programming model that you can start using immediately without reinventing too many wheels."),(0,r.yg)("p",null,"Take, for instance, this controller and a sample query that would call it. Can you tell what it does? If you are familiar with Web API then the answer is probably yes!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-cs",metastring:'title="PersonController.cs"',title:'"PersonController.cs"'},'public class PersonController: GraphController\n{\n private IPersonService _service;\n public PersonController(IPersonService personService)\n {\n _service = personService;\n }\n\n [QueryRoot("person")]\n public async Task<Person> RetrievePerson(int id)\n {\n return await _service.FetchPerson(id);\n }\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query {\n person(id: 5){\n firstName\n lastName\n title\n }\n}\n")),(0,r.yg)("p",null,"Another consideration when trying to implement GraphQL in .NET is the amount of boiler plate code required. Since C# is a strongly typed language the volume of additional coding required to generate an object graph tends to be high, especially in larger graphs. Many libraries take the approach of ultimate flexibility, requiring you to completely code your object graph, and all the fields that can be queried, and individually map all model properties and resolver methods manually. "),(0,r.yg)("p",null,"To address this, GraphQL ASP.NET has adopted an opinionated approach to its implementation. It makes some minor assumptions about how you will deliver your data in exchange for some much needed code generation and specification support. If the code in the controller above makes sense and feels natural to you; then this library might be worth a look. In terms of GraphQL, this single controller will:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Generate a fully qualified schema definition"),(0,r.yg)("li",{parentName:"ul"},"Provide introspection support"),(0,r.yg)("li",{parentName:"ul"},"Generate all required graph types (there are 17 in this example)")),(0,r.yg)("p",null,"The library will automatically wire up your graph controllers and scan your model objects. There is no additional, required configuration. When you add a new controller, new actions or new model properties they are automatically injected everywhere that object is used."),(0,r.yg)("p",null,"Are you working on a large project that has shared assemblies between services? No problem, you can direct GraphQL on where to look for controllers and model objects or even be explicit in what you want it to consume...down to the individual property level."),(0,r.yg)("admonition",{title:"You're in control",type:"note"},(0,r.yg)("p",{parentName:"admonition"}," Out of the box the library tries to pick the route of least resistance, but there are many ways to control what classes, enums etc. are included (or excluded) in your object graph. ")),(0,r.yg)("h2",{id:"plays-nice-with-web-api-controllers-razor-views-and-razor-pages"},"Plays Nice with Web API Controllers, Razor Views and Razor Pages"),(0,r.yg)("p",null,"This library is an extension to the standard ASP.NET pipeline, not a replacement. At its core, a graphql query is just another GET or POST route on your application. At startup the library registers a middleware component to handle requests using ",(0,r.yg)("inlineCode",{parentName:"p"},"appBuilder.Map()"),"."),(0,r.yg)("p",null,"Also, if you are integrating into an existing project, you'll find a lot of your utility code should work out of the box which should ease your migration. Any existing services, custom authorization and model validation attributes etc. can be directly attached to graph action methods and input models. You might even find that most of your model objects work with little to no changes as well. "),(0,r.yg)("h2",{id:"scoped-dependency-injection"},"Scoped Dependency Injection"),(0,r.yg)("p",null,"Services are injected into graph controllers in the same manner as ASP.NET controllers and with the same scope resolutions."),(0,r.yg)("h2",{id:"user-authorization"},"User Authorization"),(0,r.yg)("p",null,"The user model is exactly the same. In fact, the ",(0,r.yg)("inlineCode",{parentName:"p"},"ClaimsPrincipal")," passed to ",(0,r.yg)("inlineCode",{parentName:"p"},"this.User")," on a Web API controller is the same instance used to validate any ",(0,r.yg)("inlineCode",{parentName:"p"},"[Authorize]")," attributes on your graph controller actions. Internally, it uses the same ",(0,r.yg)("inlineCode",{parentName:"p"},"IAuthorizationService")," that gets added when you call ",(0,r.yg)("inlineCode",{parentName:"p"},".AddAuthorization()")," during startup."),(0,r.yg)("h2",{id:"custom-action-results"},"Custom Action Results"),(0,r.yg)("p",null,"Many teams define custom action results beyond ",(0,r.yg)("inlineCode",{parentName:"p"},"this.Ok()")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"this.BadRequest()")," for their REST queries to standardize how they will respond to requests on their Web API controllers to provide consistent messaging, perform some sort of logging or create a common return payload. GraphQL ASP.NET supports this model as well. Out of the box you get support for many of the relevant action results around returning data, indicating an error or denying access, but you can implement your own ",(0,r.yg)("inlineCode",{parentName:"p"},"IGraphActionResult")," to standardize how a given result is converted into a response object and used by the runtime. This includes control to invalidate the field, inject customized error messages or cancel the field request altogether."),(0,r.yg)("p",null,(0,r.yg)("em",{parentName:"p"},"Side Note:")," Not all action results make sense in GraphQL. For instance, you won't find a way to download a file or indicate a 204 (no content) result. A GraphQL field must always return a piece of data (even if its null). Since the ",(0,r.yg)("inlineCode",{parentName:"p"},"IGraphActionResult")," object is only a small piece in an entire query of many fields, its scope of abilities is paired to match."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ef5435f4.bff8a87b.js b/assets/js/ef5435f4.bff8a87b.js new file mode 100644 index 0000000..787ee23 --- /dev/null +++ b/assets/js/ef5435f4.bff8a87b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[4055],{5680:(e,t,r)=>{r.d(t,{xA:()=>c,yg:()=>y});var n=r(6540);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function l(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?i(Object(r),!0).forEach((function(t){a(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):i(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}function o(e,t){if(null==e)return{};var r,n,a=function(e,t){if(null==e)return{};var r,n,a={},i=Object.keys(e);for(n=0;n<i.length;n++)r=i[n],t.indexOf(r)>=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n<i.length;n++)r=i[n],t.indexOf(r)>=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var p=n.createContext({}),u=function(e){var t=n.useContext(p),r=t;return e&&(r="function"==typeof e?e(t):l(l({},t),e)),r},c=function(e){var t=u(e.components);return n.createElement(p.Provider,{value:t},e.children)},s="mdxType",g={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,i=e.originalType,p=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),s=u(r),d=a,y=s["".concat(p,".").concat(d)]||s[d]||g[d]||i;return r?n.createElement(y,l(l({ref:t},c),{},{components:r})):n.createElement(y,l({ref:t},c))}));function y(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=r.length,l=new Array(i);l[0]=d;var o={};for(var p in t)hasOwnProperty.call(t,p)&&(o[p]=t[p]);o.originalType=e,o[s]="string"==typeof e?e:a,l[1]=o;for(var u=2;u<i;u++)l[u]=r[u];return n.createElement.apply(null,l)}return n.createElement.apply(null,r)}d.displayName="MDXCreateElement"},614:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>s,frontMatter:()=>i,metadata:()=>o,toc:()=>u});var n=r(8168),a=(r(6540),r(5680));const i={id:"create-app",title:"Building Your First App",sidebar_label:"Your First App",sidebar_position:1,description:"Step by Step instructions for creating a sample application"},l=void 0,o={unversionedId:"quick/create-app",id:"quick/create-app",title:"Building Your First App",description:"Step by Step instructions for creating a sample application",source:"@site/docs/quick/create-app.md",sourceDirName:"quick",slug:"/quick/create-app",permalink:"/docs/quick/create-app",draft:!1,tags:[],version:"current",sidebarPosition:1,frontMatter:{id:"create-app",title:"Building Your First App",sidebar_label:"Your First App",sidebar_position:1,description:"Step by Step instructions for creating a sample application"},sidebar:"tutorialSidebar",previous:{title:"Overview",permalink:"/docs/quick/overview"},next:{title:"Code Examples",permalink:"/docs/quick/code-examples"}},p={},u=[{value:"Create a New Web API Project",id:"create-a-new-web-api-project",level:2},{value:"Add the Package From Nuget",id:"add-the-package-from-nuget",level:2},{value:"Create a Controller",id:"create-a-controller",level:2},{value:"Configure Startup",id:"configure-startup",level:2},{value:"Execute a Query",id:"execute-a-query",level:2},{value:"Results:",id:"results",level:3}],c={toc:u};function s(e){let{components:t,...i}=e;return(0,a.yg)("wrapper",(0,n.A)({},c,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"This document will help walk you through creating a new Web API project, installing the GraphQL ASP.NET library and writing your first controller."),(0,a.yg)("h2",{id:"create-a-new-web-api-project"},"Create a New Web API Project"),(0,a.yg)("p",null,"\ud83d\udcbb Setup a new ",(0,a.yg)("inlineCode",{parentName:"p"},"ASP.NET Core Web API")," project:"),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"web api project",src:r(4737).A,title:"Select ASP.NET Core Web API",width:"1518",height:"1012"})),(0,a.yg)("h2",{id:"add-the-package-from-nuget"},"Add the Package From Nuget"),(0,a.yg)("p",null,"\ud83d\udcbb Add the ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL.AspNet")," nuget package:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-powershell"},"# Using the dotnet CLI\n> dotnet add package GraphQL.AspNet\n")),(0,a.yg)("h2",{id:"create-a-controller"},"Create a Controller"),(0,a.yg)("p",null,"\ud83d\udcbb Create your first Graph Controller:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="BakeryController.cs"',title:'"BakeryController.cs"'},'using GraphQL.AspNet.Attributes;\nusing GraphQL.AspNet.Controllers;\n\npublic class BakeryController : GraphController\n{\n [QueryRoot("donut")]\n public Donut RetrieveDonut()\n {\n return new Donut()\n {\n Id = 3,\n Name = "Snowy Dream",\n Flavor = "Vanilla"\n };\n }\n}\n\npublic class Donut\n{\n public int Id { get; set; }\n public string Name { get; set; }\n public string Flavor { get; set; }\n}\n')),(0,a.yg)("h2",{id:"configure-startup"},"Configure Startup"),(0,a.yg)("p",null,"\ud83d\udcbb Register GraphQL with your services collection and your application pipeline:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Program.cs"',title:'"Program.cs"'},"using GraphQL.AspNet.Configuration;\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// Add graphql services to the DI container.\n// highlight-next-line\nbuilder.Services.AddGraphQL();\n\nvar app = builder.Build();\n\n// Configure the HTTP request pipeline.\n// highlight-next-line\napp.UseGraphQL();\napp.Run();\n")),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},(0,a.yg)("em",{parentName:"p"},"The configuration steps may vary slightly when using a Startup.cs file; typical for .NET 5 or earlier "))),(0,a.yg)("h2",{id:"execute-a-query"},"Execute a Query"),(0,a.yg)("p",null,"\ud83d\udcbb Start the application and using your favorite tool, execute a query:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Sample Query"',title:'"Sample','Query"':!0},"query {\n donut {\n id\n name\n flavor\n }\n}\n")),(0,a.yg)("h3",{id:"results"},"Results:"),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"query results",src:r(4282).A,title:"Results using GraphQL Playground",width:"1137",height:"888"})),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},(0,a.yg)("em",{parentName:"p"},"The port number on your app may be different than that shown in the image"))))}s.isMDXComponent=!0},4737:(e,t,r)=>{r.d(t,{A:()=>n});const n=r.p+"assets/images/create-new-web-api-project-f0bb6eebc200da5078430ce828d8bc60.png"},4282:(e,t,r)=>{r.d(t,{A:()=>n});const n=r.p+"assets/images/overview-sample-query-results-1425298ea533b77850b7c923c8d97983.png"}}]); \ No newline at end of file diff --git a/assets/js/fed34737.dbbb9a21.js b/assets/js/fed34737.dbbb9a21.js new file mode 100644 index 0000000..28c93db --- /dev/null +++ b/assets/js/fed34737.dbbb9a21.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[7742],{5680:(e,t,n)=>{n.d(t,{xA:()=>c,yg:()=>d});var a=n(6540);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function s(e,t){if(null==e)return{};var n,a,i=function(e,t){if(null==e)return{};var n,a,i={},r=Object.keys(e);for(a=0;a<r.length;a++)n=r[a],t.indexOf(n)>=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a<r.length;a++)n=r[a],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var o=a.createContext({}),p=function(e){var t=a.useContext(o),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},c=function(e){var t=p(e.components);return a.createElement(o.Provider,{value:t},e.children)},u="mdxType",g={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},y=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,o=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),u=p(n),y=i,d=u["".concat(o,".").concat(y)]||u[y]||g[y]||r;return n?a.createElement(d,l(l({ref:t},c),{},{components:n})):a.createElement(d,l({ref:t},c))}));function d(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,l=new Array(r);l[0]=y;var s={};for(var o in t)hasOwnProperty.call(t,o)&&(s[o]=t[o]);s.originalType=e,s[u]="string"==typeof e?e:i,l[1]=s;for(var p=2;p<r;p++)l[p]=n[p];return a.createElement.apply(null,l)}return a.createElement.apply(null,n)}y.displayName="MDXCreateElement"},5120:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>u,frontMatter:()=>r,metadata:()=>s,toc:()=>p});var a=n(8168),i=(n(6540),n(5680));const r={id:"objects",title:"The Object Graph Type",sidebar_label:"Objects",sidebar_position:0},l=void 0,s={unversionedId:"types/objects",id:"types/objects",title:"The Object Graph Type",description:"The OBJECT graph type is one of six fundamental types defined by GraphQL. We can think of a graph query like a tree and if scalar values, such as string and int, are the leafs then objects are the branches.",source:"@site/docs/types/objects.md",sourceDirName:"types",slug:"/types/objects",permalink:"/docs/types/objects",draft:!1,tags:[],version:"current",sidebarPosition:0,frontMatter:{id:"objects",title:"The Object Graph Type",sidebar_label:"Objects",sidebar_position:0},sidebar:"tutorialSidebar",previous:{title:"Batch Operations",permalink:"/docs/controllers/batch-operations"},next:{title:"Input Objects",permalink:"/docs/types/input-objects"}},o={},p=[{value:"Custom Naming",id:"custom-naming",level:2},{value:"Methods as Fields",id:"methods-as-fields",level:2},{value:"Excluding Fields",id:"excluding-fields",level:2},{value:"Excluding A Class",id:"excluding-a-class",level:2},{value:"Forced Class Exclusions",id:"forced-class-exclusions",level:2},{value:"Structs as Objects",id:"structs-as-objects",level:2},{value:"Reuse as Input Objects",id:"reuse-as-input-objects",level:2},{value:"Object Inheritance",id:"object-inheritance",level:2},{value:"Implementing Interfaces",id:"implementing-interfaces",level:2}],c={toc:p};function u(e){let{components:t,...n}=e;return(0,i.yg)("wrapper",(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"OBJECT")," graph type is one of six fundamental types defined by GraphQL. We can think of a graph query like a tree and if ",(0,i.yg)("a",{parentName:"p",href:"./scalars"},"scalar values"),", such as ",(0,i.yg)("inlineCode",{parentName:"p"},"string")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"int"),", are the leafs then objects are the branches."),(0,i.yg)("p",null,"\u2705 Use a ",(0,i.yg)("inlineCode",{parentName:"p"},"class")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"struct")," to identify an object type in a schema."),(0,i.yg)("p",null,"Here we've defined a ",(0,i.yg)("inlineCode",{parentName:"p"},"Donut")," model class. The runtime will convert it, automatically, into an object graph type. If you're familiar with GraphQL's own type definition language the equivalent expression is shown below."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Donut.cs"',title:'"Donut.cs"'},"public class Donut\n{\n public int Id { get; set; }\n public string Name { get; set; }\n public DonutType Type { get; set; }\n public decimal Price { get; set; }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Donut Type Definition"',title:'"Donut',Type:!0,'Definition"':!0},"type Donut {\n id: Int!\n name: String\n type: DonutType!\n price: Decimal!\n}\n")),(0,i.yg)("p",null,"By Default, object graph types:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Are named the same as the ",(0,i.yg)("inlineCode",{parentName:"li"},"class")," or ",(0,i.yg)("inlineCode",{parentName:"li"},"struct")," name"),(0,i.yg)("li",{parentName:"ul"},"Have all public properties with a ",(0,i.yg)("inlineCode",{parentName:"li"},"get")," statement included as fields",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"The return type of a property must be of an acceptable type or it will be skipped")))),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"You can override the default settings in your ",(0,i.yg)("a",{parentName:"p",href:"/docs/reference/schema-configuration#fielddeclarationrequirements"},"schema configuration")," or by use of the ",(0,i.yg)("a",{parentName:"p",href:"/docs/reference/attributes#graphtype"},"GraphType")," and ",(0,i.yg)("a",{parentName:"p",href:"/docs/reference/attributes#graphfield"},"GraphField")," attributes.")),(0,i.yg)("h2",{id:"custom-naming"},"Custom Naming"),(0,i.yg)("p",null,"Give the object a different name in the graph from that of your C# class with the ",(0,i.yg)("inlineCode",{parentName:"p"},"[GraphType]")," attribute."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Declaring a Custom Type Name"',title:'"Declaring',a:!0,Custom:!0,Type:!0,'Name"':!0},'[GraphType("Doughnut")]\npublic class Donut\n{\n public int Id { get; set; }\n public string Name { get; set; }\n public DonutType Type { get; set; }\n public decimal Price { get; set; }\n}\n')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Donut Type Definition"',title:'"Donut',Type:!0,'Definition"':!0},"type Doughnut {\n id: Int!\n name: String\n type: DonutType!\n price: Decimal!\n}\n")),(0,i.yg)("h2",{id:"methods-as-fields"},"Methods as Fields"),(0,i.yg)("p",null,"By default, POCO class methods are excluded from being fields on the graph but can be added by tagging the method with ",(0,i.yg)("inlineCode",{parentName:"p"},"[GraphField]"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Including a POCO method as a field"',title:'"Including',a:!0,POCO:!0,method:!0,as:!0,'field"':!0},'public class Donut\n{\n // highlight-next-line\n [GraphField("salesTax")]\n public decimal CalculateSalesTax(decimal taxPercentage)\n {\n return this.Price * taxPercentage;\n }\n\n public int Id { get; set; }\n public string Name { get; set; }\n public DonutType Type { get; set; }\n public decimal Price { get; set; }\n}\n')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Donut Type Definition"',title:'"Donut',Type:!0,'Definition"':!0},"type Donut {\n // highlight-next-line\n salesTax (taxPercentage: Decimal!): Decimal!\n id: Int!\n name: String\n type: DonutType!\n price: Decimal!\n}\n")),(0,i.yg)("p",null,"Just as with ",(0,i.yg)("a",{parentName:"p",href:"../controllers/actions"},"controller actions"),", GraphQL will analyze the signature of the method to determine its return type, expression requirements and input arguments."),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Methods on POCO classes lack many of the features of controllers such as being able to perform ",(0,i.yg)("a",{parentName:"p",href:"../controllers/model-state"},"model state")," validation or provide access to ",(0,i.yg)("inlineCode",{parentName:"p"},"this.User")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"this.Request"),". ")),(0,i.yg)("h2",{id:"excluding-fields"},"Excluding Fields"),(0,i.yg)("p",null,"To exclude a single property that you don't want to expose to GraphQL add the ",(0,i.yg)("inlineCode",{parentName:"p"},"[GraphSkip]")," attribute to it:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Excluding a property"',title:'"Excluding',a:!0,'property"':!0},"public class Donut\n{\n public int Id { get; set; }\n public string Name { get; set; }\n public DonutType Type { get; set; }\n\n // highlight-next-line\n [GraphSkip]\n public decimal Price { get; set; }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Donut Type Definition"',title:'"Donut',Type:!0,'Definition"':!0},"type Donut {\n id: Int!\n name: String\n type: DonutType!\n # price is not included\n}\n")),(0,i.yg)("p",null,"Or force GraphQL to skip all fields except those you explicitly define with a ",(0,i.yg)("inlineCode",{parentName:"p"},"[GraphField]")," attribute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Require explicit declarations for this type"',title:'"Require',explicit:!0,declarations:!0,for:!0,this:!0,'type"':!0},"// highlight-next-line\n[GraphType(FieldDeclarationRequirements = TemplateDeclarationRequirements.RequireAll)]\npublic class Donut\n{\n [GraphField]\n public int Id { get; set; }\n\n [GraphField]\n public string Name { get; set; }\n\n public DonutType Type { get; set; }\n public decimal Price { get; set; }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="Donut Type Definition"',title:'"Donut',Type:!0,'Definition"':!0},"# only id and name are included\ntype Donut {\n id: Int!\n name: String\n}\n")),(0,i.yg)("p",null,"Or set a schema-wide option during startup:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Set Field Declaration Requirements at Startup"',title:'"Set',Field:!0,Declaration:!0,Requirements:!0,at:!0,'Startup"':!0},"services.AddGraphQL(options =>\n {\n options.DeclarationOptions.FieldDeclarationRequirements = TemplateDeclarationRequirements.RequireAll;\n });\n")),(0,i.yg)("p",null,"Your schema will follow a cascading model of inclusion rules in order of increasing priority from ",(0,i.yg)("inlineCode",{parentName:"p"},"schema -> class -> field")," level declarations. This can be useful in multi-schema setups where a class may be shared but you don't want the exposed fields to be different or if there is a secure field that you want to guarantee is not exposed regardless of the schema."),(0,i.yg)("h2",{id:"excluding-a-class"},"Excluding A Class"),(0,i.yg)("p",null,"By Default, GraphQL won't include your class in a schema unless:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Its referenced in a controller OR"),(0,i.yg)("li",{parentName:"ul"},"Referenced by a graph type that is referenced in a controller OR"),(0,i.yg)("li",{parentName:"ul"},"Tagged with ",(0,i.yg)("inlineCode",{parentName:"li"},"[GraphType]"),".")),(0,i.yg)("p",null,"But certian schema configurations can override this behavior and allow GraphQL to greedily include classes that it'll never use. This can expose them in an introspection query unintentionally. You can flag a class such that it skipped unless GraphQL can determine that the object is required to fulfill a request to the schema."),(0,i.yg)("p",null,"This is also helpful to prevent objects that are only used as an ",(0,i.yg)("inlineCode",{parentName:"p"},"INPUT_OBJECT")," from being accidentally added as an ",(0,i.yg)("inlineCode",{parentName:"p"},"OBJECT")," and can reduce the clutter in your schema."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Prevent the Type From Being Auto Included"',title:'"Prevent',the:!0,Type:!0,From:!0,Being:!0,Auto:!0,'Included"':!0},"// highlight-next-line\n[GraphType(PreventAutoInclusion = true)]\npublic class Donut\n{\n [GraphField]\n public int Id { get; set; }\n\n [GraphField]\n public string Name { get; set; }\n\n public DonutType Type { get; set; }\n public decimal Price { get; set; }\n}\n")),(0,i.yg)("h2",{id:"forced-class-exclusions"},"Forced Class Exclusions"),(0,i.yg)("p",null,"There are times where preventing auto-inclusion is not enough. Perhaps there is a shared assembly amongst work teams that contains some graph types and some utility classes that absolutely, positively CANNOT be exposed to GraphQL at any cost. Yet those classes are required for the graph types to function."),(0,i.yg)("p",null,"In these cases, add ",(0,i.yg)("inlineCode",{parentName:"p"},"[GraphSkip]")," to the class itself and GraphQL will throw a ",(0,i.yg)("inlineCode",{parentName:"p"},"GraphTypeDeclarationException")," if its ever asked to include the class in a schema. Be that as an explicit reference or through discovery in a controller. This will occur when the schema is first initialized, rendering your application dead. But better a crash when a developer is testing a new change vs. unknowingly leaking sensitive information."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Prevent a Type from EVER Being Included in the Graph"',title:'"Prevent',a:!0,Type:!0,from:!0,EVER:!0,Being:!0,Included:!0,in:!0,the:!0,'Graph"':!0},'// ERROR, GraphTypeDeclarationException will be thrown!\n[GraphSkip]\npublic class SuperSensitiveData\n{\n public int SecretSaltPhrase => "123456";\n}\n')),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"This rule is enforced at the template level and is applied to the ",(0,i.yg)("inlineCode",{parentName:"p"},"System.Type")," across the board. Any class, interface, enum etc. with the ",(0,i.yg)("inlineCode",{parentName:"p"},"[GraphSkip]")," attribute will be permanantly skipped.")),(0,i.yg)("h2",{id:"structs-as-objects"},"Structs as Objects"),(0,i.yg)("p",null,"The usage of ",(0,i.yg)("inlineCode",{parentName:"p"},"struct")," types as an ",(0,i.yg)("inlineCode",{parentName:"p"},"OBJECT")," graph type is fully supported. The same rules listed above that apply to ",(0,i.yg)("inlineCode",{parentName:"p"},"class")," types also apply to ",(0,i.yg)("inlineCode",{parentName:"p"},"struct")," types. The only difference is that since structs are value types there are non-nullable by default. "),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Using a Coffee struct"',title:'"Using',a:!0,Coffee:!0,'struct"':!0},"public class CoffeeController: GraphController\n{\n [QueryRoot]\n public Coffee RetrieveCoffee(string flavor){ /*...*/}\n}\n\npublic struct Coffee\n{\n public string Flavor{ get; set; }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="GraphQL Type Definition"',title:'"GraphQL',Type:!0,'Definition"':!0},"# Coffee must be returned\ntype Query {\n retrieveCoffee(flavor: String) : Coffee!\n}\n")),(0,i.yg)("p",null,"Use the standard ",(0,i.yg)("inlineCode",{parentName:"p"},"Nullable<T>")," syntax to make them nullable (e.g. ",(0,i.yg)("inlineCode",{parentName:"p"},"Coffee?"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:"title=\"Using a 'Nullable' Coffee struct\"",title:'"Using',a:!0,"'Nullable'":!0,Coffee:!0,'struct"':!0},"public class CoffeeController: GraphController\n{\n [QueryRoot]\n public Coffee? RetrieveCoffee(string flavor){ /*...*/}\n}\n\npublic struct Coffee\n{\n public string Flavor{ get; set; }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="GraphQL Type Definition"',title:'"GraphQL',Type:!0,'Definition"':!0},"# Coffee or null may be returned\ntype Query {\n retrieveCoffee(flavor: String) : Coffee\n}\n")),(0,i.yg)("h2",{id:"reuse-as-input-objects"},"Reuse as Input Objects"),(0,i.yg)("p",null,"Both ",(0,i.yg)("inlineCode",{parentName:"p"},"class")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"struct")," types can be used as an ",(0,i.yg)("inlineCode",{parentName:"p"},"INPUT_OBJECT")," and an ",(0,i.yg)("inlineCode",{parentName:"p"},"OBJECT")," graph type. See the section on ",(0,i.yg)("a",{parentName:"p",href:"./input-objects"},"input objects")," for some of the key differences and requirements."),(0,i.yg)("h2",{id:"object-inheritance"},"Object Inheritance"),(0,i.yg)("p",null,"Class inheritance as we think of it in .NET is not concept in GraphQL. As a result, there is no association between two objects in the graph even if they share an inheritance structure in .NET."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="C# Class Inheritance"',title:'"C#',Class:!0,'Inheritance"':!0},"public class Pastry\n{\n public int Id { get; set; }\n public string Name { get; set; }\n}\n\npublic class Donut : Pastry\n{\n string Flavor{ get; set; }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="GraphQL Type Definitions"',title:'"GraphQL',Type:!0,'Definitions"':!0},"# Pastry and Donut have similar fields \n# but are NOT related in the Graph\ntype Pastry { \n id: Int!\n name: String\n}\n\ntype Donut { \n id: Int!\n name: String\n flavor: String\n}\n")),(0,i.yg)("admonition",{type:"tip"},(0,i.yg)("p",{parentName:"admonition"},"GraphQL ASP.NET is smart enough to figure out your intent with object use (i.e. ",(0,i.yg)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Liskov_substitution_principle"},"Liskov Subsitutions"),"). If you return a ",(0,i.yg)("inlineCode",{parentName:"p"},"Donut")," where a ",(0,i.yg)("inlineCode",{parentName:"p"},"Pastry")," is indicated by the graph. The library will happily use your donut as a pastry for any field resolutions.")),(0,i.yg)("h2",{id:"implementing-interfaces"},"Implementing Interfaces"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Read the section on ",(0,i.yg)("a",{parentName:"p",href:"/docs/types/interfaces"},"interfaces")," for details on how to use them.")),(0,i.yg)("p",null,'Graphql will not attempt to "auto include" the interfaces implemented by your objects in your schema. This is both a security measure to prevent information from leaking as well as a decluttering technique. Its unlikely that your schema cares about the myriad of interfaces you may declare on your business objects. '),(0,i.yg)("p",null,"However, when an interface is included in schema, graphql will sense the inclusion and automatically wire up the necessary information on your objects that implement."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Including a Pastry"',title:'"Including',a:!0,'Pastry"':!0},'public class PastryController: GraphController\n{\n [QueryRoot("retrievePastry", typeof(Donut))]\n public IPastry RetrievePastry(string id){/* ... */}\n}\n')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql",metastring:'title="GraphQL Type Definition"',title:'"GraphQL',Type:!0,'Definition"':!0},"interface IPastry {\n id: String!\n name: String\n}\n\n# donut will automatically declare that it implements IPastry when its \n# included in the schema\n// highlight-next-line\ntype Donut implements IPastry {\n id: String!\n name: String\n flavor: DonutFlavor\n}\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/fefbe673.ec47657c.js b/assets/js/fefbe673.ec47657c.js new file mode 100644 index 0000000..9d544e6 --- /dev/null +++ b/assets/js/fefbe673.ec47657c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[5227],{5680:(e,t,a)=>{a.d(t,{xA:()=>s,yg:()=>d});var n=a(6540);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function l(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function i(e){for(var t=1;t<arguments.length;t++){var a=null!=arguments[t]?arguments[t]:{};t%2?l(Object(a),!0).forEach((function(t){r(e,t,a[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(a)):l(Object(a)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(a,t))}))}return e}function p(e,t){if(null==e)return{};var a,n,r=function(e,t){if(null==e)return{};var a,n,r={},l=Object.keys(e);for(n=0;n<l.length;n++)a=l[n],t.indexOf(a)>=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n<l.length;n++)a=l[n],t.indexOf(a)>=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var o=n.createContext({}),g=function(e){var t=n.useContext(o),a=t;return e&&(a="function"==typeof e?e(t):i(i({},t),e)),a},s=function(e){var t=g(e.components);return n.createElement(o.Provider,{value:t},e.children)},u="mdxType",y={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,l=e.originalType,o=e.parentName,s=p(e,["components","mdxType","originalType","parentName"]),u=g(a),m=r,d=u["".concat(o,".").concat(m)]||u[m]||y[m]||l;return a?n.createElement(d,i(i({ref:t},s),{},{components:a})):n.createElement(d,i({ref:t},s))}));function d(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var l=a.length,i=new Array(l);i[0]=m;var p={};for(var o in t)hasOwnProperty.call(t,o)&&(p[o]=t[o]);p.originalType=e,p[u]="string"==typeof e?e:r,i[1]=p;for(var g=2;g<l;g++)i[g]=a[g];return n.createElement.apply(null,i)}return n.createElement.apply(null,a)}m.displayName="MDXCreateElement"},8449:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>o,contentTitle:()=>i,default:()=>u,frontMatter:()=>l,metadata:()=>p,toc:()=>g});var n=a(8168),r=(a(6540),a(5680));const l={id:"scalars",title:"Scalars",sidebar_label:"Scalars",sidebar_position:5},i=void 0,p={unversionedId:"types/scalars",id:"types/scalars",title:"Scalars",description:"Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being enums). You can extend GraphQL with your own custom scalars when needed.",source:"@site/docs/types/scalars.md",sourceDirName:"types",slug:"/types/scalars",permalink:"/docs/types/scalars",draft:!1,tags:[],version:"current",sidebarPosition:5,frontMatter:{id:"scalars",title:"Scalars",sidebar_label:"Scalars",sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"Enums",permalink:"/docs/types/enums"},next:{title:"List & Non-Null",permalink:"/docs/types/list-non-null"}},o={},g=[{value:"Input Value Resolution",id:"input-value-resolution",level:2},{value:"Working With Dates",id:"working-with-dates",level:4},{value:"Scalar Names Are Fixed",id:"scalar-names-are-fixed",level:2},{value:"Nullable<T>",id:"nullablet",level:4},{value:"ID Scalar",id:"id-scalar",level:2},{value:"Custom Scalars",id:"custom-scalars",level:2},{value:"Working with Structs",id:"working-with-structs",level:3}],s={toc:g};function u(e){let{components:t,...a}=e;return(0,r.yg)("wrapper",(0,n.A)({},s,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being ",(0,r.yg)("a",{parentName:"p",href:"./enums"},"enums"),"). You can extend GraphQL with your own ",(0,r.yg)("a",{parentName:"p",href:"../advanced/custom-scalars"},"custom scalars")," when needed."),(0,r.yg)("p",null,"GraphQL ASP.NET has 20 built in scalar types."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Scalar Name"),(0,r.yg)("th",{parentName:"tr",align:null},".NET Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Allowed Input Value"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"Boolean"),(0,r.yg)("td",{parentName:"tr",align:null},"System.Boolean"),(0,r.yg)("td",{parentName:"tr",align:null},"Boolean")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"Byte"),(0,r.yg)("td",{parentName:"tr",align:null},"System.Byte"),(0,r.yg)("td",{parentName:"tr",align:null},"Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"DateOnly"),(0,r.yg)("td",{parentName:"tr",align:null},"System.DateOnly"),(0,r.yg)("td",{parentName:"tr",align:null},"String or Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"DateTime"),(0,r.yg)("td",{parentName:"tr",align:null},"System.DateTime"),(0,r.yg)("td",{parentName:"tr",align:null},"String or Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"DateTimeOffset"),(0,r.yg)("td",{parentName:"tr",align:null},"System.DateTimeOffset"),(0,r.yg)("td",{parentName:"tr",align:null},"String or Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"Decimal"),(0,r.yg)("td",{parentName:"tr",align:null},"System.Decimal"),(0,r.yg)("td",{parentName:"tr",align:null},"Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"Double"),(0,r.yg)("td",{parentName:"tr",align:null},"System.Double"),(0,r.yg)("td",{parentName:"tr",align:null},"Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"Float"),(0,r.yg)("td",{parentName:"tr",align:null},"System.Single"),(0,r.yg)("td",{parentName:"tr",align:null},"Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"Guid"),(0,r.yg)("td",{parentName:"tr",align:null},"System.Guid"),(0,r.yg)("td",{parentName:"tr",align:null},"String")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"ID"),(0,r.yg)("td",{parentName:"tr",align:null},"GraphQL.AspNet.GraphId"),(0,r.yg)("td",{parentName:"tr",align:null},"String or Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"Int"),(0,r.yg)("td",{parentName:"tr",align:null},"System.Int32"),(0,r.yg)("td",{parentName:"tr",align:null},"Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"Long"),(0,r.yg)("td",{parentName:"tr",align:null},"System.Int64"),(0,r.yg)("td",{parentName:"tr",align:null},"Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"Short"),(0,r.yg)("td",{parentName:"tr",align:null},"System.Int16"),(0,r.yg)("td",{parentName:"tr",align:null},"Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"String"),(0,r.yg)("td",{parentName:"tr",align:null},"System.String"),(0,r.yg)("td",{parentName:"tr",align:null},"String")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"SignedByte"),(0,r.yg)("td",{parentName:"tr",align:null},"System.SByte"),(0,r.yg)("td",{parentName:"tr",align:null},"Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"TimeOnly"),(0,r.yg)("td",{parentName:"tr",align:null},"System.TimeOnly"),(0,r.yg)("td",{parentName:"tr",align:null},"String")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"UInt"),(0,r.yg)("td",{parentName:"tr",align:null},"System.UInt32"),(0,r.yg)("td",{parentName:"tr",align:null},"Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"ULong"),(0,r.yg)("td",{parentName:"tr",align:null},"System.UInt64"),(0,r.yg)("td",{parentName:"tr",align:null},"Number")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"Uri"),(0,r.yg)("td",{parentName:"tr",align:null},"System.Uri"),(0,r.yg)("td",{parentName:"tr",align:null},"String")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"UShort"),(0,r.yg)("td",{parentName:"tr",align:null},"System.UInt16"),(0,r.yg)("td",{parentName:"tr",align:null},"Number")))),(0,r.yg)("admonition",{type:"info"},(0,r.yg)("p",{parentName:"admonition"}," You must target .NET 8.0 or later to use ",(0,r.yg)("inlineCode",{parentName:"p"},"DateOnly")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"TimeOnly"))),(0,r.yg)("h2",{id:"input-value-resolution"},"Input Value Resolution"),(0,r.yg)("p",null,"When a scalar value is resolved, it's read from the query document (or variable collection) in one of three ways:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"String")," : A string of characters, delimited by ",(0,r.yg)("inlineCode",{parentName:"li"},'"quotes"')),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Boolean")," The value ",(0,r.yg)("inlineCode",{parentName:"li"},"true")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"false")," without quotes"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Number")," A sequence of numbers with an optional decimal point, negative sign or the letter ",(0,r.yg)("inlineCode",{parentName:"li"},"e")," without quotes",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},"example: ",(0,r.yg)("inlineCode",{parentName:"li"},"-123.456e78")),(0,r.yg)("li",{parentName:"ul"},"GraphQL numbers must conform to the ",(0,r.yg)("a",{parentName:"li",href:"https://en.wikipedia.org/wiki/IEEE_754"},"IEEE 754")," standard [Spec \xa7 ",(0,r.yg)("a",{parentName:"li",href:"https://graphql.github.io/graphql-spec/October2021/#sec-Float"},"3.5.2"),"]")))),(0,r.yg)("p",null,"Scalars used as input arguments require that any supplied value match at least one supported input format before they will attempt to convert the value into the related .NET type. If the value read from the document doesn't match an approved format it is rejected before conversion is attempted. "),(0,r.yg)("p",null,"For example, the library will accept dates as numbers or strings. If you try to supply a boolean value, ",(0,r.yg)("inlineCode",{parentName:"p"},"true"),", the query is rejected outright and no parsing attempt is made. This can come in handy for custom scalar types that may have multiple serialization options."),(0,r.yg)("p",null,"See the table above for the list of allowed formats per scalar type."),(0,r.yg)("h4",{id:"working-with-dates"},"Working With Dates"),(0,r.yg)("p",null,"Date valued scalars (e.g. ",(0,r.yg)("inlineCode",{parentName:"p"},"DateTime"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"DateTimeOffset"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"DateOnly"),") can be supplied as an ",(0,r.yg)("a",{parentName:"p",href:"https://www.rfc-editor.org/rfc/rfc3339"},"RFC 3339")," compliant string value or as a number representing the amount of time from the ",(0,r.yg)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Unix_time"},"Unix Epoch"),"."),(0,r.yg)("p",null,"Examples:"),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Supplied Value"),(0,r.yg)("th",{parentName:"tr",align:null},"Parsed Date"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("inlineCode",{parentName:"td"},'"2022-12-30T18:30:38.259+00:00"')),(0,r.yg)("td",{parentName:"tr",align:null},"Dec. 30, 2022 @ 6:30:38.259 PM (UTC - 0)")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("inlineCode",{parentName:"td"},'"2022-12-30"')),(0,r.yg)("td",{parentName:"tr",align:null},"Dec. 30, 2022 @ 00:00:00.000 AM (UTC - 0)")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("inlineCode",{parentName:"td"},"1586965574234")),(0,r.yg)("td",{parentName:"tr",align:null},"April 15, 2020 @ 3:46:14.234 AM (UTC - 0)")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("inlineCode",{parentName:"td"},"1586940374")),(0,r.yg)("td",{parentName:"tr",align:null},"April 15, 2020 @ 3:46:14.000 AM (UTC - 0)")))),(0,r.yg)("admonition",{title:"Epoch Values",type:"tip"},(0,r.yg)("p",{parentName:"admonition"},"Dates supplied as an epoch number can be supplied with or without milliseconds.")),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},"Note: If a time component is supplied to a ",(0,r.yg)("inlineCode",{parentName:"p"},"DateOnly")," scalar, it will be truncated and only the date portion will be used. ")),(0,r.yg)("p",null,"By Default, the library will serialize all dates as an ",(0,r.yg)("inlineCode",{parentName:"p"},"RFC 3339")," compliant string."),(0,r.yg)("h2",{id:"scalar-names-are-fixed"},"Scalar Names Are Fixed"),(0,r.yg)("p",null,"Unlike other graph types, scalar names are fixed across all schemas. The name defined above (including casing), is how they appear in your schema's introspection queries. These names conform to the accepted standard for graphql type names. This is true for any custom scalars you may build as well."),(0,r.yg)("h4",{id:"nullablet"},"Nullable","<","T",">"),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"int?"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"float?")," etc."),(0,r.yg)("p",null,"For the value types listed above, GraphQL will automatically coerce values into the appropriate ",(0,r.yg)("inlineCode",{parentName:"p"},"Nullable<T>")," as required by an argument's type expression."),(0,r.yg)("h2",{id:"id-scalar"},"ID Scalar"),(0,r.yg)("p",null,"GraphQL defines a special scalar value value called ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," which is defined as:"),(0,r.yg)("blockquote",null,(0,r.yg)("p",{parentName:"blockquote"},(0,r.yg)("em",{parentName:"p"},"a unique identifier, often used to refetch an object or as the key for a cache"),'" [Spec \xa7 ',(0,r.yg)("a",{parentName:"p",href:"https://graphql.github.io/graphql-spec/October2021/#sec-ID"},"3.5.5"),"].")),(0,r.yg)("p",null,"GraphQL ASP.NET maintains a struct, ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL.AspNet.GraphId")," to hold this value and will always serialize it to a string. However, per the specification, when supplying values on a query document, ID can accept strings or integers as input values. Floating point numbers and boolean values are not allowed."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Valid ID values",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},'"abc"')),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"34")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"-200")))),(0,r.yg)("li",{parentName:"ul"},"Invalid ID Values",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"true")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"4.0")," ")))),(0,r.yg)("admonition",{title:"Integer Value Range",type:"note"},(0,r.yg)("p",{parentName:"admonition"},"When using an integer value for an ID scalar, the minimum allowed value is ",(0,r.yg)("inlineCode",{parentName:"p"},"long.MinValue")," and the maximum allowed value is ",(0,r.yg)("inlineCode",{parentName:"p"},"ulong.MaxValue"))),(0,r.yg)("p",null,"You can perform an implicit and explicit conversion between ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphId")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"System.String")," as well."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-csharp",metastring:'title="Converting GraphId"',title:'"Converting','GraphId"':!0},'GraphId id = new GraphId("abc");\nstring str = id;\n// str == "abc"\n\nstring str = "abc";\nGraphId id = (GraphId)str;\n// id.Value == "abc"\n')),(0,r.yg)("h2",{id:"custom-scalars"},"Custom Scalars"),(0,r.yg)("p",null,"See the section on ",(0,r.yg)("a",{parentName:"p",href:"/docs/advanced/custom-scalars"},"custom scalars")," for details on creating your own scalar types."),(0,r.yg)("h3",{id:"working-with-structs"},"Working with Structs"),(0,r.yg)("p",null,"Structs, by default, will be treated like ",(0,r.yg)("a",{parentName:"p",href:"/docs/types/objects"},"object graph types"),". Sometimes it may make sense to create a custom scalar out of a struct, for example, the default scalar for ",(0,r.yg)("inlineCode",{parentName:"p"},"Guid"),". Use your best judgement when determining if a struct should be a scalar or not. But always try to opt for fewer scalars when possible."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/main.3f1da62e.js b/assets/js/main.3f1da62e.js new file mode 100644 index 0000000..d713da0 --- /dev/null +++ b/assets/js/main.3f1da62e.js @@ -0,0 +1,2 @@ +/*! For license information please see main.3f1da62e.js.LICENSE.txt */ +(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[8792],{8328:(e,t,n)=>{"use strict";n.d(t,{A:()=>p});var r=n(6540),a=n(8168),o=n(3259),i=n.n(o),l=n(4054);const s={"010df87a":[()=>n.e(612).then(n.bind(n,5054)),"@site/docs/reference/benchmarks.md",5054],"0536d272":[()=>n.e(2692).then(n.bind(n,6765)),"@site/docs/development/unit-testing.md",6765],"05cffa07":[()=>n.e(657).then(n.bind(n,5959)),"@site/docs/advanced/type-expressions.md",5959],"0ee64cde":[()=>n.e(4209).then(n.bind(n,546)),"@site/docs/reference/global-configuration.md",546],17896441:[()=>Promise.all([n.e(1869),n.e(7964),n.e(8401)]).then(n.bind(n,2583)),"@theme/DocItem",2583],"1a387cfc":[()=>n.e(3515).then(n.bind(n,7798)),"@site/docs/types/enums.md",7798],"1ae49e3a":[()=>n.e(3040).then(n.bind(n,5740)),"@site/docs/reference/demo-projects.md",5740],"1be78505":[()=>Promise.all([n.e(1869),n.e(8714)]).then(n.bind(n,10)),"@theme/DocPage",10],"1c85f73a":[()=>n.e(4531).then(n.bind(n,260)),"@site/docs/server-extensions/multipart-requests.md",260],"2b3b1ca0":[()=>n.e(6667).then(n.bind(n,2105)),"@site/docs/advanced/multiple-schema.md",2105],"2f931191":[()=>n.e(4303).then(n.bind(n,5689)),"@site/docs/reference/graph-controller.md",5689],"34c61599":[()=>n.e(2477).then(n.bind(n,8613)),"@site/docs/controllers/authorization.md",8613],"37bd4da2":[()=>n.e(570).then(n.bind(n,7039)),"@site/docs/reference/attributes.md",7039],"39a1aa48":[()=>n.e(7219).then(n.bind(n,4624)),"@site/docs/execution/malicious-queries.md",4624],"3ebc953e":[()=>n.e(2552).then(n.bind(n,3389)),"@site/docs/quick/code-examples.md",3389],"40dc0bc8":[()=>n.e(275).then(n.bind(n,4867)),"@site/docs/advanced/directives.md",4867],"43b0182c":[()=>n.e(4368).then(n.bind(n,9942)),"@site/docs/types/unions.md",9942],"49d4cdfe":[()=>n.e(5176).then(n.bind(n,6889)),"@site/docs/logging/subscription-events.md",6889],"522ea0c7":[()=>n.e(9644).then(n.bind(n,9815)),"@site/docs/reference/http-processor.md",9815],"52b17a27":[()=>n.e(2980).then(n.bind(n,6096)),"@site/docs/reference/graph-directive.md",6096],"575dc170":[()=>n.e(6593).then(n.bind(n,4355)),"@site/docs/advanced/graph-action-results.md",4355],"5c71343a":[()=>n.e(1294).then(n.bind(n,6501)),"@site/docs/logging/standard-events.md",6501],"5e9f5e1a":[()=>Promise.resolve().then(n.bind(n,4784)),"@generated/docusaurus.config",4784],"609dc67a":[()=>n.e(61).then(n.bind(n,4723)),"@site/docs/controllers/batch-operations.md",4723],"61e865ea":[()=>n.e(7978).then(n.t.bind(n,4061,19)),"/home/runner/work/graphql-aspnet.github.io/graphql-aspnet.github.io/.docusaurus/docusaurus-plugin-content-pages/default/plugin-route-context-module-100.json",4061],"6499ffa8":[()=>n.e(6792).then(n.bind(n,1894)),"@site/docs/advanced/custom-scalars.md",1894],"6a8cb708":[()=>n.e(9694).then(n.t.bind(n,1966,19)),"/home/runner/work/graphql-aspnet.github.io/graphql-aspnet.github.io/.docusaurus/docusaurus-plugin-content-docs/default/plugin-route-context-module-100.json",1966],"6c218552":[()=>n.e(7045).then(n.bind(n,9574)),"@site/docs/introduction/what-is-graphql.md",9574],"7966db6c":[()=>n.e(2892).then(n.bind(n,3644)),"@site/docs/development/entity-framework.md",3644],"7f4a28f4":[()=>n.e(5749).then(n.bind(n,5037)),"@site/docs/controllers/field-paths.md",5037],"878c0d65":[()=>n.e(8700).then(n.bind(n,1577)),"@site/docs/controllers/type-extensions.md",1577],"935f2afb":[()=>n.e(8581).then(n.t.bind(n,5610,19)),"~docs/default/version-current-metadata-prop-751.json",5610],"99037cd0":[()=>n.e(7776).then(n.bind(n,8682)),"@site/docs/execution/metrics.md",8682],"9f241a4b":[()=>n.e(8956).then(n.bind(n,9774)),"@site/docs/types/interfaces.md",9774],a79310a1:[()=>n.e(1442).then(n.bind(n,2190)),"@site/docs/reference/schema-configuration.md",2190],b19420f8:[()=>n.e(4231).then(n.bind(n,9075)),"@site/docs/quick/overview.md",9075],b540f618:[()=>n.e(3715).then(n.bind(n,3984)),"@site/docs/development/debugging.md",3984],b8ee5ddb:[()=>n.e(2286).then(n.bind(n,3134)),"@site/docs/reference/vocabulary.md",3134],c4f5d8e4:[()=>Promise.all([n.e(1869),n.e(7964),n.e(2634)]).then(n.bind(n,1459)),"@site/src/pages/index.js",1459],c8b5e9da:[()=>n.e(4206).then(n.bind(n,1748)),"@site/docs/types/list-non-null.md",1748],c8cba33e:[()=>n.e(2053).then(n.bind(n,8153)),"@site/docs/reference/how-it-works.md",8153],cc10add0:[()=>n.e(7937).then(n.bind(n,1839)),"@site/docs/advanced/subscriptions.md",1839],cfce05e4:[()=>n.e(4338).then(n.bind(n,753)),"@site/docs/controllers/model-state.md",753],d1dae560:[()=>n.e(3603).then(n.bind(n,7605)),"@site/docs/types/input-objects.md",7605],d99d9121:[()=>n.e(5292).then(n.bind(n,3230)),"@site/docs/reference/middleware.md",3230],de26b084:[()=>n.e(5436).then(n.bind(n,141)),"@site/docs/logging/structured-logging.md",141],e844e6f9:[()=>n.e(9182).then(n.bind(n,6291)),"@site/docs/controllers/actions.md",6291],e9cf564a:[()=>n.e(7678).then(n.bind(n,456)),"@site/docs/reference/query-caching.md",456],ec78a8f8:[()=>n.e(5731).then(n.bind(n,1512)),"@site/docs/introduction/made-for-aspnet-developers.md",1512],ef5435f4:[()=>n.e(4055).then(n.bind(n,614)),"@site/docs/quick/create-app.md",614],fed34737:[()=>n.e(7742).then(n.bind(n,5120)),"@site/docs/types/objects.md",5120],fefbe673:[()=>n.e(5227).then(n.bind(n,8449)),"@site/docs/types/scalars.md",8449]};function u(e){let{error:t,retry:n,pastDelay:a}=e;return t?r.createElement("div",{style:{textAlign:"center",color:"#fff",backgroundColor:"#fa383e",borderColor:"#fa383e",borderStyle:"solid",borderRadius:"0.25rem",borderWidth:"1px",boxSizing:"border-box",display:"block",padding:"1rem",flex:"0 0 50%",marginLeft:"25%",marginRight:"25%",marginTop:"5rem",maxWidth:"50%",width:"100%"}},r.createElement("p",null,String(t)),r.createElement("div",null,r.createElement("button",{type:"button",onClick:n},"Retry"))):a?r.createElement("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh"}},r.createElement("svg",{id:"loader",style:{width:128,height:110,position:"absolute",top:"calc(100vh - 64%)"},viewBox:"0 0 45 45",xmlns:"http://www.w3.org/2000/svg",stroke:"#61dafb"},r.createElement("g",{fill:"none",fillRule:"evenodd",transform:"translate(1 1)",strokeWidth:"2"},r.createElement("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0"},r.createElement("animate",{attributeName:"r",begin:"1.5s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),r.createElement("animate",{attributeName:"stroke-opacity",begin:"1.5s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),r.createElement("animate",{attributeName:"stroke-width",begin:"1.5s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})),r.createElement("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0"},r.createElement("animate",{attributeName:"r",begin:"3s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),r.createElement("animate",{attributeName:"stroke-opacity",begin:"3s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),r.createElement("animate",{attributeName:"stroke-width",begin:"3s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})),r.createElement("circle",{cx:"22",cy:"22",r:"8"},r.createElement("animate",{attributeName:"r",begin:"0s",dur:"1.5s",values:"6;1;2;3;4;5;6",calcMode:"linear",repeatCount:"indefinite"}))))):null}var c=n(6921),d=n(3102);function f(e,t){if("*"===e)return i()({loading:u,loader:()=>n.e(1774).then(n.bind(n,1774)),modules:["@theme/NotFound"],webpack:()=>[1774],render(e,t){const n=e.default;return r.createElement(d.W,{value:{plugin:{name:"native",id:"default"}}},r.createElement(n,t))}});const o=l[`${e}-${t}`],f={},p=[],m=[],h=(0,c.A)(o);return Object.entries(h).forEach((e=>{let[t,n]=e;const r=s[n];r&&(f[t]=r[0],p.push(r[1]),m.push(r[2]))})),i().Map({loading:u,loader:f,modules:p,webpack:()=>m,render(t,n){const i=JSON.parse(JSON.stringify(o));Object.entries(t).forEach((t=>{let[n,r]=t;const a=r.default;if(!a)throw new Error(`The page component at ${e} doesn't have a default export. This makes it impossible to render anything. Consider default-exporting a React component.`);"object"!=typeof a&&"function"!=typeof a||Object.keys(r).filter((e=>"default"!==e)).forEach((e=>{a[e]=r[e]}));let o=i;const l=n.split(".");l.slice(0,-1).forEach((e=>{o=o[e]})),o[l[l.length-1]]=a}));const l=i.__comp;delete i.__comp;const s=i.__context;return delete i.__context,r.createElement(d.W,{value:s},r.createElement(l,(0,a.A)({},i,n)))}})}const p=[{path:"/docs",component:f("/docs","79c"),routes:[{path:"/docs/advanced/custom-scalars",component:f("/docs/advanced/custom-scalars","7ba"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/advanced/directives",component:f("/docs/advanced/directives","7e4"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/advanced/graph-action-results",component:f("/docs/advanced/graph-action-results","c3d"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/advanced/multi-schema-support",component:f("/docs/advanced/multi-schema-support","594"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/advanced/subscriptions",component:f("/docs/advanced/subscriptions","f2e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/advanced/type-expressions",component:f("/docs/advanced/type-expressions","ee8"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/controllers/actions",component:f("/docs/controllers/actions","74c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/controllers/authorization",component:f("/docs/controllers/authorization","ec3"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/controllers/batch-operations",component:f("/docs/controllers/batch-operations","98a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/controllers/field-paths",component:f("/docs/controllers/field-paths","395"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/controllers/model-state",component:f("/docs/controllers/model-state","38a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/controllers/type-extensions",component:f("/docs/controllers/type-extensions","72f"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/development/debugging",component:f("/docs/development/debugging","c34"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/development/entity-framework",component:f("/docs/development/entity-framework","f77"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/development/unit-testing",component:f("/docs/development/unit-testing","5da"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/execution/malicious-queries",component:f("/docs/execution/malicious-queries","233"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/execution/metrics",component:f("/docs/execution/metrics","d1a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/introduction/made-for-aspnet-developers",component:f("/docs/introduction/made-for-aspnet-developers","91c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/introduction/what-is-graphql",component:f("/docs/introduction/what-is-graphql","844"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/logging/standard-events",component:f("/docs/logging/standard-events","018"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/logging/structured-logging",component:f("/docs/logging/structured-logging","164"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/logging/subscription-events",component:f("/docs/logging/subscription-events","e4c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/quick/code-examples",component:f("/docs/quick/code-examples","bfa"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/quick/create-app",component:f("/docs/quick/create-app","bf4"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/quick/overview",component:f("/docs/quick/overview","70e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/reference/attributes",component:f("/docs/reference/attributes","bf8"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/reference/demo-projects",component:f("/docs/reference/demo-projects","fbd"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/reference/global-configuration",component:f("/docs/reference/global-configuration","25f"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/reference/graph-controller",component:f("/docs/reference/graph-controller","e7f"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/reference/graph-directive",component:f("/docs/reference/graph-directive","a0b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/reference/how-it-works",component:f("/docs/reference/how-it-works","1ad"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/reference/http-processor",component:f("/docs/reference/http-processor","c1a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/reference/middleware",component:f("/docs/reference/middleware","b99"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/reference/performance",component:f("/docs/reference/performance","feb"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/reference/query-cache",component:f("/docs/reference/query-cache","fb3"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/reference/schema-configuration",component:f("/docs/reference/schema-configuration","13c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/reference/vocabulary",component:f("/docs/reference/vocabulary","a70"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/server-extensions/multipart-requests",component:f("/docs/server-extensions/multipart-requests","764"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/types/enums",component:f("/docs/types/enums","dca"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/types/input-objects",component:f("/docs/types/input-objects","6af"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/types/interfaces",component:f("/docs/types/interfaces","0d8"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/types/list-non-null",component:f("/docs/types/list-non-null","40d"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/types/objects",component:f("/docs/types/objects","e6d"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/types/scalars",component:f("/docs/types/scalars","f83"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/types/unions",component:f("/docs/types/unions","49a"),exact:!0,sidebar:"tutorialSidebar"}]},{path:"/",component:f("/","341"),exact:!0},{path:"*",component:f("*")}]},6125:(e,t,n)=>{"use strict";n.d(t,{o:()=>a,x:()=>o});var r=n(6540);const a=r.createContext(!1);function o(e){let{children:t}=e;const[n,o]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{o(!0)}),[]),r.createElement(a.Provider,{value:n},t)}},5660:(e,t,n)=>{"use strict";var r=n(6540),a=n(961),o=n(4625),i=n(545),l=n(8193);const s=[n(119),n(6134),n(6294),n(1043)];var u=n(8328),c=n(6347),d=n(2831);function f(e){let{children:t}=e;return r.createElement(r.Fragment,null,t)}var p=n(8168),m=n(5260),h=n(4586),g=n(6025),b=n(6342),v=n(1003),y=n(2131),w=n(4090),E=n(2967),k=n(1463);function S(){const{i18n:{defaultLocale:e,localeConfigs:t}}=(0,h.A)(),n=(0,y.o)();return r.createElement(m.A,null,Object.entries(t).map((e=>{let[t,{htmlLang:a}]=e;return r.createElement("link",{key:t,rel:"alternate",href:n.createUrl({locale:t,fullyQualified:!0}),hrefLang:a})})),r.createElement("link",{rel:"alternate",href:n.createUrl({locale:e,fullyQualified:!0}),hrefLang:"x-default"}))}function x(e){let{permalink:t}=e;const{siteConfig:{url:n}}=(0,h.A)(),a=function(){const{siteConfig:{url:e}}=(0,h.A)(),{pathname:t}=(0,c.zy)();return e+(0,g.A)(t)}(),o=t?`${n}${t}`:a;return r.createElement(m.A,null,r.createElement("meta",{property:"og:url",content:o}),r.createElement("link",{rel:"canonical",href:o}))}function _(){const{i18n:{currentLocale:e}}=(0,h.A)(),{metadata:t,image:n}=(0,b.p)();return r.createElement(r.Fragment,null,r.createElement(m.A,null,r.createElement("meta",{name:"twitter:card",content:"summary_large_image"}),r.createElement("body",{className:w.w})),n&&r.createElement(v.be,{image:n}),r.createElement(x,null),r.createElement(S,null),r.createElement(k.A,{tag:E.Cy,locale:e}),r.createElement(m.A,null,t.map(((e,t)=>r.createElement("meta",(0,p.A)({key:t},e))))))}const C=new Map;function A(e){if(C.has(e.pathname))return{...e,pathname:C.get(e.pathname)};if((0,d.u)(u.A,e.pathname).some((e=>{let{route:t}=e;return!0===t.exact})))return C.set(e.pathname,e.pathname),e;const t=e.pathname.trim().replace(/(?:\/index)?\.html$/,"")||"/";return C.set(e.pathname,t),{...e,pathname:t}}var T=n(6125),N=n(6988);function O(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];const a=s.map((t=>(t.default?.[e]??t[e])?.(...n)));return()=>a.forEach((e=>e?.()))}const L=function(e){let{children:t,location:n,previousLocation:a}=e;return(0,r.useLayoutEffect)((()=>{a!==n&&(!function(e){let{location:t,previousLocation:n}=e;if(!n)return;const r=t.pathname===n.pathname,a=t.hash===n.hash,o=t.search===n.search;if(r&&a&&!o)return;const{hash:i}=t;if(i){const e=decodeURIComponent(i.substring(1));document.getElementById(e)?.scrollIntoView()}else window.scrollTo(0,0)}({location:n,previousLocation:a}),O("onRouteDidUpdate",{previousLocation:a,location:n}))}),[a,n]),t};function P(e){const t=Array.from(new Set([e,decodeURI(e)])).map((e=>(0,d.u)(u.A,e))).flat();return Promise.all(t.map((e=>e.route.component.preload?.())))}class R extends r.Component{constructor(e){super(e),this.previousLocation=void 0,this.routeUpdateCleanupCb=void 0,this.previousLocation=null,this.routeUpdateCleanupCb=l.A.canUseDOM?O("onRouteUpdate",{previousLocation:null,location:this.props.location}):()=>{},this.state={nextRouteHasLoaded:!0}}shouldComponentUpdate(e,t){if(e.location===this.props.location)return t.nextRouteHasLoaded;const n=e.location;return this.previousLocation=this.props.location,this.setState({nextRouteHasLoaded:!1}),this.routeUpdateCleanupCb=O("onRouteUpdate",{previousLocation:this.previousLocation,location:n}),P(n.pathname).then((()=>{this.routeUpdateCleanupCb(),this.setState({nextRouteHasLoaded:!0})})).catch((e=>{console.warn(e),window.location.reload()})),!1}render(){const{children:e,location:t}=this.props;return r.createElement(L,{previousLocation:this.previousLocation,location:t},r.createElement(c.qh,{location:t,render:()=>e}))}}const I=R,M="docusaurus-base-url-issue-banner-container",D="docusaurus-base-url-issue-banner-suggestion-container",F="__DOCUSAURUS_INSERT_BASEURL_BANNER";function B(e){return`\nwindow['${F}'] = true;\n\ndocument.addEventListener('DOMContentLoaded', maybeInsertBanner);\n\nfunction maybeInsertBanner() {\n var shouldInsert = window['${F}'];\n shouldInsert && insertBanner();\n}\n\nfunction insertBanner() {\n var bannerContainer = document.getElementById('${M}');\n if (!bannerContainer) {\n return;\n }\n var bannerHtml = ${JSON.stringify(function(e){return`\n<div id="docusaurus-base-url-issue-banner" style="border: thick solid red; background-color: rgb(255, 230, 179); margin: 20px; padding: 20px; font-size: 20px;">\n <p style="font-weight: bold; font-size: 30px;">Your Docusaurus site did not load properly.</p>\n <p>A very common reason is a wrong site <a href="https://docusaurus.io/docs/docusaurus.config.js/#baseUrl" style="font-weight: bold;">baseUrl configuration</a>.</p>\n <p>Current configured baseUrl = <span style="font-weight: bold; color: red;">${e}</span> ${"/"===e?" (default value)":""}</p>\n <p>We suggest trying baseUrl = <span id="${D}" style="font-weight: bold; color: green;"></span></p>\n</div>\n`}(e)).replace(/</g,"\\<")};\n bannerContainer.innerHTML = bannerHtml;\n var suggestionContainer = document.getElementById('${D}');\n var actualHomePagePath = window.location.pathname;\n var suggestedBaseUrl = actualHomePagePath.substr(-1) === '/'\n ? actualHomePagePath\n : actualHomePagePath + '/';\n suggestionContainer.innerHTML = suggestedBaseUrl;\n}\n`}function $(){const{siteConfig:{baseUrl:e}}=(0,h.A)();return(0,r.useLayoutEffect)((()=>{window[F]=!1}),[]),r.createElement(r.Fragment,null,!l.A.canUseDOM&&r.createElement(m.A,null,r.createElement("script",null,B(e))),r.createElement("div",{id:M}))}function z(){const{siteConfig:{baseUrl:e,baseUrlIssueBanner:t}}=(0,h.A)(),{pathname:n}=(0,c.zy)();return t&&n===e?r.createElement($,null):null}function U(){const{siteConfig:{favicon:e,title:t,noIndex:n},i18n:{currentLocale:a,localeConfigs:o}}=(0,h.A)(),i=(0,g.A)(e),{htmlLang:l,direction:s}=o[a];return r.createElement(m.A,null,r.createElement("html",{lang:l,dir:s}),r.createElement("title",null,t),r.createElement("meta",{property:"og:title",content:t}),r.createElement("meta",{name:"viewport",content:"width=device-width, initial-scale=1.0"}),n&&r.createElement("meta",{name:"robots",content:"noindex, nofollow"}),e&&r.createElement("link",{rel:"icon",href:i}))}var j=n(7489);function H(){const e=(0,d.v)(u.A),t=(0,c.zy)();return r.createElement(j.A,null,r.createElement(N.l,null,r.createElement(T.x,null,r.createElement(f,null,r.createElement(U,null),r.createElement(_,null),r.createElement(z,null),r.createElement(I,{location:A(t)},e)))))}var q=n(4054);const V=function(e){try{return document.createElement("link").relList.supports(e)}catch{return!1}}("prefetch")?function(e){return new Promise(((t,n)=>{if("undefined"==typeof document)return void n();const r=document.createElement("link");r.setAttribute("rel","prefetch"),r.setAttribute("href",e),r.onload=()=>t(),r.onerror=()=>n();(document.getElementsByTagName("head")[0]??document.getElementsByName("script")[0]?.parentNode)?.appendChild(r)}))}:function(e){return new Promise(((t,n)=>{const r=new XMLHttpRequest;r.open("GET",e,!0),r.withCredentials=!0,r.onload=()=>{200===r.status?t():n()},r.send(null)}))};var G=n(6921);const W=new Set,Y=new Set,K=()=>navigator.connection?.effectiveType.includes("2g")||navigator.connection?.saveData,Q={prefetch(e){if(!(e=>!K()&&!Y.has(e)&&!W.has(e))(e))return!1;W.add(e);const t=(0,d.u)(u.A,e).flatMap((e=>{return t=e.route.path,Object.entries(q).filter((e=>{let[n]=e;return n.replace(/-[^-]+$/,"")===t})).flatMap((e=>{let[,t]=e;return Object.values((0,G.A)(t))}));var t}));return Promise.all(t.map((e=>{const t=n.gca(e);return t&&!t.includes("undefined")?V(t).catch((()=>{})):Promise.resolve()})))},preload:e=>!!(e=>!K()&&!Y.has(e))(e)&&(Y.add(e),P(e))},X=Object.freeze(Q);if(l.A.canUseDOM){window.docusaurus=X;const e=a.hydrate;P(window.location.pathname).then((()=>{e(r.createElement(i.vd,null,r.createElement(o.Kd,null,r.createElement(H,null))),document.getElementById("__docusaurus"))}))}},6988:(e,t,n)=>{"use strict";n.d(t,{o:()=>c,l:()=>d});var r=n(6540),a=n(4784);const o=JSON.parse('{"docusaurus-plugin-content-docs":{"default":{"path":"/docs","versions":[{"name":"current","label":"Next","isLast":true,"path":"/docs","mainDocId":"quick/overview","docs":[{"id":"advanced/custom-scalars","path":"/docs/advanced/custom-scalars","sidebar":"tutorialSidebar"},{"id":"advanced/directives","path":"/docs/advanced/directives","sidebar":"tutorialSidebar"},{"id":"advanced/graph-action-results","path":"/docs/advanced/graph-action-results","sidebar":"tutorialSidebar"},{"id":"advanced/multi-schema-support","path":"/docs/advanced/multi-schema-support","sidebar":"tutorialSidebar"},{"id":"advanced/subscriptions","path":"/docs/advanced/subscriptions","sidebar":"tutorialSidebar"},{"id":"advanced/type-expressions","path":"/docs/advanced/type-expressions","sidebar":"tutorialSidebar"},{"id":"controllers/actions","path":"/docs/controllers/actions","sidebar":"tutorialSidebar"},{"id":"controllers/authorization","path":"/docs/controllers/authorization","sidebar":"tutorialSidebar"},{"id":"controllers/batch-operations","path":"/docs/controllers/batch-operations","sidebar":"tutorialSidebar"},{"id":"controllers/field-paths","path":"/docs/controllers/field-paths","sidebar":"tutorialSidebar"},{"id":"controllers/model-state","path":"/docs/controllers/model-state","sidebar":"tutorialSidebar"},{"id":"controllers/type-extensions","path":"/docs/controllers/type-extensions","sidebar":"tutorialSidebar"},{"id":"development/debugging","path":"/docs/development/debugging","sidebar":"tutorialSidebar"},{"id":"development/entity-framework","path":"/docs/development/entity-framework","sidebar":"tutorialSidebar"},{"id":"development/unit-testing","path":"/docs/development/unit-testing","sidebar":"tutorialSidebar"},{"id":"execution/malicious-queries","path":"/docs/execution/malicious-queries","sidebar":"tutorialSidebar"},{"id":"execution/metrics","path":"/docs/execution/metrics","sidebar":"tutorialSidebar"},{"id":"introduction/made-for-aspnet-developers","path":"/docs/introduction/made-for-aspnet-developers","sidebar":"tutorialSidebar"},{"id":"introduction/what-is-graphql","path":"/docs/introduction/what-is-graphql","sidebar":"tutorialSidebar"},{"id":"logging/standard-events","path":"/docs/logging/standard-events","sidebar":"tutorialSidebar"},{"id":"logging/structured-logging","path":"/docs/logging/structured-logging","sidebar":"tutorialSidebar"},{"id":"logging/subscription-events","path":"/docs/logging/subscription-events","sidebar":"tutorialSidebar"},{"id":"quick/code-examples","path":"/docs/quick/code-examples","sidebar":"tutorialSidebar"},{"id":"quick/create-app","path":"/docs/quick/create-app","sidebar":"tutorialSidebar"},{"id":"quick/overview","path":"/docs/quick/overview","sidebar":"tutorialSidebar"},{"id":"reference/attributes","path":"/docs/reference/attributes","sidebar":"tutorialSidebar"},{"id":"reference/demo-projects","path":"/docs/reference/demo-projects","sidebar":"tutorialSidebar"},{"id":"reference/global-configuration","path":"/docs/reference/global-configuration","sidebar":"tutorialSidebar"},{"id":"reference/graph-controller","path":"/docs/reference/graph-controller","sidebar":"tutorialSidebar"},{"id":"reference/graph-directive","path":"/docs/reference/graph-directive","sidebar":"tutorialSidebar"},{"id":"reference/how-it-works","path":"/docs/reference/how-it-works","sidebar":"tutorialSidebar"},{"id":"reference/http-processor","path":"/docs/reference/http-processor","sidebar":"tutorialSidebar"},{"id":"reference/middleware","path":"/docs/reference/middleware","sidebar":"tutorialSidebar"},{"id":"reference/performance","path":"/docs/reference/performance","sidebar":"tutorialSidebar"},{"id":"reference/query-cache","path":"/docs/reference/query-cache","sidebar":"tutorialSidebar"},{"id":"reference/schema-configuration","path":"/docs/reference/schema-configuration","sidebar":"tutorialSidebar"},{"id":"reference/vocabulary","path":"/docs/reference/vocabulary","sidebar":"tutorialSidebar"},{"id":"server-extensions/multipart-requests","path":"/docs/server-extensions/multipart-requests","sidebar":"tutorialSidebar"},{"id":"types/enums","path":"/docs/types/enums","sidebar":"tutorialSidebar"},{"id":"types/input-objects","path":"/docs/types/input-objects","sidebar":"tutorialSidebar"},{"id":"types/interfaces","path":"/docs/types/interfaces","sidebar":"tutorialSidebar"},{"id":"types/list-non-null","path":"/docs/types/list-non-null","sidebar":"tutorialSidebar"},{"id":"types/objects","path":"/docs/types/objects","sidebar":"tutorialSidebar"},{"id":"types/scalars","path":"/docs/types/scalars","sidebar":"tutorialSidebar"},{"id":"types/unions","path":"/docs/types/unions","sidebar":"tutorialSidebar"}],"draftIds":[],"sidebars":{"tutorialSidebar":{"link":{"path":"/docs/quick/overview","label":"Overview"}}}}],"breadcrumbs":true}}}'),i=JSON.parse('{"defaultLocale":"en","locales":["en"],"path":"i18n","currentLocale":"en","localeConfigs":{"en":{"label":"English","direction":"ltr","htmlLang":"en","calendar":"gregory","path":"en"}}}');var l=n(2654);const s=JSON.parse('{"docusaurusVersion":"2.4.0","siteVersion":"0.0.0","pluginVersions":{"docusaurus-plugin-content-docs":{"type":"package","name":"@docusaurus/plugin-content-docs","version":"2.4.0"},"docusaurus-plugin-content-blog":{"type":"package","name":"@docusaurus/plugin-content-blog","version":"2.4.0"},"docusaurus-plugin-content-pages":{"type":"package","name":"@docusaurus/plugin-content-pages","version":"2.4.0"},"docusaurus-plugin-sitemap":{"type":"package","name":"@docusaurus/plugin-sitemap","version":"2.4.0"},"docusaurus-theme-classic":{"type":"package","name":"@docusaurus/theme-classic","version":"2.4.0"}}}'),u={siteConfig:a.default,siteMetadata:s,globalData:o,i18n:i,codeTranslations:l},c=r.createContext(u);function d(e){let{children:t}=e;return r.createElement(c.Provider,{value:u},t)}},7489:(e,t,n)=>{"use strict";n.d(t,{A:()=>f});var r=n(6540),a=n(8193),o=n(5260),i=n(440),l=n(9408);function s(e){let{error:t,tryAgain:n}=e;return r.createElement("div",{style:{display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"flex-start",minHeight:"100vh",width:"100%",maxWidth:"80ch",fontSize:"20px",margin:"0 auto",padding:"1rem"}},r.createElement("h1",{style:{fontSize:"3rem"}},"This page crashed"),r.createElement("button",{type:"button",onClick:n,style:{margin:"1rem 0",fontSize:"2rem",cursor:"pointer",borderRadius:20,padding:"1rem"}},"Try again"),r.createElement(u,{error:t}))}function u(e){let{error:t}=e;const n=(0,i.getErrorCausalChain)(t).map((e=>e.message)).join("\n\nCause:\n");return r.createElement("p",{style:{whiteSpace:"pre-wrap"}},n)}function c(e){let{error:t,tryAgain:n}=e;return r.createElement(f,{fallback:()=>r.createElement(s,{error:t,tryAgain:n})},r.createElement(o.A,null,r.createElement("title",null,"Page Error")),r.createElement(l.A,null,r.createElement(s,{error:t,tryAgain:n})))}const d=e=>r.createElement(c,e);class f extends r.Component{constructor(e){super(e),this.state={error:null}}componentDidCatch(e){a.A.canUseDOM&&this.setState({error:e})}render(){const{children:e}=this.props,{error:t}=this.state;if(t){const e={error:t,tryAgain:()=>this.setState({error:null})};return(this.props.fallback??d)(e)}return e??null}}},8193:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});const r="undefined"!=typeof window&&"document"in window&&"createElement"in window.document,a={canUseDOM:r,canUseEventListeners:r&&("addEventListener"in window||"attachEvent"in window),canUseIntersectionObserver:r&&"IntersectionObserver"in window,canUseViewport:r&&"screen"in window}},5260:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(545);function o(e){return r.createElement(a.mg,e)}},5489:(e,t,n)=>{"use strict";n.d(t,{A:()=>p});var r=n(8168),a=n(6540),o=n(4625),i=n(440),l=n(4586),s=n(6654),u=n(8193);const c=a.createContext({collectLink:()=>{}});var d=n(6025);function f(e,t){let{isNavLink:n,to:f,href:p,activeClassName:m,isActive:h,"data-noBrokenLinkCheck":g,autoAddBaseUrl:b=!0,...v}=e;const{siteConfig:{trailingSlash:y,baseUrl:w}}=(0,l.A)(),{withBaseUrl:E}=(0,d.h)(),k=(0,a.useContext)(c),S=(0,a.useRef)(null);(0,a.useImperativeHandle)(t,(()=>S.current));const x=f||p;const _=(0,s.A)(x),C=x?.replace("pathname://","");let A=void 0!==C?(T=C,b&&(e=>e.startsWith("/"))(T)?E(T):T):void 0;var T;A&&_&&(A=(0,i.applyTrailingSlash)(A,{trailingSlash:y,baseUrl:w}));const N=(0,a.useRef)(!1),O=n?o.k2:o.N_,L=u.A.canUseIntersectionObserver,P=(0,a.useRef)(),R=()=>{N.current||null==A||(window.docusaurus.preload(A),N.current=!0)};(0,a.useEffect)((()=>(!L&&_&&null!=A&&window.docusaurus.prefetch(A),()=>{L&&P.current&&P.current.disconnect()})),[P,A,L,_]);const I=A?.startsWith("#")??!1,M=!A||!_||I;return M||g||k.collectLink(A),M?a.createElement("a",(0,r.A)({ref:S,href:A},x&&!_&&{target:"_blank",rel:"noopener noreferrer"},v)):a.createElement(O,(0,r.A)({},v,{onMouseEnter:R,onTouchStart:R,innerRef:e=>{S.current=e,L&&e&&_&&(P.current=new window.IntersectionObserver((t=>{t.forEach((t=>{e===t.target&&(t.isIntersecting||t.intersectionRatio>0)&&(P.current.unobserve(e),P.current.disconnect(),null!=A&&window.docusaurus.prefetch(A))}))})),P.current.observe(e))},to:A},n&&{isActive:h,activeClassName:m}))}const p=a.forwardRef(f)},418:(e,t,n)=>{"use strict";n.d(t,{A:()=>r});const r=()=>null},1312:(e,t,n)=>{"use strict";n.d(t,{A:()=>s,T:()=>l});var r=n(6540);function a(e,t){const n=e.split(/(\{\w+\})/).map(((e,n)=>{if(n%2==1){const n=t?.[e.slice(1,-1)];if(void 0!==n)return n}return e}));return n.some((e=>(0,r.isValidElement)(e)))?n.map(((e,t)=>(0,r.isValidElement)(e)?r.cloneElement(e,{key:t}):e)).filter((e=>""!==e)):n.join("")}var o=n(2654);function i(e){let{id:t,message:n}=e;if(void 0===t&&void 0===n)throw new Error("Docusaurus translation declarations must have at least a translation id or a default translation message");return o[t??n]??n??t}function l(e,t){let{message:n,id:r}=e;return a(i({message:n,id:r}),t)}function s(e){let{children:t,id:n,values:o}=e;if(t&&"string"!=typeof t)throw console.warn("Illegal <Translate> children",t),new Error("The Docusaurus <Translate> component only accept simple string values");const l=i({message:t,id:n});return r.createElement(r.Fragment,null,a(l,o))}},7065:(e,t,n)=>{"use strict";n.d(t,{W:()=>r});const r="default"},6654:(e,t,n)=>{"use strict";function r(e){return/^(?:\w*:|\/\/)/.test(e)}function a(e){return void 0!==e&&!r(e)}n.d(t,{A:()=>a,z:()=>r})},6025:(e,t,n)=>{"use strict";n.d(t,{A:()=>l,h:()=>i});var r=n(6540),a=n(4586),o=n(6654);function i(){const{siteConfig:{baseUrl:e,url:t}}=(0,a.A)(),n=(0,r.useCallback)(((n,r)=>function(e,t,n,r){let{forcePrependBaseUrl:a=!1,absolute:i=!1}=void 0===r?{}:r;if(!n||n.startsWith("#")||(0,o.z)(n))return n;if(a)return t+n.replace(/^\//,"");if(n===t.replace(/\/$/,""))return t;const l=n.startsWith(t)?n:t+n.replace(/^\//,"");return i?e+l:l}(t,e,n,r)),[t,e]);return{withBaseUrl:n}}function l(e,t){void 0===t&&(t={});const{withBaseUrl:n}=i();return n(e,t)}},4586:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(6988);function o(){return(0,r.useContext)(a.o)}},2303:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(6125);function o(){return(0,r.useContext)(a.o)}},6921:(e,t,n)=>{"use strict";n.d(t,{A:()=>r});function r(e){const t={};return function e(n,r){Object.entries(n).forEach((n=>{let[a,o]=n;const i=r?`${r}.${a}`:a;var l;"object"==typeof(l=o)&&l&&Object.keys(l).length>0?e(o,i):t[i]=o}))}(e),t}},3102:(e,t,n)=>{"use strict";n.d(t,{W:()=>o,o:()=>a});var r=n(6540);const a=r.createContext(null);function o(e){let{children:t,value:n}=e;const o=r.useContext(a),i=(0,r.useMemo)((()=>function(e){let{parent:t,value:n}=e;if(!t){if(!n)throw new Error("Unexpected: no Docusaurus route context found");if(!("plugin"in n))throw new Error("Unexpected: Docusaurus topmost route context has no `plugin` attribute");return n}const r={...t.data,...n?.data};return{plugin:t.plugin,data:r}}({parent:o,value:n})),[o,n]);return r.createElement(a.Provider,{value:i},t)}},4070:(e,t,n)=>{"use strict";n.d(t,{zK:()=>h,vT:()=>f,Gy:()=>c,HW:()=>g,ht:()=>d,r7:()=>m,jh:()=>p});var r=n(6347),a=n(4586),o=n(7065);function i(e,t){void 0===t&&(t={});const n=function(){const{globalData:e}=(0,a.A)();return e}()[e];if(!n&&t.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin.`);return n}const l=e=>e.versions.find((e=>e.isLast));function s(e,t){const n=function(e,t){const n=l(e);return[...e.versions.filter((e=>e!==n)),n].find((e=>!!(0,r.B6)(t,{path:e.path,exact:!1,strict:!1})))}(e,t),a=n?.docs.find((e=>!!(0,r.B6)(t,{path:e.path,exact:!0,strict:!1})));return{activeVersion:n,activeDoc:a,alternateDocVersions:a?function(t){const n={};return e.versions.forEach((e=>{e.docs.forEach((r=>{r.id===t&&(n[e.name]=r)}))})),n}(a.id):{}}}const u={},c=()=>i("docusaurus-plugin-content-docs")??u,d=e=>function(e,t,n){void 0===t&&(t=o.W),void 0===n&&(n={});const r=i(e)?.[t];if(!r&&n.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin with id "${t}".`);return r}("docusaurus-plugin-content-docs",e,{failfast:!0});function f(e){void 0===e&&(e={});const t=c(),{pathname:n}=(0,r.zy)();return function(e,t,n){void 0===n&&(n={});const a=Object.entries(e).sort(((e,t)=>t[1].path.localeCompare(e[1].path))).find((e=>{let[,n]=e;return!!(0,r.B6)(t,{path:n.path,exact:!1,strict:!1})})),o=a?{pluginId:a[0],pluginData:a[1]}:void 0;if(!o&&n.failfast)throw new Error(`Can't find active docs plugin for "${t}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values(e).map((e=>e.path)).join(", ")}`);return o}(t,n,e)}function p(e){return d(e).versions}function m(e){const t=d(e);return l(t)}function h(e){const t=d(e),{pathname:n}=(0,r.zy)();return s(t,n)}function g(e){const t=d(e),{pathname:n}=(0,r.zy)();return function(e,t){const n=l(e);return{latestDocSuggestion:s(e,t).alternateDocVersions[n.name],latestVersionSuggestion:n}}(t,n)}},6294:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(5947),a=n.n(r);a().configure({showSpinner:!1});const o={onRouteUpdate(e){let{location:t,previousLocation:n}=e;if(n&&t.pathname!==n.pathname){const e=window.setTimeout((()=>{a().start()}),200);return()=>window.clearTimeout(e)}},onRouteDidUpdate(){a().done()}}},6134:(e,t,n)=>{"use strict";var r=n(1258),a=n(4784);!function(e){const{themeConfig:{prism:t}}=a.default,{additionalLanguages:r}=t;globalThis.Prism=e,r.forEach((e=>{n(3213)(`./prism-${e}`)})),delete globalThis.Prism}(r.A)},3186:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540);const a="iconExternalLink_nPIU";function o(e){let{width:t=13.5,height:n=13.5}=e;return r.createElement("svg",{width:t,height:n,"aria-hidden":"true",viewBox:"0 0 24 24",className:a},r.createElement("path",{fill:"currentColor",d:"M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"}))}},9408:(e,t,n)=>{"use strict";n.d(t,{A:()=>mt});var r=n(6540),a=n(53),o=n(7489),i=n(1003),l=n(8168),s=n(6347),u=n(1312),c=n(5062);const d="docusaurus_skipToContent_fallback";function f(e){e.setAttribute("tabindex","-1"),e.focus(),e.removeAttribute("tabindex")}function p(){const e=(0,r.useRef)(null),{action:t}=(0,s.W6)(),n=(0,r.useCallback)((e=>{e.preventDefault();const t=document.querySelector("main:first-of-type")??document.getElementById(d);t&&f(t)}),[]);return(0,c.$)((n=>{let{location:r}=n;e.current&&!r.hash&&"PUSH"===t&&f(e.current)})),{containerRef:e,onClick:n}}const m=(0,u.T)({id:"theme.common.skipToMainContent",description:"The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation",message:"Skip to main content"});function h(e){const t=e.children??m,{containerRef:n,onClick:a}=p();return r.createElement("div",{ref:n,role:"region","aria-label":m},r.createElement("a",(0,l.A)({},e,{href:`#${d}`,onClick:a}),t))}var g=n(7559),b=n(4090);const v="skipToContent_fXgn";function y(){return r.createElement(h,{className:v})}var w=n(6342),E=n(5041);function k(e){let{width:t=21,height:n=21,color:a="currentColor",strokeWidth:o=1.2,className:i,...s}=e;return r.createElement("svg",(0,l.A)({viewBox:"0 0 15 15",width:t,height:n},s),r.createElement("g",{stroke:a,strokeWidth:o},r.createElement("path",{d:"M.75.75l13.5 13.5M14.25.75L.75 14.25"})))}const S="closeButton_CVFx";function x(e){return r.createElement("button",(0,l.A)({type:"button","aria-label":(0,u.T)({id:"theme.AnnouncementBar.closeButtonAriaLabel",message:"Close",description:"The ARIA label for close button of announcement bar"})},e,{className:(0,a.A)("clean-btn close",S,e.className)}),r.createElement(k,{width:14,height:14,strokeWidth:3.1}))}const _="content_knG7";function C(e){const{announcementBar:t}=(0,w.p)(),{content:n}=t;return r.createElement("div",(0,l.A)({},e,{className:(0,a.A)(_,e.className),dangerouslySetInnerHTML:{__html:n}}))}const A="announcementBar_mb4j",T="announcementBarPlaceholder_vyr4",N="announcementBarClose_gvF7",O="announcementBarContent_xLdY";function L(){const{announcementBar:e}=(0,w.p)(),{isActive:t,close:n}=(0,E.Mj)();if(!t)return null;const{backgroundColor:a,textColor:o,isCloseable:i}=e;return r.createElement("div",{className:A,style:{backgroundColor:a,color:o},role:"banner"},i&&r.createElement("div",{className:T}),r.createElement(C,{className:O}),i&&r.createElement(x,{onClick:n,className:N}))}var P=n(9876),R=n(3104);var I=n(9532),M=n(5600);const D=r.createContext(null);function F(e){let{children:t}=e;const n=function(){const e=(0,P.M)(),t=(0,M.YL)(),[n,a]=(0,r.useState)(!1),o=null!==t.component,i=(0,I.ZC)(o);return(0,r.useEffect)((()=>{o&&!i&&a(!0)}),[o,i]),(0,r.useEffect)((()=>{o?e.shown||a(!0):a(!1)}),[e.shown,o]),(0,r.useMemo)((()=>[n,a]),[n])}();return r.createElement(D.Provider,{value:n},t)}function B(e){if(e.component){const t=e.component;return r.createElement(t,e.props)}}function $(){const e=(0,r.useContext)(D);if(!e)throw new I.dV("NavbarSecondaryMenuDisplayProvider");const[t,n]=e,a=(0,r.useCallback)((()=>n(!1)),[n]),o=(0,M.YL)();return(0,r.useMemo)((()=>({shown:t,hide:a,content:B(o)})),[a,o,t])}function z(e){let{header:t,primaryMenu:n,secondaryMenu:o}=e;const{shown:i}=$();return r.createElement("div",{className:"navbar-sidebar"},t,r.createElement("div",{className:(0,a.A)("navbar-sidebar__items",{"navbar-sidebar__items--show-secondary":i})},r.createElement("div",{className:"navbar-sidebar__item menu"},n),r.createElement("div",{className:"navbar-sidebar__item menu"},o)))}var U=n(5293),j=n(2303);function H(e){return r.createElement("svg",(0,l.A)({viewBox:"0 0 24 24",width:24,height:24},e),r.createElement("path",{fill:"currentColor",d:"M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"}))}function q(e){return r.createElement("svg",(0,l.A)({viewBox:"0 0 24 24",width:24,height:24},e),r.createElement("path",{fill:"currentColor",d:"M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"}))}const V={toggle:"toggle_vylO",toggleButton:"toggleButton_gllP",darkToggleIcon:"darkToggleIcon_wfgR",lightToggleIcon:"lightToggleIcon_pyhR",toggleButtonDisabled:"toggleButtonDisabled_aARS"};function G(e){let{className:t,buttonClassName:n,value:o,onChange:i}=e;const l=(0,j.A)(),s=(0,u.T)({message:"Switch between dark and light mode (currently {mode})",id:"theme.colorToggle.ariaLabel",description:"The ARIA label for the navbar color mode toggle"},{mode:"dark"===o?(0,u.T)({message:"dark mode",id:"theme.colorToggle.ariaLabel.mode.dark",description:"The name for the dark color mode"}):(0,u.T)({message:"light mode",id:"theme.colorToggle.ariaLabel.mode.light",description:"The name for the light color mode"})});return r.createElement("div",{className:(0,a.A)(V.toggle,t)},r.createElement("button",{className:(0,a.A)("clean-btn",V.toggleButton,!l&&V.toggleButtonDisabled,n),type:"button",onClick:()=>i("dark"===o?"light":"dark"),disabled:!l,title:s,"aria-label":s,"aria-live":"polite"},r.createElement(H,{className:(0,a.A)(V.toggleIcon,V.lightToggleIcon)}),r.createElement(q,{className:(0,a.A)(V.toggleIcon,V.darkToggleIcon)})))}const W=r.memo(G),Y="darkNavbarColorModeToggle_X3D1";function K(e){let{className:t}=e;const n=(0,w.p)().navbar.style,a=(0,w.p)().colorMode.disableSwitch,{colorMode:o,setColorMode:i}=(0,U.G)();return a?null:r.createElement(W,{className:t,buttonClassName:"dark"===n?Y:void 0,value:o,onChange:i})}var Q=n(3465);function X(){return r.createElement(Q.A,{className:"navbar__brand",imageClassName:"navbar__logo",titleClassName:"navbar__title text--truncate"})}function Z(){const e=(0,P.M)();return r.createElement("button",{type:"button","aria-label":(0,u.T)({id:"theme.docs.sidebar.closeSidebarButtonAriaLabel",message:"Close navigation bar",description:"The ARIA label for close button of mobile sidebar"}),className:"clean-btn navbar-sidebar__close",onClick:()=>e.toggle()},r.createElement(k,{color:"var(--ifm-color-emphasis-600)"}))}function J(){return r.createElement("div",{className:"navbar-sidebar__brand"},r.createElement(X,null),r.createElement(K,{className:"margin-right--md"}),r.createElement(Z,null))}var ee=n(5489),te=n(6025),ne=n(6654);function re(e,t){return void 0!==e&&void 0!==t&&new RegExp(e,"gi").test(t)}var ae=n(3186);function oe(e){let{activeBasePath:t,activeBaseRegex:n,to:a,href:o,label:i,html:s,isDropdownLink:u,prependBaseUrlToHref:c,...d}=e;const f=(0,te.A)(a),p=(0,te.A)(t),m=(0,te.A)(o,{forcePrependBaseUrl:!0}),h=i&&o&&!(0,ne.A)(o),g=s?{dangerouslySetInnerHTML:{__html:s}}:{children:r.createElement(r.Fragment,null,i,h&&r.createElement(ae.A,u&&{width:12,height:12}))};return o?r.createElement(ee.A,(0,l.A)({href:c?m:o},d,g)):r.createElement(ee.A,(0,l.A)({to:f,isNavLink:!0},(t||n)&&{isActive:(e,t)=>n?re(n,t.pathname):t.pathname.startsWith(p)},d,g))}function ie(e){let{className:t,isDropdownItem:n=!1,...o}=e;const i=r.createElement(oe,(0,l.A)({className:(0,a.A)(n?"dropdown__link":"navbar__item navbar__link",t),isDropdownLink:n},o));return n?r.createElement("li",null,i):i}function le(e){let{className:t,isDropdownItem:n,...o}=e;return r.createElement("li",{className:"menu__list-item"},r.createElement(oe,(0,l.A)({className:(0,a.A)("menu__link",t)},o)))}function se(e){let{mobile:t=!1,position:n,...a}=e;const o=t?le:ie;return r.createElement(o,(0,l.A)({},a,{activeClassName:a.activeClassName??(t?"menu__link--active":"navbar__link--active")}))}var ue=n(1422),ce=n(9169),de=n(4586);function fe(e,t){return e.some((e=>function(e,t){return!!(0,ce.ys)(e.to,t)||!!re(e.activeBaseRegex,t)||!(!e.activeBasePath||!t.startsWith(e.activeBasePath))}(e,t)))}function pe(e){let{items:t,position:n,className:o,onClick:i,...s}=e;const u=(0,r.useRef)(null),[c,d]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{const e=e=>{u.current&&!u.current.contains(e.target)&&d(!1)};return document.addEventListener("mousedown",e),document.addEventListener("touchstart",e),document.addEventListener("focusin",e),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("touchstart",e),document.removeEventListener("focusin",e)}}),[u]),r.createElement("div",{ref:u,className:(0,a.A)("navbar__item","dropdown","dropdown--hoverable",{"dropdown--right":"right"===n,"dropdown--show":c})},r.createElement(oe,(0,l.A)({"aria-haspopup":"true","aria-expanded":c,role:"button",href:s.to?void 0:"#",className:(0,a.A)("navbar__link",o)},s,{onClick:s.to?void 0:e=>e.preventDefault(),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),d(!c))}}),s.children??s.label),r.createElement("ul",{className:"dropdown__menu"},t.map(((e,t)=>r.createElement(Ae,(0,l.A)({isDropdownItem:!0,activeClassName:"dropdown__link--active"},e,{key:t}))))))}function me(e){let{items:t,className:n,position:o,onClick:i,...u}=e;const c=function(){const{siteConfig:{baseUrl:e}}=(0,de.A)(),{pathname:t}=(0,s.zy)();return t.replace(e,"/")}(),d=fe(t,c),{collapsed:f,toggleCollapsed:p,setCollapsed:m}=(0,ue.u)({initialState:()=>!d});return(0,r.useEffect)((()=>{d&&m(!d)}),[c,d,m]),r.createElement("li",{className:(0,a.A)("menu__list-item",{"menu__list-item--collapsed":f})},r.createElement(oe,(0,l.A)({role:"button",className:(0,a.A)("menu__link menu__link--sublist menu__link--sublist-caret",n)},u,{onClick:e=>{e.preventDefault(),p()}}),u.children??u.label),r.createElement(ue.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:f},t.map(((e,t)=>r.createElement(Ae,(0,l.A)({mobile:!0,isDropdownItem:!0,onClick:i,activeClassName:"menu__link--active"},e,{key:t}))))))}function he(e){let{mobile:t=!1,...n}=e;const a=t?me:pe;return r.createElement(a,n)}var ge=n(2131);function be(e){let{width:t=20,height:n=20,...a}=e;return r.createElement("svg",(0,l.A)({viewBox:"0 0 24 24",width:t,height:n,"aria-hidden":!0},a),r.createElement("path",{fill:"currentColor",d:"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"}))}const ve="iconLanguage_nlXk";var ye=n(418);const we="searchBox_ZlJk";function Ee(e){let{children:t,className:n}=e;return r.createElement("div",{className:(0,a.A)(n,we)},t)}var ke=n(4070),Se=n(1754);var xe=n(5597);const _e=e=>e.docs.find((t=>t.id===e.mainDocId));const Ce={default:se,localeDropdown:function(e){let{mobile:t,dropdownItemsBefore:n,dropdownItemsAfter:a,...o}=e;const{i18n:{currentLocale:i,locales:c,localeConfigs:d}}=(0,de.A)(),f=(0,ge.o)(),{search:p,hash:m}=(0,s.zy)(),h=[...n,...c.map((e=>{const n=`${`pathname://${f.createUrl({locale:e,fullyQualified:!1})}`}${p}${m}`;return{label:d[e].label,lang:d[e].htmlLang,to:n,target:"_self",autoAddBaseUrl:!1,className:e===i?t?"menu__link--active":"dropdown__link--active":""}})),...a],g=t?(0,u.T)({message:"Languages",id:"theme.navbar.mobileLanguageDropdown.label",description:"The label for the mobile language switcher dropdown"}):d[i].label;return r.createElement(he,(0,l.A)({},o,{mobile:t,label:r.createElement(r.Fragment,null,r.createElement(be,{className:ve}),g),items:h}))},search:function(e){let{mobile:t,className:n}=e;return t?null:r.createElement(Ee,{className:n},r.createElement(ye.A,null))},dropdown:he,html:function(e){let{value:t,className:n,mobile:o=!1,isDropdownItem:i=!1}=e;const l=i?"li":"div";return r.createElement(l,{className:(0,a.A)({navbar__item:!o&&!i,"menu__list-item":o},n),dangerouslySetInnerHTML:{__html:t}})},doc:function(e){let{docId:t,label:n,docsPluginId:a,...o}=e;const{activeDoc:i}=(0,ke.zK)(a),s=(0,Se.QB)(t,a);return null===s?null:r.createElement(se,(0,l.A)({exact:!0},o,{isActive:()=>i?.path===s.path||!!i?.sidebar&&i.sidebar===s.sidebar,label:n??s.id,to:s.path}))},docSidebar:function(e){let{sidebarId:t,label:n,docsPluginId:a,...o}=e;const{activeDoc:i}=(0,ke.zK)(a),s=(0,Se.fW)(t,a).link;if(!s)throw new Error(`DocSidebarNavbarItem: Sidebar with ID "${t}" doesn't have anything to be linked to.`);return r.createElement(se,(0,l.A)({exact:!0},o,{isActive:()=>i?.sidebar===t,label:n??s.label,to:s.path}))},docsVersion:function(e){let{label:t,to:n,docsPluginId:a,...o}=e;const i=(0,Se.Vd)(a)[0],s=t??i.label,u=n??(e=>e.docs.find((t=>t.id===e.mainDocId)))(i).path;return r.createElement(se,(0,l.A)({},o,{label:s,to:u}))},docsVersionDropdown:function(e){let{mobile:t,docsPluginId:n,dropdownActiveClassDisabled:a,dropdownItemsBefore:o,dropdownItemsAfter:i,...c}=e;const{search:d,hash:f}=(0,s.zy)(),p=(0,ke.zK)(n),m=(0,ke.jh)(n),{savePreferredVersionName:h}=(0,xe.g1)(n),g=[...o,...m.map((e=>{const t=p.alternateDocVersions[e.name]??_e(e);return{label:e.label,to:`${t.path}${d}${f}`,isActive:()=>e===p.activeVersion,onClick:()=>h(e.name)}})),...i],b=(0,Se.Vd)(n)[0],v=t&&g.length>1?(0,u.T)({id:"theme.navbar.mobileVersionsDropdown.label",message:"Versions",description:"The label for the navbar versions dropdown on mobile view"}):b.label,y=t&&g.length>1?void 0:_e(b).path;return g.length<=1?r.createElement(se,(0,l.A)({},c,{mobile:t,label:v,to:y,isActive:a?()=>!1:void 0})):r.createElement(he,(0,l.A)({},c,{mobile:t,label:v,to:y,items:g,isActive:a?()=>!1:void 0}))}};function Ae(e){let{type:t,...n}=e;const a=function(e,t){return e&&"default"!==e?e:"items"in t?"dropdown":"default"}(t,n),o=Ce[a];if(!o)throw new Error(`No NavbarItem component found for type "${t}".`);return r.createElement(o,n)}function Te(){const e=(0,P.M)(),t=(0,w.p)().navbar.items;return r.createElement("ul",{className:"menu__list"},t.map(((t,n)=>r.createElement(Ae,(0,l.A)({mobile:!0},t,{onClick:()=>e.toggle(),key:n})))))}function Ne(e){return r.createElement("button",(0,l.A)({},e,{type:"button",className:"clean-btn navbar-sidebar__back"}),r.createElement(u.A,{id:"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel",description:"The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)"},"\u2190 Back to main menu"))}function Oe(){const e=0===(0,w.p)().navbar.items.length,t=$();return r.createElement(r.Fragment,null,!e&&r.createElement(Ne,{onClick:()=>t.hide()}),t.content)}function Le(){const e=(0,P.M)();var t;return void 0===(t=e.shown)&&(t=!0),(0,r.useEffect)((()=>(document.body.style.overflow=t?"hidden":"visible",()=>{document.body.style.overflow="visible"})),[t]),e.shouldRender?r.createElement(z,{header:r.createElement(J,null),primaryMenu:r.createElement(Te,null),secondaryMenu:r.createElement(Oe,null)}):null}const Pe="navbarHideable_m1mJ",Re="navbarHidden_jGov";function Ie(e){return r.createElement("div",(0,l.A)({role:"presentation"},e,{className:(0,a.A)("navbar-sidebar__backdrop",e.className)}))}function Me(e){let{children:t}=e;const{navbar:{hideOnScroll:n,style:o}}=(0,w.p)(),i=(0,P.M)(),{navbarRef:l,isNavbarVisible:s}=function(e){const[t,n]=(0,r.useState)(e),a=(0,r.useRef)(!1),o=(0,r.useRef)(0),i=(0,r.useCallback)((e=>{null!==e&&(o.current=e.getBoundingClientRect().height)}),[]);return(0,R.Mq)(((t,r)=>{let{scrollY:i}=t;if(!e)return;if(i<o.current)return void n(!0);if(a.current)return void(a.current=!1);const l=r?.scrollY,s=document.documentElement.scrollHeight-o.current,u=window.innerHeight;l&&i>=l?n(!1):i+u<s&&n(!0)})),(0,c.$)((t=>{if(!e)return;const r=t.location.hash;if(r?document.getElementById(r.substring(1)):void 0)return a.current=!0,void n(!1);n(!0)})),{navbarRef:i,isNavbarVisible:t}}(n);return r.createElement("nav",{ref:l,"aria-label":(0,u.T)({id:"theme.NavBar.navAriaLabel",message:"Main",description:"The ARIA label for the main navigation"}),className:(0,a.A)("navbar","navbar--fixed-top",n&&[Pe,!s&&Re],{"navbar--dark":"dark"===o,"navbar--primary":"primary"===o,"navbar-sidebar--show":i.shown})},t,r.createElement(Ie,{onClick:i.toggle}),r.createElement(Le,null))}var De=n(440);const Fe="errorBoundaryError_a6uf";function Be(e){return r.createElement("button",(0,l.A)({type:"button"},e),r.createElement(u.A,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again rendering when the React error boundary captures an error"},"Try again"))}function $e(e){let{error:t}=e;const n=(0,De.getErrorCausalChain)(t).map((e=>e.message)).join("\n\nCause:\n");return r.createElement("p",{className:Fe},n)}class ze extends r.Component{componentDidCatch(e,t){throw this.props.onError(e,t)}render(){return this.props.children}}function Ue(e){let{width:t=30,height:n=30,className:a,...o}=e;return r.createElement("svg",(0,l.A)({className:a,width:t,height:n,viewBox:"0 0 30 30","aria-hidden":"true"},o),r.createElement("path",{stroke:"currentColor",strokeLinecap:"round",strokeMiterlimit:"10",strokeWidth:"2",d:"M4 7h22M4 15h22M4 23h22"}))}function je(){const{toggle:e,shown:t}=(0,P.M)();return r.createElement("button",{onClick:e,"aria-label":(0,u.T)({id:"theme.docs.sidebar.toggleSidebarButtonAriaLabel",message:"Toggle navigation bar",description:"The ARIA label for hamburger menu button of mobile navigation"}),"aria-expanded":t,className:"navbar__toggle clean-btn",type:"button"},r.createElement(Ue,null))}const He="colorModeToggle_DEke";function qe(e){let{items:t}=e;return r.createElement(r.Fragment,null,t.map(((e,t)=>r.createElement(ze,{key:t,onError:t=>new Error(`A theme navbar item failed to render.\nPlease double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:\n${JSON.stringify(e,null,2)}`,{cause:t})},r.createElement(Ae,e)))))}function Ve(e){let{left:t,right:n}=e;return r.createElement("div",{className:"navbar__inner"},r.createElement("div",{className:"navbar__items"},t),r.createElement("div",{className:"navbar__items navbar__items--right"},n))}function Ge(){const e=(0,P.M)(),t=(0,w.p)().navbar.items,[n,a]=function(e){function t(e){return"left"===(e.position??"right")}return[e.filter(t),e.filter((e=>!t(e)))]}(t),o=t.find((e=>"search"===e.type));return r.createElement(Ve,{left:r.createElement(r.Fragment,null,!e.disabled&&r.createElement(je,null),r.createElement(X,null),r.createElement(qe,{items:n})),right:r.createElement(r.Fragment,null,r.createElement(qe,{items:a}),r.createElement(K,{className:He}),!o&&r.createElement(Ee,null,r.createElement(ye.A,null)))})}function We(){return r.createElement(Me,null,r.createElement(Ge,null))}function Ye(e){let{item:t}=e;const{to:n,href:a,label:o,prependBaseUrlToHref:i,...s}=t,u=(0,te.A)(n),c=(0,te.A)(a,{forcePrependBaseUrl:!0});return r.createElement(ee.A,(0,l.A)({className:"footer__link-item"},a?{href:i?c:a}:{to:u},s),o,a&&!(0,ne.A)(a)&&r.createElement(ae.A,null))}function Ke(e){let{item:t}=e;return t.html?r.createElement("li",{className:"footer__item",dangerouslySetInnerHTML:{__html:t.html}}):r.createElement("li",{key:t.href??t.to,className:"footer__item"},r.createElement(Ye,{item:t}))}function Qe(e){let{column:t}=e;return r.createElement("div",{className:"col footer__col"},r.createElement("div",{className:"footer__title"},t.title),r.createElement("ul",{className:"footer__items clean-list"},t.items.map(((e,t)=>r.createElement(Ke,{key:t,item:e})))))}function Xe(e){let{columns:t}=e;return r.createElement("div",{className:"row footer__links"},t.map(((e,t)=>r.createElement(Qe,{key:t,column:e}))))}function Ze(){return r.createElement("span",{className:"footer__link-separator"},"\xb7")}function Je(e){let{item:t}=e;return t.html?r.createElement("span",{className:"footer__link-item",dangerouslySetInnerHTML:{__html:t.html}}):r.createElement(Ye,{item:t})}function et(e){let{links:t}=e;return r.createElement("div",{className:"footer__links text--center"},r.createElement("div",{className:"footer__links"},t.map(((e,n)=>r.createElement(r.Fragment,{key:n},r.createElement(Je,{item:e}),t.length!==n+1&&r.createElement(Ze,null))))))}function tt(e){let{links:t}=e;return function(e){return"title"in e[0]}(t)?r.createElement(Xe,{columns:t}):r.createElement(et,{links:t})}var nt=n(1653);const rt="footerLogoLink_BH7S";function at(e){let{logo:t}=e;const{withBaseUrl:n}=(0,te.h)(),o={light:n(t.src),dark:n(t.srcDark??t.src)};return r.createElement(nt.A,{className:(0,a.A)("footer__logo",t.className),alt:t.alt,sources:o,width:t.width,height:t.height,style:t.style})}function ot(e){let{logo:t}=e;return t.href?r.createElement(ee.A,{href:t.href,className:rt,target:t.target},r.createElement(at,{logo:t})):r.createElement(at,{logo:t})}function it(e){let{copyright:t}=e;return r.createElement("div",{className:"footer__copyright",dangerouslySetInnerHTML:{__html:t}})}function lt(e){let{style:t,links:n,logo:o,copyright:i}=e;return r.createElement("footer",{className:(0,a.A)("footer",{"footer--dark":"dark"===t})},r.createElement("div",{className:"container container-fluid"},n,(o||i)&&r.createElement("div",{className:"footer__bottom text--center"},o&&r.createElement("div",{className:"margin-bottom--sm"},o),i)))}function st(){const{footer:e}=(0,w.p)();if(!e)return null;const{copyright:t,links:n,logo:a,style:o}=e;return r.createElement(lt,{style:o,links:n&&n.length>0&&r.createElement(tt,{links:n}),logo:a&&r.createElement(ot,{logo:a}),copyright:t&&r.createElement(it,{copyright:t})})}const ut=r.memo(st),ct=(0,I.fM)([U.a,E.oq,R.Tv,xe.VQ,i.Jx,function(e){let{children:t}=e;return r.createElement(M.y_,null,r.createElement(P.e,null,r.createElement(F,null,t)))}]);function dt(e){let{children:t}=e;return r.createElement(ct,null,t)}function ft(e){let{error:t,tryAgain:n}=e;return r.createElement("main",{className:"container margin-vert--xl"},r.createElement("div",{className:"row"},r.createElement("div",{className:"col col--6 col--offset-3"},r.createElement("h1",{className:"hero__title"},r.createElement(u.A,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed"},"This page crashed.")),r.createElement("div",{className:"margin-vert--lg"},r.createElement(Be,{onClick:n,className:"button button--primary shadow--lw"})),r.createElement("hr",null),r.createElement("div",{className:"margin-vert--md"},r.createElement($e,{error:t})))))}const pt="mainWrapper_z2l0";function mt(e){const{children:t,noFooter:n,wrapperClassName:l,title:s,description:u}=e;return(0,b.J)(),r.createElement(dt,null,r.createElement(i.be,{title:s,description:u}),r.createElement(y,null),r.createElement(L,null),r.createElement(We,null),r.createElement("div",{id:d,className:(0,a.A)(g.G.wrapper.main,pt,l)},r.createElement(o.A,{fallback:e=>r.createElement(ft,e)},t)),!n&&r.createElement(ut,null))}},3465:(e,t,n)=>{"use strict";n.d(t,{A:()=>d});var r=n(8168),a=n(6540),o=n(5489),i=n(6025),l=n(4586),s=n(6342),u=n(1653);function c(e){let{logo:t,alt:n,imageClassName:r}=e;const o={light:(0,i.A)(t.src),dark:(0,i.A)(t.srcDark||t.src)},l=a.createElement(u.A,{className:t.className,sources:o,height:t.height,width:t.width,alt:n,style:t.style});return r?a.createElement("div",{className:r},l):l}function d(e){const{siteConfig:{title:t}}=(0,l.A)(),{navbar:{title:n,logo:u}}=(0,s.p)(),{imageClassName:d,titleClassName:f,...p}=e,m=(0,i.A)(u?.href||"/"),h=n?"":t,g=u?.alt??h;return a.createElement(o.A,(0,r.A)({to:m},p,u?.target&&{target:u.target}),u&&a.createElement(c,{logo:u,alt:g,imageClassName:d}),null!=n&&a.createElement("b",{className:f},n))}},1463:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(5260);function o(e){let{locale:t,version:n,tag:o}=e;const i=t;return r.createElement(a.A,null,t&&r.createElement("meta",{name:"docusaurus_locale",content:t}),n&&r.createElement("meta",{name:"docusaurus_version",content:n}),o&&r.createElement("meta",{name:"docusaurus_tag",content:o}),i&&r.createElement("meta",{name:"docsearch:language",content:i}),n&&r.createElement("meta",{name:"docsearch:version",content:n}),o&&r.createElement("meta",{name:"docsearch:docusaurus_tag",content:o}))}},1653:(e,t,n)=>{"use strict";n.d(t,{A:()=>u});var r=n(8168),a=n(6540),o=n(53),i=n(2303),l=n(5293);const s={themedImage:"themedImage_ToTc","themedImage--light":"themedImage--light_HNdA","themedImage--dark":"themedImage--dark_i4oU"};function u(e){const t=(0,i.A)(),{colorMode:n}=(0,l.G)(),{sources:u,className:c,alt:d,...f}=e,p=t?"dark"===n?["dark"]:["light"]:["light","dark"];return a.createElement(a.Fragment,null,p.map((e=>a.createElement("img",(0,r.A)({key:e,src:u[e],alt:d,className:(0,o.A)(s.themedImage,s[`themedImage--${e}`],c)},f)))))}},1422:(e,t,n)=>{"use strict";n.d(t,{N:()=>h,u:()=>i});var r=n(8168),a=n(6540),o=n(8193);function i(e){let{initialState:t}=e;const[n,r]=(0,a.useState)(t??!1),o=(0,a.useCallback)((()=>{r((e=>!e))}),[]);return{collapsed:n,setCollapsed:r,toggleCollapsed:o}}const l={display:"none",overflow:"hidden",height:"0px"},s={display:"block",overflow:"visible",height:"auto"};function u(e,t){const n=t?l:s;e.style.display=n.display,e.style.overflow=n.overflow,e.style.height=n.height}function c(e){if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return 0;const t=e/36;return Math.round(10*(4+15*t**.25+t/5))}function d(e){let{collapsibleRef:t,collapsed:n,animation:r}=e;const o=(0,a.useRef)(!1);(0,a.useEffect)((()=>{const e=t.current;function a(){const t=function(){const t=e.scrollHeight;return{transition:`height ${r?.duration??c(t)}ms ${r?.easing??"ease-in-out"}`,height:`${t}px`}}();e.style.transition=t.transition,e.style.height=t.height}if(!o.current)return u(e,n),void(o.current=!0);return e.style.willChange="height",function(){const t=requestAnimationFrame((()=>{n?(a(),requestAnimationFrame((()=>{e.style.height=l.height,e.style.overflow=l.overflow}))):(e.style.display="block",requestAnimationFrame((()=>{a()})))}));return()=>cancelAnimationFrame(t)}()}),[t,n,r])}function f(e){if(!o.A.canUseDOM)return e?l:s}function p(e){let{as:t="div",collapsed:n,children:r,animation:o,onCollapseTransitionEnd:i,className:l,disableSSRStyle:s}=e;const c=(0,a.useRef)(null);return d({collapsibleRef:c,collapsed:n,animation:o}),a.createElement(t,{ref:c,style:s?void 0:f(n),onTransitionEnd:e=>{"height"===e.propertyName&&(u(c.current,n),i?.(n))},className:l},r)}function m(e){let{collapsed:t,...n}=e;const[o,i]=(0,a.useState)(!t),[l,s]=(0,a.useState)(t);return(0,a.useLayoutEffect)((()=>{t||i(!0)}),[t]),(0,a.useLayoutEffect)((()=>{o&&s(t)}),[o,t]),o?a.createElement(p,(0,r.A)({},n,{collapsed:l})):null}function h(e){let{lazy:t,...n}=e;const r=t?m:p;return a.createElement(r,n)}},5041:(e,t,n)=>{"use strict";n.d(t,{Mj:()=>m,oq:()=>p});var r=n(6540),a=n(2303),o=n(9466),i=n(9532),l=n(6342);const s=(0,o.Wf)("docusaurus.announcement.dismiss"),u=(0,o.Wf)("docusaurus.announcement.id"),c=()=>"true"===s.get(),d=e=>s.set(String(e)),f=r.createContext(null);function p(e){let{children:t}=e;const n=function(){const{announcementBar:e}=(0,l.p)(),t=(0,a.A)(),[n,o]=(0,r.useState)((()=>!!t&&c()));(0,r.useEffect)((()=>{o(c())}),[]);const i=(0,r.useCallback)((()=>{d(!0),o(!0)}),[]);return(0,r.useEffect)((()=>{if(!e)return;const{id:t}=e;let n=u.get();"annoucement-bar"===n&&(n="announcement-bar");const r=t!==n;u.set(t),r&&d(!1),!r&&c()||o(!1)}),[e]),(0,r.useMemo)((()=>({isActive:!!e&&!n,close:i})),[e,n,i])}();return r.createElement(f.Provider,{value:n},t)}function m(){const e=(0,r.useContext)(f);if(!e)throw new i.dV("AnnouncementBarProvider");return e}},5293:(e,t,n)=>{"use strict";n.d(t,{G:()=>g,a:()=>h});var r=n(6540),a=n(8193),o=n(9532),i=n(9466),l=n(6342);const s=r.createContext(void 0),u="theme",c=(0,i.Wf)(u),d="light",f="dark",p=e=>e===f?f:d;function m(){const{colorMode:{defaultMode:e,disableSwitch:t,respectPrefersColorScheme:n}}=(0,l.p)(),[o,i]=(0,r.useState)((e=>a.A.canUseDOM?p(document.documentElement.getAttribute("data-theme")):p(e))(e));(0,r.useEffect)((()=>{t&&c.del()}),[t]);const s=(0,r.useCallback)((function(t,r){void 0===r&&(r={});const{persist:a=!0}=r;t?(i(t),a&&(e=>{c.set(p(e))})(t)):(i(n?window.matchMedia("(prefers-color-scheme: dark)").matches?f:d:e),c.del())}),[n,e]);(0,r.useEffect)((()=>{document.documentElement.setAttribute("data-theme",p(o))}),[o]),(0,r.useEffect)((()=>{if(t)return;const e=e=>{if(e.key!==u)return;const t=c.get();null!==t&&s(p(t))};return window.addEventListener("storage",e),()=>window.removeEventListener("storage",e)}),[t,s]);const m=(0,r.useRef)(!1);return(0,r.useEffect)((()=>{if(t&&!n)return;const e=window.matchMedia("(prefers-color-scheme: dark)"),r=()=>{window.matchMedia("print").matches||m.current?m.current=window.matchMedia("print").matches:s(null)};return e.addListener(r),()=>e.removeListener(r)}),[s,t,n]),(0,r.useMemo)((()=>({colorMode:o,setColorMode:s,get isDarkTheme(){return o===f},setLightTheme(){s(d)},setDarkTheme(){s(f)}})),[o,s])}function h(e){let{children:t}=e;const n=m();return r.createElement(s.Provider,{value:n},t)}function g(){const e=(0,r.useContext)(s);if(null==e)throw new o.dV("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},5597:(e,t,n)=>{"use strict";n.d(t,{VQ:()=>b,g1:()=>y});var r=n(6540),a=n(4070),o=n(7065),i=n(6342),l=n(1754),s=n(9532),u=n(9466);const c=e=>`docs-preferred-version-${e}`,d=(e,t,n)=>{(0,u.Wf)(c(e),{persistence:t}).set(n)},f=(e,t)=>(0,u.Wf)(c(e),{persistence:t}).get(),p=(e,t)=>{(0,u.Wf)(c(e),{persistence:t}).del()};const m=r.createContext(null);function h(){const e=(0,a.Gy)(),t=(0,i.p)().docs.versionPersistence,n=(0,r.useMemo)((()=>Object.keys(e)),[e]),[o,l]=(0,r.useState)((()=>(e=>Object.fromEntries(e.map((e=>[e,{preferredVersionName:null}]))))(n)));(0,r.useEffect)((()=>{l(function(e){let{pluginIds:t,versionPersistence:n,allDocsData:r}=e;function a(e){const t=f(e,n);return r[e].versions.some((e=>e.name===t))?{preferredVersionName:t}:(p(e,n),{preferredVersionName:null})}return Object.fromEntries(t.map((e=>[e,a(e)])))}({allDocsData:e,versionPersistence:t,pluginIds:n}))}),[e,t,n]);return[o,(0,r.useMemo)((()=>({savePreferredVersion:function(e,n){d(e,t,n),l((t=>({...t,[e]:{preferredVersionName:n}})))}})),[t])]}function g(e){let{children:t}=e;const n=h();return r.createElement(m.Provider,{value:n},t)}function b(e){let{children:t}=e;return l.C5?r.createElement(g,null,t):r.createElement(r.Fragment,null,t)}function v(){const e=(0,r.useContext)(m);if(!e)throw new s.dV("DocsPreferredVersionContextProvider");return e}function y(e){void 0===e&&(e=o.W);const t=(0,a.ht)(e),[n,i]=v(),{preferredVersionName:l}=n[e];return{preferredVersion:t.versions.find((e=>e.name===l))??null,savePreferredVersionName:(0,r.useCallback)((t=>{i.savePreferredVersion(e,t)}),[i,e])}}},6588:(e,t,n)=>{"use strict";n.d(t,{V:()=>l,t:()=>s});var r=n(6540),a=n(9532);const o=Symbol("EmptyContext"),i=r.createContext(o);function l(e){let{children:t,name:n,items:a}=e;const o=(0,r.useMemo)((()=>n&&a?{name:n,items:a}:null),[n,a]);return r.createElement(i.Provider,{value:o},t)}function s(){const e=(0,r.useContext)(i);if(e===o)throw new a.dV("DocsSidebarProvider");return e}},9876:(e,t,n)=>{"use strict";n.d(t,{e:()=>f,M:()=>p});var r=n(6540),a=n(5600),o=n(4581),i=n(6347),l=(n(9888),n(9532));function s(e){!function(e){const t=(0,i.W6)(),n=(0,l._q)(e);(0,r.useEffect)((()=>t.block(((e,t)=>n(e,t)))),[t,n])}(((t,n)=>{if("POP"===n)return e(t,n)}))}var u=n(6342);const c=r.createContext(void 0);function d(){const e=function(){const e=(0,a.YL)(),{items:t}=(0,u.p)().navbar;return 0===t.length&&!e.component}(),t=(0,o.l)(),n=!e&&"mobile"===t,[i,l]=(0,r.useState)(!1);s((()=>{if(i)return l(!1),!1}));const c=(0,r.useCallback)((()=>{l((e=>!e))}),[]);return(0,r.useEffect)((()=>{"desktop"===t&&l(!1)}),[t]),(0,r.useMemo)((()=>({disabled:e,shouldRender:n,toggle:c,shown:i})),[e,n,c,i])}function f(e){let{children:t}=e;const n=d();return r.createElement(c.Provider,{value:n},t)}function p(){const e=r.useContext(c);if(void 0===e)throw new l.dV("NavbarMobileSidebarProvider");return e}},5600:(e,t,n)=>{"use strict";n.d(t,{GX:()=>s,YL:()=>l,y_:()=>i});var r=n(6540),a=n(9532);const o=r.createContext(null);function i(e){let{children:t}=e;const n=(0,r.useState)({component:null,props:null});return r.createElement(o.Provider,{value:n},t)}function l(){const e=(0,r.useContext)(o);if(!e)throw new a.dV("NavbarSecondaryMenuContentProvider");return e[0]}function s(e){let{component:t,props:n}=e;const i=(0,r.useContext)(o);if(!i)throw new a.dV("NavbarSecondaryMenuContentProvider");const[,l]=i,s=(0,a.Be)(n);return(0,r.useEffect)((()=>{l({component:t,props:s})}),[l,t,s]),(0,r.useEffect)((()=>()=>l({component:null,props:null})),[l]),null}},4090:(e,t,n)=>{"use strict";n.d(t,{w:()=>a,J:()=>o});var r=n(6540);const a="navigation-with-keyboard";function o(){(0,r.useEffect)((()=>{function e(e){"keydown"===e.type&&"Tab"===e.key&&document.body.classList.add(a),"mousedown"===e.type&&document.body.classList.remove(a)}return document.addEventListener("keydown",e),document.addEventListener("mousedown",e),()=>{document.body.classList.remove(a),document.removeEventListener("keydown",e),document.removeEventListener("mousedown",e)}}),[])}},4581:(e,t,n)=>{"use strict";n.d(t,{l:()=>u});var r=n(6540),a=n(8193);const o="desktop",i="mobile",l="ssr";function s(){return a.A.canUseDOM?window.innerWidth>996?o:i:l}function u(){const[e,t]=(0,r.useState)((()=>s()));return(0,r.useEffect)((()=>{function e(){t(s())}return window.addEventListener("resize",e),()=>{window.removeEventListener("resize",e),clearTimeout(undefined)}}),[]),e}},7559:(e,t,n)=>{"use strict";n.d(t,{G:()=>r});const r={page:{blogListPage:"blog-list-page",blogPostPage:"blog-post-page",blogTagsListPage:"blog-tags-list-page",blogTagPostListPage:"blog-tags-post-list-page",docsDocPage:"docs-doc-page",docsTagsListPage:"docs-tags-list-page",docsTagDocListPage:"docs-tags-doc-list-page",mdxPage:"mdx-page"},wrapper:{main:"main-wrapper",blogPages:"blog-wrapper",docsPages:"docs-wrapper",mdxPages:"mdx-wrapper"},common:{editThisPage:"theme-edit-this-page",lastUpdated:"theme-last-updated",backToTopButton:"theme-back-to-top-button",codeBlock:"theme-code-block",admonition:"theme-admonition",admonitionType:e=>`theme-admonition-${e}`},layout:{},docs:{docVersionBanner:"theme-doc-version-banner",docVersionBadge:"theme-doc-version-badge",docBreadcrumbs:"theme-doc-breadcrumbs",docMarkdown:"theme-doc-markdown",docTocMobile:"theme-doc-toc-mobile",docTocDesktop:"theme-doc-toc-desktop",docFooter:"theme-doc-footer",docFooterTagsRow:"theme-doc-footer-tags-row",docFooterEditMetaRow:"theme-doc-footer-edit-meta-row",docSidebarContainer:"theme-doc-sidebar-container",docSidebarMenu:"theme-doc-sidebar-menu",docSidebarItemCategory:"theme-doc-sidebar-item-category",docSidebarItemLink:"theme-doc-sidebar-item-link",docSidebarItemCategoryLevel:e=>`theme-doc-sidebar-item-category-level-${e}`,docSidebarItemLinkLevel:e=>`theme-doc-sidebar-item-link-level-${e}`},blog:{}}},1754:(e,t,n)=>{"use strict";n.d(t,{_o:()=>f,w8:()=>m,C5:()=>d,mz:()=>w,Vd:()=>b,QB:()=>y,fW:()=>v,OF:()=>g});var r=n(6540),a=n(6347),o=n(2831),i=n(4070),l=n(5597),s=n(6588);function u(e){return Array.from(new Set(e))}var c=n(9169);const d=!!i.Gy;function f(e){if(e.href)return e.href;for(const t of e.items){if("link"===t.type)return t.href;if("category"===t.type){const e=f(t);if(e)return e}}}const p=(e,t)=>void 0!==e&&(0,c.ys)(e,t);function m(e,t){return"link"===e.type?p(e.href,t):"category"===e.type&&(p(e.href,t)||((e,t)=>e.some((e=>m(e,t))))(e.items,t))}function h(e){let{sidebarItems:t,pathname:n,onlyCategories:r=!1}=e;const a=[];return function e(t){for(const o of t)if("category"===o.type&&((0,c.ys)(o.href,n)||e(o.items))||"link"===o.type&&(0,c.ys)(o.href,n)){return r&&"category"!==o.type||a.unshift(o),!0}return!1}(t),a}function g(){const e=(0,s.t)(),{pathname:t}=(0,a.zy)(),n=(0,i.vT)()?.pluginData.breadcrumbs;return!1!==n&&e?h({sidebarItems:e.items,pathname:t}):null}function b(e){const{activeVersion:t}=(0,i.zK)(e),{preferredVersion:n}=(0,l.g1)(e),a=(0,i.r7)(e);return(0,r.useMemo)((()=>u([t,n,a].filter(Boolean))),[t,n,a])}function v(e,t){const n=b(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.sidebars?Object.entries(e.sidebars):[])),r=t.find((t=>t[0]===e));if(!r)throw new Error(`Can't find any sidebar with id "${e}" in version${n.length>1?"s":""} ${n.map((e=>e.name)).join(", ")}".\nAvailable sidebar ids are:\n- ${Object.keys(t).join("\n- ")}`);return r[1]}),[e,n])}function y(e,t){const n=b(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.docs)),r=t.find((t=>t.id===e));if(!r){if(n.flatMap((e=>e.draftIds)).includes(e))return null;throw new Error(`Couldn't find any doc with id "${e}" in version${n.length>1?"s":""} "${n.map((e=>e.name)).join(", ")}".\nAvailable doc ids are:\n- ${u(t.map((e=>e.id))).join("\n- ")}`)}return r}),[e,n])}function w(e){let{route:t,versionMetadata:n}=e;const r=(0,a.zy)(),i=t.routes,l=i.find((e=>(0,a.B6)(r.pathname,e)));if(!l)return null;const s=l.sidebar,u=s?n.docsSidebars[s]:void 0;return{docElement:(0,o.v)(i),sidebarName:s,sidebarItems:u}}},1003:(e,t,n)=>{"use strict";n.d(t,{e3:()=>f,be:()=>c,Jx:()=>p});var r=n(6540),a=n(53),o=n(5260),i=n(3102);function l(){const e=r.useContext(i.o);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}var s=n(6025),u=n(4586);function c(e){let{title:t,description:n,keywords:a,image:i,children:l}=e;const c=function(e){const{siteConfig:t}=(0,u.A)(),{title:n,titleDelimiter:r}=t;return e?.trim().length?`${e.trim()} ${r} ${n}`:n}(t),{withBaseUrl:d}=(0,s.h)(),f=i?d(i,{absolute:!0}):void 0;return r.createElement(o.A,null,t&&r.createElement("title",null,c),t&&r.createElement("meta",{property:"og:title",content:c}),n&&r.createElement("meta",{name:"description",content:n}),n&&r.createElement("meta",{property:"og:description",content:n}),a&&r.createElement("meta",{name:"keywords",content:Array.isArray(a)?a.join(","):a}),f&&r.createElement("meta",{property:"og:image",content:f}),f&&r.createElement("meta",{name:"twitter:image",content:f}),l)}const d=r.createContext(void 0);function f(e){let{className:t,children:n}=e;const i=r.useContext(d),l=(0,a.A)(i,t);return r.createElement(d.Provider,{value:l},r.createElement(o.A,null,r.createElement("html",{className:l})),n)}function p(e){let{children:t}=e;const n=l(),o=`plugin-${n.plugin.name.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,"")}`;const i=`plugin-id-${n.plugin.id}`;return r.createElement(f,{className:(0,a.A)(o,i)},t)}},9532:(e,t,n)=>{"use strict";n.d(t,{Be:()=>s,ZC:()=>i,_q:()=>o,dV:()=>l,fM:()=>u});var r=n(6540);const a=n(8193).A.canUseDOM?r.useLayoutEffect:r.useEffect;function o(e){const t=(0,r.useRef)(e);return a((()=>{t.current=e}),[e]),(0,r.useCallback)((function(){return t.current(...arguments)}),[])}function i(e){const t=(0,r.useRef)();return a((()=>{t.current=e})),t.current}class l extends Error{constructor(e,t){super(),this.name="ReactContextError",this.message=`Hook ${this.stack?.split("\n")[1]?.match(/at (?:\w+\.)?(?<name>\w+)/)?.groups.name??""} is called outside the <${e}>. ${t??""}`}}function s(e){const t=Object.entries(e);return t.sort(((e,t)=>e[0].localeCompare(t[0]))),(0,r.useMemo)((()=>e),t.flat())}function u(e){return t=>{let{children:n}=t;return r.createElement(r.Fragment,null,e.reduceRight(((e,t)=>r.createElement(t,null,e)),n))}}},9169:(e,t,n)=>{"use strict";n.d(t,{Dt:()=>l,ys:()=>i});var r=n(6540),a=n(8328),o=n(4586);function i(e,t){const n=e=>(!e||e.endsWith("/")?e:`${e}/`)?.toLowerCase();return n(e)===n(t)}function l(){const{baseUrl:e}=(0,o.A)().siteConfig;return(0,r.useMemo)((()=>function(e){let{baseUrl:t,routes:n}=e;function r(e){return e.path===t&&!0===e.exact}function a(e){return e.path===t&&!e.exact}return function e(t){if(0===t.length)return;return t.find(r)||e(t.filter(a).flatMap((e=>e.routes??[])))}(n)}({routes:a.A,baseUrl:e})),[e])}},3104:(e,t,n)=>{"use strict";n.d(t,{Mq:()=>d,Tv:()=>s,gk:()=>f});var r=n(6540),a=n(8193),o=n(2303),i=n(9532);const l=r.createContext(void 0);function s(e){let{children:t}=e;const n=function(){const e=(0,r.useRef)(!0);return(0,r.useMemo)((()=>({scrollEventsEnabledRef:e,enableScrollEvents:()=>{e.current=!0},disableScrollEvents:()=>{e.current=!1}})),[])}();return r.createElement(l.Provider,{value:n},t)}function u(){const e=(0,r.useContext)(l);if(null==e)throw new i.dV("ScrollControllerProvider");return e}const c=()=>a.A.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null;function d(e,t){void 0===t&&(t=[]);const{scrollEventsEnabledRef:n}=u(),a=(0,r.useRef)(c()),o=(0,i._q)(e);(0,r.useEffect)((()=>{const e=()=>{if(!n.current)return;const e=c();o(e,a.current),a.current=e},t={passive:!0};return e(),window.addEventListener("scroll",e,t),()=>window.removeEventListener("scroll",e,t)}),[o,n,...t])}function f(){const e=(0,r.useRef)(null),t=(0,o.A)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:n=>{e.current=t?function(e){return window.scrollTo({top:e,behavior:"smooth"}),()=>{}}(n):function(e){let t=null;const n=document.documentElement.scrollTop>e;return function r(){const a=document.documentElement.scrollTop;(n&&a>e||!n&&a<e)&&(t=requestAnimationFrame(r),window.scrollTo(0,Math.floor(.85*(a-e))+e))}(),()=>t&&cancelAnimationFrame(t)}(n)},cancelScroll:()=>e.current?.()}}},2967:(e,t,n)=>{"use strict";n.d(t,{Cy:()=>r,tU:()=>a});n(4586);const r="default";function a(e,t){return`docs-${e}-${t}`}},9466:(e,t,n)=>{"use strict";n.d(t,{Wf:()=>s});n(6540),n(9888);const r="localStorage";function a(e){let{key:t,oldValue:n,newValue:r,storage:a}=e;if(n===r)return;const o=document.createEvent("StorageEvent");o.initStorageEvent("storage",!1,!1,t,n,r,window.location.href,a),window.dispatchEvent(o)}function o(e){if(void 0===e&&(e=r),"undefined"==typeof window)throw new Error("Browser storage is not available on Node.js/Docusaurus SSR process.");if("none"===e)return null;try{return window[e]}catch(n){return t=n,i||(console.warn("Docusaurus browser storage is not available.\nPossible reasons: running Docusaurus in an iframe, in an incognito browser session, or using too strict browser privacy settings.",t),i=!0),null}var t}let i=!1;const l={get:()=>null,set:()=>{},del:()=>{},listen:()=>()=>{}};function s(e,t){if("undefined"==typeof window)return function(e){function t(){throw new Error(`Illegal storage API usage for storage key "${e}".\nDocusaurus storage APIs are not supposed to be called on the server-rendering process.\nPlease only call storage APIs in effects and event handlers.`)}return{get:t,set:t,del:t,listen:t}}(e);const n=o(t?.persistence);return null===n?l:{get:()=>{try{return n.getItem(e)}catch(t){return console.error(`Docusaurus storage error, can't get key=${e}`,t),null}},set:t=>{try{const r=n.getItem(e);n.setItem(e,t),a({key:e,oldValue:r,newValue:t,storage:n})}catch(r){console.error(`Docusaurus storage error, can't set ${e}=${t}`,r)}},del:()=>{try{const t=n.getItem(e);n.removeItem(e),a({key:e,oldValue:t,newValue:null,storage:n})}catch(t){console.error(`Docusaurus storage error, can't delete key=${e}`,t)}},listen:t=>{try{const r=r=>{r.storageArea===n&&r.key===e&&t(r)};return window.addEventListener("storage",r),()=>window.removeEventListener("storage",r)}catch(r){return console.error(`Docusaurus storage error, can't listen for changes of key=${e}`,r),()=>{}}}}}},2131:(e,t,n)=>{"use strict";n.d(t,{o:()=>o});var r=n(4586),a=n(6347);function o(){const{siteConfig:{baseUrl:e,url:t},i18n:{defaultLocale:n,currentLocale:o}}=(0,r.A)(),{pathname:i}=(0,a.zy)(),l=o===n?e:e.replace(`/${o}/`,"/"),s=i.replace(e,"");return{createUrl:function(e){let{locale:r,fullyQualified:a}=e;return`${a?t:""}${function(e){return e===n?`${l}`:`${l}${e}/`}(r)}${s}`}}}},5062:(e,t,n)=>{"use strict";n.d(t,{$:()=>i});var r=n(6540),a=n(6347),o=n(9532);function i(e){const t=(0,a.zy)(),n=(0,o.ZC)(t),i=(0,o._q)(e);(0,r.useEffect)((()=>{n&&t!==n&&i({location:t,previousLocation:n})}),[i,t,n])}},6342:(e,t,n)=>{"use strict";n.d(t,{p:()=>a});var r=n(4586);function a(){return(0,r.A)().siteConfig.themeConfig}},2983:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const{trailingSlash:n,baseUrl:r}=t;if(e.startsWith("#"))return e;if(void 0===n)return e;const[a]=e.split(/[#?]/),o="/"===a||a===r?a:(i=a,n?function(e){return e.endsWith("/")?e:`${e}/`}(i):function(e){return e.endsWith("/")?e.slice(0,-1):e}(i));var i;return e.replace(a,o)}},253:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=void 0,t.getErrorCausalChain=function e(t){return t.cause?[t,...e(t.cause)]:[t]}},440:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=t.applyTrailingSlash=t.blogPostContainerID=void 0,t.blogPostContainerID="post-content";var a=n(2983);Object.defineProperty(t,"applyTrailingSlash",{enumerable:!0,get:function(){return r(a).default}});var o=n(253);Object.defineProperty(t,"getErrorCausalChain",{enumerable:!0,get:function(){return o.getErrorCausalChain}})},53:(e,t,n)=>{"use strict";function r(e){var t,n,a="";if("string"==typeof e||"number"==typeof e)a+=e;else if("object"==typeof e)if(Array.isArray(e))for(t=0;t<e.length;t++)e[t]&&(n=r(e[t]))&&(a&&(a+=" "),a+=n);else for(t in e)e[t]&&(a&&(a+=" "),a+=t);return a}n.d(t,{A:()=>a});const a=function(){for(var e,t,n=0,a="";n<arguments.length;)(e=arguments[n++])&&(t=r(e))&&(a&&(a+=" "),a+=t);return a}},1513:(e,t,n)=>{"use strict";n.d(t,{zR:()=>w,TM:()=>C,yJ:()=>p,sC:()=>T,AO:()=>f});var r=n(8168);function a(e){return"/"===e.charAt(0)}function o(e,t){for(var n=t,r=n+1,a=e.length;r<a;n+=1,r+=1)e[n]=e[r];e.pop()}const i=function(e,t){void 0===t&&(t="");var n,r=e&&e.split("/")||[],i=t&&t.split("/")||[],l=e&&a(e),s=t&&a(t),u=l||s;if(e&&a(e)?i=r:r.length&&(i.pop(),i=i.concat(r)),!i.length)return"/";if(i.length){var c=i[i.length-1];n="."===c||".."===c||""===c}else n=!1;for(var d=0,f=i.length;f>=0;f--){var p=i[f];"."===p?o(i,f):".."===p?(o(i,f),d++):d&&(o(i,f),d--)}if(!u)for(;d--;d)i.unshift("..");!u||""===i[0]||i[0]&&a(i[0])||i.unshift("");var m=i.join("/");return n&&"/"!==m.substr(-1)&&(m+="/"),m};var l=n(1561);function s(e){return"/"===e.charAt(0)?e:"/"+e}function u(e){return"/"===e.charAt(0)?e.substr(1):e}function c(e,t){return function(e,t){return 0===e.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(t.length))}(e,t)?e.substr(t.length):e}function d(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function f(e){var t=e.pathname,n=e.search,r=e.hash,a=t||"/";return n&&"?"!==n&&(a+="?"===n.charAt(0)?n:"?"+n),r&&"#"!==r&&(a+="#"===r.charAt(0)?r:"#"+r),a}function p(e,t,n,a){var o;"string"==typeof e?(o=function(e){var t=e||"/",n="",r="",a=t.indexOf("#");-1!==a&&(r=t.substr(a),t=t.substr(0,a));var o=t.indexOf("?");return-1!==o&&(n=t.substr(o),t=t.substr(0,o)),{pathname:t,search:"?"===n?"":n,hash:"#"===r?"":r}}(e),o.state=t):(void 0===(o=(0,r.A)({},e)).pathname&&(o.pathname=""),o.search?"?"!==o.search.charAt(0)&&(o.search="?"+o.search):o.search="",o.hash?"#"!==o.hash.charAt(0)&&(o.hash="#"+o.hash):o.hash="",void 0!==t&&void 0===o.state&&(o.state=t));try{o.pathname=decodeURI(o.pathname)}catch(l){throw l instanceof URIError?new URIError('Pathname "'+o.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):l}return n&&(o.key=n),a?o.pathname?"/"!==o.pathname.charAt(0)&&(o.pathname=i(o.pathname,a.pathname)):o.pathname=a.pathname:o.pathname||(o.pathname="/"),o}function m(){var e=null;var t=[];return{setPrompt:function(t){return e=t,function(){e===t&&(e=null)}},confirmTransitionTo:function(t,n,r,a){if(null!=e){var o="function"==typeof e?e(t,n):e;"string"==typeof o?"function"==typeof r?r(o,a):a(!0):a(!1!==o)}else a(!0)},appendListener:function(e){var n=!0;function r(){n&&e.apply(void 0,arguments)}return t.push(r),function(){n=!1,t=t.filter((function(e){return e!==r}))}},notifyListeners:function(){for(var e=arguments.length,n=new Array(e),r=0;r<e;r++)n[r]=arguments[r];t.forEach((function(e){return e.apply(void 0,n)}))}}}var h=!("undefined"==typeof window||!window.document||!window.document.createElement);function g(e,t){t(window.confirm(e))}var b="popstate",v="hashchange";function y(){try{return window.history.state||{}}catch(e){return{}}}function w(e){void 0===e&&(e={}),h||(0,l.A)(!1);var t,n=window.history,a=(-1===(t=window.navigator.userAgent).indexOf("Android 2.")&&-1===t.indexOf("Android 4.0")||-1===t.indexOf("Mobile Safari")||-1!==t.indexOf("Chrome")||-1!==t.indexOf("Windows Phone"))&&window.history&&"pushState"in window.history,o=!(-1===window.navigator.userAgent.indexOf("Trident")),i=e,u=i.forceRefresh,w=void 0!==u&&u,E=i.getUserConfirmation,k=void 0===E?g:E,S=i.keyLength,x=void 0===S?6:S,_=e.basename?d(s(e.basename)):"";function C(e){var t=e||{},n=t.key,r=t.state,a=window.location,o=a.pathname+a.search+a.hash;return _&&(o=c(o,_)),p(o,r,n)}function A(){return Math.random().toString(36).substr(2,x)}var T=m();function N(e){(0,r.A)(U,e),U.length=n.length,T.notifyListeners(U.location,U.action)}function O(e){(function(e){return void 0===e.state&&-1===navigator.userAgent.indexOf("CriOS")})(e)||R(C(e.state))}function L(){R(C(y()))}var P=!1;function R(e){if(P)P=!1,N();else{T.confirmTransitionTo(e,"POP",k,(function(t){t?N({action:"POP",location:e}):function(e){var t=U.location,n=M.indexOf(t.key);-1===n&&(n=0);var r=M.indexOf(e.key);-1===r&&(r=0);var a=n-r;a&&(P=!0,F(a))}(e)}))}}var I=C(y()),M=[I.key];function D(e){return _+f(e)}function F(e){n.go(e)}var B=0;function $(e){1===(B+=e)&&1===e?(window.addEventListener(b,O),o&&window.addEventListener(v,L)):0===B&&(window.removeEventListener(b,O),o&&window.removeEventListener(v,L))}var z=!1;var U={length:n.length,action:"POP",location:I,createHref:D,push:function(e,t){var r="PUSH",o=p(e,t,A(),U.location);T.confirmTransitionTo(o,r,k,(function(e){if(e){var t=D(o),i=o.key,l=o.state;if(a)if(n.pushState({key:i,state:l},null,t),w)window.location.href=t;else{var s=M.indexOf(U.location.key),u=M.slice(0,s+1);u.push(o.key),M=u,N({action:r,location:o})}else window.location.href=t}}))},replace:function(e,t){var r="REPLACE",o=p(e,t,A(),U.location);T.confirmTransitionTo(o,r,k,(function(e){if(e){var t=D(o),i=o.key,l=o.state;if(a)if(n.replaceState({key:i,state:l},null,t),w)window.location.replace(t);else{var s=M.indexOf(U.location.key);-1!==s&&(M[s]=o.key),N({action:r,location:o})}else window.location.replace(t)}}))},go:F,goBack:function(){F(-1)},goForward:function(){F(1)},block:function(e){void 0===e&&(e=!1);var t=T.setPrompt(e);return z||($(1),z=!0),function(){return z&&(z=!1,$(-1)),t()}},listen:function(e){var t=T.appendListener(e);return $(1),function(){$(-1),t()}}};return U}var E="hashchange",k={hashbang:{encodePath:function(e){return"!"===e.charAt(0)?e:"!/"+u(e)},decodePath:function(e){return"!"===e.charAt(0)?e.substr(1):e}},noslash:{encodePath:u,decodePath:s},slash:{encodePath:s,decodePath:s}};function S(e){var t=e.indexOf("#");return-1===t?e:e.slice(0,t)}function x(){var e=window.location.href,t=e.indexOf("#");return-1===t?"":e.substring(t+1)}function _(e){window.location.replace(S(window.location.href)+"#"+e)}function C(e){void 0===e&&(e={}),h||(0,l.A)(!1);var t=window.history,n=(window.navigator.userAgent.indexOf("Firefox"),e),a=n.getUserConfirmation,o=void 0===a?g:a,i=n.hashType,u=void 0===i?"slash":i,b=e.basename?d(s(e.basename)):"",v=k[u],y=v.encodePath,w=v.decodePath;function C(){var e=w(x());return b&&(e=c(e,b)),p(e)}var A=m();function T(e){(0,r.A)(z,e),z.length=t.length,A.notifyListeners(z.location,z.action)}var N=!1,O=null;function L(){var e,t,n=x(),r=y(n);if(n!==r)_(r);else{var a=C(),i=z.location;if(!N&&(t=a,(e=i).pathname===t.pathname&&e.search===t.search&&e.hash===t.hash))return;if(O===f(a))return;O=null,function(e){if(N)N=!1,T();else{var t="POP";A.confirmTransitionTo(e,t,o,(function(n){n?T({action:t,location:e}):function(e){var t=z.location,n=M.lastIndexOf(f(t));-1===n&&(n=0);var r=M.lastIndexOf(f(e));-1===r&&(r=0);var a=n-r;a&&(N=!0,D(a))}(e)}))}}(a)}}var P=x(),R=y(P);P!==R&&_(R);var I=C(),M=[f(I)];function D(e){t.go(e)}var F=0;function B(e){1===(F+=e)&&1===e?window.addEventListener(E,L):0===F&&window.removeEventListener(E,L)}var $=!1;var z={length:t.length,action:"POP",location:I,createHref:function(e){var t=document.querySelector("base"),n="";return t&&t.getAttribute("href")&&(n=S(window.location.href)),n+"#"+y(b+f(e))},push:function(e,t){var n="PUSH",r=p(e,void 0,void 0,z.location);A.confirmTransitionTo(r,n,o,(function(e){if(e){var t=f(r),a=y(b+t);if(x()!==a){O=t,function(e){window.location.hash=e}(a);var o=M.lastIndexOf(f(z.location)),i=M.slice(0,o+1);i.push(t),M=i,T({action:n,location:r})}else T()}}))},replace:function(e,t){var n="REPLACE",r=p(e,void 0,void 0,z.location);A.confirmTransitionTo(r,n,o,(function(e){if(e){var t=f(r),a=y(b+t);x()!==a&&(O=t,_(a));var o=M.indexOf(f(z.location));-1!==o&&(M[o]=t),T({action:n,location:r})}}))},go:D,goBack:function(){D(-1)},goForward:function(){D(1)},block:function(e){void 0===e&&(e=!1);var t=A.setPrompt(e);return $||(B(1),$=!0),function(){return $&&($=!1,B(-1)),t()}},listen:function(e){var t=A.appendListener(e);return B(1),function(){B(-1),t()}}};return z}function A(e,t,n){return Math.min(Math.max(e,t),n)}function T(e){void 0===e&&(e={});var t=e,n=t.getUserConfirmation,a=t.initialEntries,o=void 0===a?["/"]:a,i=t.initialIndex,l=void 0===i?0:i,s=t.keyLength,u=void 0===s?6:s,c=m();function d(e){(0,r.A)(w,e),w.length=w.entries.length,c.notifyListeners(w.location,w.action)}function h(){return Math.random().toString(36).substr(2,u)}var g=A(l,0,o.length-1),b=o.map((function(e){return p(e,void 0,"string"==typeof e?h():e.key||h())})),v=f;function y(e){var t=A(w.index+e,0,w.entries.length-1),r=w.entries[t];c.confirmTransitionTo(r,"POP",n,(function(e){e?d({action:"POP",location:r,index:t}):d()}))}var w={length:b.length,action:"POP",location:b[g],index:g,entries:b,createHref:v,push:function(e,t){var r="PUSH",a=p(e,t,h(),w.location);c.confirmTransitionTo(a,r,n,(function(e){if(e){var t=w.index+1,n=w.entries.slice(0);n.length>t?n.splice(t,n.length-t,a):n.push(a),d({action:r,location:a,index:t,entries:n})}}))},replace:function(e,t){var r="REPLACE",a=p(e,t,h(),w.location);c.confirmTransitionTo(a,r,n,(function(e){e&&(w.entries[w.index]=a,d({action:r,location:a}))}))},go:y,goBack:function(){y(-1)},goForward:function(){y(1)},canGo:function(e){var t=w.index+e;return t>=0&&t<w.entries.length},block:function(e){return void 0===e&&(e=!1),c.setPrompt(e)},listen:function(e){return c.appendListener(e)}};return w}},4146:(e,t,n)=>{"use strict";var r=n(4363),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},o={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},l={};function s(e){return r.isMemo(e)?i:l[e.$$typeof]||a}l[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},l[r.Memo]=i;var u=Object.defineProperty,c=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,f=Object.getOwnPropertyDescriptor,p=Object.getPrototypeOf,m=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(m){var a=p(n);a&&a!==m&&e(t,a,r)}var i=c(n);d&&(i=i.concat(d(n)));for(var l=s(t),h=s(n),g=0;g<i.length;++g){var b=i[g];if(!(o[b]||r&&r[b]||h&&h[b]||l&&l[b])){var v=f(n,b);try{u(t,b,v)}catch(y){}}}}return t}},311:e=>{"use strict";e.exports=function(e,t,n,r,a,o,i,l){if(!e){var s;if(void 0===t)s=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var u=[n,r,a,o,i,l],c=0;(s=new Error(t.replace(/%s/g,(function(){return u[c++]})))).name="Invariant Violation"}throw s.framesToPop=1,s}}},4634:e=>{e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},119:(e,t,n)=>{"use strict";n.r(t)},1043:(e,t,n)=>{"use strict";n.r(t)},5947:function(e,t,n){var r,a;r=function(){var e,t,n={version:"0.2.0"},r=n.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>'};function a(e,t,n){return e<t?t:e>n?n:e}function o(e){return 100*(-1+e)}function i(e,t,n){var a;return(a="translate3d"===r.positionUsing?{transform:"translate3d("+o(e)+"%,0,0)"}:"translate"===r.positionUsing?{transform:"translate("+o(e)+"%,0)"}:{"margin-left":o(e)+"%"}).transition="all "+t+"ms "+n,a}n.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(r[t]=n);return this},n.status=null,n.set=function(e){var t=n.isStarted();e=a(e,r.minimum,1),n.status=1===e?null:e;var o=n.render(!t),u=o.querySelector(r.barSelector),c=r.speed,d=r.easing;return o.offsetWidth,l((function(t){""===r.positionUsing&&(r.positionUsing=n.getPositioningCSS()),s(u,i(e,c,d)),1===e?(s(o,{transition:"none",opacity:1}),o.offsetWidth,setTimeout((function(){s(o,{transition:"all "+c+"ms linear",opacity:0}),setTimeout((function(){n.remove(),t()}),c)}),c)):setTimeout(t,c)})),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var e=function(){setTimeout((function(){n.status&&(n.trickle(),e())}),r.trickleSpeed)};return r.trickle&&e(),this},n.done=function(e){return e||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(e){var t=n.status;return t?("number"!=typeof e&&(e=(1-t)*a(Math.random()*t,.1,.95)),t=a(t+e,0,.994),n.set(t)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},e=0,t=0,n.promise=function(r){return r&&"resolved"!==r.state()?(0===t&&n.start(),e++,t++,r.always((function(){0==--t?(e=0,n.done()):n.set((e-t)/e)})),this):this},n.render=function(e){if(n.isRendered())return document.getElementById("nprogress");c(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=r.template;var a,i=t.querySelector(r.barSelector),l=e?"-100":o(n.status||0),u=document.querySelector(r.parent);return s(i,{transition:"all 0 linear",transform:"translate3d("+l+"%,0,0)"}),r.showSpinner||(a=t.querySelector(r.spinnerSelector))&&p(a),u!=document.body&&c(u,"nprogress-custom-parent"),u.appendChild(t),t},n.remove=function(){d(document.documentElement,"nprogress-busy"),d(document.querySelector(r.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&p(e)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var l=function(){var e=[];function t(){var n=e.shift();n&&n(t)}return function(n){e.push(n),1==e.length&&t()}}(),s=function(){var e=["Webkit","O","Moz","ms"],t={};function n(e){return e.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,(function(e,t){return t.toUpperCase()}))}function r(t){var n=document.body.style;if(t in n)return t;for(var r,a=e.length,o=t.charAt(0).toUpperCase()+t.slice(1);a--;)if((r=e[a]+o)in n)return r;return t}function a(e){return e=n(e),t[e]||(t[e]=r(e))}function o(e,t,n){t=a(t),e.style[t]=n}return function(e,t){var n,r,a=arguments;if(2==a.length)for(n in t)void 0!==(r=t[n])&&t.hasOwnProperty(n)&&o(e,n,r);else o(e,a[1],a[2])}}();function u(e,t){return("string"==typeof e?e:f(e)).indexOf(" "+t+" ")>=0}function c(e,t){var n=f(e),r=n+t;u(n,t)||(e.className=r.substring(1))}function d(e,t){var n,r=f(e);u(e,t)&&(n=r.replace(" "+t+" "," "),e.className=n.substring(1,n.length-1))}function f(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function p(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return n},void 0===(a="function"==typeof r?r.call(t,n,t,e):r)||(e.exports=a)},5228:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;function a(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(a){return!1}}()?Object.assign:function(e,o){for(var i,l,s=a(e),u=1;u<arguments.length;u++){for(var c in i=Object(arguments[u]))n.call(i,c)&&(s[c]=i[c]);if(t){l=t(i);for(var d=0;d<l.length;d++)r.call(i,l[d])&&(s[l[d]]=i[l[d]])}}return s}},1258:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=function(){var e=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,n={},r={util:{encode:function e(t){return t instanceof a?new a(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function e(t,n){var a,o;switch(n=n||{},r.util.type(t)){case"Object":if(o=r.util.objId(t),n[o])return n[o];for(var i in a={},n[o]=a,t)t.hasOwnProperty(i)&&(a[i]=e(t[i],n));return a;case"Array":return o=r.util.objId(t),n[o]?n[o]:(a=[],n[o]=a,t.forEach((function(t,r){a[r]=e(t,n)})),a);default:return t}},getLanguage:function(t){for(;t;){var n=e.exec(t.className);if(n)return n[1].toLowerCase();t=t.parentElement}return"none"},setLanguage:function(t,n){t.className=t.className.replace(RegExp(e,"gi"),""),t.classList.add("language-"+n)},isActive:function(e,t,n){for(var r="no-"+t;e;){var a=e.classList;if(a.contains(t))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!n}},languages:{plain:n,plaintext:n,text:n,txt:n,extend:function(e,t){var n=r.util.clone(r.languages[e]);for(var a in t)n[a]=t[a];return n},insertBefore:function(e,t,n,a){var o=(a=a||r.languages)[e],i={};for(var l in o)if(o.hasOwnProperty(l)){if(l==t)for(var s in n)n.hasOwnProperty(s)&&(i[s]=n[s]);n.hasOwnProperty(l)||(i[l]=o[l])}var u=a[e];return a[e]=i,r.languages.DFS(r.languages,(function(t,n){n===u&&t!=e&&(this[t]=i)})),i},DFS:function e(t,n,a,o){o=o||{};var i=r.util.objId;for(var l in t)if(t.hasOwnProperty(l)){n.call(t,l,t[l],a||l);var s=t[l],u=r.util.type(s);"Object"!==u||o[i(s)]?"Array"!==u||o[i(s)]||(o[i(s)]=!0,e(s,n,l,o)):(o[i(s)]=!0,e(s,n,null,o))}}},plugins:{},highlight:function(e,t,n){var o={code:e,grammar:t,language:n};return r.hooks.run("before-tokenize",o),o.tokens=r.tokenize(o.code,o.grammar),r.hooks.run("after-tokenize",o),a.stringify(r.util.encode(o.tokens),o.language)},tokenize:function(e,t){var n=t.rest;if(n){for(var r in n)t[r]=n[r];delete t.rest}var a=new l;return s(a,a.head,e),i(e,a,t,a.head,0),function(e){var t=[],n=e.head.next;for(;n!==e.tail;)t.push(n.value),n=n.next;return t}(a)},hooks:{all:{},add:function(e,t){var n=r.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=r.hooks.all[e];if(n&&n.length)for(var a,o=0;a=n[o++];)a(t)}},Token:a};function a(e,t,n,r){this.type=e,this.content=t,this.alias=n,this.length=0|(r||"").length}function o(e,t,n,r){e.lastIndex=t;var a=e.exec(n);if(a&&r&&a[1]){var o=a[1].length;a.index+=o,a[0]=a[0].slice(o)}return a}function i(e,t,n,l,c,d){for(var f in n)if(n.hasOwnProperty(f)&&n[f]){var p=n[f];p=Array.isArray(p)?p:[p];for(var m=0;m<p.length;++m){if(d&&d.cause==f+","+m)return;var h=p[m],g=h.inside,b=!!h.lookbehind,v=!!h.greedy,y=h.alias;if(v&&!h.pattern.global){var w=h.pattern.toString().match(/[imsuy]*$/)[0];h.pattern=RegExp(h.pattern.source,w+"g")}for(var E=h.pattern||h,k=l.next,S=c;k!==t.tail&&!(d&&S>=d.reach);S+=k.value.length,k=k.next){var x=k.value;if(t.length>e.length)return;if(!(x instanceof a)){var _,C=1;if(v){if(!(_=o(E,S,e,b))||_.index>=e.length)break;var A=_.index,T=_.index+_[0].length,N=S;for(N+=k.value.length;A>=N;)N+=(k=k.next).value.length;if(S=N-=k.value.length,k.value instanceof a)continue;for(var O=k;O!==t.tail&&(N<T||"string"==typeof O.value);O=O.next)C++,N+=O.value.length;C--,x=e.slice(S,N),_.index-=S}else if(!(_=o(E,0,x,b)))continue;A=_.index;var L=_[0],P=x.slice(0,A),R=x.slice(A+L.length),I=S+x.length;d&&I>d.reach&&(d.reach=I);var M=k.prev;if(P&&(M=s(t,M,P),S+=P.length),u(t,M,C),k=s(t,M,new a(f,g?r.tokenize(L,g):L,y,L)),R&&s(t,k,R),C>1){var D={cause:f+","+m,reach:I};i(e,t,n,k.prev,S,D),d&&D.reach>d.reach&&(d.reach=D.reach)}}}}}}function l(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function s(e,t,n){var r=t.next,a={value:n,prev:t,next:r};return t.next=a,r.prev=a,e.length++,a}function u(e,t,n){for(var r=t.next,a=0;a<n&&r!==e.tail;a++)r=r.next;t.next=r,r.prev=t,e.length-=a}return a.stringify=function e(t,n){if("string"==typeof t)return t;if(Array.isArray(t)){var a="";return t.forEach((function(t){a+=e(t,n)})),a}var o={type:t.type,content:e(t.content,n),tag:"span",classes:["token",t.type],attributes:{},language:n},i=t.alias;i&&(Array.isArray(i)?Array.prototype.push.apply(o.classes,i):o.classes.push(i)),r.hooks.run("wrap",o);var l="";for(var s in o.attributes)l+=" "+s+'="'+(o.attributes[s]||"").replace(/"/g,""")+'"';return"<"+o.tag+' class="'+o.classes.join(" ")+'"'+l+">"+o.content+"</"+o.tag+">"},r}(),a=r;r.default=r,a.languages.markup={comment:{pattern:/<!--(?:(?!<!--)[\s\S])*?-->/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},a.languages.markup.tag.inside["attr-value"].inside.entity=a.languages.markup.entity,a.languages.markup.doctype.inside["internal-subset"].inside=a.languages.markup,a.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(a.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:a.languages[t]},n.cdata=/^<!\[CDATA\[|\]\]>$/i;var r={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:n}};r["language-"+t]={pattern:/[\s\S]+/,inside:a.languages[t]};var o={};o[e]={pattern:RegExp(/(<__[^>]*>)(?:<!\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]>|(?!<!\[CDATA\[)[\s\S])*?(?=<\/__>)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:r},a.languages.insertBefore("markup","cdata",o)}}),Object.defineProperty(a.languages.markup.tag,"addAttribute",{value:function(e,t){a.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:a.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),a.languages.html=a.languages.markup,a.languages.mathml=a.languages.markup,a.languages.svg=a.languages.markup,a.languages.xml=a.languages.extend("markup",{}),a.languages.ssml=a.languages.xml,a.languages.atom=a.languages.xml,a.languages.rss=a.languages.xml,function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},r={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:r},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:r},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:r.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:r.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var a=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],o=r.variable[1].inside,i=0;i<a.length;i++)o[a[i]]=e.languages.bash[a[i]];e.languages.shell=e.languages.bash}(a),a.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},a.languages.c=a.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),a.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),a.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},a.languages.c.string],char:a.languages.c.char,comment:a.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:a.languages.c}}}}),a.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete a.languages.c.boolean,function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n=/\b(?!<keyword>)\w+(?:\s*\.\s*\w+)*\b/.source.replace(/<keyword>/g,(function(){return t.source}));e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!<keyword>)\w+/.source.replace(/<keyword>/g,(function(){return t.source}))),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/<mod-name>(?:\s*:\s*<mod-name>)?|:\s*<mod-name>/.source.replace(/<mod-name>/g,(function(){return n}))+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])}(a),function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var n=e.languages.markup;n&&(n.tag.addInlined("style","css"),n.tag.addAttribute("style","css"))}(a),function(e){var t,n=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;e.languages.css.selector={pattern:e.languages.css.selector.pattern,lookbehind:!0,inside:t={"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+/,class:/\.[-\w]+/,id:/#[-\w]+/,attribute:{pattern:RegExp("\\[(?:[^[\\]\"']|"+n.source+")*\\]"),greedy:!0,inside:{punctuation:/^\[|\]$/,"case-sensitivity":{pattern:/(\s)[si]$/i,lookbehind:!0,alias:"keyword"},namespace:{pattern:/^(\s*)(?:(?!\s)[-*\w\xA0-\uFFFF])*\|(?!=)/,lookbehind:!0,inside:{punctuation:/\|$/}},"attr-name":{pattern:/^(\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+/,lookbehind:!0},"attr-value":[n,{pattern:/(=\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+(?=\s*$)/,lookbehind:!0}],operator:/[|~*^$]?=/}},"n-th":[{pattern:/(\(\s*)[+-]?\d*[\dn](?:\s*[+-]\s*\d+)?(?=\s*\))/,lookbehind:!0,inside:{number:/[\dn]+/,operator:/[+-]/}},{pattern:/(\(\s*)(?:even|odd)(?=\s*\))/i,lookbehind:!0}],combinator:/>|\+|~|\|\|/,punctuation:/[(),]/}},e.languages.css.atrule.inside["selector-function-argument"].inside=t,e.languages.insertBefore("css","property",{variable:{pattern:/(^|[^-\w\xA0-\uFFFF])--(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*/i,lookbehind:!0}});var r={pattern:/(\b\d+)(?:%|[a-z]+(?![\w-]))/,lookbehind:!0},a={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0};e.languages.insertBefore("css","function",{operator:{pattern:/(\s)[+\-*\/](?=\s)/,lookbehind:!0},hexcode:{pattern:/\B#[\da-f]{3,8}\b/i,alias:"color"},color:[{pattern:/(^|[^\w-])(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)(?![\w-])/i,lookbehind:!0},{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:r,number:a,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:r,number:a})}(a),a.languages.javascript=a.languages.extend("clike",{"class-name":[a.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),a.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,a.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:a.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:a.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:a.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:a.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:a.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),a.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:a.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),a.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),a.languages.markup&&(a.languages.markup.tag.addInlined("script","javascript"),a.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),a.languages.js=a.languages.javascript,function(e){var t=/#(?!\{).+/,n={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:n}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:n}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},script:{pattern:/[\s\S]+/,alias:"language-javascript",inside:e.languages.javascript}}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:n}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript}(a),function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,r="(?:"+n.source+"(?:[ \t]+"+t.source+")?|"+t.source+"(?:[ \t]+"+n.source+")?)",a=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-]<PLAIN>)(?:[ \t]*(?:(?![#:])<PLAIN>|:<PLAIN>))*/.source.replace(/<PLAIN>/g,(function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source})),o=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function i(e,t){t=(t||"").replace(/m/g,"")+"m";var n=/([:\-,[{]\s*(?:\s<<prop>>[ \t]+)?)(?:<<value>>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<<prop>>/g,(function(){return r})).replace(/<<value>>/g,(function(){return e}));return RegExp(n,t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<<prop>>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<<prop>>/g,(function(){return r}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<<prop>>[ \t]+)?)<<key>>(?=\s*:\s)/.source.replace(/<<prop>>/g,(function(){return r})).replace(/<<key>>/g,(function(){return"(?:"+a+"|"+o+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:i(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:i(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:i(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:i(o),lookbehind:!0,greedy:!0},number:{pattern:i(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(a),function(e){var t=/(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;function n(e){return e=e.replace(/<inner>/g,(function(){return t})),RegExp(/((?:^|[^\\])(?:\\{2})*)/.source+"(?:"+e+")")}var r=/(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source,a=/\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g,(function(){return r})),o=/\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source;e.languages.markdown=e.languages.extend("markup",{}),e.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:e.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+a+o+"(?:"+a+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+a+o+")(?:"+a+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(r),inside:e.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+a+")"+o+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+a+"$"),inside:{"table-header":{pattern:RegExp(r),alias:"important",inside:e.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:n(/\b__(?:(?!_)<inner>|_(?:(?!_)<inner>)+_)+__\b|\*\*(?:(?!\*)<inner>|\*(?:(?!\*)<inner>)+\*)+\*\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n(/\b_(?:(?!_)<inner>|__(?:(?!_)<inner>)+__)+_\b|\*(?:(?!\*)<inner>|\*\*(?:(?!\*)<inner>)+\*\*)+\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n(/(~~?)(?:(?!~)<inner>)+\2/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:n(/!?\[(?:(?!\])<inner>)+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\])<inner>)+\])/.source),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach((function(t){["url","bold","italic","strike","code-snippet"].forEach((function(n){t!==n&&(e.languages.markdown[t].inside.content.inside[n]=e.languages.markdown[n])}))})),e.hooks.add("after-tokenize",(function(e){"markdown"!==e.language&&"md"!==e.language||function e(t){if(t&&"string"!=typeof t)for(var n=0,r=t.length;n<r;n++){var a=t[n];if("code"===a.type){var o=a.content[1],i=a.content[3];if(o&&i&&"code-language"===o.type&&"code-block"===i.type&&"string"==typeof o.content){var l=o.content.replace(/\b#/g,"sharp").replace(/\b\+\+/g,"pp"),s="language-"+(l=(/[a-z][\w-]*/i.exec(l)||[""])[0].toLowerCase());i.alias?"string"==typeof i.alias?i.alias=[i.alias,s]:i.alias.push(s):i.alias=[s]}}else e(a.content)}}(e.tokens)})),e.hooks.add("wrap",(function(t){if("code-block"===t.type){for(var n="",r=0,a=t.classes.length;r<a;r++){var o=t.classes[r],u=/language-(.+)/.exec(o);if(u){n=u[1];break}}var c,d=e.languages[n];if(d)t.content=e.highlight((c=t.content,c.replace(i,"").replace(/&(\w{1,8}|#x?[\da-f]{1,8});/gi,(function(e,t){var n;if("#"===(t=t.toLowerCase())[0])return n="x"===t[1]?parseInt(t.slice(2),16):Number(t.slice(1)),s(n);var r=l[t];return r||e}))),d,n);else if(n&&"none"!==n&&e.plugins.autoloader){var f="md-"+(new Date).valueOf()+"-"+Math.floor(1e16*Math.random());t.attributes.id=f,e.plugins.autoloader.loadLanguages(n,(function(){var t=document.getElementById(f);t&&(t.innerHTML=e.highlight(t.textContent,e.languages[n],n))}))}}}));var i=RegExp(e.languages.markup.tag.pattern.source,"gi"),l={amp:"&",lt:"<",gt:">",quot:'"'},s=String.fromCodePoint||String.fromCharCode;e.languages.md=e.languages.markdown}(a),a.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:a.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:false|true)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/\b[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"atom-input":{pattern:/\b[A-Z]\w*Input\b/,alias:"class-name"},scalar:/\b(?:Boolean|Float|ID|Int|String)\b/,constant:/\b[A-Z][A-Z_\d]*\b/,"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*|:\s*|\[)[A-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-mutation":{pattern:/(\bmutation\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-query":{pattern:/(\bquery\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/,operator:/[!=|&]|\.{3}/,"property-query":/\w+(?=\s*\()/,object:/\w+(?=\s*\{)/,punctuation:/[!(){}\[\]:=,]/,property:/\w+/},a.hooks.add("after-tokenize",(function(e){if("graphql"===e.language)for(var t=e.tokens.filter((function(e){return"string"!=typeof e&&"comment"!==e.type&&"scalar"!==e.type})),n=0;n<t.length;){var r=t[n++];if("keyword"===r.type&&"mutation"===r.content){var a=[];if(d(["definition-mutation","punctuation"])&&"("===c(1).content){n+=2;var o=f(/^\($/,/^\)$/);if(-1===o)continue;for(;n<o;n++){var i=c(0);"variable"===i.type&&(p(i,"variable-input"),a.push(i.content))}n=o+1}if(d(["punctuation","property-query"])&&"{"===c(0).content&&(n++,p(c(0),"property-mutation"),a.length>0)){var l=f(/^\{$/,/^\}$/);if(-1===l)continue;for(var s=n;s<l;s++){var u=t[s];"variable"===u.type&&a.indexOf(u.content)>=0&&p(u,"variable-input")}}}}function c(e){return t[n+e]}function d(e,t){t=t||0;for(var n=0;n<e.length;n++){var r=c(n+t);if(!r||r.type!==e[n])return!1}return!0}function f(e,r){for(var a=1,o=n;o<t.length;o++){var i=t[o],l=i.content;if("punctuation"===i.type&&"string"==typeof l)if(e.test(l))a++;else if(r.test(l)&&0===--a)return o}return-1}function p(e,t){var n=e.alias;n?Array.isArray(n)||(e.alias=n=[n]):e.alias=n=[],n.push(t)}})),a.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/},function(e){var t=e.languages.javascript["template-string"],n=t.pattern.source,r=t.inside.interpolation,a=r.inside["interpolation-punctuation"],o=r.pattern.source;function i(t,r){if(e.languages[t])return{pattern:RegExp("((?:"+r+")\\s*)"+n),lookbehind:!0,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},"embedded-code":{pattern:/[\s\S]+/,alias:t}}}}function l(e,t){return"___"+t.toUpperCase()+"_"+e+"___"}function s(t,n,r){var a={code:t,grammar:n,language:r};return e.hooks.run("before-tokenize",a),a.tokens=e.tokenize(a.code,a.grammar),e.hooks.run("after-tokenize",a),a.tokens}function u(t){var n={};n["interpolation-punctuation"]=a;var o=e.tokenize(t,n);if(3===o.length){var i=[1,1];i.push.apply(i,s(o[1],e.languages.javascript,"javascript")),o.splice.apply(o,i)}return new e.Token("interpolation",o,r.alias,t)}function c(t,n,r){var a=e.tokenize(t,{interpolation:{pattern:RegExp(o),lookbehind:!0}}),i=0,c={},d=s(a.map((function(e){if("string"==typeof e)return e;for(var n,a=e.content;-1!==t.indexOf(n=l(i++,r)););return c[n]=a,n})).join(""),n,r),f=Object.keys(c);return i=0,function e(t){for(var n=0;n<t.length;n++){if(i>=f.length)return;var r=t[n];if("string"==typeof r||"string"==typeof r.content){var a=f[i],o="string"==typeof r?r:r.content,l=o.indexOf(a);if(-1!==l){++i;var s=o.substring(0,l),d=u(c[a]),p=o.substring(l+a.length),m=[];if(s&&m.push(s),m.push(d),p){var h=[p];e(h),m.push.apply(m,h)}"string"==typeof r?(t.splice.apply(t,[n,1].concat(m)),n+=m.length-1):r.content=m}}else{var g=r.content;Array.isArray(g)?e(g):e([g])}}}(d),new e.Token(r,d,"language-"+r,t)}e.languages.javascript["template-string"]=[i("css",/\b(?:styled(?:\([^)]*\))?(?:\s*\.\s*\w+(?:\([^)]*\))*)*|css(?:\s*\.\s*(?:global|resolve))?|createGlobalStyle|keyframes)/.source),i("html",/\bhtml|\.\s*(?:inner|outer)HTML\s*\+?=/.source),i("svg",/\bsvg/.source),i("markdown",/\b(?:markdown|md)/.source),i("graphql",/\b(?:gql|graphql(?:\s*\.\s*experimental)?)/.source),i("sql",/\bsql/.source),t].filter(Boolean);var d={javascript:!0,js:!0,typescript:!0,ts:!0,jsx:!0,tsx:!0};function f(e){return"string"==typeof e?e:Array.isArray(e)?e.map(f).join(""):f(e.content)}e.hooks.add("after-tokenize",(function(t){t.language in d&&function t(n){for(var r=0,a=n.length;r<a;r++){var o=n[r];if("string"!=typeof o){var i=o.content;if(Array.isArray(i))if("template-string"===o.type){var l=i[1];if(3===i.length&&"string"!=typeof l&&"embedded-code"===l.type){var s=f(l),u=l.alias,d=Array.isArray(u)?u[0]:u,p=e.languages[d];if(!p)continue;i[1]=c(s,p,d)}}else t(i);else"string"!=typeof i&&t([i])}}}(t.tokens)}))}(a),function(e){e.languages.typescript=e.languages.extend("javascript",{"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|type)\s+)(?!keyof\b)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?:\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var t=e.languages.extend("typescript",{});delete t["class-name"],e.languages.typescript["class-name"].inside=t,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:t}}}}),e.languages.ts=e.languages.typescript}(a),function(e){function t(e,t){return RegExp(e.replace(/<ID>/g,(function(){return/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/.source})),t)}e.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+e.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),e.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+e.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),e.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|(?:Weak)?(?:Map|Set)|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),e.languages.insertBefore("javascript","keyword",{imports:{pattern:t(/(\bimport\b\s*)(?:<ID>(?:\s*,\s*(?:\*\s*as\s+<ID>|\{[^{}]*\}))?|\*\s*as\s+<ID>|\{[^{}]*\})(?=\s*\bfrom\b)/.source),lookbehind:!0,inside:e.languages.javascript},exports:{pattern:t(/(\bexport\b\s*)(?:\*(?:\s*as\s+<ID>)?(?=\s*\bfrom\b)|\{[^{}]*\})/.source),lookbehind:!0,inside:e.languages.javascript}}),e.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|finally|for|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),e.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),e.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:t(/(\.\s*)#?<ID>/.source),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|(?:local|session)Storage|location|navigator|performance|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var n=["function","function-variable","method","method-variable","property-access"],r=0;r<n.length;r++){var a=n[r],o=e.languages.javascript[a];"RegExp"===e.util.type(o)&&(o=e.languages.javascript[a]={pattern:o});var i=o.inside||{};o.inside=i,i["maybe-class-name"]=/^[A-Z][\s\S]*/}}(a),function(e){var t=e.util.clone(e.languages.javascript),n=/(?:\s|\/\/.*(?!.)|\/\*(?:[^*]|\*(?!\/))\*\/)/.source,r=/(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])*\})/.source,a=/(?:\{<S>*\.{3}(?:[^{}]|<BRACES>)*\})/.source;function o(e,t){return e=e.replace(/<S>/g,(function(){return n})).replace(/<BRACES>/g,(function(){return r})).replace(/<SPREAD>/g,(function(){return a})),RegExp(e,t)}a=o(a).source,e.languages.jsx=e.languages.extend("markup",t),e.languages.jsx.tag.pattern=o(/<\/?(?:[\w.:-]+(?:<S>+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:o(/<SPREAD>/.source),inside:e.languages.jsx}},e.languages.jsx.tag),e.languages.insertBefore("inside","special-attr",{script:{pattern:o(/=<BRACES>/.source),alias:"language-javascript",inside:{"script-punctuation":{pattern:/^=(?=\{)/,alias:"punctuation"},rest:e.languages.jsx}}},e.languages.jsx.tag);var i=function(e){return e?"string"==typeof e?e:"string"==typeof e.content?e.content:e.content.map(i).join(""):""},l=function(t){for(var n=[],r=0;r<t.length;r++){var a=t[r],o=!1;if("string"!=typeof a&&("tag"===a.type&&a.content[0]&&"tag"===a.content[0].type?"</"===a.content[0].content[0].content?n.length>0&&n[n.length-1].tagName===i(a.content[0].content[1])&&n.pop():"/>"===a.content[a.content.length-1].content||n.push({tagName:i(a.content[0].content[1]),openedBraces:0}):n.length>0&&"punctuation"===a.type&&"{"===a.content?n[n.length-1].openedBraces++:n.length>0&&n[n.length-1].openedBraces>0&&"punctuation"===a.type&&"}"===a.content?n[n.length-1].openedBraces--:o=!0),(o||"string"==typeof a)&&n.length>0&&0===n[n.length-1].openedBraces){var s=i(a);r<t.length-1&&("string"==typeof t[r+1]||"plain-text"===t[r+1].type)&&(s+=i(t[r+1]),t.splice(r+1,1)),r>0&&("string"==typeof t[r-1]||"plain-text"===t[r-1].type)&&(s=i(t[r-1])+s,t.splice(r-1,1),r--),t[r]=new e.Token("plain-text",s,null,s)}a.content&&"string"!=typeof a.content&&l(a.content)}};e.hooks.add("after-tokenize",(function(e){"jsx"!==e.language&&"tsx"!==e.language||l(e.tokens)}))}(a),function(e){e.languages.diff={coord:[/^(?:\*{3}|-{3}|\+{3}).*$/m,/^@@.*@@$/m,/^\d.*$/m]};var t={"deleted-sign":"-","deleted-arrow":"<","inserted-sign":"+","inserted-arrow":">",unchanged:" ",diff:"!"};Object.keys(t).forEach((function(n){var r=t[n],a=[];/^\w+$/.test(n)||a.push(/\w+/.exec(n)[0]),"diff"===n&&a.push("bold"),e.languages.diff[n]={pattern:RegExp("^(?:["+r+"].*(?:\r\n?|\n|(?![\\s\\S])))+","m"),alias:a,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(n)[0]}}}})),Object.defineProperty(e.languages.diff,"PREFIXES",{value:t})}(a),a.languages.git={comment:/^#.*/m,deleted:/^[-\u2013].*/m,inserted:/^\+.*/m,string:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,command:{pattern:/^.*\$ git .*$/m,inside:{parameter:/\s--?\w+/}},coord:/^@@.*@@$/m,"commit-sha1":/^commit \w{40}$/m},a.languages.go=a.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),a.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete a.languages.go["class-name"],function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,a,o){if(n.language===r){var i=n.tokenStack=[];n.code=n.code.replace(a,(function(e){if("function"==typeof o&&!o(e))return e;for(var a,l=i.length;-1!==n.code.indexOf(a=t(r,l));)++l;return i[l]=e,a})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var a=0,o=Object.keys(n.tokenStack);!function i(l){for(var s=0;s<l.length&&!(a>=o.length);s++){var u=l[s];if("string"==typeof u||u.content&&"string"==typeof u.content){var c=o[a],d=n.tokenStack[c],f="string"==typeof u?u:u.content,p=t(r,c),m=f.indexOf(p);if(m>-1){++a;var h=f.substring(0,m),g=new e.Token(r,e.tokenize(d,n.grammar),"language-"+r,d),b=f.substring(m+p.length),v=[];h&&v.push.apply(v,i([h])),v.push(g),b&&v.push.apply(v,i([b])),"string"==typeof u?l.splice.apply(l,[s,1].concat(v)):u.content=v}}else u.content&&i(u.content)}return l}(n.tokens)}}}})}(a),function(e){e.languages.handlebars={comment:/\{\{![\s\S]*?\}\}/,delimiter:{pattern:/^\{\{\{?|\}\}\}?$/,alias:"punctuation"},string:/(["'])(?:\\.|(?!\1)[^\\\r\n])*\1/,number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee][+-]?\d+)?/,boolean:/\b(?:false|true)\b/,block:{pattern:/^(\s*(?:~\s*)?)[#\/]\S+?(?=\s*(?:~\s*)?$|\s)/,lookbehind:!0,alias:"keyword"},brackets:{pattern:/\[[^\]]+\]/,inside:{punctuation:/\[|\]/,variable:/[\s\S]+/}},punctuation:/[!"#%&':()*+,.\/;<=>@\[\\\]^`{|}~]/,variable:/[^!"#%&'()*+,\/;<=>@\[\\\]^`{|}~\s]+/},e.hooks.add("before-tokenize",(function(t){e.languages["markup-templating"].buildPlaceholders(t,"handlebars",/\{\{\{[\s\S]+?\}\}\}|\{\{[\s\S]+?\}\}/g)})),e.hooks.add("after-tokenize",(function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"handlebars")})),e.languages.hbs=e.languages.handlebars}(a),a.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},a.languages.webmanifest=a.languages.json,a.languages.less=a.languages.extend("css",{comment:[/\/\*[\s\S]*?\*\//,{pattern:/(^|[^\\])\/\/.*/,lookbehind:!0}],atrule:{pattern:/@[\w-](?:\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};\s]|\s+(?!\s))*?(?=\s*\{)/,inside:{punctuation:/[:()]/}},selector:{pattern:/(?:@\{[\w-]+\}|[^{};\s@])(?:@\{[\w-]+\}|\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};@\s]|\s+(?!\s))*?(?=\s*\{)/,inside:{variable:/@+[\w-]+/}},property:/(?:@\{[\w-]+\}|[\w-])+(?:\+_?)?(?=\s*:)/,operator:/[+\-*\/]/}),a.languages.insertBefore("less","property",{variable:[{pattern:/@[\w-]+\s*:/,inside:{punctuation:/:/}},/@@?[\w-]+/],"mixin-usage":{pattern:/([{;]\s*)[.#](?!\d)[\w-].*?(?=[(;])/,lookbehind:!0,alias:"function"}}),a.languages.makefile={comment:{pattern:/(^|[^\\])#(?:\\(?:\r\n|[\s\S])|[^\\\r\n])*/,lookbehind:!0},string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"builtin-target":{pattern:/\.[A-Z][^:#=\s]+(?=\s*:(?!=))/,alias:"builtin"},target:{pattern:/^(?:[^:=\s]|[ \t]+(?![\s:]))+(?=\s*:(?!=))/m,alias:"symbol",inside:{variable:/\$+(?:(?!\$)[^(){}:#=\s]+|(?=[({]))/}},variable:/\$+(?:(?!\$)[^(){}:#=\s]+|\([@*%<^+?][DF]\)|(?=[({]))/,keyword:/-include\b|\b(?:define|else|endef|endif|export|ifn?def|ifn?eq|include|override|private|sinclude|undefine|unexport|vpath)\b/,function:{pattern:/(\()(?:abspath|addsuffix|and|basename|call|dir|error|eval|file|filter(?:-out)?|findstring|firstword|flavor|foreach|guile|if|info|join|lastword|load|notdir|or|origin|patsubst|realpath|shell|sort|strip|subst|suffix|value|warning|wildcard|word(?:list|s)?)(?=[ \t])/,lookbehind:!0},operator:/(?:::|[?:+!])?=|[|@]/,punctuation:/[:;(){}]/},a.languages.objectivec=a.languages.extend("c",{string:{pattern:/@?"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|in|inline|int|long|register|return|self|short|signed|sizeof|static|struct|super|switch|typedef|typeof|union|unsigned|void|volatile|while)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete a.languages.objectivec["class-name"],a.languages.objc=a.languages.objectivec,a.languages.ocaml={comment:{pattern:/\(\*[\s\S]*?\*\)/,greedy:!0},char:{pattern:/'(?:[^\\\r\n']|\\(?:.|[ox]?[0-9a-f]{1,3}))'/i,greedy:!0},string:[{pattern:/"(?:\\(?:[\s\S]|\r\n)|[^\\\r\n"])*"/,greedy:!0},{pattern:/\{([a-z_]*)\|[\s\S]*?\|\1\}/,greedy:!0}],number:[/\b(?:0b[01][01_]*|0o[0-7][0-7_]*)\b/i,/\b0x[a-f0-9][a-f0-9_]*(?:\.[a-f0-9_]*)?(?:p[+-]?\d[\d_]*)?(?!\w)/i,/\b\d[\d_]*(?:\.[\d_]*)?(?:e[+-]?\d[\d_]*)?(?!\w)/i],directive:{pattern:/\B#\w+/,alias:"property"},label:{pattern:/\B~\w+/,alias:"property"},"type-variable":{pattern:/\B'\w+/,alias:"function"},variant:{pattern:/`\w+/,alias:"symbol"},keyword:/\b(?:as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|match|method|module|mutable|new|nonrec|object|of|open|private|rec|sig|struct|then|to|try|type|val|value|virtual|when|where|while|with)\b/,boolean:/\b(?:false|true)\b/,"operator-like-punctuation":{pattern:/\[[<>|]|[>|]\]|\{<|>\}/,alias:"punctuation"},operator:/\.[.~]|:[=>]|[=<>@^|&+\-*\/$%!?~][!$%&*+\-.\/:<=>?@^|~]*|\b(?:and|asr|land|lor|lsl|lsr|lxor|mod|or)\b/,punctuation:/;;|::|[(){}\[\].,:;#]|\b_\b/},a.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},a.languages.python["string-interpolation"].inside.interpolation.inside.rest=a.languages.python,a.languages.py=a.languages.python,a.languages.reason=a.languages.extend("clike",{string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},"class-name":/\b[A-Z]\w*/,keyword:/\b(?:and|as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|method|module|mutable|new|nonrec|object|of|open|or|private|rec|sig|struct|switch|then|to|try|type|val|virtual|when|while|with)\b/,operator:/\.{3}|:[:=]|\|>|->|=(?:==?|>)?|<=?|>=?|[|^?'#!~`]|[+\-*\/]\.?|\b(?:asr|land|lor|lsl|lsr|lxor|mod)\b/}),a.languages.insertBefore("reason","class-name",{char:{pattern:/'(?:\\x[\da-f]{2}|\\o[0-3][0-7][0-7]|\\\d{3}|\\.|[^'\\\r\n])'/,greedy:!0},constructor:/\b[A-Z]\w*\b(?!\s*\.)/,label:{pattern:/\b[a-z]\w*(?=::)/,alias:"symbol"}}),delete a.languages.reason.function,function(e){e.languages.sass=e.languages.extend("css",{comment:{pattern:/^([ \t]*)\/[\/*].*(?:(?:\r?\n|\r)\1[ \t].+)*/m,lookbehind:!0,greedy:!0}}),e.languages.insertBefore("sass","atrule",{"atrule-line":{pattern:/^(?:[ \t]*)[@+=].+/m,greedy:!0,inside:{atrule:/(?:@[\w-]+|[+=])/}}}),delete e.languages.sass.atrule;var t=/\$[-\w]+|#\{\$[-\w]+\}/,n=[/[+*\/%]|[=!]=|<=?|>=?|\b(?:and|not|or)\b/,{pattern:/(\s)-(?=\s)/,lookbehind:!0}];e.languages.insertBefore("sass","property",{"variable-line":{pattern:/^[ \t]*\$.+/m,greedy:!0,inside:{punctuation:/:/,variable:t,operator:n}},"property-line":{pattern:/^[ \t]*(?:[^:\s]+ *:.*|:[^:\s].*)/m,greedy:!0,inside:{property:[/[^:\s]+(?=\s*:)/,{pattern:/(:)[^:\s]+/,lookbehind:!0}],punctuation:/:/,variable:t,operator:n,important:e.languages.sass.important}}}),delete e.languages.sass.property,delete e.languages.sass.important,e.languages.insertBefore("sass","punctuation",{selector:{pattern:/^([ \t]*)\S(?:,[^,\r\n]+|[^,\r\n]*)(?:,[^,\r\n]+)*(?:,(?:\r?\n|\r)\1[ \t]+\S(?:,[^,\r\n]+|[^,\r\n]*)(?:,[^,\r\n]+)*)*/m,lookbehind:!0,greedy:!0}})}(a),a.languages.scss=a.languages.extend("css",{comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},atrule:{pattern:/@[\w-](?:\([^()]+\)|[^()\s]|\s+(?!\s))*?(?=\s+[{;])/,inside:{rule:/@[\w-]+/}},url:/(?:[-a-z]+-)?url(?=\()/i,selector:{pattern:/(?=\S)[^@;{}()]?(?:[^@;{}()\s]|\s+(?!\s)|#\{\$[-\w]+\})+(?=\s*\{(?:\}|\s|[^}][^:{}]*[:{][^}]))/,inside:{parent:{pattern:/&/,alias:"important"},placeholder:/%[-\w]+/,variable:/\$[-\w]+|#\{\$[-\w]+\}/}},property:{pattern:/(?:[-\w]|\$[-\w]|#\{\$[-\w]+\})+(?=\s*:)/,inside:{variable:/\$[-\w]+|#\{\$[-\w]+\}/}}}),a.languages.insertBefore("scss","atrule",{keyword:[/@(?:content|debug|each|else(?: if)?|extend|for|forward|function|if|import|include|mixin|return|use|warn|while)\b/i,{pattern:/( )(?:from|through)(?= )/,lookbehind:!0}]}),a.languages.insertBefore("scss","important",{variable:/\$[-\w]+|#\{\$[-\w]+\}/}),a.languages.insertBefore("scss","function",{"module-modifier":{pattern:/\b(?:as|hide|show|with)\b/i,alias:"keyword"},placeholder:{pattern:/%[-\w]+/,alias:"selector"},statement:{pattern:/\B!(?:default|optional)\b/i,alias:"keyword"},boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"},operator:{pattern:/(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|not|or)(?=\s)/,lookbehind:!0}}),a.languages.scss.atrule.inside.rest=a.languages.scss,function(e){var t={pattern:/(\b\d+)(?:%|[a-z]+)/,lookbehind:!0},n={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0},r={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},url:{pattern:/\burl\((["']?).*?\1\)/i,greedy:!0},string:{pattern:/("|')(?:(?!\1)[^\\\r\n]|\\(?:\r\n|[\s\S]))*\1/,greedy:!0},interpolation:null,func:null,important:/\B!(?:important|optional)\b/i,keyword:{pattern:/(^|\s+)(?:(?:else|for|if|return|unless)(?=\s|$)|@[\w-]+)/,lookbehind:!0},hexcode:/#[\da-f]{3,6}/i,color:[/\b(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)\b/i,{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:t,number:n,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:t,boolean:/\b(?:false|true)\b/,operator:[/~|[+!\/%<>?=]=?|[-:]=|\*[*=]?|\.{2,3}|&&|\|\||\B-\B|\b(?:and|in|is(?: a| defined| not|nt)?|not|or)\b/],number:n,punctuation:/[{}()\[\];:,]/};r.interpolation={pattern:/\{[^\r\n}:]+\}/,alias:"variable",inside:{delimiter:{pattern:/^\{|\}$/,alias:"punctuation"},rest:r}},r.func={pattern:/[\w-]+\([^)]*\).*/,inside:{function:/^[^(]+/,rest:r}},e.languages.stylus={"atrule-declaration":{pattern:/(^[ \t]*)@.+/m,lookbehind:!0,inside:{atrule:/^@[\w-]+/,rest:r}},"variable-declaration":{pattern:/(^[ \t]*)[\w$-]+\s*.?=[ \t]*(?:\{[^{}]*\}|\S.*|$)/m,lookbehind:!0,inside:{variable:/^\S+/,rest:r}},statement:{pattern:/(^[ \t]*)(?:else|for|if|return|unless)[ \t].+/m,lookbehind:!0,inside:{keyword:/^\S+/,rest:r}},"property-declaration":{pattern:/((?:^|\{)([ \t]*))(?:[\w-]|\{[^}\r\n]+\})+(?:\s*:\s*|[ \t]+)(?!\s)[^{\r\n]*(?:;|[^{\r\n,]$(?!(?:\r?\n|\r)(?:\{|\2[ \t])))/m,lookbehind:!0,inside:{property:{pattern:/^[^\s:]+/,inside:{interpolation:r.interpolation}},rest:r}},selector:{pattern:/(^[ \t]*)(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\)|(?![\w-]))|\{[^}\r\n]+\})+)(?:(?:\r?\n|\r)(?:\1(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\)|(?![\w-]))|\{[^}\r\n]+\})+)))*(?:,$|\{|(?=(?:\r?\n|\r)(?:\{|\1[ \t])))/m,lookbehind:!0,inside:{interpolation:r.interpolation,comment:r.comment,punctuation:/[{},]/}},func:r.func,string:r.string,comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0,greedy:!0},interpolation:r.interpolation,punctuation:/[{}()\[\];:.]/}}(a),function(e){var t=e.util.clone(e.languages.typescript);e.languages.tsx=e.languages.extend("jsx",t),delete e.languages.tsx.parameter,delete e.languages.tsx["literal-property"];var n=e.languages.tsx.tag;n.pattern=RegExp(/(^|[^\w$]|(?=<\/))/.source+"(?:"+n.pattern.source+")",n.pattern.flags),n.lookbehind=!0}(a),a.languages.wasm={comment:[/\(;[\s\S]*?;\)/,{pattern:/;;.*/,greedy:!0}],string:{pattern:/"(?:\\[\s\S]|[^"\\])*"/,greedy:!0},keyword:[{pattern:/\b(?:align|offset)=/,inside:{operator:/=/}},{pattern:/\b(?:(?:f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|neg?|nearest|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|sqrt|store(?:8|16|32)?|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))?|memory\.(?:grow|size))\b/,inside:{punctuation:/\./}},/\b(?:anyfunc|block|br(?:_if|_table)?|call(?:_indirect)?|data|drop|elem|else|end|export|func|get_(?:global|local)|global|if|import|local|loop|memory|module|mut|nop|offset|param|result|return|select|set_(?:global|local)|start|table|tee_local|then|type|unreachable)\b/],variable:/\$[\w!#$%&'*+\-./:<=>?@\\^`|~]+/,number:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/,punctuation:/[()]/};const o=a},5651:()=>{!function(e){function t(e,t){return e.replace(/<<(\d+)>>/g,(function(e,n){return"(?:"+t[+n]+")"}))}function n(e,n,r){return RegExp(t(e,n),r||"")}function r(e,t){for(var n=0;n<t;n++)e=e.replace(/<<self>>/g,(function(){return"(?:"+e+")"}));return e.replace(/<<self>>/g,"[^\\s\\S]")}var a="bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void",o="class enum interface record struct",i="add alias and ascending async await by descending from(?=\\s*(?:\\w|$)) get global group into init(?=\\s*;) join let nameof not notnull on or orderby partial remove select set unmanaged value when where with(?=\\s*{)",l="abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield";function s(e){return"\\b(?:"+e.trim().replace(/ /g,"|")+")\\b"}var u=s(o),c=RegExp(s(a+" "+o+" "+i+" "+l)),d=s(o+" "+i+" "+l),f=s(a+" "+o+" "+l),p=r(/<(?:[^<>;=+\-*/%&|^]|<<self>>)*>/.source,2),m=r(/\((?:[^()]|<<self>>)*\)/.source,2),h=/@?\b[A-Za-z_]\w*\b/.source,g=t(/<<0>>(?:\s*<<1>>)?/.source,[h,p]),b=t(/(?!<<0>>)<<1>>(?:\s*\.\s*<<1>>)*/.source,[d,g]),v=/\[\s*(?:,\s*)*\]/.source,y=t(/<<0>>(?:\s*(?:\?\s*)?<<1>>)*(?:\s*\?)?/.source,[b,v]),w=t(/[^,()<>[\];=+\-*/%&|^]|<<0>>|<<1>>|<<2>>/.source,[p,m,v]),E=t(/\(<<0>>+(?:,<<0>>+)+\)/.source,[w]),k=t(/(?:<<0>>|<<1>>)(?:\s*(?:\?\s*)?<<2>>)*(?:\s*\?)?/.source,[E,b,v]),S={keyword:c,punctuation:/[<>()?,.:[\]]/},x=/'(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'/.source,_=/"(?:\\.|[^\\"\r\n])*"/.source,C=/@"(?:""|\\[\s\S]|[^\\"])*"(?!")/.source;e.languages.csharp=e.languages.extend("clike",{string:[{pattern:n(/(^|[^$\\])<<0>>/.source,[C]),lookbehind:!0,greedy:!0},{pattern:n(/(^|[^@$\\])<<0>>/.source,[_]),lookbehind:!0,greedy:!0}],"class-name":[{pattern:n(/(\busing\s+static\s+)<<0>>(?=\s*;)/.source,[b]),lookbehind:!0,inside:S},{pattern:n(/(\busing\s+<<0>>\s*=\s*)<<1>>(?=\s*;)/.source,[h,k]),lookbehind:!0,inside:S},{pattern:n(/(\busing\s+)<<0>>(?=\s*=)/.source,[h]),lookbehind:!0},{pattern:n(/(\b<<0>>\s+)<<1>>/.source,[u,g]),lookbehind:!0,inside:S},{pattern:n(/(\bcatch\s*\(\s*)<<0>>/.source,[b]),lookbehind:!0,inside:S},{pattern:n(/(\bwhere\s+)<<0>>/.source,[h]),lookbehind:!0},{pattern:n(/(\b(?:is(?:\s+not)?|as)\s+)<<0>>/.source,[y]),lookbehind:!0,inside:S},{pattern:n(/\b<<0>>(?=\s+(?!<<1>>|with\s*\{)<<2>>(?:\s*[=,;:{)\]]|\s+(?:in|when)\b))/.source,[k,f,h]),inside:S}],keyword:c,number:/(?:\b0(?:x[\da-f_]*[\da-f]|b[01_]*[01])|(?:\B\.\d+(?:_+\d+)*|\b\d+(?:_+\d+)*(?:\.\d+(?:_+\d+)*)?)(?:e[-+]?\d+(?:_+\d+)*)?)(?:[dflmu]|lu|ul)?\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\1|~|\?\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),e.languages.insertBefore("csharp","number",{range:{pattern:/\.\./,alias:"operator"}}),e.languages.insertBefore("csharp","punctuation",{"named-parameter":{pattern:n(/([(,]\s*)<<0>>(?=\s*:)/.source,[h]),lookbehind:!0,alias:"punctuation"}}),e.languages.insertBefore("csharp","class-name",{namespace:{pattern:n(/(\b(?:namespace|using)\s+)<<0>>(?:\s*\.\s*<<0>>)*(?=\s*[;{])/.source,[h]),lookbehind:!0,inside:{punctuation:/\./}},"type-expression":{pattern:n(/(\b(?:default|sizeof|typeof)\s*\(\s*(?!\s))(?:[^()\s]|\s(?!\s)|<<0>>)*(?=\s*\))/.source,[m]),lookbehind:!0,alias:"class-name",inside:S},"return-type":{pattern:n(/<<0>>(?=\s+(?:<<1>>\s*(?:=>|[({]|\.\s*this\s*\[)|this\s*\[))/.source,[k,b]),inside:S,alias:"class-name"},"constructor-invocation":{pattern:n(/(\bnew\s+)<<0>>(?=\s*[[({])/.source,[k]),lookbehind:!0,inside:S,alias:"class-name"},"generic-method":{pattern:n(/<<0>>\s*<<1>>(?=\s*\()/.source,[h,p]),inside:{function:n(/^<<0>>/.source,[h]),generic:{pattern:RegExp(p),alias:"class-name",inside:S}}},"type-list":{pattern:n(/\b((?:<<0>>\s+<<1>>|record\s+<<1>>\s*<<5>>|where\s+<<2>>)\s*:\s*)(?:<<3>>|<<4>>|<<1>>\s*<<5>>|<<6>>)(?:\s*,\s*(?:<<3>>|<<4>>|<<6>>))*(?=\s*(?:where|[{;]|=>|$))/.source,[u,g,h,k,c.source,m,/\bnew\s*\(\s*\)/.source]),lookbehind:!0,inside:{"record-arguments":{pattern:n(/(^(?!new\s*\()<<0>>\s*)<<1>>/.source,[g,m]),lookbehind:!0,greedy:!0,inside:e.languages.csharp},keyword:c,"class-name":{pattern:RegExp(k),greedy:!0,inside:S},punctuation:/[,()]/}},preprocessor:{pattern:/(^[\t ]*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(#)\b(?:define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}});var A=_+"|"+x,T=t(/\/(?![*/])|\/\/[^\r\n]*[\r\n]|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>/.source,[A]),N=r(t(/[^"'/()]|<<0>>|\(<<self>>*\)/.source,[T]),2),O=/\b(?:assembly|event|field|method|module|param|property|return|type)\b/.source,L=t(/<<0>>(?:\s*\(<<1>>*\))?/.source,[b,N]);e.languages.insertBefore("csharp","class-name",{attribute:{pattern:n(/((?:^|[^\s\w>)?])\s*\[\s*)(?:<<0>>\s*:\s*)?<<1>>(?:\s*,\s*<<1>>)*(?=\s*\])/.source,[O,L]),lookbehind:!0,greedy:!0,inside:{target:{pattern:n(/^<<0>>(?=\s*:)/.source,[O]),alias:"keyword"},"attribute-arguments":{pattern:n(/\(<<0>>*\)/.source,[N]),inside:e.languages.csharp},"class-name":{pattern:RegExp(b),inside:{punctuation:/\./}},punctuation:/[:,]/}}});var P=/:[^}\r\n]+/.source,R=r(t(/[^"'/()]|<<0>>|\(<<self>>*\)/.source,[T]),2),I=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[R,P]),M=r(t(/[^"'/()]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>|\(<<self>>*\)/.source,[A]),2),D=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[M,P]);function F(t,r){return{interpolation:{pattern:n(/((?:^|[^{])(?:\{\{)*)<<0>>/.source,[t]),lookbehind:!0,inside:{"format-string":{pattern:n(/(^\{(?:(?![}:])<<0>>)*)<<1>>(?=\}$)/.source,[r,P]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-csharp",inside:e.languages.csharp}}},string:/[\s\S]+/}}e.languages.insertBefore("csharp","string",{"interpolation-string":[{pattern:n(/(^|[^\\])(?:\$@|@\$)"(?:""|\\[\s\S]|\{\{|<<0>>|[^\\{"])*"/.source,[I]),lookbehind:!0,greedy:!0,inside:F(I,R)},{pattern:n(/(^|[^@\\])\$"(?:\\.|\{\{|<<0>>|[^\\"{])*"/.source,[D]),lookbehind:!0,greedy:!0,inside:F(D,M)}],char:{pattern:RegExp(x),greedy:!0}}),e.languages.dotnet=e.languages.cs=e.languages.csharp}(Prism)},905:()=>{!function(e){var t=e.languages.powershell={comment:[{pattern:/(^|[^`])<#[\s\S]*?#>/,lookbehind:!0},{pattern:/(^|[^`])#.*/,lookbehind:!0}],string:[{pattern:/"(?:`[\s\S]|[^`"])*"/,greedy:!0,inside:null},{pattern:/'(?:[^']|'')*'/,greedy:!0}],namespace:/\[[a-z](?:\[(?:\[[^\]]*\]|[^\[\]])*\]|[^\[\]])*\]/i,boolean:/\$(?:false|true)\b/i,variable:/\$\w+\b/,function:[/\b(?:Add|Approve|Assert|Backup|Block|Checkpoint|Clear|Close|Compare|Complete|Compress|Confirm|Connect|Convert|ConvertFrom|ConvertTo|Copy|Debug|Deny|Disable|Disconnect|Dismount|Edit|Enable|Enter|Exit|Expand|Export|Find|ForEach|Format|Get|Grant|Group|Hide|Import|Initialize|Install|Invoke|Join|Limit|Lock|Measure|Merge|Move|New|Open|Optimize|Out|Ping|Pop|Protect|Publish|Push|Read|Receive|Redo|Register|Remove|Rename|Repair|Request|Reset|Resize|Resolve|Restart|Restore|Resume|Revoke|Save|Search|Select|Send|Set|Show|Skip|Sort|Split|Start|Step|Stop|Submit|Suspend|Switch|Sync|Tee|Test|Trace|Unblock|Undo|Uninstall|Unlock|Unprotect|Unpublish|Unregister|Update|Use|Wait|Watch|Where|Write)-[a-z]+\b/i,/\b(?:ac|cat|chdir|clc|cli|clp|clv|compare|copy|cp|cpi|cpp|cvpa|dbp|del|diff|dir|ebp|echo|epal|epcsv|epsn|erase|fc|fl|ft|fw|gal|gbp|gc|gci|gcs|gdr|gi|gl|gm|gp|gps|group|gsv|gu|gv|gwmi|iex|ii|ipal|ipcsv|ipsn|irm|iwmi|iwr|kill|lp|ls|measure|mi|mount|move|mp|mv|nal|ndr|ni|nv|ogv|popd|ps|pushd|pwd|rbp|rd|rdr|ren|ri|rm|rmdir|rni|rnp|rp|rv|rvpa|rwmi|sal|saps|sasv|sbp|sc|select|set|shcm|si|sl|sleep|sls|sort|sp|spps|spsv|start|sv|swmi|tee|trcm|type|write)\b/i],keyword:/\b(?:Begin|Break|Catch|Class|Continue|Data|Define|Do|DynamicParam|Else|ElseIf|End|Exit|Filter|Finally|For|ForEach|From|Function|If|InlineScript|Parallel|Param|Process|Return|Sequence|Switch|Throw|Trap|Try|Until|Using|Var|While|Workflow)\b/i,operator:{pattern:/(^|\W)(?:!|-(?:b?(?:and|x?or)|as|(?:Not)?(?:Contains|In|Like|Match)|eq|ge|gt|is(?:Not)?|Join|le|lt|ne|not|Replace|sh[lr])\b|-[-=]?|\+[+=]?|[*\/%]=?)/i,lookbehind:!0},punctuation:/[|{}[\];(),.]/};t.string[0].inside={function:{pattern:/(^|[^`])\$\((?:\$\([^\r\n()]*\)|(?!\$\()[^\r\n)])*\)/,lookbehind:!0,inside:t},boolean:t.boolean,variable:t.variable}}(Prism)},3213:(e,t,n)=>{var r={"./prism-csharp":5651,"./prism-powershell":905};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=3213},2694:(e,t,n)=>{"use strict";var r=n(6925);function a(){}function o(){}o.resetWarningCache=a,e.exports=function(){function e(e,t,n,a,o,i){if(i!==r){var l=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw l.name="Invariant Violation",l}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:a};return n.PropTypes=n,n}},5556:(e,t,n)=>{e.exports=n(2694)()},6925:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},2551:(e,t,n)=>{"use strict";var r=n(6540),a=n(5228),o=n(9982);function i(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}if(!r)throw Error(i(227));var l=new Set,s={};function u(e,t){c(e,t),c(e+"Capture",t)}function c(e,t){for(s[e]=t,e=0;e<t.length;e++)l.add(t[e])}var d=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement),f=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,p=Object.prototype.hasOwnProperty,m={},h={};function g(e,t,n,r,a,o,i){this.acceptsBooleans=2===t||3===t||4===t,this.attributeName=r,this.attributeNamespace=a,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=o,this.removeEmptyString=i}var b={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach((function(e){b[e]=new g(e,0,!1,e,null,!1,!1)})),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach((function(e){var t=e[0];b[t]=new g(t,1,!1,e[1],null,!1,!1)})),["contentEditable","draggable","spellCheck","value"].forEach((function(e){b[e]=new g(e,2,!1,e.toLowerCase(),null,!1,!1)})),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach((function(e){b[e]=new g(e,2,!1,e,null,!1,!1)})),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach((function(e){b[e]=new g(e,3,!1,e.toLowerCase(),null,!1,!1)})),["checked","multiple","muted","selected"].forEach((function(e){b[e]=new g(e,3,!0,e,null,!1,!1)})),["capture","download"].forEach((function(e){b[e]=new g(e,4,!1,e,null,!1,!1)})),["cols","rows","size","span"].forEach((function(e){b[e]=new g(e,6,!1,e,null,!1,!1)})),["rowSpan","start"].forEach((function(e){b[e]=new g(e,5,!1,e.toLowerCase(),null,!1,!1)}));var v=/[\-:]([a-z])/g;function y(e){return e[1].toUpperCase()}function w(e,t,n,r){var a=b.hasOwnProperty(t)?b[t]:null;(null!==a?0===a.type:!r&&(2<t.length&&("o"===t[0]||"O"===t[0])&&("n"===t[1]||"N"===t[1])))||(function(e,t,n,r){if(null==t||function(e,t,n,r){if(null!==n&&0===n.type)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return!r&&(null!==n?!n.acceptsBooleans:"data-"!==(e=e.toLowerCase().slice(0,5))&&"aria-"!==e);default:return!1}}(e,t,n,r))return!0;if(r)return!1;if(null!==n)switch(n.type){case 3:return!t;case 4:return!1===t;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}(t,n,a,r)&&(n=null),r||null===a?function(e){return!!p.call(h,e)||!p.call(m,e)&&(f.test(e)?h[e]=!0:(m[e]=!0,!1))}(t)&&(null===n?e.removeAttribute(t):e.setAttribute(t,""+n)):a.mustUseProperty?e[a.propertyName]=null===n?3!==a.type&&"":n:(t=a.attributeName,r=a.attributeNamespace,null===n?e.removeAttribute(t):(n=3===(a=a.type)||4===a&&!0===n?"":""+n,r?e.setAttributeNS(r,t,n):e.setAttribute(t,n))))}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach((function(e){var t=e.replace(v,y);b[t]=new g(t,1,!1,e,null,!1,!1)})),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach((function(e){var t=e.replace(v,y);b[t]=new g(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)})),["xml:base","xml:lang","xml:space"].forEach((function(e){var t=e.replace(v,y);b[t]=new g(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)})),["tabIndex","crossOrigin"].forEach((function(e){b[e]=new g(e,1,!1,e.toLowerCase(),null,!1,!1)})),b.xlinkHref=new g("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach((function(e){b[e]=new g(e,1,!1,e.toLowerCase(),null,!0,!0)}));var E=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,k=60103,S=60106,x=60107,_=60108,C=60114,A=60109,T=60110,N=60112,O=60113,L=60120,P=60115,R=60116,I=60121,M=60128,D=60129,F=60130,B=60131;if("function"==typeof Symbol&&Symbol.for){var $=Symbol.for;k=$("react.element"),S=$("react.portal"),x=$("react.fragment"),_=$("react.strict_mode"),C=$("react.profiler"),A=$("react.provider"),T=$("react.context"),N=$("react.forward_ref"),O=$("react.suspense"),L=$("react.suspense_list"),P=$("react.memo"),R=$("react.lazy"),I=$("react.block"),$("react.scope"),M=$("react.opaque.id"),D=$("react.debug_trace_mode"),F=$("react.offscreen"),B=$("react.legacy_hidden")}var z,U="function"==typeof Symbol&&Symbol.iterator;function j(e){return null===e||"object"!=typeof e?null:"function"==typeof(e=U&&e[U]||e["@@iterator"])?e:null}function H(e){if(void 0===z)try{throw Error()}catch(n){var t=n.stack.trim().match(/\n( *(at )?)/);z=t&&t[1]||""}return"\n"+z+e}var q=!1;function V(e,t){if(!e||q)return"";q=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(t)if(t=function(){throw Error()},Object.defineProperty(t.prototype,"props",{set:function(){throw Error()}}),"object"==typeof Reflect&&Reflect.construct){try{Reflect.construct(t,[])}catch(s){var r=s}Reflect.construct(e,[],t)}else{try{t.call()}catch(s){r=s}e.call(t.prototype)}else{try{throw Error()}catch(s){r=s}e()}}catch(s){if(s&&r&&"string"==typeof s.stack){for(var a=s.stack.split("\n"),o=r.stack.split("\n"),i=a.length-1,l=o.length-1;1<=i&&0<=l&&a[i]!==o[l];)l--;for(;1<=i&&0<=l;i--,l--)if(a[i]!==o[l]){if(1!==i||1!==l)do{if(i--,0>--l||a[i]!==o[l])return"\n"+a[i].replace(" at new "," at ")}while(1<=i&&0<=l);break}}}finally{q=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?H(e):""}function G(e){switch(e.tag){case 5:return H(e.type);case 16:return H("Lazy");case 13:return H("Suspense");case 19:return H("SuspenseList");case 0:case 2:case 15:return e=V(e.type,!1);case 11:return e=V(e.type.render,!1);case 22:return e=V(e.type._render,!1);case 1:return e=V(e.type,!0);default:return""}}function W(e){if(null==e)return null;if("function"==typeof e)return e.displayName||e.name||null;if("string"==typeof e)return e;switch(e){case x:return"Fragment";case S:return"Portal";case C:return"Profiler";case _:return"StrictMode";case O:return"Suspense";case L:return"SuspenseList"}if("object"==typeof e)switch(e.$$typeof){case T:return(e.displayName||"Context")+".Consumer";case A:return(e._context.displayName||"Context")+".Provider";case N:var t=e.render;return t=t.displayName||t.name||"",e.displayName||(""!==t?"ForwardRef("+t+")":"ForwardRef");case P:return W(e.type);case I:return W(e._render);case R:t=e._payload,e=e._init;try{return W(e(t))}catch(n){}}return null}function Y(e){switch(typeof e){case"boolean":case"number":case"object":case"string":case"undefined":return e;default:return""}}function K(e){var t=e.type;return(e=e.nodeName)&&"input"===e.toLowerCase()&&("checkbox"===t||"radio"===t)}function Q(e){e._valueTracker||(e._valueTracker=function(e){var t=K(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&void 0!==n&&"function"==typeof n.get&&"function"==typeof n.set){var a=n.get,o=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return a.call(this)},set:function(e){r=""+e,o.call(this,e)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(e){r=""+e},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}(e))}function X(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=K(e)?e.checked?"true":"false":e.value),(e=r)!==n&&(t.setValue(e),!0)}function Z(e){if(void 0===(e=e||("undefined"!=typeof document?document:void 0)))return null;try{return e.activeElement||e.body}catch(t){return e.body}}function J(e,t){var n=t.checked;return a({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:null!=n?n:e._wrapperState.initialChecked})}function ee(e,t){var n=null==t.defaultValue?"":t.defaultValue,r=null!=t.checked?t.checked:t.defaultChecked;n=Y(null!=t.value?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:"checkbox"===t.type||"radio"===t.type?null!=t.checked:null!=t.value}}function te(e,t){null!=(t=t.checked)&&w(e,"checked",t,!1)}function ne(e,t){te(e,t);var n=Y(t.value),r=t.type;if(null!=n)"number"===r?(0===n&&""===e.value||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if("submit"===r||"reset"===r)return void e.removeAttribute("value");t.hasOwnProperty("value")?ae(e,t.type,n):t.hasOwnProperty("defaultValue")&&ae(e,t.type,Y(t.defaultValue)),null==t.checked&&null!=t.defaultChecked&&(e.defaultChecked=!!t.defaultChecked)}function re(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!("submit"!==r&&"reset"!==r||void 0!==t.value&&null!==t.value))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}""!==(n=e.name)&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,""!==n&&(e.name=n)}function ae(e,t,n){"number"===t&&Z(e.ownerDocument)===e||(null==n?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}function oe(e,t){return e=a({children:void 0},t),(t=function(e){var t="";return r.Children.forEach(e,(function(e){null!=e&&(t+=e)})),t}(t.children))&&(e.children=t),e}function ie(e,t,n,r){if(e=e.options,t){t={};for(var a=0;a<n.length;a++)t["$"+n[a]]=!0;for(n=0;n<e.length;n++)a=t.hasOwnProperty("$"+e[n].value),e[n].selected!==a&&(e[n].selected=a),a&&r&&(e[n].defaultSelected=!0)}else{for(n=""+Y(n),t=null,a=0;a<e.length;a++){if(e[a].value===n)return e[a].selected=!0,void(r&&(e[a].defaultSelected=!0));null!==t||e[a].disabled||(t=e[a])}null!==t&&(t.selected=!0)}}function le(e,t){if(null!=t.dangerouslySetInnerHTML)throw Error(i(91));return a({},t,{value:void 0,defaultValue:void 0,children:""+e._wrapperState.initialValue})}function se(e,t){var n=t.value;if(null==n){if(n=t.children,t=t.defaultValue,null!=n){if(null!=t)throw Error(i(92));if(Array.isArray(n)){if(!(1>=n.length))throw Error(i(93));n=n[0]}t=n}null==t&&(t=""),n=t}e._wrapperState={initialValue:Y(n)}}function ue(e,t){var n=Y(t.value),r=Y(t.defaultValue);null!=n&&((n=""+n)!==e.value&&(e.value=n),null==t.defaultValue&&e.defaultValue!==n&&(e.defaultValue=n)),null!=r&&(e.defaultValue=""+r)}function ce(e){var t=e.textContent;t===e._wrapperState.initialValue&&""!==t&&null!==t&&(e.value=t)}var de="http://www.w3.org/1999/xhtml",fe="http://www.w3.org/2000/svg";function pe(e){switch(e){case"svg":return"http://www.w3.org/2000/svg";case"math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function me(e,t){return null==e||"http://www.w3.org/1999/xhtml"===e?pe(t):"http://www.w3.org/2000/svg"===e&&"foreignObject"===t?"http://www.w3.org/1999/xhtml":e}var he,ge,be=(ge=function(e,t){if(e.namespaceURI!==fe||"innerHTML"in e)e.innerHTML=t;else{for((he=he||document.createElement("div")).innerHTML="<svg>"+t.valueOf().toString()+"</svg>",t=he.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}},"undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(e,t,n,r){MSApp.execUnsafeLocalFunction((function(){return ge(e,t)}))}:ge);function ve(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t}var ye={animationIterationCount:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},we=["Webkit","ms","Moz","O"];function Ee(e,t,n){return null==t||"boolean"==typeof t||""===t?"":n||"number"!=typeof t||0===t||ye.hasOwnProperty(e)&&ye[e]?(""+t).trim():t+"px"}function ke(e,t){for(var n in e=e.style,t)if(t.hasOwnProperty(n)){var r=0===n.indexOf("--"),a=Ee(n,t[n],r);"float"===n&&(n="cssFloat"),r?e.setProperty(n,a):e[n]=a}}Object.keys(ye).forEach((function(e){we.forEach((function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),ye[t]=ye[e]}))}));var Se=a({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function xe(e,t){if(t){if(Se[e]&&(null!=t.children||null!=t.dangerouslySetInnerHTML))throw Error(i(137,e));if(null!=t.dangerouslySetInnerHTML){if(null!=t.children)throw Error(i(60));if("object"!=typeof t.dangerouslySetInnerHTML||!("__html"in t.dangerouslySetInnerHTML))throw Error(i(61))}if(null!=t.style&&"object"!=typeof t.style)throw Error(i(62))}}function _e(e,t){if(-1===e.indexOf("-"))return"string"==typeof t.is;switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}function Ce(e){return(e=e.target||e.srcElement||window).correspondingUseElement&&(e=e.correspondingUseElement),3===e.nodeType?e.parentNode:e}var Ae=null,Te=null,Ne=null;function Oe(e){if(e=ra(e)){if("function"!=typeof Ae)throw Error(i(280));var t=e.stateNode;t&&(t=oa(t),Ae(e.stateNode,e.type,t))}}function Le(e){Te?Ne?Ne.push(e):Ne=[e]:Te=e}function Pe(){if(Te){var e=Te,t=Ne;if(Ne=Te=null,Oe(e),t)for(e=0;e<t.length;e++)Oe(t[e])}}function Re(e,t){return e(t)}function Ie(e,t,n,r,a){return e(t,n,r,a)}function Me(){}var De=Re,Fe=!1,Be=!1;function $e(){null===Te&&null===Ne||(Me(),Pe())}function ze(e,t){var n=e.stateNode;if(null===n)return null;var r=oa(n);if(null===r)return null;n=r[t];e:switch(t){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(r=!r.disabled)||(r=!("button"===(e=e.type)||"input"===e||"select"===e||"textarea"===e)),e=!r;break e;default:e=!1}if(e)return null;if(n&&"function"!=typeof n)throw Error(i(231,t,typeof n));return n}var Ue=!1;if(d)try{var je={};Object.defineProperty(je,"passive",{get:function(){Ue=!0}}),window.addEventListener("test",je,je),window.removeEventListener("test",je,je)}catch(ge){Ue=!1}function He(e,t,n,r,a,o,i,l,s){var u=Array.prototype.slice.call(arguments,3);try{t.apply(n,u)}catch(c){this.onError(c)}}var qe=!1,Ve=null,Ge=!1,We=null,Ye={onError:function(e){qe=!0,Ve=e}};function Ke(e,t,n,r,a,o,i,l,s){qe=!1,Ve=null,He.apply(Ye,arguments)}function Qe(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do{0!=(1026&(t=e).flags)&&(n=t.return),e=t.return}while(e)}return 3===t.tag?n:null}function Xe(e){if(13===e.tag){var t=e.memoizedState;if(null===t&&(null!==(e=e.alternate)&&(t=e.memoizedState)),null!==t)return t.dehydrated}return null}function Ze(e){if(Qe(e)!==e)throw Error(i(188))}function Je(e){if(e=function(e){var t=e.alternate;if(!t){if(null===(t=Qe(e)))throw Error(i(188));return t!==e?null:e}for(var n=e,r=t;;){var a=n.return;if(null===a)break;var o=a.alternate;if(null===o){if(null!==(r=a.return)){n=r;continue}break}if(a.child===o.child){for(o=a.child;o;){if(o===n)return Ze(a),e;if(o===r)return Ze(a),t;o=o.sibling}throw Error(i(188))}if(n.return!==r.return)n=a,r=o;else{for(var l=!1,s=a.child;s;){if(s===n){l=!0,n=a,r=o;break}if(s===r){l=!0,r=a,n=o;break}s=s.sibling}if(!l){for(s=o.child;s;){if(s===n){l=!0,n=o,r=a;break}if(s===r){l=!0,r=o,n=a;break}s=s.sibling}if(!l)throw Error(i(189))}}if(n.alternate!==r)throw Error(i(190))}if(3!==n.tag)throw Error(i(188));return n.stateNode.current===n?e:t}(e),!e)return null;for(var t=e;;){if(5===t.tag||6===t.tag)return t;if(t.child)t.child.return=t,t=t.child;else{if(t===e)break;for(;!t.sibling;){if(!t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}}return null}function et(e,t){for(var n=e.alternate;null!==t;){if(t===e||t===n)return!0;t=t.return}return!1}var tt,nt,rt,at,ot=!1,it=[],lt=null,st=null,ut=null,ct=new Map,dt=new Map,ft=[],pt="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset submit".split(" ");function mt(e,t,n,r,a){return{blockedOn:e,domEventName:t,eventSystemFlags:16|n,nativeEvent:a,targetContainers:[r]}}function ht(e,t){switch(e){case"focusin":case"focusout":lt=null;break;case"dragenter":case"dragleave":st=null;break;case"mouseover":case"mouseout":ut=null;break;case"pointerover":case"pointerout":ct.delete(t.pointerId);break;case"gotpointercapture":case"lostpointercapture":dt.delete(t.pointerId)}}function gt(e,t,n,r,a,o){return null===e||e.nativeEvent!==o?(e=mt(t,n,r,a,o),null!==t&&(null!==(t=ra(t))&&nt(t)),e):(e.eventSystemFlags|=r,t=e.targetContainers,null!==a&&-1===t.indexOf(a)&&t.push(a),e)}function bt(e){var t=na(e.target);if(null!==t){var n=Qe(t);if(null!==n)if(13===(t=n.tag)){if(null!==(t=Xe(n)))return e.blockedOn=t,void at(e.lanePriority,(function(){o.unstable_runWithPriority(e.priority,(function(){rt(n)}))}))}else if(3===t&&n.stateNode.hydrate)return void(e.blockedOn=3===n.tag?n.stateNode.containerInfo:null)}e.blockedOn=null}function vt(e){if(null!==e.blockedOn)return!1;for(var t=e.targetContainers;0<t.length;){var n=Jt(e.domEventName,e.eventSystemFlags,t[0],e.nativeEvent);if(null!==n)return null!==(t=ra(n))&&nt(t),e.blockedOn=n,!1;t.shift()}return!0}function yt(e,t,n){vt(e)&&n.delete(t)}function wt(){for(ot=!1;0<it.length;){var e=it[0];if(null!==e.blockedOn){null!==(e=ra(e.blockedOn))&&tt(e);break}for(var t=e.targetContainers;0<t.length;){var n=Jt(e.domEventName,e.eventSystemFlags,t[0],e.nativeEvent);if(null!==n){e.blockedOn=n;break}t.shift()}null===e.blockedOn&&it.shift()}null!==lt&&vt(lt)&&(lt=null),null!==st&&vt(st)&&(st=null),null!==ut&&vt(ut)&&(ut=null),ct.forEach(yt),dt.forEach(yt)}function Et(e,t){e.blockedOn===t&&(e.blockedOn=null,ot||(ot=!0,o.unstable_scheduleCallback(o.unstable_NormalPriority,wt)))}function kt(e){function t(t){return Et(t,e)}if(0<it.length){Et(it[0],e);for(var n=1;n<it.length;n++){var r=it[n];r.blockedOn===e&&(r.blockedOn=null)}}for(null!==lt&&Et(lt,e),null!==st&&Et(st,e),null!==ut&&Et(ut,e),ct.forEach(t),dt.forEach(t),n=0;n<ft.length;n++)(r=ft[n]).blockedOn===e&&(r.blockedOn=null);for(;0<ft.length&&null===(n=ft[0]).blockedOn;)bt(n),null===n.blockedOn&&ft.shift()}function St(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var xt={animationend:St("Animation","AnimationEnd"),animationiteration:St("Animation","AnimationIteration"),animationstart:St("Animation","AnimationStart"),transitionend:St("Transition","TransitionEnd")},_t={},Ct={};function At(e){if(_t[e])return _t[e];if(!xt[e])return e;var t,n=xt[e];for(t in n)if(n.hasOwnProperty(t)&&t in Ct)return _t[e]=n[t];return e}d&&(Ct=document.createElement("div").style,"AnimationEvent"in window||(delete xt.animationend.animation,delete xt.animationiteration.animation,delete xt.animationstart.animation),"TransitionEvent"in window||delete xt.transitionend.transition);var Tt=At("animationend"),Nt=At("animationiteration"),Ot=At("animationstart"),Lt=At("transitionend"),Pt=new Map,Rt=new Map,It=["abort","abort",Tt,"animationEnd",Nt,"animationIteration",Ot,"animationStart","canplay","canPlay","canplaythrough","canPlayThrough","durationchange","durationChange","emptied","emptied","encrypted","encrypted","ended","ended","error","error","gotpointercapture","gotPointerCapture","load","load","loadeddata","loadedData","loadedmetadata","loadedMetadata","loadstart","loadStart","lostpointercapture","lostPointerCapture","playing","playing","progress","progress","seeking","seeking","stalled","stalled","suspend","suspend","timeupdate","timeUpdate",Lt,"transitionEnd","waiting","waiting"];function Mt(e,t){for(var n=0;n<e.length;n+=2){var r=e[n],a=e[n+1];a="on"+(a[0].toUpperCase()+a.slice(1)),Rt.set(r,t),Pt.set(r,a),u(a,[r])}}(0,o.unstable_now)();var Dt=8;function Ft(e){if(0!=(1&e))return Dt=15,1;if(0!=(2&e))return Dt=14,2;if(0!=(4&e))return Dt=13,4;var t=24&e;return 0!==t?(Dt=12,t):0!=(32&e)?(Dt=11,32):0!==(t=192&e)?(Dt=10,t):0!=(256&e)?(Dt=9,256):0!==(t=3584&e)?(Dt=8,t):0!=(4096&e)?(Dt=7,4096):0!==(t=4186112&e)?(Dt=6,t):0!==(t=62914560&e)?(Dt=5,t):67108864&e?(Dt=4,67108864):0!=(134217728&e)?(Dt=3,134217728):0!==(t=805306368&e)?(Dt=2,t):0!=(1073741824&e)?(Dt=1,1073741824):(Dt=8,e)}function Bt(e,t){var n=e.pendingLanes;if(0===n)return Dt=0;var r=0,a=0,o=e.expiredLanes,i=e.suspendedLanes,l=e.pingedLanes;if(0!==o)r=o,a=Dt=15;else if(0!==(o=134217727&n)){var s=o&~i;0!==s?(r=Ft(s),a=Dt):0!==(l&=o)&&(r=Ft(l),a=Dt)}else 0!==(o=n&~i)?(r=Ft(o),a=Dt):0!==l&&(r=Ft(l),a=Dt);if(0===r)return 0;if(r=n&((0>(r=31-qt(r))?0:1<<r)<<1)-1,0!==t&&t!==r&&0==(t&i)){if(Ft(t),a<=Dt)return t;Dt=a}if(0!==(t=e.entangledLanes))for(e=e.entanglements,t&=r;0<t;)a=1<<(n=31-qt(t)),r|=e[n],t&=~a;return r}function $t(e){return 0!==(e=-1073741825&e.pendingLanes)?e:1073741824&e?1073741824:0}function zt(e,t){switch(e){case 15:return 1;case 14:return 2;case 12:return 0===(e=Ut(24&~t))?zt(10,t):e;case 10:return 0===(e=Ut(192&~t))?zt(8,t):e;case 8:return 0===(e=Ut(3584&~t))&&(0===(e=Ut(4186112&~t))&&(e=512)),e;case 2:return 0===(t=Ut(805306368&~t))&&(t=268435456),t}throw Error(i(358,e))}function Ut(e){return e&-e}function jt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function Ht(e,t,n){e.pendingLanes|=t;var r=t-1;e.suspendedLanes&=r,e.pingedLanes&=r,(e=e.eventTimes)[t=31-qt(t)]=n}var qt=Math.clz32?Math.clz32:function(e){return 0===e?32:31-(Vt(e)/Gt|0)|0},Vt=Math.log,Gt=Math.LN2;var Wt=o.unstable_UserBlockingPriority,Yt=o.unstable_runWithPriority,Kt=!0;function Qt(e,t,n,r){Fe||Me();var a=Zt,o=Fe;Fe=!0;try{Ie(a,e,t,n,r)}finally{(Fe=o)||$e()}}function Xt(e,t,n,r){Yt(Wt,Zt.bind(null,e,t,n,r))}function Zt(e,t,n,r){var a;if(Kt)if((a=0==(4&t))&&0<it.length&&-1<pt.indexOf(e))e=mt(null,e,t,n,r),it.push(e);else{var o=Jt(e,t,n,r);if(null===o)a&&ht(e,r);else{if(a){if(-1<pt.indexOf(e))return e=mt(o,e,t,n,r),void it.push(e);if(function(e,t,n,r,a){switch(t){case"focusin":return lt=gt(lt,e,t,n,r,a),!0;case"dragenter":return st=gt(st,e,t,n,r,a),!0;case"mouseover":return ut=gt(ut,e,t,n,r,a),!0;case"pointerover":var o=a.pointerId;return ct.set(o,gt(ct.get(o)||null,e,t,n,r,a)),!0;case"gotpointercapture":return o=a.pointerId,dt.set(o,gt(dt.get(o)||null,e,t,n,r,a)),!0}return!1}(o,e,t,n,r))return;ht(e,r)}Mr(e,t,r,null,n)}}}function Jt(e,t,n,r){var a=Ce(r);if(null!==(a=na(a))){var o=Qe(a);if(null===o)a=null;else{var i=o.tag;if(13===i){if(null!==(a=Xe(o)))return a;a=null}else if(3===i){if(o.stateNode.hydrate)return 3===o.tag?o.stateNode.containerInfo:null;a=null}else o!==a&&(a=null)}}return Mr(e,t,r,a,n),null}var en=null,tn=null,nn=null;function rn(){if(nn)return nn;var e,t,n=tn,r=n.length,a="value"in en?en.value:en.textContent,o=a.length;for(e=0;e<r&&n[e]===a[e];e++);var i=r-e;for(t=1;t<=i&&n[r-t]===a[o-t];t++);return nn=a.slice(e,1<t?1-t:void 0)}function an(e){var t=e.keyCode;return"charCode"in e?0===(e=e.charCode)&&13===t&&(e=13):e=t,10===e&&(e=13),32<=e||13===e?e:0}function on(){return!0}function ln(){return!1}function sn(e){function t(t,n,r,a,o){for(var i in this._reactName=t,this._targetInst=r,this.type=n,this.nativeEvent=a,this.target=o,this.currentTarget=null,e)e.hasOwnProperty(i)&&(t=e[i],this[i]=t?t(a):a[i]);return this.isDefaultPrevented=(null!=a.defaultPrevented?a.defaultPrevented:!1===a.returnValue)?on:ln,this.isPropagationStopped=ln,this}return a(t.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=on)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=on)},persist:function(){},isPersistent:on}),t}var un,cn,dn,fn={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},pn=sn(fn),mn=a({},fn,{view:0,detail:0}),hn=sn(mn),gn=a({},mn,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:Tn,button:0,buttons:0,relatedTarget:function(e){return void 0===e.relatedTarget?e.fromElement===e.srcElement?e.toElement:e.fromElement:e.relatedTarget},movementX:function(e){return"movementX"in e?e.movementX:(e!==dn&&(dn&&"mousemove"===e.type?(un=e.screenX-dn.screenX,cn=e.screenY-dn.screenY):cn=un=0,dn=e),un)},movementY:function(e){return"movementY"in e?e.movementY:cn}}),bn=sn(gn),vn=sn(a({},gn,{dataTransfer:0})),yn=sn(a({},mn,{relatedTarget:0})),wn=sn(a({},fn,{animationName:0,elapsedTime:0,pseudoElement:0})),En=a({},fn,{clipboardData:function(e){return"clipboardData"in e?e.clipboardData:window.clipboardData}}),kn=sn(En),Sn=sn(a({},fn,{data:0})),xn={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},_n={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},Cn={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function An(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):!!(e=Cn[e])&&!!t[e]}function Tn(){return An}var Nn=a({},mn,{key:function(e){if(e.key){var t=xn[e.key]||e.key;if("Unidentified"!==t)return t}return"keypress"===e.type?13===(e=an(e))?"Enter":String.fromCharCode(e):"keydown"===e.type||"keyup"===e.type?_n[e.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,repeat:0,locale:0,getModifierState:Tn,charCode:function(e){return"keypress"===e.type?an(e):0},keyCode:function(e){return"keydown"===e.type||"keyup"===e.type?e.keyCode:0},which:function(e){return"keypress"===e.type?an(e):"keydown"===e.type||"keyup"===e.type?e.keyCode:0}}),On=sn(Nn),Ln=sn(a({},gn,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0})),Pn=sn(a({},mn,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,ctrlKey:0,shiftKey:0,getModifierState:Tn})),Rn=sn(a({},fn,{propertyName:0,elapsedTime:0,pseudoElement:0})),In=a({},gn,{deltaX:function(e){return"deltaX"in e?e.deltaX:"wheelDeltaX"in e?-e.wheelDeltaX:0},deltaY:function(e){return"deltaY"in e?e.deltaY:"wheelDeltaY"in e?-e.wheelDeltaY:"wheelDelta"in e?-e.wheelDelta:0},deltaZ:0,deltaMode:0}),Mn=sn(In),Dn=[9,13,27,32],Fn=d&&"CompositionEvent"in window,Bn=null;d&&"documentMode"in document&&(Bn=document.documentMode);var $n=d&&"TextEvent"in window&&!Bn,zn=d&&(!Fn||Bn&&8<Bn&&11>=Bn),Un=String.fromCharCode(32),jn=!1;function Hn(e,t){switch(e){case"keyup":return-1!==Dn.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function qn(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var Vn=!1;var Gn={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function Wn(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!Gn[e.type]:"textarea"===t}function Yn(e,t,n,r){Le(r),0<(t=Fr(t,"onChange")).length&&(n=new pn("onChange","change",null,n,r),e.push({event:n,listeners:t}))}var Kn=null,Qn=null;function Xn(e){Nr(e,0)}function Zn(e){if(X(aa(e)))return e}function Jn(e,t){if("change"===e)return t}var er=!1;if(d){var tr;if(d){var nr="oninput"in document;if(!nr){var rr=document.createElement("div");rr.setAttribute("oninput","return;"),nr="function"==typeof rr.oninput}tr=nr}else tr=!1;er=tr&&(!document.documentMode||9<document.documentMode)}function ar(){Kn&&(Kn.detachEvent("onpropertychange",or),Qn=Kn=null)}function or(e){if("value"===e.propertyName&&Zn(Qn)){var t=[];if(Yn(t,Qn,e,Ce(e)),e=Xn,Fe)e(t);else{Fe=!0;try{Re(e,t)}finally{Fe=!1,$e()}}}}function ir(e,t,n){"focusin"===e?(ar(),Qn=n,(Kn=t).attachEvent("onpropertychange",or)):"focusout"===e&&ar()}function lr(e){if("selectionchange"===e||"keyup"===e||"keydown"===e)return Zn(Qn)}function sr(e,t){if("click"===e)return Zn(t)}function ur(e,t){if("input"===e||"change"===e)return Zn(t)}var cr="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},dr=Object.prototype.hasOwnProperty;function fr(e,t){if(cr(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(r=0;r<n.length;r++)if(!dr.call(t,n[r])||!cr(e[n[r]],t[n[r]]))return!1;return!0}function pr(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function mr(e,t){var n,r=pr(e);for(e=0;r;){if(3===r.nodeType){if(n=e+r.textContent.length,e<=t&&n>=t)return{node:r,offset:t-e};e=n}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=pr(r)}}function hr(e,t){return!(!e||!t)&&(e===t||(!e||3!==e.nodeType)&&(t&&3===t.nodeType?hr(e,t.parentNode):"contains"in e?e.contains(t):!!e.compareDocumentPosition&&!!(16&e.compareDocumentPosition(t))))}function gr(){for(var e=window,t=Z();t instanceof e.HTMLIFrameElement;){try{var n="string"==typeof t.contentWindow.location.href}catch(r){n=!1}if(!n)break;t=Z((e=t.contentWindow).document)}return t}function br(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&("input"===t&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===t||"true"===e.contentEditable)}var vr=d&&"documentMode"in document&&11>=document.documentMode,yr=null,wr=null,Er=null,kr=!1;function Sr(e,t,n){var r=n.window===n?n.document:9===n.nodeType?n:n.ownerDocument;kr||null==yr||yr!==Z(r)||("selectionStart"in(r=yr)&&br(r)?r={start:r.selectionStart,end:r.selectionEnd}:r={anchorNode:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset},Er&&fr(Er,r)||(Er=r,0<(r=Fr(wr,"onSelect")).length&&(t=new pn("onSelect","select",null,t,n),e.push({event:t,listeners:r}),t.target=yr)))}Mt("cancel cancel click click close close contextmenu contextMenu copy copy cut cut auxclick auxClick dblclick doubleClick dragend dragEnd dragstart dragStart drop drop focusin focus focusout blur input input invalid invalid keydown keyDown keypress keyPress keyup keyUp mousedown mouseDown mouseup mouseUp paste paste pause pause play play pointercancel pointerCancel pointerdown pointerDown pointerup pointerUp ratechange rateChange reset reset seeked seeked submit submit touchcancel touchCancel touchend touchEnd touchstart touchStart volumechange volumeChange".split(" "),0),Mt("drag drag dragenter dragEnter dragexit dragExit dragleave dragLeave dragover dragOver mousemove mouseMove mouseout mouseOut mouseover mouseOver pointermove pointerMove pointerout pointerOut pointerover pointerOver scroll scroll toggle toggle touchmove touchMove wheel wheel".split(" "),1),Mt(It,2);for(var xr="change selectionchange textInput compositionstart compositionend compositionupdate".split(" "),_r=0;_r<xr.length;_r++)Rt.set(xr[_r],0);c("onMouseEnter",["mouseout","mouseover"]),c("onMouseLeave",["mouseout","mouseover"]),c("onPointerEnter",["pointerout","pointerover"]),c("onPointerLeave",["pointerout","pointerover"]),u("onChange","change click focusin focusout input keydown keyup selectionchange".split(" ")),u("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" ")),u("onBeforeInput",["compositionend","keypress","textInput","paste"]),u("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" ")),u("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" ")),u("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));var Cr="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Ar=new Set("cancel close invalid load scroll toggle".split(" ").concat(Cr));function Tr(e,t,n){var r=e.type||"unknown-event";e.currentTarget=n,function(e,t,n,r,a,o,l,s,u){if(Ke.apply(this,arguments),qe){if(!qe)throw Error(i(198));var c=Ve;qe=!1,Ve=null,Ge||(Ge=!0,We=c)}}(r,t,void 0,e),e.currentTarget=null}function Nr(e,t){t=0!=(4&t);for(var n=0;n<e.length;n++){var r=e[n],a=r.event;r=r.listeners;e:{var o=void 0;if(t)for(var i=r.length-1;0<=i;i--){var l=r[i],s=l.instance,u=l.currentTarget;if(l=l.listener,s!==o&&a.isPropagationStopped())break e;Tr(a,l,u),o=s}else for(i=0;i<r.length;i++){if(s=(l=r[i]).instance,u=l.currentTarget,l=l.listener,s!==o&&a.isPropagationStopped())break e;Tr(a,l,u),o=s}}}if(Ge)throw e=We,Ge=!1,We=null,e}function Or(e,t){var n=ia(t),r=e+"__bubble";n.has(r)||(Ir(t,e,2,!1),n.add(r))}var Lr="_reactListening"+Math.random().toString(36).slice(2);function Pr(e){e[Lr]||(e[Lr]=!0,l.forEach((function(t){Ar.has(t)||Rr(t,!1,e,null),Rr(t,!0,e,null)})))}function Rr(e,t,n,r){var a=4<arguments.length&&void 0!==arguments[4]?arguments[4]:0,o=n;if("selectionchange"===e&&9!==n.nodeType&&(o=n.ownerDocument),null!==r&&!t&&Ar.has(e)){if("scroll"!==e)return;a|=2,o=r}var i=ia(o),l=e+"__"+(t?"capture":"bubble");i.has(l)||(t&&(a|=4),Ir(o,e,a,t),i.add(l))}function Ir(e,t,n,r){var a=Rt.get(t);switch(void 0===a?2:a){case 0:a=Qt;break;case 1:a=Xt;break;default:a=Zt}n=a.bind(null,t,n,e),a=void 0,!Ue||"touchstart"!==t&&"touchmove"!==t&&"wheel"!==t||(a=!0),r?void 0!==a?e.addEventListener(t,n,{capture:!0,passive:a}):e.addEventListener(t,n,!0):void 0!==a?e.addEventListener(t,n,{passive:a}):e.addEventListener(t,n,!1)}function Mr(e,t,n,r,a){var o=r;if(0==(1&t)&&0==(2&t)&&null!==r)e:for(;;){if(null===r)return;var i=r.tag;if(3===i||4===i){var l=r.stateNode.containerInfo;if(l===a||8===l.nodeType&&l.parentNode===a)break;if(4===i)for(i=r.return;null!==i;){var s=i.tag;if((3===s||4===s)&&((s=i.stateNode.containerInfo)===a||8===s.nodeType&&s.parentNode===a))return;i=i.return}for(;null!==l;){if(null===(i=na(l)))return;if(5===(s=i.tag)||6===s){r=o=i;continue e}l=l.parentNode}}r=r.return}!function(e,t,n){if(Be)return e(t,n);Be=!0;try{De(e,t,n)}finally{Be=!1,$e()}}((function(){var r=o,a=Ce(n),i=[];e:{var l=Pt.get(e);if(void 0!==l){var s=pn,u=e;switch(e){case"keypress":if(0===an(n))break e;case"keydown":case"keyup":s=On;break;case"focusin":u="focus",s=yn;break;case"focusout":u="blur",s=yn;break;case"beforeblur":case"afterblur":s=yn;break;case"click":if(2===n.button)break e;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":s=bn;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":s=vn;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":s=Pn;break;case Tt:case Nt:case Ot:s=wn;break;case Lt:s=Rn;break;case"scroll":s=hn;break;case"wheel":s=Mn;break;case"copy":case"cut":case"paste":s=kn;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":s=Ln}var c=0!=(4&t),d=!c&&"scroll"===e,f=c?null!==l?l+"Capture":null:l;c=[];for(var p,m=r;null!==m;){var h=(p=m).stateNode;if(5===p.tag&&null!==h&&(p=h,null!==f&&(null!=(h=ze(m,f))&&c.push(Dr(m,h,p)))),d)break;m=m.return}0<c.length&&(l=new s(l,u,null,n,a),i.push({event:l,listeners:c}))}}if(0==(7&t)){if(s="mouseout"===e||"pointerout"===e,(!(l="mouseover"===e||"pointerover"===e)||0!=(16&t)||!(u=n.relatedTarget||n.fromElement)||!na(u)&&!u[ea])&&(s||l)&&(l=a.window===a?a:(l=a.ownerDocument)?l.defaultView||l.parentWindow:window,s?(s=r,null!==(u=(u=n.relatedTarget||n.toElement)?na(u):null)&&(u!==(d=Qe(u))||5!==u.tag&&6!==u.tag)&&(u=null)):(s=null,u=r),s!==u)){if(c=bn,h="onMouseLeave",f="onMouseEnter",m="mouse","pointerout"!==e&&"pointerover"!==e||(c=Ln,h="onPointerLeave",f="onPointerEnter",m="pointer"),d=null==s?l:aa(s),p=null==u?l:aa(u),(l=new c(h,m+"leave",s,n,a)).target=d,l.relatedTarget=p,h=null,na(a)===r&&((c=new c(f,m+"enter",u,n,a)).target=p,c.relatedTarget=d,h=c),d=h,s&&u)e:{for(f=u,m=0,p=c=s;p;p=Br(p))m++;for(p=0,h=f;h;h=Br(h))p++;for(;0<m-p;)c=Br(c),m--;for(;0<p-m;)f=Br(f),p--;for(;m--;){if(c===f||null!==f&&c===f.alternate)break e;c=Br(c),f=Br(f)}c=null}else c=null;null!==s&&$r(i,l,s,c,!1),null!==u&&null!==d&&$r(i,d,u,c,!0)}if("select"===(s=(l=r?aa(r):window).nodeName&&l.nodeName.toLowerCase())||"input"===s&&"file"===l.type)var g=Jn;else if(Wn(l))if(er)g=ur;else{g=lr;var b=ir}else(s=l.nodeName)&&"input"===s.toLowerCase()&&("checkbox"===l.type||"radio"===l.type)&&(g=sr);switch(g&&(g=g(e,r))?Yn(i,g,n,a):(b&&b(e,l,r),"focusout"===e&&(b=l._wrapperState)&&b.controlled&&"number"===l.type&&ae(l,"number",l.value)),b=r?aa(r):window,e){case"focusin":(Wn(b)||"true"===b.contentEditable)&&(yr=b,wr=r,Er=null);break;case"focusout":Er=wr=yr=null;break;case"mousedown":kr=!0;break;case"contextmenu":case"mouseup":case"dragend":kr=!1,Sr(i,n,a);break;case"selectionchange":if(vr)break;case"keydown":case"keyup":Sr(i,n,a)}var v;if(Fn)e:{switch(e){case"compositionstart":var y="onCompositionStart";break e;case"compositionend":y="onCompositionEnd";break e;case"compositionupdate":y="onCompositionUpdate";break e}y=void 0}else Vn?Hn(e,n)&&(y="onCompositionEnd"):"keydown"===e&&229===n.keyCode&&(y="onCompositionStart");y&&(zn&&"ko"!==n.locale&&(Vn||"onCompositionStart"!==y?"onCompositionEnd"===y&&Vn&&(v=rn()):(tn="value"in(en=a)?en.value:en.textContent,Vn=!0)),0<(b=Fr(r,y)).length&&(y=new Sn(y,e,null,n,a),i.push({event:y,listeners:b}),v?y.data=v:null!==(v=qn(n))&&(y.data=v))),(v=$n?function(e,t){switch(e){case"compositionend":return qn(t);case"keypress":return 32!==t.which?null:(jn=!0,Un);case"textInput":return(e=t.data)===Un&&jn?null:e;default:return null}}(e,n):function(e,t){if(Vn)return"compositionend"===e||!Fn&&Hn(e,t)?(e=rn(),nn=tn=en=null,Vn=!1,e):null;switch(e){case"paste":default:return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1<t.char.length)return t.char;if(t.which)return String.fromCharCode(t.which)}return null;case"compositionend":return zn&&"ko"!==t.locale?null:t.data}}(e,n))&&(0<(r=Fr(r,"onBeforeInput")).length&&(a=new Sn("onBeforeInput","beforeinput",null,n,a),i.push({event:a,listeners:r}),a.data=v))}Nr(i,t)}))}function Dr(e,t,n){return{instance:e,listener:t,currentTarget:n}}function Fr(e,t){for(var n=t+"Capture",r=[];null!==e;){var a=e,o=a.stateNode;5===a.tag&&null!==o&&(a=o,null!=(o=ze(e,n))&&r.unshift(Dr(e,o,a)),null!=(o=ze(e,t))&&r.push(Dr(e,o,a))),e=e.return}return r}function Br(e){if(null===e)return null;do{e=e.return}while(e&&5!==e.tag);return e||null}function $r(e,t,n,r,a){for(var o=t._reactName,i=[];null!==n&&n!==r;){var l=n,s=l.alternate,u=l.stateNode;if(null!==s&&s===r)break;5===l.tag&&null!==u&&(l=u,a?null!=(s=ze(n,o))&&i.unshift(Dr(n,s,l)):a||null!=(s=ze(n,o))&&i.push(Dr(n,s,l))),n=n.return}0!==i.length&&e.push({event:t,listeners:i})}function zr(){}var Ur=null,jr=null;function Hr(e,t){switch(e){case"button":case"input":case"select":case"textarea":return!!t.autoFocus}return!1}function qr(e,t){return"textarea"===e||"option"===e||"noscript"===e||"string"==typeof t.children||"number"==typeof t.children||"object"==typeof t.dangerouslySetInnerHTML&&null!==t.dangerouslySetInnerHTML&&null!=t.dangerouslySetInnerHTML.__html}var Vr="function"==typeof setTimeout?setTimeout:void 0,Gr="function"==typeof clearTimeout?clearTimeout:void 0;function Wr(e){1===e.nodeType?e.textContent="":9===e.nodeType&&(null!=(e=e.body)&&(e.textContent=""))}function Yr(e){for(;null!=e;e=e.nextSibling){var t=e.nodeType;if(1===t||3===t)break}return e}function Kr(e){e=e.previousSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if("$"===n||"$!"===n||"$?"===n){if(0===t)return e;t--}else"/$"===n&&t++}e=e.previousSibling}return null}var Qr=0;var Xr=Math.random().toString(36).slice(2),Zr="__reactFiber$"+Xr,Jr="__reactProps$"+Xr,ea="__reactContainer$"+Xr,ta="__reactEvents$"+Xr;function na(e){var t=e[Zr];if(t)return t;for(var n=e.parentNode;n;){if(t=n[ea]||n[Zr]){if(n=t.alternate,null!==t.child||null!==n&&null!==n.child)for(e=Kr(e);null!==e;){if(n=e[Zr])return n;e=Kr(e)}return t}n=(e=n).parentNode}return null}function ra(e){return!(e=e[Zr]||e[ea])||5!==e.tag&&6!==e.tag&&13!==e.tag&&3!==e.tag?null:e}function aa(e){if(5===e.tag||6===e.tag)return e.stateNode;throw Error(i(33))}function oa(e){return e[Jr]||null}function ia(e){var t=e[ta];return void 0===t&&(t=e[ta]=new Set),t}var la=[],sa=-1;function ua(e){return{current:e}}function ca(e){0>sa||(e.current=la[sa],la[sa]=null,sa--)}function da(e,t){sa++,la[sa]=e.current,e.current=t}var fa={},pa=ua(fa),ma=ua(!1),ha=fa;function ga(e,t){var n=e.type.contextTypes;if(!n)return fa;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var a,o={};for(a in n)o[a]=t[a];return r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=o),o}function ba(e){return null!=(e=e.childContextTypes)}function va(){ca(ma),ca(pa)}function ya(e,t,n){if(pa.current!==fa)throw Error(i(168));da(pa,t),da(ma,n)}function wa(e,t,n){var r=e.stateNode;if(e=t.childContextTypes,"function"!=typeof r.getChildContext)return n;for(var o in r=r.getChildContext())if(!(o in e))throw Error(i(108,W(t)||"Unknown",o));return a({},n,r)}function Ea(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||fa,ha=pa.current,da(pa,e),da(ma,ma.current),!0}function ka(e,t,n){var r=e.stateNode;if(!r)throw Error(i(169));n?(e=wa(e,t,ha),r.__reactInternalMemoizedMergedChildContext=e,ca(ma),ca(pa),da(pa,e)):ca(ma),da(ma,n)}var Sa=null,xa=null,_a=o.unstable_runWithPriority,Ca=o.unstable_scheduleCallback,Aa=o.unstable_cancelCallback,Ta=o.unstable_shouldYield,Na=o.unstable_requestPaint,Oa=o.unstable_now,La=o.unstable_getCurrentPriorityLevel,Pa=o.unstable_ImmediatePriority,Ra=o.unstable_UserBlockingPriority,Ia=o.unstable_NormalPriority,Ma=o.unstable_LowPriority,Da=o.unstable_IdlePriority,Fa={},Ba=void 0!==Na?Na:function(){},$a=null,za=null,Ua=!1,ja=Oa(),Ha=1e4>ja?Oa:function(){return Oa()-ja};function qa(){switch(La()){case Pa:return 99;case Ra:return 98;case Ia:return 97;case Ma:return 96;case Da:return 95;default:throw Error(i(332))}}function Va(e){switch(e){case 99:return Pa;case 98:return Ra;case 97:return Ia;case 96:return Ma;case 95:return Da;default:throw Error(i(332))}}function Ga(e,t){return e=Va(e),_a(e,t)}function Wa(e,t,n){return e=Va(e),Ca(e,t,n)}function Ya(){if(null!==za){var e=za;za=null,Aa(e)}Ka()}function Ka(){if(!Ua&&null!==$a){Ua=!0;var e=0;try{var t=$a;Ga(99,(function(){for(;e<t.length;e++){var n=t[e];do{n=n(!0)}while(null!==n)}})),$a=null}catch(n){throw null!==$a&&($a=$a.slice(e+1)),Ca(Pa,Ya),n}finally{Ua=!1}}}var Qa=E.ReactCurrentBatchConfig;function Xa(e,t){if(e&&e.defaultProps){for(var n in t=a({},t),e=e.defaultProps)void 0===t[n]&&(t[n]=e[n]);return t}return t}var Za=ua(null),Ja=null,eo=null,to=null;function no(){to=eo=Ja=null}function ro(e){var t=Za.current;ca(Za),e.type._context._currentValue=t}function ao(e,t){for(;null!==e;){var n=e.alternate;if((e.childLanes&t)===t){if(null===n||(n.childLanes&t)===t)break;n.childLanes|=t}else e.childLanes|=t,null!==n&&(n.childLanes|=t);e=e.return}}function oo(e,t){Ja=e,to=eo=null,null!==(e=e.dependencies)&&null!==e.firstContext&&(0!=(e.lanes&t)&&(Fi=!0),e.firstContext=null)}function io(e,t){if(to!==e&&!1!==t&&0!==t)if("number"==typeof t&&1073741823!==t||(to=e,t=1073741823),t={context:e,observedBits:t,next:null},null===eo){if(null===Ja)throw Error(i(308));eo=t,Ja.dependencies={lanes:0,firstContext:t,responders:null}}else eo=eo.next=t;return e._currentValue}var lo=!1;function so(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null},effects:null}}function uo(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function co(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function fo(e,t){if(null!==(e=e.updateQueue)){var n=(e=e.shared).pending;null===n?t.next=t:(t.next=n.next,n.next=t),e.pending=t}}function po(e,t){var n=e.updateQueue,r=e.alternate;if(null!==r&&n===(r=r.updateQueue)){var a=null,o=null;if(null!==(n=n.firstBaseUpdate)){do{var i={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};null===o?a=o=i:o=o.next=i,n=n.next}while(null!==n);null===o?a=o=t:o=o.next=t}else a=o=t;return n={baseState:r.baseState,firstBaseUpdate:a,lastBaseUpdate:o,shared:r.shared,effects:r.effects},void(e.updateQueue=n)}null===(e=n.lastBaseUpdate)?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function mo(e,t,n,r){var o=e.updateQueue;lo=!1;var i=o.firstBaseUpdate,l=o.lastBaseUpdate,s=o.shared.pending;if(null!==s){o.shared.pending=null;var u=s,c=u.next;u.next=null,null===l?i=c:l.next=c,l=u;var d=e.alternate;if(null!==d){var f=(d=d.updateQueue).lastBaseUpdate;f!==l&&(null===f?d.firstBaseUpdate=c:f.next=c,d.lastBaseUpdate=u)}}if(null!==i){for(f=o.baseState,l=0,d=c=u=null;;){s=i.lane;var p=i.eventTime;if((r&s)===s){null!==d&&(d=d.next={eventTime:p,lane:0,tag:i.tag,payload:i.payload,callback:i.callback,next:null});e:{var m=e,h=i;switch(s=t,p=n,h.tag){case 1:if("function"==typeof(m=h.payload)){f=m.call(p,f,s);break e}f=m;break e;case 3:m.flags=-4097&m.flags|64;case 0:if(null==(s="function"==typeof(m=h.payload)?m.call(p,f,s):m))break e;f=a({},f,s);break e;case 2:lo=!0}}null!==i.callback&&(e.flags|=32,null===(s=o.effects)?o.effects=[i]:s.push(i))}else p={eventTime:p,lane:s,tag:i.tag,payload:i.payload,callback:i.callback,next:null},null===d?(c=d=p,u=f):d=d.next=p,l|=s;if(null===(i=i.next)){if(null===(s=o.shared.pending))break;i=s.next,s.next=null,o.lastBaseUpdate=s,o.shared.pending=null}}null===d&&(u=f),o.baseState=u,o.firstBaseUpdate=c,o.lastBaseUpdate=d,Ul|=l,e.lanes=l,e.memoizedState=f}}function ho(e,t,n){if(e=t.effects,t.effects=null,null!==e)for(t=0;t<e.length;t++){var r=e[t],a=r.callback;if(null!==a){if(r.callback=null,r=n,"function"!=typeof a)throw Error(i(191,a));a.call(r)}}}var go=(new r.Component).refs;function bo(e,t,n,r){n=null==(n=n(r,t=e.memoizedState))?t:a({},t,n),e.memoizedState=n,0===e.lanes&&(e.updateQueue.baseState=n)}var vo={isMounted:function(e){return!!(e=e._reactInternals)&&Qe(e)===e},enqueueSetState:function(e,t,n){e=e._reactInternals;var r=fs(),a=ps(e),o=co(r,a);o.payload=t,null!=n&&(o.callback=n),fo(e,o),ms(e,a,r)},enqueueReplaceState:function(e,t,n){e=e._reactInternals;var r=fs(),a=ps(e),o=co(r,a);o.tag=1,o.payload=t,null!=n&&(o.callback=n),fo(e,o),ms(e,a,r)},enqueueForceUpdate:function(e,t){e=e._reactInternals;var n=fs(),r=ps(e),a=co(n,r);a.tag=2,null!=t&&(a.callback=t),fo(e,a),ms(e,r,n)}};function yo(e,t,n,r,a,o,i){return"function"==typeof(e=e.stateNode).shouldComponentUpdate?e.shouldComponentUpdate(r,o,i):!t.prototype||!t.prototype.isPureReactComponent||(!fr(n,r)||!fr(a,o))}function wo(e,t,n){var r=!1,a=fa,o=t.contextType;return"object"==typeof o&&null!==o?o=io(o):(a=ba(t)?ha:pa.current,o=(r=null!=(r=t.contextTypes))?ga(e,a):fa),t=new t(n,o),e.memoizedState=null!==t.state&&void 0!==t.state?t.state:null,t.updater=vo,e.stateNode=t,t._reactInternals=e,r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=a,e.__reactInternalMemoizedMaskedChildContext=o),t}function Eo(e,t,n,r){e=t.state,"function"==typeof t.componentWillReceiveProps&&t.componentWillReceiveProps(n,r),"function"==typeof t.UNSAFE_componentWillReceiveProps&&t.UNSAFE_componentWillReceiveProps(n,r),t.state!==e&&vo.enqueueReplaceState(t,t.state,null)}function ko(e,t,n,r){var a=e.stateNode;a.props=n,a.state=e.memoizedState,a.refs=go,so(e);var o=t.contextType;"object"==typeof o&&null!==o?a.context=io(o):(o=ba(t)?ha:pa.current,a.context=ga(e,o)),mo(e,n,a,r),a.state=e.memoizedState,"function"==typeof(o=t.getDerivedStateFromProps)&&(bo(e,t,o,n),a.state=e.memoizedState),"function"==typeof t.getDerivedStateFromProps||"function"==typeof a.getSnapshotBeforeUpdate||"function"!=typeof a.UNSAFE_componentWillMount&&"function"!=typeof a.componentWillMount||(t=a.state,"function"==typeof a.componentWillMount&&a.componentWillMount(),"function"==typeof a.UNSAFE_componentWillMount&&a.UNSAFE_componentWillMount(),t!==a.state&&vo.enqueueReplaceState(a,a.state,null),mo(e,n,a,r),a.state=e.memoizedState),"function"==typeof a.componentDidMount&&(e.flags|=4)}var So=Array.isArray;function xo(e,t,n){if(null!==(e=n.ref)&&"function"!=typeof e&&"object"!=typeof e){if(n._owner){if(n=n._owner){if(1!==n.tag)throw Error(i(309));var r=n.stateNode}if(!r)throw Error(i(147,e));var a=""+e;return null!==t&&null!==t.ref&&"function"==typeof t.ref&&t.ref._stringRef===a?t.ref:(t=function(e){var t=r.refs;t===go&&(t=r.refs={}),null===e?delete t[a]:t[a]=e},t._stringRef=a,t)}if("string"!=typeof e)throw Error(i(284));if(!n._owner)throw Error(i(290,e))}return e}function _o(e,t){if("textarea"!==e.type)throw Error(i(31,"[object Object]"===Object.prototype.toString.call(t)?"object with keys {"+Object.keys(t).join(", ")+"}":t))}function Co(e){function t(t,n){if(e){var r=t.lastEffect;null!==r?(r.nextEffect=n,t.lastEffect=n):t.firstEffect=t.lastEffect=n,n.nextEffect=null,n.flags=8}}function n(n,r){if(!e)return null;for(;null!==r;)t(n,r),r=r.sibling;return null}function r(e,t){for(e=new Map;null!==t;)null!==t.key?e.set(t.key,t):e.set(t.index,t),t=t.sibling;return e}function a(e,t){return(e=Gs(e,t)).index=0,e.sibling=null,e}function o(t,n,r){return t.index=r,e?null!==(r=t.alternate)?(r=r.index)<n?(t.flags=2,n):r:(t.flags=2,n):n}function l(t){return e&&null===t.alternate&&(t.flags=2),t}function s(e,t,n,r){return null===t||6!==t.tag?((t=Qs(n,e.mode,r)).return=e,t):((t=a(t,n)).return=e,t)}function u(e,t,n,r){return null!==t&&t.elementType===n.type?((r=a(t,n.props)).ref=xo(e,t,n),r.return=e,r):((r=Ws(n.type,n.key,n.props,null,e.mode,r)).ref=xo(e,t,n),r.return=e,r)}function c(e,t,n,r){return null===t||4!==t.tag||t.stateNode.containerInfo!==n.containerInfo||t.stateNode.implementation!==n.implementation?((t=Xs(n,e.mode,r)).return=e,t):((t=a(t,n.children||[])).return=e,t)}function d(e,t,n,r,o){return null===t||7!==t.tag?((t=Ys(n,e.mode,r,o)).return=e,t):((t=a(t,n)).return=e,t)}function f(e,t,n){if("string"==typeof t||"number"==typeof t)return(t=Qs(""+t,e.mode,n)).return=e,t;if("object"==typeof t&&null!==t){switch(t.$$typeof){case k:return(n=Ws(t.type,t.key,t.props,null,e.mode,n)).ref=xo(e,null,t),n.return=e,n;case S:return(t=Xs(t,e.mode,n)).return=e,t}if(So(t)||j(t))return(t=Ys(t,e.mode,n,null)).return=e,t;_o(e,t)}return null}function p(e,t,n,r){var a=null!==t?t.key:null;if("string"==typeof n||"number"==typeof n)return null!==a?null:s(e,t,""+n,r);if("object"==typeof n&&null!==n){switch(n.$$typeof){case k:return n.key===a?n.type===x?d(e,t,n.props.children,r,a):u(e,t,n,r):null;case S:return n.key===a?c(e,t,n,r):null}if(So(n)||j(n))return null!==a?null:d(e,t,n,r,null);_o(e,n)}return null}function m(e,t,n,r,a){if("string"==typeof r||"number"==typeof r)return s(t,e=e.get(n)||null,""+r,a);if("object"==typeof r&&null!==r){switch(r.$$typeof){case k:return e=e.get(null===r.key?n:r.key)||null,r.type===x?d(t,e,r.props.children,a,r.key):u(t,e,r,a);case S:return c(t,e=e.get(null===r.key?n:r.key)||null,r,a)}if(So(r)||j(r))return d(t,e=e.get(n)||null,r,a,null);_o(t,r)}return null}function h(a,i,l,s){for(var u=null,c=null,d=i,h=i=0,g=null;null!==d&&h<l.length;h++){d.index>h?(g=d,d=null):g=d.sibling;var b=p(a,d,l[h],s);if(null===b){null===d&&(d=g);break}e&&d&&null===b.alternate&&t(a,d),i=o(b,i,h),null===c?u=b:c.sibling=b,c=b,d=g}if(h===l.length)return n(a,d),u;if(null===d){for(;h<l.length;h++)null!==(d=f(a,l[h],s))&&(i=o(d,i,h),null===c?u=d:c.sibling=d,c=d);return u}for(d=r(a,d);h<l.length;h++)null!==(g=m(d,a,h,l[h],s))&&(e&&null!==g.alternate&&d.delete(null===g.key?h:g.key),i=o(g,i,h),null===c?u=g:c.sibling=g,c=g);return e&&d.forEach((function(e){return t(a,e)})),u}function g(a,l,s,u){var c=j(s);if("function"!=typeof c)throw Error(i(150));if(null==(s=c.call(s)))throw Error(i(151));for(var d=c=null,h=l,g=l=0,b=null,v=s.next();null!==h&&!v.done;g++,v=s.next()){h.index>g?(b=h,h=null):b=h.sibling;var y=p(a,h,v.value,u);if(null===y){null===h&&(h=b);break}e&&h&&null===y.alternate&&t(a,h),l=o(y,l,g),null===d?c=y:d.sibling=y,d=y,h=b}if(v.done)return n(a,h),c;if(null===h){for(;!v.done;g++,v=s.next())null!==(v=f(a,v.value,u))&&(l=o(v,l,g),null===d?c=v:d.sibling=v,d=v);return c}for(h=r(a,h);!v.done;g++,v=s.next())null!==(v=m(h,a,g,v.value,u))&&(e&&null!==v.alternate&&h.delete(null===v.key?g:v.key),l=o(v,l,g),null===d?c=v:d.sibling=v,d=v);return e&&h.forEach((function(e){return t(a,e)})),c}return function(e,r,o,s){var u="object"==typeof o&&null!==o&&o.type===x&&null===o.key;u&&(o=o.props.children);var c="object"==typeof o&&null!==o;if(c)switch(o.$$typeof){case k:e:{for(c=o.key,u=r;null!==u;){if(u.key===c){if(7===u.tag){if(o.type===x){n(e,u.sibling),(r=a(u,o.props.children)).return=e,e=r;break e}}else if(u.elementType===o.type){n(e,u.sibling),(r=a(u,o.props)).ref=xo(e,u,o),r.return=e,e=r;break e}n(e,u);break}t(e,u),u=u.sibling}o.type===x?((r=Ys(o.props.children,e.mode,s,o.key)).return=e,e=r):((s=Ws(o.type,o.key,o.props,null,e.mode,s)).ref=xo(e,r,o),s.return=e,e=s)}return l(e);case S:e:{for(u=o.key;null!==r;){if(r.key===u){if(4===r.tag&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),(r=a(r,o.children||[])).return=e,e=r;break e}n(e,r);break}t(e,r),r=r.sibling}(r=Xs(o,e.mode,s)).return=e,e=r}return l(e)}if("string"==typeof o||"number"==typeof o)return o=""+o,null!==r&&6===r.tag?(n(e,r.sibling),(r=a(r,o)).return=e,e=r):(n(e,r),(r=Qs(o,e.mode,s)).return=e,e=r),l(e);if(So(o))return h(e,r,o,s);if(j(o))return g(e,r,o,s);if(c&&_o(e,o),void 0===o&&!u)switch(e.tag){case 1:case 22:case 0:case 11:case 15:throw Error(i(152,W(e.type)||"Component"))}return n(e,r)}}var Ao=Co(!0),To=Co(!1),No={},Oo=ua(No),Lo=ua(No),Po=ua(No);function Ro(e){if(e===No)throw Error(i(174));return e}function Io(e,t){switch(da(Po,t),da(Lo,e),da(Oo,No),e=t.nodeType){case 9:case 11:t=(t=t.documentElement)?t.namespaceURI:me(null,"");break;default:t=me(t=(e=8===e?t.parentNode:t).namespaceURI||null,e=e.tagName)}ca(Oo),da(Oo,t)}function Mo(){ca(Oo),ca(Lo),ca(Po)}function Do(e){Ro(Po.current);var t=Ro(Oo.current),n=me(t,e.type);t!==n&&(da(Lo,e),da(Oo,n))}function Fo(e){Lo.current===e&&(ca(Oo),ca(Lo))}var Bo=ua(0);function $o(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||"$?"===n.data||"$!"===n.data))return t}else if(19===t.tag&&void 0!==t.memoizedProps.revealOrder){if(0!=(64&t.flags))return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}var zo=null,Uo=null,jo=!1;function Ho(e,t){var n=qs(5,null,null,0);n.elementType="DELETED",n.type="DELETED",n.stateNode=t,n.return=e,n.flags=8,null!==e.lastEffect?(e.lastEffect.nextEffect=n,e.lastEffect=n):e.firstEffect=e.lastEffect=n}function qo(e,t){switch(e.tag){case 5:var n=e.type;return null!==(t=1!==t.nodeType||n.toLowerCase()!==t.nodeName.toLowerCase()?null:t)&&(e.stateNode=t,!0);case 6:return null!==(t=""===e.pendingProps||3!==t.nodeType?null:t)&&(e.stateNode=t,!0);default:return!1}}function Vo(e){if(jo){var t=Uo;if(t){var n=t;if(!qo(e,t)){if(!(t=Yr(n.nextSibling))||!qo(e,t))return e.flags=-1025&e.flags|2,jo=!1,void(zo=e);Ho(zo,n)}zo=e,Uo=Yr(t.firstChild)}else e.flags=-1025&e.flags|2,jo=!1,zo=e}}function Go(e){for(e=e.return;null!==e&&5!==e.tag&&3!==e.tag&&13!==e.tag;)e=e.return;zo=e}function Wo(e){if(e!==zo)return!1;if(!jo)return Go(e),jo=!0,!1;var t=e.type;if(5!==e.tag||"head"!==t&&"body"!==t&&!qr(t,e.memoizedProps))for(t=Uo;t;)Ho(e,t),t=Yr(t.nextSibling);if(Go(e),13===e.tag){if(!(e=null!==(e=e.memoizedState)?e.dehydrated:null))throw Error(i(317));e:{for(e=e.nextSibling,t=0;e;){if(8===e.nodeType){var n=e.data;if("/$"===n){if(0===t){Uo=Yr(e.nextSibling);break e}t--}else"$"!==n&&"$!"!==n&&"$?"!==n||t++}e=e.nextSibling}Uo=null}}else Uo=zo?Yr(e.stateNode.nextSibling):null;return!0}function Yo(){Uo=zo=null,jo=!1}var Ko=[];function Qo(){for(var e=0;e<Ko.length;e++)Ko[e]._workInProgressVersionPrimary=null;Ko.length=0}var Xo=E.ReactCurrentDispatcher,Zo=E.ReactCurrentBatchConfig,Jo=0,ei=null,ti=null,ni=null,ri=!1,ai=!1;function oi(){throw Error(i(321))}function ii(e,t){if(null===t)return!1;for(var n=0;n<t.length&&n<e.length;n++)if(!cr(e[n],t[n]))return!1;return!0}function li(e,t,n,r,a,o){if(Jo=o,ei=t,t.memoizedState=null,t.updateQueue=null,t.lanes=0,Xo.current=null===e||null===e.memoizedState?Ri:Ii,e=n(r,a),ai){o=0;do{if(ai=!1,!(25>o))throw Error(i(301));o+=1,ni=ti=null,t.updateQueue=null,Xo.current=Mi,e=n(r,a)}while(ai)}if(Xo.current=Pi,t=null!==ti&&null!==ti.next,Jo=0,ni=ti=ei=null,ri=!1,t)throw Error(i(300));return e}function si(){var e={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return null===ni?ei.memoizedState=ni=e:ni=ni.next=e,ni}function ui(){if(null===ti){var e=ei.alternate;e=null!==e?e.memoizedState:null}else e=ti.next;var t=null===ni?ei.memoizedState:ni.next;if(null!==t)ni=t,ti=e;else{if(null===e)throw Error(i(310));e={memoizedState:(ti=e).memoizedState,baseState:ti.baseState,baseQueue:ti.baseQueue,queue:ti.queue,next:null},null===ni?ei.memoizedState=ni=e:ni=ni.next=e}return ni}function ci(e,t){return"function"==typeof t?t(e):t}function di(e){var t=ui(),n=t.queue;if(null===n)throw Error(i(311));n.lastRenderedReducer=e;var r=ti,a=r.baseQueue,o=n.pending;if(null!==o){if(null!==a){var l=a.next;a.next=o.next,o.next=l}r.baseQueue=a=o,n.pending=null}if(null!==a){a=a.next,r=r.baseState;var s=l=o=null,u=a;do{var c=u.lane;if((Jo&c)===c)null!==s&&(s=s.next={lane:0,action:u.action,eagerReducer:u.eagerReducer,eagerState:u.eagerState,next:null}),r=u.eagerReducer===e?u.eagerState:e(r,u.action);else{var d={lane:c,action:u.action,eagerReducer:u.eagerReducer,eagerState:u.eagerState,next:null};null===s?(l=s=d,o=r):s=s.next=d,ei.lanes|=c,Ul|=c}u=u.next}while(null!==u&&u!==a);null===s?o=r:s.next=l,cr(r,t.memoizedState)||(Fi=!0),t.memoizedState=r,t.baseState=o,t.baseQueue=s,n.lastRenderedState=r}return[t.memoizedState,n.dispatch]}function fi(e){var t=ui(),n=t.queue;if(null===n)throw Error(i(311));n.lastRenderedReducer=e;var r=n.dispatch,a=n.pending,o=t.memoizedState;if(null!==a){n.pending=null;var l=a=a.next;do{o=e(o,l.action),l=l.next}while(l!==a);cr(o,t.memoizedState)||(Fi=!0),t.memoizedState=o,null===t.baseQueue&&(t.baseState=o),n.lastRenderedState=o}return[o,r]}function pi(e,t,n){var r=t._getVersion;r=r(t._source);var a=t._workInProgressVersionPrimary;if(null!==a?e=a===r:(e=e.mutableReadLanes,(e=(Jo&e)===e)&&(t._workInProgressVersionPrimary=r,Ko.push(t))),e)return n(t._source);throw Ko.push(t),Error(i(350))}function mi(e,t,n,r){var a=Rl;if(null===a)throw Error(i(349));var o=t._getVersion,l=o(t._source),s=Xo.current,u=s.useState((function(){return pi(a,t,n)})),c=u[1],d=u[0];u=ni;var f=e.memoizedState,p=f.refs,m=p.getSnapshot,h=f.source;f=f.subscribe;var g=ei;return e.memoizedState={refs:p,source:t,subscribe:r},s.useEffect((function(){p.getSnapshot=n,p.setSnapshot=c;var e=o(t._source);if(!cr(l,e)){e=n(t._source),cr(d,e)||(c(e),e=ps(g),a.mutableReadLanes|=e&a.pendingLanes),e=a.mutableReadLanes,a.entangledLanes|=e;for(var r=a.entanglements,i=e;0<i;){var s=31-qt(i),u=1<<s;r[s]|=e,i&=~u}}}),[n,t,r]),s.useEffect((function(){return r(t._source,(function(){var e=p.getSnapshot,n=p.setSnapshot;try{n(e(t._source));var r=ps(g);a.mutableReadLanes|=r&a.pendingLanes}catch(o){n((function(){throw o}))}}))}),[t,r]),cr(m,n)&&cr(h,t)&&cr(f,r)||((e={pending:null,dispatch:null,lastRenderedReducer:ci,lastRenderedState:d}).dispatch=c=Li.bind(null,ei,e),u.queue=e,u.baseQueue=null,d=pi(a,t,n),u.memoizedState=u.baseState=d),d}function hi(e,t,n){return mi(ui(),e,t,n)}function gi(e){var t=si();return"function"==typeof e&&(e=e()),t.memoizedState=t.baseState=e,e=(e=t.queue={pending:null,dispatch:null,lastRenderedReducer:ci,lastRenderedState:e}).dispatch=Li.bind(null,ei,e),[t.memoizedState,e]}function bi(e,t,n,r){return e={tag:e,create:t,destroy:n,deps:r,next:null},null===(t=ei.updateQueue)?(t={lastEffect:null},ei.updateQueue=t,t.lastEffect=e.next=e):null===(n=t.lastEffect)?t.lastEffect=e.next=e:(r=n.next,n.next=e,e.next=r,t.lastEffect=e),e}function vi(e){return e={current:e},si().memoizedState=e}function yi(){return ui().memoizedState}function wi(e,t,n,r){var a=si();ei.flags|=e,a.memoizedState=bi(1|t,n,void 0,void 0===r?null:r)}function Ei(e,t,n,r){var a=ui();r=void 0===r?null:r;var o=void 0;if(null!==ti){var i=ti.memoizedState;if(o=i.destroy,null!==r&&ii(r,i.deps))return void bi(t,n,o,r)}ei.flags|=e,a.memoizedState=bi(1|t,n,o,r)}function ki(e,t){return wi(516,4,e,t)}function Si(e,t){return Ei(516,4,e,t)}function xi(e,t){return Ei(4,2,e,t)}function _i(e,t){return"function"==typeof t?(e=e(),t(e),function(){t(null)}):null!=t?(e=e(),t.current=e,function(){t.current=null}):void 0}function Ci(e,t,n){return n=null!=n?n.concat([e]):null,Ei(4,2,_i.bind(null,t,e),n)}function Ai(){}function Ti(e,t){var n=ui();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&ii(t,r[1])?r[0]:(n.memoizedState=[e,t],e)}function Ni(e,t){var n=ui();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&ii(t,r[1])?r[0]:(e=e(),n.memoizedState=[e,t],e)}function Oi(e,t){var n=qa();Ga(98>n?98:n,(function(){e(!0)})),Ga(97<n?97:n,(function(){var n=Zo.transition;Zo.transition=1;try{e(!1),t()}finally{Zo.transition=n}}))}function Li(e,t,n){var r=fs(),a=ps(e),o={lane:a,action:n,eagerReducer:null,eagerState:null,next:null},i=t.pending;if(null===i?o.next=o:(o.next=i.next,i.next=o),t.pending=o,i=e.alternate,e===ei||null!==i&&i===ei)ai=ri=!0;else{if(0===e.lanes&&(null===i||0===i.lanes)&&null!==(i=t.lastRenderedReducer))try{var l=t.lastRenderedState,s=i(l,n);if(o.eagerReducer=i,o.eagerState=s,cr(s,l))return}catch(u){}ms(e,a,r)}}var Pi={readContext:io,useCallback:oi,useContext:oi,useEffect:oi,useImperativeHandle:oi,useLayoutEffect:oi,useMemo:oi,useReducer:oi,useRef:oi,useState:oi,useDebugValue:oi,useDeferredValue:oi,useTransition:oi,useMutableSource:oi,useOpaqueIdentifier:oi,unstable_isNewReconciler:!1},Ri={readContext:io,useCallback:function(e,t){return si().memoizedState=[e,void 0===t?null:t],e},useContext:io,useEffect:ki,useImperativeHandle:function(e,t,n){return n=null!=n?n.concat([e]):null,wi(4,2,_i.bind(null,t,e),n)},useLayoutEffect:function(e,t){return wi(4,2,e,t)},useMemo:function(e,t){var n=si();return t=void 0===t?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=si();return t=void 0!==n?n(t):t,r.memoizedState=r.baseState=t,e=(e=r.queue={pending:null,dispatch:null,lastRenderedReducer:e,lastRenderedState:t}).dispatch=Li.bind(null,ei,e),[r.memoizedState,e]},useRef:vi,useState:gi,useDebugValue:Ai,useDeferredValue:function(e){var t=gi(e),n=t[0],r=t[1];return ki((function(){var t=Zo.transition;Zo.transition=1;try{r(e)}finally{Zo.transition=t}}),[e]),n},useTransition:function(){var e=gi(!1),t=e[0];return vi(e=Oi.bind(null,e[1])),[e,t]},useMutableSource:function(e,t,n){var r=si();return r.memoizedState={refs:{getSnapshot:t,setSnapshot:null},source:e,subscribe:n},mi(r,e,t,n)},useOpaqueIdentifier:function(){if(jo){var e=!1,t=function(e){return{$$typeof:M,toString:e,valueOf:e}}((function(){throw e||(e=!0,n("r:"+(Qr++).toString(36))),Error(i(355))})),n=gi(t)[1];return 0==(2&ei.mode)&&(ei.flags|=516,bi(5,(function(){n("r:"+(Qr++).toString(36))}),void 0,null)),t}return gi(t="r:"+(Qr++).toString(36)),t},unstable_isNewReconciler:!1},Ii={readContext:io,useCallback:Ti,useContext:io,useEffect:Si,useImperativeHandle:Ci,useLayoutEffect:xi,useMemo:Ni,useReducer:di,useRef:yi,useState:function(){return di(ci)},useDebugValue:Ai,useDeferredValue:function(e){var t=di(ci),n=t[0],r=t[1];return Si((function(){var t=Zo.transition;Zo.transition=1;try{r(e)}finally{Zo.transition=t}}),[e]),n},useTransition:function(){var e=di(ci)[0];return[yi().current,e]},useMutableSource:hi,useOpaqueIdentifier:function(){return di(ci)[0]},unstable_isNewReconciler:!1},Mi={readContext:io,useCallback:Ti,useContext:io,useEffect:Si,useImperativeHandle:Ci,useLayoutEffect:xi,useMemo:Ni,useReducer:fi,useRef:yi,useState:function(){return fi(ci)},useDebugValue:Ai,useDeferredValue:function(e){var t=fi(ci),n=t[0],r=t[1];return Si((function(){var t=Zo.transition;Zo.transition=1;try{r(e)}finally{Zo.transition=t}}),[e]),n},useTransition:function(){var e=fi(ci)[0];return[yi().current,e]},useMutableSource:hi,useOpaqueIdentifier:function(){return fi(ci)[0]},unstable_isNewReconciler:!1},Di=E.ReactCurrentOwner,Fi=!1;function Bi(e,t,n,r){t.child=null===e?To(t,null,n,r):Ao(t,e.child,n,r)}function $i(e,t,n,r,a){n=n.render;var o=t.ref;return oo(t,a),r=li(e,t,n,r,o,a),null===e||Fi?(t.flags|=1,Bi(e,t,r,a),t.child):(t.updateQueue=e.updateQueue,t.flags&=-517,e.lanes&=~a,ol(e,t,a))}function zi(e,t,n,r,a,o){if(null===e){var i=n.type;return"function"!=typeof i||Vs(i)||void 0!==i.defaultProps||null!==n.compare||void 0!==n.defaultProps?((e=Ws(n.type,null,r,t,t.mode,o)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=i,Ui(e,t,i,r,a,o))}return i=e.child,0==(a&o)&&(a=i.memoizedProps,(n=null!==(n=n.compare)?n:fr)(a,r)&&e.ref===t.ref)?ol(e,t,o):(t.flags|=1,(e=Gs(i,r)).ref=t.ref,e.return=t,t.child=e)}function Ui(e,t,n,r,a,o){if(null!==e&&fr(e.memoizedProps,r)&&e.ref===t.ref){if(Fi=!1,0==(o&a))return t.lanes=e.lanes,ol(e,t,o);0!=(16384&e.flags)&&(Fi=!0)}return qi(e,t,n,r,o)}function ji(e,t,n){var r=t.pendingProps,a=r.children,o=null!==e?e.memoizedState:null;if("hidden"===r.mode||"unstable-defer-without-hiding"===r.mode)if(0==(4&t.mode))t.memoizedState={baseLanes:0},ks(t,n);else{if(0==(1073741824&n))return e=null!==o?o.baseLanes|n:n,t.lanes=t.childLanes=1073741824,t.memoizedState={baseLanes:e},ks(t,e),null;t.memoizedState={baseLanes:0},ks(t,null!==o?o.baseLanes:n)}else null!==o?(r=o.baseLanes|n,t.memoizedState=null):r=n,ks(t,r);return Bi(e,t,a,n),t.child}function Hi(e,t){var n=t.ref;(null===e&&null!==n||null!==e&&e.ref!==n)&&(t.flags|=128)}function qi(e,t,n,r,a){var o=ba(n)?ha:pa.current;return o=ga(t,o),oo(t,a),n=li(e,t,n,r,o,a),null===e||Fi?(t.flags|=1,Bi(e,t,n,a),t.child):(t.updateQueue=e.updateQueue,t.flags&=-517,e.lanes&=~a,ol(e,t,a))}function Vi(e,t,n,r,a){if(ba(n)){var o=!0;Ea(t)}else o=!1;if(oo(t,a),null===t.stateNode)null!==e&&(e.alternate=null,t.alternate=null,t.flags|=2),wo(t,n,r),ko(t,n,r,a),r=!0;else if(null===e){var i=t.stateNode,l=t.memoizedProps;i.props=l;var s=i.context,u=n.contextType;"object"==typeof u&&null!==u?u=io(u):u=ga(t,u=ba(n)?ha:pa.current);var c=n.getDerivedStateFromProps,d="function"==typeof c||"function"==typeof i.getSnapshotBeforeUpdate;d||"function"!=typeof i.UNSAFE_componentWillReceiveProps&&"function"!=typeof i.componentWillReceiveProps||(l!==r||s!==u)&&Eo(t,i,r,u),lo=!1;var f=t.memoizedState;i.state=f,mo(t,r,i,a),s=t.memoizedState,l!==r||f!==s||ma.current||lo?("function"==typeof c&&(bo(t,n,c,r),s=t.memoizedState),(l=lo||yo(t,n,l,r,f,s,u))?(d||"function"!=typeof i.UNSAFE_componentWillMount&&"function"!=typeof i.componentWillMount||("function"==typeof i.componentWillMount&&i.componentWillMount(),"function"==typeof i.UNSAFE_componentWillMount&&i.UNSAFE_componentWillMount()),"function"==typeof i.componentDidMount&&(t.flags|=4)):("function"==typeof i.componentDidMount&&(t.flags|=4),t.memoizedProps=r,t.memoizedState=s),i.props=r,i.state=s,i.context=u,r=l):("function"==typeof i.componentDidMount&&(t.flags|=4),r=!1)}else{i=t.stateNode,uo(e,t),l=t.memoizedProps,u=t.type===t.elementType?l:Xa(t.type,l),i.props=u,d=t.pendingProps,f=i.context,"object"==typeof(s=n.contextType)&&null!==s?s=io(s):s=ga(t,s=ba(n)?ha:pa.current);var p=n.getDerivedStateFromProps;(c="function"==typeof p||"function"==typeof i.getSnapshotBeforeUpdate)||"function"!=typeof i.UNSAFE_componentWillReceiveProps&&"function"!=typeof i.componentWillReceiveProps||(l!==d||f!==s)&&Eo(t,i,r,s),lo=!1,f=t.memoizedState,i.state=f,mo(t,r,i,a);var m=t.memoizedState;l!==d||f!==m||ma.current||lo?("function"==typeof p&&(bo(t,n,p,r),m=t.memoizedState),(u=lo||yo(t,n,u,r,f,m,s))?(c||"function"!=typeof i.UNSAFE_componentWillUpdate&&"function"!=typeof i.componentWillUpdate||("function"==typeof i.componentWillUpdate&&i.componentWillUpdate(r,m,s),"function"==typeof i.UNSAFE_componentWillUpdate&&i.UNSAFE_componentWillUpdate(r,m,s)),"function"==typeof i.componentDidUpdate&&(t.flags|=4),"function"==typeof i.getSnapshotBeforeUpdate&&(t.flags|=256)):("function"!=typeof i.componentDidUpdate||l===e.memoizedProps&&f===e.memoizedState||(t.flags|=4),"function"!=typeof i.getSnapshotBeforeUpdate||l===e.memoizedProps&&f===e.memoizedState||(t.flags|=256),t.memoizedProps=r,t.memoizedState=m),i.props=r,i.state=m,i.context=s,r=u):("function"!=typeof i.componentDidUpdate||l===e.memoizedProps&&f===e.memoizedState||(t.flags|=4),"function"!=typeof i.getSnapshotBeforeUpdate||l===e.memoizedProps&&f===e.memoizedState||(t.flags|=256),r=!1)}return Gi(e,t,n,r,o,a)}function Gi(e,t,n,r,a,o){Hi(e,t);var i=0!=(64&t.flags);if(!r&&!i)return a&&ka(t,n,!1),ol(e,t,o);r=t.stateNode,Di.current=t;var l=i&&"function"!=typeof n.getDerivedStateFromError?null:r.render();return t.flags|=1,null!==e&&i?(t.child=Ao(t,e.child,null,o),t.child=Ao(t,null,l,o)):Bi(e,t,l,o),t.memoizedState=r.state,a&&ka(t,n,!0),t.child}function Wi(e){var t=e.stateNode;t.pendingContext?ya(0,t.pendingContext,t.pendingContext!==t.context):t.context&&ya(0,t.context,!1),Io(e,t.containerInfo)}var Yi,Ki,Qi,Xi={dehydrated:null,retryLane:0};function Zi(e,t,n){var r,a=t.pendingProps,o=Bo.current,i=!1;return(r=0!=(64&t.flags))||(r=(null===e||null!==e.memoizedState)&&0!=(2&o)),r?(i=!0,t.flags&=-65):null!==e&&null===e.memoizedState||void 0===a.fallback||!0===a.unstable_avoidThisFallback||(o|=1),da(Bo,1&o),null===e?(void 0!==a.fallback&&Vo(t),e=a.children,o=a.fallback,i?(e=Ji(t,e,o,n),t.child.memoizedState={baseLanes:n},t.memoizedState=Xi,e):"number"==typeof a.unstable_expectedLoadTime?(e=Ji(t,e,o,n),t.child.memoizedState={baseLanes:n},t.memoizedState=Xi,t.lanes=33554432,e):((n=Ks({mode:"visible",children:e},t.mode,n,null)).return=t,t.child=n)):(e.memoizedState,i?(a=tl(e,t,a.children,a.fallback,n),i=t.child,o=e.child.memoizedState,i.memoizedState=null===o?{baseLanes:n}:{baseLanes:o.baseLanes|n},i.childLanes=e.childLanes&~n,t.memoizedState=Xi,a):(n=el(e,t,a.children,n),t.memoizedState=null,n))}function Ji(e,t,n,r){var a=e.mode,o=e.child;return t={mode:"hidden",children:t},0==(2&a)&&null!==o?(o.childLanes=0,o.pendingProps=t):o=Ks(t,a,0,null),n=Ys(n,a,r,null),o.return=e,n.return=e,o.sibling=n,e.child=o,n}function el(e,t,n,r){var a=e.child;return e=a.sibling,n=Gs(a,{mode:"visible",children:n}),0==(2&t.mode)&&(n.lanes=r),n.return=t,n.sibling=null,null!==e&&(e.nextEffect=null,e.flags=8,t.firstEffect=t.lastEffect=e),t.child=n}function tl(e,t,n,r,a){var o=t.mode,i=e.child;e=i.sibling;var l={mode:"hidden",children:n};return 0==(2&o)&&t.child!==i?((n=t.child).childLanes=0,n.pendingProps=l,null!==(i=n.lastEffect)?(t.firstEffect=n.firstEffect,t.lastEffect=i,i.nextEffect=null):t.firstEffect=t.lastEffect=null):n=Gs(i,l),null!==e?r=Gs(e,r):(r=Ys(r,o,a,null)).flags|=2,r.return=t,n.return=t,n.sibling=r,t.child=n,r}function nl(e,t){e.lanes|=t;var n=e.alternate;null!==n&&(n.lanes|=t),ao(e.return,t)}function rl(e,t,n,r,a,o){var i=e.memoizedState;null===i?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailMode:a,lastEffect:o}:(i.isBackwards=t,i.rendering=null,i.renderingStartTime=0,i.last=r,i.tail=n,i.tailMode=a,i.lastEffect=o)}function al(e,t,n){var r=t.pendingProps,a=r.revealOrder,o=r.tail;if(Bi(e,t,r.children,n),0!=(2&(r=Bo.current)))r=1&r|2,t.flags|=64;else{if(null!==e&&0!=(64&e.flags))e:for(e=t.child;null!==e;){if(13===e.tag)null!==e.memoizedState&&nl(e,n);else if(19===e.tag)nl(e,n);else if(null!==e.child){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;null===e.sibling;){if(null===e.return||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}r&=1}if(da(Bo,r),0==(2&t.mode))t.memoizedState=null;else switch(a){case"forwards":for(n=t.child,a=null;null!==n;)null!==(e=n.alternate)&&null===$o(e)&&(a=n),n=n.sibling;null===(n=a)?(a=t.child,t.child=null):(a=n.sibling,n.sibling=null),rl(t,!1,a,n,o,t.lastEffect);break;case"backwards":for(n=null,a=t.child,t.child=null;null!==a;){if(null!==(e=a.alternate)&&null===$o(e)){t.child=a;break}e=a.sibling,a.sibling=n,n=a,a=e}rl(t,!0,n,null,o,t.lastEffect);break;case"together":rl(t,!1,null,null,void 0,t.lastEffect);break;default:t.memoizedState=null}return t.child}function ol(e,t,n){if(null!==e&&(t.dependencies=e.dependencies),Ul|=t.lanes,0!=(n&t.childLanes)){if(null!==e&&t.child!==e.child)throw Error(i(153));if(null!==t.child){for(n=Gs(e=t.child,e.pendingProps),t.child=n,n.return=t;null!==e.sibling;)e=e.sibling,(n=n.sibling=Gs(e,e.pendingProps)).return=t;n.sibling=null}return t.child}return null}function il(e,t){if(!jo)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;null!==t;)null!==t.alternate&&(n=t),t=t.sibling;null===n?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;null!==n;)null!==n.alternate&&(r=n),n=n.sibling;null===r?t||null===e.tail?e.tail=null:e.tail.sibling=null:r.sibling=null}}function ll(e,t,n){var r=t.pendingProps;switch(t.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return null;case 1:case 17:return ba(t.type)&&va(),null;case 3:return Mo(),ca(ma),ca(pa),Qo(),(r=t.stateNode).pendingContext&&(r.context=r.pendingContext,r.pendingContext=null),null!==e&&null!==e.child||(Wo(t)?t.flags|=4:r.hydrate||(t.flags|=256)),null;case 5:Fo(t);var o=Ro(Po.current);if(n=t.type,null!==e&&null!=t.stateNode)Ki(e,t,n,r),e.ref!==t.ref&&(t.flags|=128);else{if(!r){if(null===t.stateNode)throw Error(i(166));return null}if(e=Ro(Oo.current),Wo(t)){r=t.stateNode,n=t.type;var l=t.memoizedProps;switch(r[Zr]=t,r[Jr]=l,n){case"dialog":Or("cancel",r),Or("close",r);break;case"iframe":case"object":case"embed":Or("load",r);break;case"video":case"audio":for(e=0;e<Cr.length;e++)Or(Cr[e],r);break;case"source":Or("error",r);break;case"img":case"image":case"link":Or("error",r),Or("load",r);break;case"details":Or("toggle",r);break;case"input":ee(r,l),Or("invalid",r);break;case"select":r._wrapperState={wasMultiple:!!l.multiple},Or("invalid",r);break;case"textarea":se(r,l),Or("invalid",r)}for(var u in xe(n,l),e=null,l)l.hasOwnProperty(u)&&(o=l[u],"children"===u?"string"==typeof o?r.textContent!==o&&(e=["children",o]):"number"==typeof o&&r.textContent!==""+o&&(e=["children",""+o]):s.hasOwnProperty(u)&&null!=o&&"onScroll"===u&&Or("scroll",r));switch(n){case"input":Q(r),re(r,l,!0);break;case"textarea":Q(r),ce(r);break;case"select":case"option":break;default:"function"==typeof l.onClick&&(r.onclick=zr)}r=e,t.updateQueue=r,null!==r&&(t.flags|=4)}else{switch(u=9===o.nodeType?o:o.ownerDocument,e===de&&(e=pe(n)),e===de?"script"===n?((e=u.createElement("div")).innerHTML="<script><\/script>",e=e.removeChild(e.firstChild)):"string"==typeof r.is?e=u.createElement(n,{is:r.is}):(e=u.createElement(n),"select"===n&&(u=e,r.multiple?u.multiple=!0:r.size&&(u.size=r.size))):e=u.createElementNS(e,n),e[Zr]=t,e[Jr]=r,Yi(e,t),t.stateNode=e,u=_e(n,r),n){case"dialog":Or("cancel",e),Or("close",e),o=r;break;case"iframe":case"object":case"embed":Or("load",e),o=r;break;case"video":case"audio":for(o=0;o<Cr.length;o++)Or(Cr[o],e);o=r;break;case"source":Or("error",e),o=r;break;case"img":case"image":case"link":Or("error",e),Or("load",e),o=r;break;case"details":Or("toggle",e),o=r;break;case"input":ee(e,r),o=J(e,r),Or("invalid",e);break;case"option":o=oe(e,r);break;case"select":e._wrapperState={wasMultiple:!!r.multiple},o=a({},r,{value:void 0}),Or("invalid",e);break;case"textarea":se(e,r),o=le(e,r),Or("invalid",e);break;default:o=r}xe(n,o);var c=o;for(l in c)if(c.hasOwnProperty(l)){var d=c[l];"style"===l?ke(e,d):"dangerouslySetInnerHTML"===l?null!=(d=d?d.__html:void 0)&&be(e,d):"children"===l?"string"==typeof d?("textarea"!==n||""!==d)&&ve(e,d):"number"==typeof d&&ve(e,""+d):"suppressContentEditableWarning"!==l&&"suppressHydrationWarning"!==l&&"autoFocus"!==l&&(s.hasOwnProperty(l)?null!=d&&"onScroll"===l&&Or("scroll",e):null!=d&&w(e,l,d,u))}switch(n){case"input":Q(e),re(e,r,!1);break;case"textarea":Q(e),ce(e);break;case"option":null!=r.value&&e.setAttribute("value",""+Y(r.value));break;case"select":e.multiple=!!r.multiple,null!=(l=r.value)?ie(e,!!r.multiple,l,!1):null!=r.defaultValue&&ie(e,!!r.multiple,r.defaultValue,!0);break;default:"function"==typeof o.onClick&&(e.onclick=zr)}Hr(n,r)&&(t.flags|=4)}null!==t.ref&&(t.flags|=128)}return null;case 6:if(e&&null!=t.stateNode)Qi(0,t,e.memoizedProps,r);else{if("string"!=typeof r&&null===t.stateNode)throw Error(i(166));n=Ro(Po.current),Ro(Oo.current),Wo(t)?(r=t.stateNode,n=t.memoizedProps,r[Zr]=t,r.nodeValue!==n&&(t.flags|=4)):((r=(9===n.nodeType?n:n.ownerDocument).createTextNode(r))[Zr]=t,t.stateNode=r)}return null;case 13:return ca(Bo),r=t.memoizedState,0!=(64&t.flags)?(t.lanes=n,t):(r=null!==r,n=!1,null===e?void 0!==t.memoizedProps.fallback&&Wo(t):n=null!==e.memoizedState,r&&!n&&0!=(2&t.mode)&&(null===e&&!0!==t.memoizedProps.unstable_avoidThisFallback||0!=(1&Bo.current)?0===Bl&&(Bl=3):(0!==Bl&&3!==Bl||(Bl=4),null===Rl||0==(134217727&Ul)&&0==(134217727&jl)||vs(Rl,Ml))),(r||n)&&(t.flags|=4),null);case 4:return Mo(),null===e&&Pr(t.stateNode.containerInfo),null;case 10:return ro(t),null;case 19:if(ca(Bo),null===(r=t.memoizedState))return null;if(l=0!=(64&t.flags),null===(u=r.rendering))if(l)il(r,!1);else{if(0!==Bl||null!==e&&0!=(64&e.flags))for(e=t.child;null!==e;){if(null!==(u=$o(e))){for(t.flags|=64,il(r,!1),null!==(l=u.updateQueue)&&(t.updateQueue=l,t.flags|=4),null===r.lastEffect&&(t.firstEffect=null),t.lastEffect=r.lastEffect,r=n,n=t.child;null!==n;)e=r,(l=n).flags&=2,l.nextEffect=null,l.firstEffect=null,l.lastEffect=null,null===(u=l.alternate)?(l.childLanes=0,l.lanes=e,l.child=null,l.memoizedProps=null,l.memoizedState=null,l.updateQueue=null,l.dependencies=null,l.stateNode=null):(l.childLanes=u.childLanes,l.lanes=u.lanes,l.child=u.child,l.memoizedProps=u.memoizedProps,l.memoizedState=u.memoizedState,l.updateQueue=u.updateQueue,l.type=u.type,e=u.dependencies,l.dependencies=null===e?null:{lanes:e.lanes,firstContext:e.firstContext}),n=n.sibling;return da(Bo,1&Bo.current|2),t.child}e=e.sibling}null!==r.tail&&Ha()>Gl&&(t.flags|=64,l=!0,il(r,!1),t.lanes=33554432)}else{if(!l)if(null!==(e=$o(u))){if(t.flags|=64,l=!0,null!==(n=e.updateQueue)&&(t.updateQueue=n,t.flags|=4),il(r,!0),null===r.tail&&"hidden"===r.tailMode&&!u.alternate&&!jo)return null!==(t=t.lastEffect=r.lastEffect)&&(t.nextEffect=null),null}else 2*Ha()-r.renderingStartTime>Gl&&1073741824!==n&&(t.flags|=64,l=!0,il(r,!1),t.lanes=33554432);r.isBackwards?(u.sibling=t.child,t.child=u):(null!==(n=r.last)?n.sibling=u:t.child=u,r.last=u)}return null!==r.tail?(n=r.tail,r.rendering=n,r.tail=n.sibling,r.lastEffect=t.lastEffect,r.renderingStartTime=Ha(),n.sibling=null,t=Bo.current,da(Bo,l?1&t|2:1&t),n):null;case 23:case 24:return Ss(),null!==e&&null!==e.memoizedState!=(null!==t.memoizedState)&&"unstable-defer-without-hiding"!==r.mode&&(t.flags|=4),null}throw Error(i(156,t.tag))}function sl(e){switch(e.tag){case 1:ba(e.type)&&va();var t=e.flags;return 4096&t?(e.flags=-4097&t|64,e):null;case 3:if(Mo(),ca(ma),ca(pa),Qo(),0!=(64&(t=e.flags)))throw Error(i(285));return e.flags=-4097&t|64,e;case 5:return Fo(e),null;case 13:return ca(Bo),4096&(t=e.flags)?(e.flags=-4097&t|64,e):null;case 19:return ca(Bo),null;case 4:return Mo(),null;case 10:return ro(e),null;case 23:case 24:return Ss(),null;default:return null}}function ul(e,t){try{var n="",r=t;do{n+=G(r),r=r.return}while(r);var a=n}catch(o){a="\nError generating stack: "+o.message+"\n"+o.stack}return{value:e,source:t,stack:a}}function cl(e,t){try{console.error(t.value)}catch(n){setTimeout((function(){throw n}))}}Yi=function(e,t){for(var n=t.child;null!==n;){if(5===n.tag||6===n.tag)e.appendChild(n.stateNode);else if(4!==n.tag&&null!==n.child){n.child.return=n,n=n.child;continue}if(n===t)break;for(;null===n.sibling;){if(null===n.return||n.return===t)return;n=n.return}n.sibling.return=n.return,n=n.sibling}},Ki=function(e,t,n,r){var o=e.memoizedProps;if(o!==r){e=t.stateNode,Ro(Oo.current);var i,l=null;switch(n){case"input":o=J(e,o),r=J(e,r),l=[];break;case"option":o=oe(e,o),r=oe(e,r),l=[];break;case"select":o=a({},o,{value:void 0}),r=a({},r,{value:void 0}),l=[];break;case"textarea":o=le(e,o),r=le(e,r),l=[];break;default:"function"!=typeof o.onClick&&"function"==typeof r.onClick&&(e.onclick=zr)}for(d in xe(n,r),n=null,o)if(!r.hasOwnProperty(d)&&o.hasOwnProperty(d)&&null!=o[d])if("style"===d){var u=o[d];for(i in u)u.hasOwnProperty(i)&&(n||(n={}),n[i]="")}else"dangerouslySetInnerHTML"!==d&&"children"!==d&&"suppressContentEditableWarning"!==d&&"suppressHydrationWarning"!==d&&"autoFocus"!==d&&(s.hasOwnProperty(d)?l||(l=[]):(l=l||[]).push(d,null));for(d in r){var c=r[d];if(u=null!=o?o[d]:void 0,r.hasOwnProperty(d)&&c!==u&&(null!=c||null!=u))if("style"===d)if(u){for(i in u)!u.hasOwnProperty(i)||c&&c.hasOwnProperty(i)||(n||(n={}),n[i]="");for(i in c)c.hasOwnProperty(i)&&u[i]!==c[i]&&(n||(n={}),n[i]=c[i])}else n||(l||(l=[]),l.push(d,n)),n=c;else"dangerouslySetInnerHTML"===d?(c=c?c.__html:void 0,u=u?u.__html:void 0,null!=c&&u!==c&&(l=l||[]).push(d,c)):"children"===d?"string"!=typeof c&&"number"!=typeof c||(l=l||[]).push(d,""+c):"suppressContentEditableWarning"!==d&&"suppressHydrationWarning"!==d&&(s.hasOwnProperty(d)?(null!=c&&"onScroll"===d&&Or("scroll",e),l||u===c||(l=[])):"object"==typeof c&&null!==c&&c.$$typeof===M?c.toString():(l=l||[]).push(d,c))}n&&(l=l||[]).push("style",n);var d=l;(t.updateQueue=d)&&(t.flags|=4)}},Qi=function(e,t,n,r){n!==r&&(t.flags|=4)};var dl="function"==typeof WeakMap?WeakMap:Map;function fl(e,t,n){(n=co(-1,n)).tag=3,n.payload={element:null};var r=t.value;return n.callback=function(){Ql||(Ql=!0,Xl=r),cl(0,t)},n}function pl(e,t,n){(n=co(-1,n)).tag=3;var r=e.type.getDerivedStateFromError;if("function"==typeof r){var a=t.value;n.payload=function(){return cl(0,t),r(a)}}var o=e.stateNode;return null!==o&&"function"==typeof o.componentDidCatch&&(n.callback=function(){"function"!=typeof r&&(null===Zl?Zl=new Set([this]):Zl.add(this),cl(0,t));var e=t.stack;this.componentDidCatch(t.value,{componentStack:null!==e?e:""})}),n}var ml="function"==typeof WeakSet?WeakSet:Set;function hl(e){var t=e.ref;if(null!==t)if("function"==typeof t)try{t(null)}catch(n){zs(e,n)}else t.current=null}function gl(e,t){switch(t.tag){case 0:case 11:case 15:case 22:case 5:case 6:case 4:case 17:return;case 1:if(256&t.flags&&null!==e){var n=e.memoizedProps,r=e.memoizedState;t=(e=t.stateNode).getSnapshotBeforeUpdate(t.elementType===t.type?n:Xa(t.type,n),r),e.__reactInternalSnapshotBeforeUpdate=t}return;case 3:return void(256&t.flags&&Wr(t.stateNode.containerInfo))}throw Error(i(163))}function bl(e,t,n){switch(n.tag){case 0:case 11:case 15:case 22:if(null!==(t=null!==(t=n.updateQueue)?t.lastEffect:null)){e=t=t.next;do{if(3==(3&e.tag)){var r=e.create;e.destroy=r()}e=e.next}while(e!==t)}if(null!==(t=null!==(t=n.updateQueue)?t.lastEffect:null)){e=t=t.next;do{var a=e;r=a.next,0!=(4&(a=a.tag))&&0!=(1&a)&&(Fs(n,e),Ds(n,e)),e=r}while(e!==t)}return;case 1:return e=n.stateNode,4&n.flags&&(null===t?e.componentDidMount():(r=n.elementType===n.type?t.memoizedProps:Xa(n.type,t.memoizedProps),e.componentDidUpdate(r,t.memoizedState,e.__reactInternalSnapshotBeforeUpdate))),void(null!==(t=n.updateQueue)&&ho(n,t,e));case 3:if(null!==(t=n.updateQueue)){if(e=null,null!==n.child)switch(n.child.tag){case 5:case 1:e=n.child.stateNode}ho(n,t,e)}return;case 5:return e=n.stateNode,void(null===t&&4&n.flags&&Hr(n.type,n.memoizedProps)&&e.focus());case 6:case 4:case 12:case 19:case 17:case 20:case 21:case 23:case 24:return;case 13:return void(null===n.memoizedState&&(n=n.alternate,null!==n&&(n=n.memoizedState,null!==n&&(n=n.dehydrated,null!==n&&kt(n)))))}throw Error(i(163))}function vl(e,t){for(var n=e;;){if(5===n.tag){var r=n.stateNode;if(t)"function"==typeof(r=r.style).setProperty?r.setProperty("display","none","important"):r.display="none";else{r=n.stateNode;var a=n.memoizedProps.style;a=null!=a&&a.hasOwnProperty("display")?a.display:null,r.style.display=Ee("display",a)}}else if(6===n.tag)n.stateNode.nodeValue=t?"":n.memoizedProps;else if((23!==n.tag&&24!==n.tag||null===n.memoizedState||n===e)&&null!==n.child){n.child.return=n,n=n.child;continue}if(n===e)break;for(;null===n.sibling;){if(null===n.return||n.return===e)return;n=n.return}n.sibling.return=n.return,n=n.sibling}}function yl(e,t){if(xa&&"function"==typeof xa.onCommitFiberUnmount)try{xa.onCommitFiberUnmount(Sa,t)}catch(o){}switch(t.tag){case 0:case 11:case 14:case 15:case 22:if(null!==(e=t.updateQueue)&&null!==(e=e.lastEffect)){var n=e=e.next;do{var r=n,a=r.destroy;if(r=r.tag,void 0!==a)if(0!=(4&r))Fs(t,n);else{r=t;try{a()}catch(o){zs(r,o)}}n=n.next}while(n!==e)}break;case 1:if(hl(t),"function"==typeof(e=t.stateNode).componentWillUnmount)try{e.props=t.memoizedProps,e.state=t.memoizedState,e.componentWillUnmount()}catch(o){zs(t,o)}break;case 5:hl(t);break;case 4:_l(e,t)}}function wl(e){e.alternate=null,e.child=null,e.dependencies=null,e.firstEffect=null,e.lastEffect=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.return=null,e.updateQueue=null}function El(e){return 5===e.tag||3===e.tag||4===e.tag}function kl(e){e:{for(var t=e.return;null!==t;){if(El(t))break e;t=t.return}throw Error(i(160))}var n=t;switch(t=n.stateNode,n.tag){case 5:var r=!1;break;case 3:case 4:t=t.containerInfo,r=!0;break;default:throw Error(i(161))}16&n.flags&&(ve(t,""),n.flags&=-17);e:t:for(n=e;;){for(;null===n.sibling;){if(null===n.return||El(n.return)){n=null;break e}n=n.return}for(n.sibling.return=n.return,n=n.sibling;5!==n.tag&&6!==n.tag&&18!==n.tag;){if(2&n.flags)continue t;if(null===n.child||4===n.tag)continue t;n.child.return=n,n=n.child}if(!(2&n.flags)){n=n.stateNode;break e}}r?Sl(e,n,t):xl(e,n,t)}function Sl(e,t,n){var r=e.tag,a=5===r||6===r;if(a)e=a?e.stateNode:e.stateNode.instance,t?8===n.nodeType?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(8===n.nodeType?(t=n.parentNode).insertBefore(e,n):(t=n).appendChild(e),null!=(n=n._reactRootContainer)||null!==t.onclick||(t.onclick=zr));else if(4!==r&&null!==(e=e.child))for(Sl(e,t,n),e=e.sibling;null!==e;)Sl(e,t,n),e=e.sibling}function xl(e,t,n){var r=e.tag,a=5===r||6===r;if(a)e=a?e.stateNode:e.stateNode.instance,t?n.insertBefore(e,t):n.appendChild(e);else if(4!==r&&null!==(e=e.child))for(xl(e,t,n),e=e.sibling;null!==e;)xl(e,t,n),e=e.sibling}function _l(e,t){for(var n,r,a=t,o=!1;;){if(!o){o=a.return;e:for(;;){if(null===o)throw Error(i(160));switch(n=o.stateNode,o.tag){case 5:r=!1;break e;case 3:case 4:n=n.containerInfo,r=!0;break e}o=o.return}o=!0}if(5===a.tag||6===a.tag){e:for(var l=e,s=a,u=s;;)if(yl(l,u),null!==u.child&&4!==u.tag)u.child.return=u,u=u.child;else{if(u===s)break e;for(;null===u.sibling;){if(null===u.return||u.return===s)break e;u=u.return}u.sibling.return=u.return,u=u.sibling}r?(l=n,s=a.stateNode,8===l.nodeType?l.parentNode.removeChild(s):l.removeChild(s)):n.removeChild(a.stateNode)}else if(4===a.tag){if(null!==a.child){n=a.stateNode.containerInfo,r=!0,a.child.return=a,a=a.child;continue}}else if(yl(e,a),null!==a.child){a.child.return=a,a=a.child;continue}if(a===t)break;for(;null===a.sibling;){if(null===a.return||a.return===t)return;4===(a=a.return).tag&&(o=!1)}a.sibling.return=a.return,a=a.sibling}}function Cl(e,t){switch(t.tag){case 0:case 11:case 14:case 15:case 22:var n=t.updateQueue;if(null!==(n=null!==n?n.lastEffect:null)){var r=n=n.next;do{3==(3&r.tag)&&(e=r.destroy,r.destroy=void 0,void 0!==e&&e()),r=r.next}while(r!==n)}return;case 1:case 12:case 17:return;case 5:if(null!=(n=t.stateNode)){r=t.memoizedProps;var a=null!==e?e.memoizedProps:r;e=t.type;var o=t.updateQueue;if(t.updateQueue=null,null!==o){for(n[Jr]=r,"input"===e&&"radio"===r.type&&null!=r.name&&te(n,r),_e(e,a),t=_e(e,r),a=0;a<o.length;a+=2){var l=o[a],s=o[a+1];"style"===l?ke(n,s):"dangerouslySetInnerHTML"===l?be(n,s):"children"===l?ve(n,s):w(n,l,s,t)}switch(e){case"input":ne(n,r);break;case"textarea":ue(n,r);break;case"select":e=n._wrapperState.wasMultiple,n._wrapperState.wasMultiple=!!r.multiple,null!=(o=r.value)?ie(n,!!r.multiple,o,!1):e!==!!r.multiple&&(null!=r.defaultValue?ie(n,!!r.multiple,r.defaultValue,!0):ie(n,!!r.multiple,r.multiple?[]:"",!1))}}}return;case 6:if(null===t.stateNode)throw Error(i(162));return void(t.stateNode.nodeValue=t.memoizedProps);case 3:return void((n=t.stateNode).hydrate&&(n.hydrate=!1,kt(n.containerInfo)));case 13:return null!==t.memoizedState&&(Vl=Ha(),vl(t.child,!0)),void Al(t);case 19:return void Al(t);case 23:case 24:return void vl(t,null!==t.memoizedState)}throw Error(i(163))}function Al(e){var t=e.updateQueue;if(null!==t){e.updateQueue=null;var n=e.stateNode;null===n&&(n=e.stateNode=new ml),t.forEach((function(t){var r=js.bind(null,e,t);n.has(t)||(n.add(t),t.then(r,r))}))}}function Tl(e,t){return null!==e&&(null===(e=e.memoizedState)||null!==e.dehydrated)&&(null!==(t=t.memoizedState)&&null===t.dehydrated)}var Nl=Math.ceil,Ol=E.ReactCurrentDispatcher,Ll=E.ReactCurrentOwner,Pl=0,Rl=null,Il=null,Ml=0,Dl=0,Fl=ua(0),Bl=0,$l=null,zl=0,Ul=0,jl=0,Hl=0,ql=null,Vl=0,Gl=1/0;function Wl(){Gl=Ha()+500}var Yl,Kl=null,Ql=!1,Xl=null,Zl=null,Jl=!1,es=null,ts=90,ns=[],rs=[],as=null,os=0,is=null,ls=-1,ss=0,us=0,cs=null,ds=!1;function fs(){return 0!=(48&Pl)?Ha():-1!==ls?ls:ls=Ha()}function ps(e){if(0==(2&(e=e.mode)))return 1;if(0==(4&e))return 99===qa()?1:2;if(0===ss&&(ss=zl),0!==Qa.transition){0!==us&&(us=null!==ql?ql.pendingLanes:0),e=ss;var t=4186112&~us;return 0===(t&=-t)&&(0===(t=(e=4186112&~e)&-e)&&(t=8192)),t}return e=qa(),0!=(4&Pl)&&98===e?e=zt(12,ss):e=zt(e=function(e){switch(e){case 99:return 15;case 98:return 10;case 97:case 96:return 8;case 95:return 2;default:return 0}}(e),ss),e}function ms(e,t,n){if(50<os)throw os=0,is=null,Error(i(185));if(null===(e=hs(e,t)))return null;Ht(e,t,n),e===Rl&&(jl|=t,4===Bl&&vs(e,Ml));var r=qa();1===t?0!=(8&Pl)&&0==(48&Pl)?ys(e):(gs(e,n),0===Pl&&(Wl(),Ya())):(0==(4&Pl)||98!==r&&99!==r||(null===as?as=new Set([e]):as.add(e)),gs(e,n)),ql=e}function hs(e,t){e.lanes|=t;var n=e.alternate;for(null!==n&&(n.lanes|=t),n=e,e=e.return;null!==e;)e.childLanes|=t,null!==(n=e.alternate)&&(n.childLanes|=t),n=e,e=e.return;return 3===n.tag?n.stateNode:null}function gs(e,t){for(var n=e.callbackNode,r=e.suspendedLanes,a=e.pingedLanes,o=e.expirationTimes,l=e.pendingLanes;0<l;){var s=31-qt(l),u=1<<s,c=o[s];if(-1===c){if(0==(u&r)||0!=(u&a)){c=t,Ft(u);var d=Dt;o[s]=10<=d?c+250:6<=d?c+5e3:-1}}else c<=t&&(e.expiredLanes|=u);l&=~u}if(r=Bt(e,e===Rl?Ml:0),t=Dt,0===r)null!==n&&(n!==Fa&&Aa(n),e.callbackNode=null,e.callbackPriority=0);else{if(null!==n){if(e.callbackPriority===t)return;n!==Fa&&Aa(n)}15===t?(n=ys.bind(null,e),null===$a?($a=[n],za=Ca(Pa,Ka)):$a.push(n),n=Fa):14===t?n=Wa(99,ys.bind(null,e)):(n=function(e){switch(e){case 15:case 14:return 99;case 13:case 12:case 11:case 10:return 98;case 9:case 8:case 7:case 6:case 4:case 5:return 97;case 3:case 2:case 1:return 95;case 0:return 90;default:throw Error(i(358,e))}}(t),n=Wa(n,bs.bind(null,e))),e.callbackPriority=t,e.callbackNode=n}}function bs(e){if(ls=-1,us=ss=0,0!=(48&Pl))throw Error(i(327));var t=e.callbackNode;if(Ms()&&e.callbackNode!==t)return null;var n=Bt(e,e===Rl?Ml:0);if(0===n)return null;var r=n,a=Pl;Pl|=16;var o=Cs();for(Rl===e&&Ml===r||(Wl(),xs(e,r));;)try{Ns();break}catch(s){_s(e,s)}if(no(),Ol.current=o,Pl=a,null!==Il?r=0:(Rl=null,Ml=0,r=Bl),0!=(zl&jl))xs(e,0);else if(0!==r){if(2===r&&(Pl|=64,e.hydrate&&(e.hydrate=!1,Wr(e.containerInfo)),0!==(n=$t(e))&&(r=As(e,n))),1===r)throw t=$l,xs(e,0),vs(e,n),gs(e,Ha()),t;switch(e.finishedWork=e.current.alternate,e.finishedLanes=n,r){case 0:case 1:throw Error(i(345));case 2:case 5:Ps(e);break;case 3:if(vs(e,n),(62914560&n)===n&&10<(r=Vl+500-Ha())){if(0!==Bt(e,0))break;if(((a=e.suspendedLanes)&n)!==n){fs(),e.pingedLanes|=e.suspendedLanes&a;break}e.timeoutHandle=Vr(Ps.bind(null,e),r);break}Ps(e);break;case 4:if(vs(e,n),(4186112&n)===n)break;for(r=e.eventTimes,a=-1;0<n;){var l=31-qt(n);o=1<<l,(l=r[l])>a&&(a=l),n&=~o}if(n=a,10<(n=(120>(n=Ha()-n)?120:480>n?480:1080>n?1080:1920>n?1920:3e3>n?3e3:4320>n?4320:1960*Nl(n/1960))-n)){e.timeoutHandle=Vr(Ps.bind(null,e),n);break}Ps(e);break;default:throw Error(i(329))}}return gs(e,Ha()),e.callbackNode===t?bs.bind(null,e):null}function vs(e,t){for(t&=~Hl,t&=~jl,e.suspendedLanes|=t,e.pingedLanes&=~t,e=e.expirationTimes;0<t;){var n=31-qt(t),r=1<<n;e[n]=-1,t&=~r}}function ys(e){if(0!=(48&Pl))throw Error(i(327));if(Ms(),e===Rl&&0!=(e.expiredLanes&Ml)){var t=Ml,n=As(e,t);0!=(zl&jl)&&(n=As(e,t=Bt(e,t)))}else n=As(e,t=Bt(e,0));if(0!==e.tag&&2===n&&(Pl|=64,e.hydrate&&(e.hydrate=!1,Wr(e.containerInfo)),0!==(t=$t(e))&&(n=As(e,t))),1===n)throw n=$l,xs(e,0),vs(e,t),gs(e,Ha()),n;return e.finishedWork=e.current.alternate,e.finishedLanes=t,Ps(e),gs(e,Ha()),null}function ws(e,t){var n=Pl;Pl|=1;try{return e(t)}finally{0===(Pl=n)&&(Wl(),Ya())}}function Es(e,t){var n=Pl;Pl&=-2,Pl|=8;try{return e(t)}finally{0===(Pl=n)&&(Wl(),Ya())}}function ks(e,t){da(Fl,Dl),Dl|=t,zl|=t}function Ss(){Dl=Fl.current,ca(Fl)}function xs(e,t){e.finishedWork=null,e.finishedLanes=0;var n=e.timeoutHandle;if(-1!==n&&(e.timeoutHandle=-1,Gr(n)),null!==Il)for(n=Il.return;null!==n;){var r=n;switch(r.tag){case 1:null!=(r=r.type.childContextTypes)&&va();break;case 3:Mo(),ca(ma),ca(pa),Qo();break;case 5:Fo(r);break;case 4:Mo();break;case 13:case 19:ca(Bo);break;case 10:ro(r);break;case 23:case 24:Ss()}n=n.return}Rl=e,Il=Gs(e.current,null),Ml=Dl=zl=t,Bl=0,$l=null,Hl=jl=Ul=0}function _s(e,t){for(;;){var n=Il;try{if(no(),Xo.current=Pi,ri){for(var r=ei.memoizedState;null!==r;){var a=r.queue;null!==a&&(a.pending=null),r=r.next}ri=!1}if(Jo=0,ni=ti=ei=null,ai=!1,Ll.current=null,null===n||null===n.return){Bl=1,$l=t,Il=null;break}e:{var o=e,i=n.return,l=n,s=t;if(t=Ml,l.flags|=2048,l.firstEffect=l.lastEffect=null,null!==s&&"object"==typeof s&&"function"==typeof s.then){var u=s;if(0==(2&l.mode)){var c=l.alternate;c?(l.updateQueue=c.updateQueue,l.memoizedState=c.memoizedState,l.lanes=c.lanes):(l.updateQueue=null,l.memoizedState=null)}var d=0!=(1&Bo.current),f=i;do{var p;if(p=13===f.tag){var m=f.memoizedState;if(null!==m)p=null!==m.dehydrated;else{var h=f.memoizedProps;p=void 0!==h.fallback&&(!0!==h.unstable_avoidThisFallback||!d)}}if(p){var g=f.updateQueue;if(null===g){var b=new Set;b.add(u),f.updateQueue=b}else g.add(u);if(0==(2&f.mode)){if(f.flags|=64,l.flags|=16384,l.flags&=-2981,1===l.tag)if(null===l.alternate)l.tag=17;else{var v=co(-1,1);v.tag=2,fo(l,v)}l.lanes|=1;break e}s=void 0,l=t;var y=o.pingCache;if(null===y?(y=o.pingCache=new dl,s=new Set,y.set(u,s)):void 0===(s=y.get(u))&&(s=new Set,y.set(u,s)),!s.has(l)){s.add(l);var w=Us.bind(null,o,u,l);u.then(w,w)}f.flags|=4096,f.lanes=t;break e}f=f.return}while(null!==f);s=Error((W(l.type)||"A React component")+" suspended while rendering, but no fallback UI was specified.\n\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display.")}5!==Bl&&(Bl=2),s=ul(s,l),f=i;do{switch(f.tag){case 3:o=s,f.flags|=4096,t&=-t,f.lanes|=t,po(f,fl(0,o,t));break e;case 1:o=s;var E=f.type,k=f.stateNode;if(0==(64&f.flags)&&("function"==typeof E.getDerivedStateFromError||null!==k&&"function"==typeof k.componentDidCatch&&(null===Zl||!Zl.has(k)))){f.flags|=4096,t&=-t,f.lanes|=t,po(f,pl(f,o,t));break e}}f=f.return}while(null!==f)}Ls(n)}catch(S){t=S,Il===n&&null!==n&&(Il=n=n.return);continue}break}}function Cs(){var e=Ol.current;return Ol.current=Pi,null===e?Pi:e}function As(e,t){var n=Pl;Pl|=16;var r=Cs();for(Rl===e&&Ml===t||xs(e,t);;)try{Ts();break}catch(a){_s(e,a)}if(no(),Pl=n,Ol.current=r,null!==Il)throw Error(i(261));return Rl=null,Ml=0,Bl}function Ts(){for(;null!==Il;)Os(Il)}function Ns(){for(;null!==Il&&!Ta();)Os(Il)}function Os(e){var t=Yl(e.alternate,e,Dl);e.memoizedProps=e.pendingProps,null===t?Ls(e):Il=t,Ll.current=null}function Ls(e){var t=e;do{var n=t.alternate;if(e=t.return,0==(2048&t.flags)){if(null!==(n=ll(n,t,Dl)))return void(Il=n);if(24!==(n=t).tag&&23!==n.tag||null===n.memoizedState||0!=(1073741824&Dl)||0==(4&n.mode)){for(var r=0,a=n.child;null!==a;)r|=a.lanes|a.childLanes,a=a.sibling;n.childLanes=r}null!==e&&0==(2048&e.flags)&&(null===e.firstEffect&&(e.firstEffect=t.firstEffect),null!==t.lastEffect&&(null!==e.lastEffect&&(e.lastEffect.nextEffect=t.firstEffect),e.lastEffect=t.lastEffect),1<t.flags&&(null!==e.lastEffect?e.lastEffect.nextEffect=t:e.firstEffect=t,e.lastEffect=t))}else{if(null!==(n=sl(t)))return n.flags&=2047,void(Il=n);null!==e&&(e.firstEffect=e.lastEffect=null,e.flags|=2048)}if(null!==(t=t.sibling))return void(Il=t);Il=t=e}while(null!==t);0===Bl&&(Bl=5)}function Ps(e){var t=qa();return Ga(99,Rs.bind(null,e,t)),null}function Rs(e,t){do{Ms()}while(null!==es);if(0!=(48&Pl))throw Error(i(327));var n=e.finishedWork;if(null===n)return null;if(e.finishedWork=null,e.finishedLanes=0,n===e.current)throw Error(i(177));e.callbackNode=null;var r=n.lanes|n.childLanes,a=r,o=e.pendingLanes&~a;e.pendingLanes=a,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=a,e.mutableReadLanes&=a,e.entangledLanes&=a,a=e.entanglements;for(var l=e.eventTimes,s=e.expirationTimes;0<o;){var u=31-qt(o),c=1<<u;a[u]=0,l[u]=-1,s[u]=-1,o&=~c}if(null!==as&&0==(24&r)&&as.has(e)&&as.delete(e),e===Rl&&(Il=Rl=null,Ml=0),1<n.flags?null!==n.lastEffect?(n.lastEffect.nextEffect=n,r=n.firstEffect):r=n:r=n.firstEffect,null!==r){if(a=Pl,Pl|=32,Ll.current=null,Ur=Kt,br(l=gr())){if("selectionStart"in l)s={start:l.selectionStart,end:l.selectionEnd};else e:if(s=(s=l.ownerDocument)&&s.defaultView||window,(c=s.getSelection&&s.getSelection())&&0!==c.rangeCount){s=c.anchorNode,o=c.anchorOffset,u=c.focusNode,c=c.focusOffset;try{s.nodeType,u.nodeType}catch(C){s=null;break e}var d=0,f=-1,p=-1,m=0,h=0,g=l,b=null;t:for(;;){for(var v;g!==s||0!==o&&3!==g.nodeType||(f=d+o),g!==u||0!==c&&3!==g.nodeType||(p=d+c),3===g.nodeType&&(d+=g.nodeValue.length),null!==(v=g.firstChild);)b=g,g=v;for(;;){if(g===l)break t;if(b===s&&++m===o&&(f=d),b===u&&++h===c&&(p=d),null!==(v=g.nextSibling))break;b=(g=b).parentNode}g=v}s=-1===f||-1===p?null:{start:f,end:p}}else s=null;s=s||{start:0,end:0}}else s=null;jr={focusedElem:l,selectionRange:s},Kt=!1,cs=null,ds=!1,Kl=r;do{try{Is()}catch(C){if(null===Kl)throw Error(i(330));zs(Kl,C),Kl=Kl.nextEffect}}while(null!==Kl);cs=null,Kl=r;do{try{for(l=e;null!==Kl;){var y=Kl.flags;if(16&y&&ve(Kl.stateNode,""),128&y){var w=Kl.alternate;if(null!==w){var E=w.ref;null!==E&&("function"==typeof E?E(null):E.current=null)}}switch(1038&y){case 2:kl(Kl),Kl.flags&=-3;break;case 6:kl(Kl),Kl.flags&=-3,Cl(Kl.alternate,Kl);break;case 1024:Kl.flags&=-1025;break;case 1028:Kl.flags&=-1025,Cl(Kl.alternate,Kl);break;case 4:Cl(Kl.alternate,Kl);break;case 8:_l(l,s=Kl);var k=s.alternate;wl(s),null!==k&&wl(k)}Kl=Kl.nextEffect}}catch(C){if(null===Kl)throw Error(i(330));zs(Kl,C),Kl=Kl.nextEffect}}while(null!==Kl);if(E=jr,w=gr(),y=E.focusedElem,l=E.selectionRange,w!==y&&y&&y.ownerDocument&&hr(y.ownerDocument.documentElement,y)){null!==l&&br(y)&&(w=l.start,void 0===(E=l.end)&&(E=w),"selectionStart"in y?(y.selectionStart=w,y.selectionEnd=Math.min(E,y.value.length)):(E=(w=y.ownerDocument||document)&&w.defaultView||window).getSelection&&(E=E.getSelection(),s=y.textContent.length,k=Math.min(l.start,s),l=void 0===l.end?k:Math.min(l.end,s),!E.extend&&k>l&&(s=l,l=k,k=s),s=mr(y,k),o=mr(y,l),s&&o&&(1!==E.rangeCount||E.anchorNode!==s.node||E.anchorOffset!==s.offset||E.focusNode!==o.node||E.focusOffset!==o.offset)&&((w=w.createRange()).setStart(s.node,s.offset),E.removeAllRanges(),k>l?(E.addRange(w),E.extend(o.node,o.offset)):(w.setEnd(o.node,o.offset),E.addRange(w))))),w=[];for(E=y;E=E.parentNode;)1===E.nodeType&&w.push({element:E,left:E.scrollLeft,top:E.scrollTop});for("function"==typeof y.focus&&y.focus(),y=0;y<w.length;y++)(E=w[y]).element.scrollLeft=E.left,E.element.scrollTop=E.top}Kt=!!Ur,jr=Ur=null,e.current=n,Kl=r;do{try{for(y=e;null!==Kl;){var S=Kl.flags;if(36&S&&bl(y,Kl.alternate,Kl),128&S){w=void 0;var x=Kl.ref;if(null!==x){var _=Kl.stateNode;Kl.tag,w=_,"function"==typeof x?x(w):x.current=w}}Kl=Kl.nextEffect}}catch(C){if(null===Kl)throw Error(i(330));zs(Kl,C),Kl=Kl.nextEffect}}while(null!==Kl);Kl=null,Ba(),Pl=a}else e.current=n;if(Jl)Jl=!1,es=e,ts=t;else for(Kl=r;null!==Kl;)t=Kl.nextEffect,Kl.nextEffect=null,8&Kl.flags&&((S=Kl).sibling=null,S.stateNode=null),Kl=t;if(0===(r=e.pendingLanes)&&(Zl=null),1===r?e===is?os++:(os=0,is=e):os=0,n=n.stateNode,xa&&"function"==typeof xa.onCommitFiberRoot)try{xa.onCommitFiberRoot(Sa,n,void 0,64==(64&n.current.flags))}catch(C){}if(gs(e,Ha()),Ql)throw Ql=!1,e=Xl,Xl=null,e;return 0!=(8&Pl)||Ya(),null}function Is(){for(;null!==Kl;){var e=Kl.alternate;ds||null===cs||(0!=(8&Kl.flags)?et(Kl,cs)&&(ds=!0):13===Kl.tag&&Tl(e,Kl)&&et(Kl,cs)&&(ds=!0));var t=Kl.flags;0!=(256&t)&&gl(e,Kl),0==(512&t)||Jl||(Jl=!0,Wa(97,(function(){return Ms(),null}))),Kl=Kl.nextEffect}}function Ms(){if(90!==ts){var e=97<ts?97:ts;return ts=90,Ga(e,Bs)}return!1}function Ds(e,t){ns.push(t,e),Jl||(Jl=!0,Wa(97,(function(){return Ms(),null})))}function Fs(e,t){rs.push(t,e),Jl||(Jl=!0,Wa(97,(function(){return Ms(),null})))}function Bs(){if(null===es)return!1;var e=es;if(es=null,0!=(48&Pl))throw Error(i(331));var t=Pl;Pl|=32;var n=rs;rs=[];for(var r=0;r<n.length;r+=2){var a=n[r],o=n[r+1],l=a.destroy;if(a.destroy=void 0,"function"==typeof l)try{l()}catch(u){if(null===o)throw Error(i(330));zs(o,u)}}for(n=ns,ns=[],r=0;r<n.length;r+=2){a=n[r],o=n[r+1];try{var s=a.create;a.destroy=s()}catch(u){if(null===o)throw Error(i(330));zs(o,u)}}for(s=e.current.firstEffect;null!==s;)e=s.nextEffect,s.nextEffect=null,8&s.flags&&(s.sibling=null,s.stateNode=null),s=e;return Pl=t,Ya(),!0}function $s(e,t,n){fo(e,t=fl(0,t=ul(n,t),1)),t=fs(),null!==(e=hs(e,1))&&(Ht(e,1,t),gs(e,t))}function zs(e,t){if(3===e.tag)$s(e,e,t);else for(var n=e.return;null!==n;){if(3===n.tag){$s(n,e,t);break}if(1===n.tag){var r=n.stateNode;if("function"==typeof n.type.getDerivedStateFromError||"function"==typeof r.componentDidCatch&&(null===Zl||!Zl.has(r))){var a=pl(n,e=ul(t,e),1);if(fo(n,a),a=fs(),null!==(n=hs(n,1)))Ht(n,1,a),gs(n,a);else if("function"==typeof r.componentDidCatch&&(null===Zl||!Zl.has(r)))try{r.componentDidCatch(t,e)}catch(o){}break}}n=n.return}}function Us(e,t,n){var r=e.pingCache;null!==r&&r.delete(t),t=fs(),e.pingedLanes|=e.suspendedLanes&n,Rl===e&&(Ml&n)===n&&(4===Bl||3===Bl&&(62914560&Ml)===Ml&&500>Ha()-Vl?xs(e,0):Hl|=n),gs(e,t)}function js(e,t){var n=e.stateNode;null!==n&&n.delete(t),0===(t=0)&&(0==(2&(t=e.mode))?t=1:0==(4&t)?t=99===qa()?1:2:(0===ss&&(ss=zl),0===(t=Ut(62914560&~ss))&&(t=4194304))),n=fs(),null!==(e=hs(e,t))&&(Ht(e,t,n),gs(e,n))}function Hs(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.flags=0,this.lastEffect=this.firstEffect=this.nextEffect=null,this.childLanes=this.lanes=0,this.alternate=null}function qs(e,t,n,r){return new Hs(e,t,n,r)}function Vs(e){return!(!(e=e.prototype)||!e.isReactComponent)}function Gs(e,t){var n=e.alternate;return null===n?((n=qs(e.tag,t,e.key,e.mode)).elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.nextEffect=null,n.firstEffect=null,n.lastEffect=null),n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=null===t?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Ws(e,t,n,r,a,o){var l=2;if(r=e,"function"==typeof e)Vs(e)&&(l=1);else if("string"==typeof e)l=5;else e:switch(e){case x:return Ys(n.children,a,o,t);case D:l=8,a|=16;break;case _:l=8,a|=1;break;case C:return(e=qs(12,n,t,8|a)).elementType=C,e.type=C,e.lanes=o,e;case O:return(e=qs(13,n,t,a)).type=O,e.elementType=O,e.lanes=o,e;case L:return(e=qs(19,n,t,a)).elementType=L,e.lanes=o,e;case F:return Ks(n,a,o,t);case B:return(e=qs(24,n,t,a)).elementType=B,e.lanes=o,e;default:if("object"==typeof e&&null!==e)switch(e.$$typeof){case A:l=10;break e;case T:l=9;break e;case N:l=11;break e;case P:l=14;break e;case R:l=16,r=null;break e;case I:l=22;break e}throw Error(i(130,null==e?e:typeof e,""))}return(t=qs(l,n,t,a)).elementType=e,t.type=r,t.lanes=o,t}function Ys(e,t,n,r){return(e=qs(7,e,r,t)).lanes=n,e}function Ks(e,t,n,r){return(e=qs(23,e,r,t)).elementType=F,e.lanes=n,e}function Qs(e,t,n){return(e=qs(6,e,null,t)).lanes=n,e}function Xs(e,t,n){return(t=qs(4,null!==e.children?e.children:[],e.key,t)).lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Zs(e,t,n){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.pendingContext=this.context=null,this.hydrate=n,this.callbackNode=null,this.callbackPriority=0,this.eventTimes=jt(0),this.expirationTimes=jt(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=jt(0),this.mutableSourceEagerHydrationData=null}function Js(e,t,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:S,key:null==r?null:""+r,children:e,containerInfo:t,implementation:n}}function eu(e,t,n,r){var a=t.current,o=fs(),l=ps(a);e:if(n){t:{if(Qe(n=n._reactInternals)!==n||1!==n.tag)throw Error(i(170));var s=n;do{switch(s.tag){case 3:s=s.stateNode.context;break t;case 1:if(ba(s.type)){s=s.stateNode.__reactInternalMemoizedMergedChildContext;break t}}s=s.return}while(null!==s);throw Error(i(171))}if(1===n.tag){var u=n.type;if(ba(u)){n=wa(n,u,s);break e}}n=s}else n=fa;return null===t.context?t.context=n:t.pendingContext=n,(t=co(o,l)).payload={element:e},null!==(r=void 0===r?null:r)&&(t.callback=r),fo(a,t),ms(a,l,o),l}function tu(e){return(e=e.current).child?(e.child.tag,e.child.stateNode):null}function nu(e,t){if(null!==(e=e.memoizedState)&&null!==e.dehydrated){var n=e.retryLane;e.retryLane=0!==n&&n<t?n:t}}function ru(e,t){nu(e,t),(e=e.alternate)&&nu(e,t)}function au(e,t,n){var r=null!=n&&null!=n.hydrationOptions&&n.hydrationOptions.mutableSources||null;if(n=new Zs(e,t,null!=n&&!0===n.hydrate),t=qs(3,null,null,2===t?7:1===t?3:0),n.current=t,t.stateNode=n,so(t),e[ea]=n.current,Pr(8===e.nodeType?e.parentNode:e),r)for(e=0;e<r.length;e++){var a=(t=r[e])._getVersion;a=a(t._source),null==n.mutableSourceEagerHydrationData?n.mutableSourceEagerHydrationData=[t,a]:n.mutableSourceEagerHydrationData.push(t,a)}this._internalRoot=n}function ou(e){return!(!e||1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType&&(8!==e.nodeType||" react-mount-point-unstable "!==e.nodeValue))}function iu(e,t,n,r,a){var o=n._reactRootContainer;if(o){var i=o._internalRoot;if("function"==typeof a){var l=a;a=function(){var e=tu(i);l.call(e)}}eu(t,i,e,a)}else{if(o=n._reactRootContainer=function(e,t){if(t||(t=!(!(t=e?9===e.nodeType?e.documentElement:e.firstChild:null)||1!==t.nodeType||!t.hasAttribute("data-reactroot"))),!t)for(var n;n=e.lastChild;)e.removeChild(n);return new au(e,0,t?{hydrate:!0}:void 0)}(n,r),i=o._internalRoot,"function"==typeof a){var s=a;a=function(){var e=tu(i);s.call(e)}}Es((function(){eu(t,i,e,a)}))}return tu(i)}function lu(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;if(!ou(t))throw Error(i(200));return Js(e,t,null,n)}Yl=function(e,t,n){var r=t.lanes;if(null!==e)if(e.memoizedProps!==t.pendingProps||ma.current)Fi=!0;else{if(0==(n&r)){switch(Fi=!1,t.tag){case 3:Wi(t),Yo();break;case 5:Do(t);break;case 1:ba(t.type)&&Ea(t);break;case 4:Io(t,t.stateNode.containerInfo);break;case 10:r=t.memoizedProps.value;var a=t.type._context;da(Za,a._currentValue),a._currentValue=r;break;case 13:if(null!==t.memoizedState)return 0!=(n&t.child.childLanes)?Zi(e,t,n):(da(Bo,1&Bo.current),null!==(t=ol(e,t,n))?t.sibling:null);da(Bo,1&Bo.current);break;case 19:if(r=0!=(n&t.childLanes),0!=(64&e.flags)){if(r)return al(e,t,n);t.flags|=64}if(null!==(a=t.memoizedState)&&(a.rendering=null,a.tail=null,a.lastEffect=null),da(Bo,Bo.current),r)break;return null;case 23:case 24:return t.lanes=0,ji(e,t,n)}return ol(e,t,n)}Fi=0!=(16384&e.flags)}else Fi=!1;switch(t.lanes=0,t.tag){case 2:if(r=t.type,null!==e&&(e.alternate=null,t.alternate=null,t.flags|=2),e=t.pendingProps,a=ga(t,pa.current),oo(t,n),a=li(null,t,r,e,a,n),t.flags|=1,"object"==typeof a&&null!==a&&"function"==typeof a.render&&void 0===a.$$typeof){if(t.tag=1,t.memoizedState=null,t.updateQueue=null,ba(r)){var o=!0;Ea(t)}else o=!1;t.memoizedState=null!==a.state&&void 0!==a.state?a.state:null,so(t);var l=r.getDerivedStateFromProps;"function"==typeof l&&bo(t,r,l,e),a.updater=vo,t.stateNode=a,a._reactInternals=t,ko(t,r,e,n),t=Gi(null,t,r,!0,o,n)}else t.tag=0,Bi(null,t,a,n),t=t.child;return t;case 16:a=t.elementType;e:{switch(null!==e&&(e.alternate=null,t.alternate=null,t.flags|=2),e=t.pendingProps,a=(o=a._init)(a._payload),t.type=a,o=t.tag=function(e){if("function"==typeof e)return Vs(e)?1:0;if(null!=e){if((e=e.$$typeof)===N)return 11;if(e===P)return 14}return 2}(a),e=Xa(a,e),o){case 0:t=qi(null,t,a,e,n);break e;case 1:t=Vi(null,t,a,e,n);break e;case 11:t=$i(null,t,a,e,n);break e;case 14:t=zi(null,t,a,Xa(a.type,e),r,n);break e}throw Error(i(306,a,""))}return t;case 0:return r=t.type,a=t.pendingProps,qi(e,t,r,a=t.elementType===r?a:Xa(r,a),n);case 1:return r=t.type,a=t.pendingProps,Vi(e,t,r,a=t.elementType===r?a:Xa(r,a),n);case 3:if(Wi(t),r=t.updateQueue,null===e||null===r)throw Error(i(282));if(r=t.pendingProps,a=null!==(a=t.memoizedState)?a.element:null,uo(e,t),mo(t,r,null,n),(r=t.memoizedState.element)===a)Yo(),t=ol(e,t,n);else{if((o=(a=t.stateNode).hydrate)&&(Uo=Yr(t.stateNode.containerInfo.firstChild),zo=t,o=jo=!0),o){if(null!=(e=a.mutableSourceEagerHydrationData))for(a=0;a<e.length;a+=2)(o=e[a])._workInProgressVersionPrimary=e[a+1],Ko.push(o);for(n=To(t,null,r,n),t.child=n;n;)n.flags=-3&n.flags|1024,n=n.sibling}else Bi(e,t,r,n),Yo();t=t.child}return t;case 5:return Do(t),null===e&&Vo(t),r=t.type,a=t.pendingProps,o=null!==e?e.memoizedProps:null,l=a.children,qr(r,a)?l=null:null!==o&&qr(r,o)&&(t.flags|=16),Hi(e,t),Bi(e,t,l,n),t.child;case 6:return null===e&&Vo(t),null;case 13:return Zi(e,t,n);case 4:return Io(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=Ao(t,null,r,n):Bi(e,t,r,n),t.child;case 11:return r=t.type,a=t.pendingProps,$i(e,t,r,a=t.elementType===r?a:Xa(r,a),n);case 7:return Bi(e,t,t.pendingProps,n),t.child;case 8:case 12:return Bi(e,t,t.pendingProps.children,n),t.child;case 10:e:{r=t.type._context,a=t.pendingProps,l=t.memoizedProps,o=a.value;var s=t.type._context;if(da(Za,s._currentValue),s._currentValue=o,null!==l)if(s=l.value,0===(o=cr(s,o)?0:0|("function"==typeof r._calculateChangedBits?r._calculateChangedBits(s,o):1073741823))){if(l.children===a.children&&!ma.current){t=ol(e,t,n);break e}}else for(null!==(s=t.child)&&(s.return=t);null!==s;){var u=s.dependencies;if(null!==u){l=s.child;for(var c=u.firstContext;null!==c;){if(c.context===r&&0!=(c.observedBits&o)){1===s.tag&&((c=co(-1,n&-n)).tag=2,fo(s,c)),s.lanes|=n,null!==(c=s.alternate)&&(c.lanes|=n),ao(s.return,n),u.lanes|=n;break}c=c.next}}else l=10===s.tag&&s.type===t.type?null:s.child;if(null!==l)l.return=s;else for(l=s;null!==l;){if(l===t){l=null;break}if(null!==(s=l.sibling)){s.return=l.return,l=s;break}l=l.return}s=l}Bi(e,t,a.children,n),t=t.child}return t;case 9:return a=t.type,r=(o=t.pendingProps).children,oo(t,n),r=r(a=io(a,o.unstable_observedBits)),t.flags|=1,Bi(e,t,r,n),t.child;case 14:return o=Xa(a=t.type,t.pendingProps),zi(e,t,a,o=Xa(a.type,o),r,n);case 15:return Ui(e,t,t.type,t.pendingProps,r,n);case 17:return r=t.type,a=t.pendingProps,a=t.elementType===r?a:Xa(r,a),null!==e&&(e.alternate=null,t.alternate=null,t.flags|=2),t.tag=1,ba(r)?(e=!0,Ea(t)):e=!1,oo(t,n),wo(t,r,a),ko(t,r,a,n),Gi(null,t,r,!0,e,n);case 19:return al(e,t,n);case 23:case 24:return ji(e,t,n)}throw Error(i(156,t.tag))},au.prototype.render=function(e){eu(e,this._internalRoot,null,null)},au.prototype.unmount=function(){var e=this._internalRoot,t=e.containerInfo;eu(null,e,null,(function(){t[ea]=null}))},tt=function(e){13===e.tag&&(ms(e,4,fs()),ru(e,4))},nt=function(e){13===e.tag&&(ms(e,67108864,fs()),ru(e,67108864))},rt=function(e){if(13===e.tag){var t=fs(),n=ps(e);ms(e,n,t),ru(e,n)}},at=function(e,t){return t()},Ae=function(e,t,n){switch(t){case"input":if(ne(e,n),t=n.name,"radio"===n.type&&null!=t){for(n=e;n.parentNode;)n=n.parentNode;for(n=n.querySelectorAll("input[name="+JSON.stringify(""+t)+'][type="radio"]'),t=0;t<n.length;t++){var r=n[t];if(r!==e&&r.form===e.form){var a=oa(r);if(!a)throw Error(i(90));X(r),ne(r,a)}}}break;case"textarea":ue(e,n);break;case"select":null!=(t=n.value)&&ie(e,!!n.multiple,t,!1)}},Re=ws,Ie=function(e,t,n,r,a){var o=Pl;Pl|=4;try{return Ga(98,e.bind(null,t,n,r,a))}finally{0===(Pl=o)&&(Wl(),Ya())}},Me=function(){0==(49&Pl)&&(function(){if(null!==as){var e=as;as=null,e.forEach((function(e){e.expiredLanes|=24&e.pendingLanes,gs(e,Ha())}))}Ya()}(),Ms())},De=function(e,t){var n=Pl;Pl|=2;try{return e(t)}finally{0===(Pl=n)&&(Wl(),Ya())}};var su={Events:[ra,aa,oa,Le,Pe,Ms,{current:!1}]},uu={findFiberByHostInstance:na,bundleType:0,version:"17.0.2",rendererPackageName:"react-dom"},cu={bundleType:uu.bundleType,version:uu.version,rendererPackageName:uu.rendererPackageName,rendererConfig:uu.rendererConfig,overrideHookState:null,overrideHookStateDeletePath:null,overrideHookStateRenamePath:null,overrideProps:null,overridePropsDeletePath:null,overridePropsRenamePath:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:E.ReactCurrentDispatcher,findHostInstanceByFiber:function(e){return null===(e=Je(e))?null:e.stateNode},findFiberByHostInstance:uu.findFiberByHostInstance||function(){return null},findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null};if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__){var du=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!du.isDisabled&&du.supportsFiber)try{Sa=du.inject(cu),xa=du}catch(ge){}}t.hydrate=function(e,t,n){if(!ou(t))throw Error(i(200));return iu(null,e,t,!0,n)}},961:(e,t,n)=>{"use strict";!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}(),e.exports=n(2551)},115:e=>{var t="undefined"!=typeof Element,n="function"==typeof Map,r="function"==typeof Set,a="function"==typeof ArrayBuffer&&!!ArrayBuffer.isView;function o(e,i){if(e===i)return!0;if(e&&i&&"object"==typeof e&&"object"==typeof i){if(e.constructor!==i.constructor)return!1;var l,s,u,c;if(Array.isArray(e)){if((l=e.length)!=i.length)return!1;for(s=l;0!=s--;)if(!o(e[s],i[s]))return!1;return!0}if(n&&e instanceof Map&&i instanceof Map){if(e.size!==i.size)return!1;for(c=e.entries();!(s=c.next()).done;)if(!i.has(s.value[0]))return!1;for(c=e.entries();!(s=c.next()).done;)if(!o(s.value[1],i.get(s.value[0])))return!1;return!0}if(r&&e instanceof Set&&i instanceof Set){if(e.size!==i.size)return!1;for(c=e.entries();!(s=c.next()).done;)if(!i.has(s.value[0]))return!1;return!0}if(a&&ArrayBuffer.isView(e)&&ArrayBuffer.isView(i)){if((l=e.length)!=i.length)return!1;for(s=l;0!=s--;)if(e[s]!==i[s])return!1;return!0}if(e.constructor===RegExp)return e.source===i.source&&e.flags===i.flags;if(e.valueOf!==Object.prototype.valueOf)return e.valueOf()===i.valueOf();if(e.toString!==Object.prototype.toString)return e.toString()===i.toString();if((l=(u=Object.keys(e)).length)!==Object.keys(i).length)return!1;for(s=l;0!=s--;)if(!Object.prototype.hasOwnProperty.call(i,u[s]))return!1;if(t&&e instanceof Element)return!1;for(s=l;0!=s--;)if(("_owner"!==u[s]&&"__v"!==u[s]&&"__o"!==u[s]||!e.$$typeof)&&!o(e[u[s]],i[u[s]]))return!1;return!0}return e!=e&&i!=i}e.exports=function(e,t){try{return o(e,t)}catch(n){if((n.message||"").match(/stack|recursion/i))return console.warn("react-fast-compare cannot handle circular refs"),!1;throw n}}},545:(e,t,n)=>{"use strict";n.d(t,{mg:()=>J,vd:()=>V});var r=n(6540),a=n(5556),o=n.n(a),i=n(115),l=n.n(i),s=n(311),u=n.n(s),c=n(2833),d=n.n(c);function f(){return f=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},f.apply(this,arguments)}function p(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,m(e,t)}function m(e,t){return m=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},m(e,t)}function h(e,t){if(null==e)return{};var n,r,a={},o=Object.keys(e);for(r=0;r<o.length;r++)t.indexOf(n=o[r])>=0||(a[n]=e[n]);return a}var g={BASE:"base",BODY:"body",HEAD:"head",HTML:"html",LINK:"link",META:"meta",NOSCRIPT:"noscript",SCRIPT:"script",STYLE:"style",TITLE:"title",FRAGMENT:"Symbol(react.fragment)"},b={rel:["amphtml","canonical","alternate"]},v={type:["application/ld+json"]},y={charset:"",name:["robots","description"],property:["og:type","og:title","og:url","og:image","og:image:alt","og:description","twitter:url","twitter:title","twitter:description","twitter:image","twitter:image:alt","twitter:card","twitter:site"]},w=Object.keys(g).map((function(e){return g[e]})),E={accesskey:"accessKey",charset:"charSet",class:"className",contenteditable:"contentEditable",contextmenu:"contextMenu","http-equiv":"httpEquiv",itemprop:"itemProp",tabindex:"tabIndex"},k=Object.keys(E).reduce((function(e,t){return e[E[t]]=t,e}),{}),S=function(e,t){for(var n=e.length-1;n>=0;n-=1){var r=e[n];if(Object.prototype.hasOwnProperty.call(r,t))return r[t]}return null},x=function(e){var t=S(e,g.TITLE),n=S(e,"titleTemplate");if(Array.isArray(t)&&(t=t.join("")),n&&t)return n.replace(/%s/g,(function(){return t}));var r=S(e,"defaultTitle");return t||r||void 0},_=function(e){return S(e,"onChangeClientState")||function(){}},C=function(e,t){return t.filter((function(t){return void 0!==t[e]})).map((function(t){return t[e]})).reduce((function(e,t){return f({},e,t)}),{})},A=function(e,t){return t.filter((function(e){return void 0!==e[g.BASE]})).map((function(e){return e[g.BASE]})).reverse().reduce((function(t,n){if(!t.length)for(var r=Object.keys(n),a=0;a<r.length;a+=1){var o=r[a].toLowerCase();if(-1!==e.indexOf(o)&&n[o])return t.concat(n)}return t}),[])},T=function(e,t,n){var r={};return n.filter((function(t){return!!Array.isArray(t[e])||(void 0!==t[e]&&console&&"function"==typeof console.warn&&console.warn("Helmet: "+e+' should be of type "Array". Instead found type "'+typeof t[e]+'"'),!1)})).map((function(t){return t[e]})).reverse().reduce((function(e,n){var a={};n.filter((function(e){for(var n,o=Object.keys(e),i=0;i<o.length;i+=1){var l=o[i],s=l.toLowerCase();-1===t.indexOf(s)||"rel"===n&&"canonical"===e[n].toLowerCase()||"rel"===s&&"stylesheet"===e[s].toLowerCase()||(n=s),-1===t.indexOf(l)||"innerHTML"!==l&&"cssText"!==l&&"itemprop"!==l||(n=l)}if(!n||!e[n])return!1;var u=e[n].toLowerCase();return r[n]||(r[n]={}),a[n]||(a[n]={}),!r[n][u]&&(a[n][u]=!0,!0)})).reverse().forEach((function(t){return e.push(t)}));for(var o=Object.keys(a),i=0;i<o.length;i+=1){var l=o[i],s=f({},r[l],a[l]);r[l]=s}return e}),[]).reverse()},N=function(e,t){if(Array.isArray(e)&&e.length)for(var n=0;n<e.length;n+=1)if(e[n][t])return!0;return!1},O=function(e){return Array.isArray(e)?e.join(""):e},L=function(e,t){return Array.isArray(e)?e.reduce((function(e,n){return function(e,t){for(var n=Object.keys(e),r=0;r<n.length;r+=1)if(t[n[r]]&&t[n[r]].includes(e[n[r]]))return!0;return!1}(n,t)?e.priority.push(n):e.default.push(n),e}),{priority:[],default:[]}):{default:e}},P=function(e,t){var n;return f({},e,((n={})[t]=void 0,n))},R=[g.NOSCRIPT,g.SCRIPT,g.STYLE],I=function(e,t){return void 0===t&&(t=!0),!1===t?String(e):String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")},M=function(e){return Object.keys(e).reduce((function(t,n){var r=void 0!==e[n]?n+'="'+e[n]+'"':""+n;return t?t+" "+r:r}),"")},D=function(e,t){return void 0===t&&(t={}),Object.keys(e).reduce((function(t,n){return t[E[n]||n]=e[n],t}),t)},F=function(e,t){return t.map((function(t,n){var a,o=((a={key:n})["data-rh"]=!0,a);return Object.keys(t).forEach((function(e){var n=E[e]||e;"innerHTML"===n||"cssText"===n?o.dangerouslySetInnerHTML={__html:t.innerHTML||t.cssText}:o[n]=t[e]})),r.createElement(e,o)}))},B=function(e,t,n){switch(e){case g.TITLE:return{toComponent:function(){return n=t.titleAttributes,(a={key:e=t.title})["data-rh"]=!0,o=D(n,a),[r.createElement(g.TITLE,o,e)];var e,n,a,o},toString:function(){return function(e,t,n,r){var a=M(n),o=O(t);return a?"<"+e+' data-rh="true" '+a+">"+I(o,r)+"</"+e+">":"<"+e+' data-rh="true">'+I(o,r)+"</"+e+">"}(e,t.title,t.titleAttributes,n)}};case"bodyAttributes":case"htmlAttributes":return{toComponent:function(){return D(t)},toString:function(){return M(t)}};default:return{toComponent:function(){return F(e,t)},toString:function(){return function(e,t,n){return t.reduce((function(t,r){var a=Object.keys(r).filter((function(e){return!("innerHTML"===e||"cssText"===e)})).reduce((function(e,t){var a=void 0===r[t]?t:t+'="'+I(r[t],n)+'"';return e?e+" "+a:a}),""),o=r.innerHTML||r.cssText||"",i=-1===R.indexOf(e);return t+"<"+e+' data-rh="true" '+a+(i?"/>":">"+o+"</"+e+">")}),"")}(e,t,n)}}}},$=function(e){var t=e.baseTag,n=e.bodyAttributes,r=e.encode,a=e.htmlAttributes,o=e.noscriptTags,i=e.styleTags,l=e.title,s=void 0===l?"":l,u=e.titleAttributes,c=e.linkTags,d=e.metaTags,f=e.scriptTags,p={toComponent:function(){},toString:function(){return""}};if(e.prioritizeSeoTags){var m=function(e){var t=e.linkTags,n=e.scriptTags,r=e.encode,a=L(e.metaTags,y),o=L(t,b),i=L(n,v);return{priorityMethods:{toComponent:function(){return[].concat(F(g.META,a.priority),F(g.LINK,o.priority),F(g.SCRIPT,i.priority))},toString:function(){return B(g.META,a.priority,r)+" "+B(g.LINK,o.priority,r)+" "+B(g.SCRIPT,i.priority,r)}},metaTags:a.default,linkTags:o.default,scriptTags:i.default}}(e);p=m.priorityMethods,c=m.linkTags,d=m.metaTags,f=m.scriptTags}return{priority:p,base:B(g.BASE,t,r),bodyAttributes:B("bodyAttributes",n,r),htmlAttributes:B("htmlAttributes",a,r),link:B(g.LINK,c,r),meta:B(g.META,d,r),noscript:B(g.NOSCRIPT,o,r),script:B(g.SCRIPT,f,r),style:B(g.STYLE,i,r),title:B(g.TITLE,{title:s,titleAttributes:u},r)}},z=[],U=function(e,t){var n=this;void 0===t&&(t="undefined"!=typeof document),this.instances=[],this.value={setHelmet:function(e){n.context.helmet=e},helmetInstances:{get:function(){return n.canUseDOM?z:n.instances},add:function(e){(n.canUseDOM?z:n.instances).push(e)},remove:function(e){var t=(n.canUseDOM?z:n.instances).indexOf(e);(n.canUseDOM?z:n.instances).splice(t,1)}}},this.context=e,this.canUseDOM=t,t||(e.helmet=$({baseTag:[],bodyAttributes:{},encodeSpecialCharacters:!0,htmlAttributes:{},linkTags:[],metaTags:[],noscriptTags:[],scriptTags:[],styleTags:[],title:"",titleAttributes:{}}))},j=r.createContext({}),H=o().shape({setHelmet:o().func,helmetInstances:o().shape({get:o().func,add:o().func,remove:o().func})}),q="undefined"!=typeof document,V=function(e){function t(n){var r;return(r=e.call(this,n)||this).helmetData=new U(r.props.context,t.canUseDOM),r}return p(t,e),t.prototype.render=function(){return r.createElement(j.Provider,{value:this.helmetData.value},this.props.children)},t}(r.Component);V.canUseDOM=q,V.propTypes={context:o().shape({helmet:o().shape()}),children:o().node.isRequired},V.defaultProps={context:{}},V.displayName="HelmetProvider";var G=function(e,t){var n,r=document.head||document.querySelector(g.HEAD),a=r.querySelectorAll(e+"[data-rh]"),o=[].slice.call(a),i=[];return t&&t.length&&t.forEach((function(t){var r=document.createElement(e);for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&("innerHTML"===a?r.innerHTML=t.innerHTML:"cssText"===a?r.styleSheet?r.styleSheet.cssText=t.cssText:r.appendChild(document.createTextNode(t.cssText)):r.setAttribute(a,void 0===t[a]?"":t[a]));r.setAttribute("data-rh","true"),o.some((function(e,t){return n=t,r.isEqualNode(e)}))?o.splice(n,1):i.push(r)})),o.forEach((function(e){return e.parentNode.removeChild(e)})),i.forEach((function(e){return r.appendChild(e)})),{oldTags:o,newTags:i}},W=function(e,t){var n=document.getElementsByTagName(e)[0];if(n){for(var r=n.getAttribute("data-rh"),a=r?r.split(","):[],o=[].concat(a),i=Object.keys(t),l=0;l<i.length;l+=1){var s=i[l],u=t[s]||"";n.getAttribute(s)!==u&&n.setAttribute(s,u),-1===a.indexOf(s)&&a.push(s);var c=o.indexOf(s);-1!==c&&o.splice(c,1)}for(var d=o.length-1;d>=0;d-=1)n.removeAttribute(o[d]);a.length===o.length?n.removeAttribute("data-rh"):n.getAttribute("data-rh")!==i.join(",")&&n.setAttribute("data-rh",i.join(","))}},Y=function(e,t){var n=e.baseTag,r=e.htmlAttributes,a=e.linkTags,o=e.metaTags,i=e.noscriptTags,l=e.onChangeClientState,s=e.scriptTags,u=e.styleTags,c=e.title,d=e.titleAttributes;W(g.BODY,e.bodyAttributes),W(g.HTML,r),function(e,t){void 0!==e&&document.title!==e&&(document.title=O(e)),W(g.TITLE,t)}(c,d);var f={baseTag:G(g.BASE,n),linkTags:G(g.LINK,a),metaTags:G(g.META,o),noscriptTags:G(g.NOSCRIPT,i),scriptTags:G(g.SCRIPT,s),styleTags:G(g.STYLE,u)},p={},m={};Object.keys(f).forEach((function(e){var t=f[e],n=t.newTags,r=t.oldTags;n.length&&(p[e]=n),r.length&&(m[e]=f[e].oldTags)})),t&&t(),l(e,p,m)},K=null,Q=function(e){function t(){for(var t,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).rendered=!1,t}p(t,e);var n=t.prototype;return n.shouldComponentUpdate=function(e){return!d()(e,this.props)},n.componentDidUpdate=function(){this.emitChange()},n.componentWillUnmount=function(){this.props.context.helmetInstances.remove(this),this.emitChange()},n.emitChange=function(){var e,t,n=this.props.context,r=n.setHelmet,a=null,o=(e=n.helmetInstances.get().map((function(e){var t=f({},e.props);return delete t.context,t})),{baseTag:A(["href"],e),bodyAttributes:C("bodyAttributes",e),defer:S(e,"defer"),encode:S(e,"encodeSpecialCharacters"),htmlAttributes:C("htmlAttributes",e),linkTags:T(g.LINK,["rel","href"],e),metaTags:T(g.META,["name","charset","http-equiv","property","itemprop"],e),noscriptTags:T(g.NOSCRIPT,["innerHTML"],e),onChangeClientState:_(e),scriptTags:T(g.SCRIPT,["src","innerHTML"],e),styleTags:T(g.STYLE,["cssText"],e),title:x(e),titleAttributes:C("titleAttributes",e),prioritizeSeoTags:N(e,"prioritizeSeoTags")});V.canUseDOM?(t=o,K&&cancelAnimationFrame(K),t.defer?K=requestAnimationFrame((function(){Y(t,(function(){K=null}))})):(Y(t),K=null)):$&&(a=$(o)),r(a)},n.init=function(){this.rendered||(this.rendered=!0,this.props.context.helmetInstances.add(this),this.emitChange())},n.render=function(){return this.init(),null},t}(r.Component);Q.propTypes={context:H.isRequired},Q.displayName="HelmetDispatcher";var X=["children"],Z=["children"],J=function(e){function t(){return e.apply(this,arguments)||this}p(t,e);var n=t.prototype;return n.shouldComponentUpdate=function(e){return!l()(P(this.props,"helmetData"),P(e,"helmetData"))},n.mapNestedChildrenToProps=function(e,t){if(!t)return null;switch(e.type){case g.SCRIPT:case g.NOSCRIPT:return{innerHTML:t};case g.STYLE:return{cssText:t};default:throw new Error("<"+e.type+" /> elements are self-closing and can not contain children. Refer to our API for more information.")}},n.flattenArrayTypeChildren=function(e){var t,n=e.child,r=e.arrayTypeChildren;return f({},r,((t={})[n.type]=[].concat(r[n.type]||[],[f({},e.newChildProps,this.mapNestedChildrenToProps(n,e.nestedChildren))]),t))},n.mapObjectTypeChildren=function(e){var t,n,r=e.child,a=e.newProps,o=e.newChildProps,i=e.nestedChildren;switch(r.type){case g.TITLE:return f({},a,((t={})[r.type]=i,t.titleAttributes=f({},o),t));case g.BODY:return f({},a,{bodyAttributes:f({},o)});case g.HTML:return f({},a,{htmlAttributes:f({},o)});default:return f({},a,((n={})[r.type]=f({},o),n))}},n.mapArrayTypeChildrenToProps=function(e,t){var n=f({},t);return Object.keys(e).forEach((function(t){var r;n=f({},n,((r={})[t]=e[t],r))})),n},n.warnOnInvalidChildren=function(e,t){return u()(w.some((function(t){return e.type===t})),"function"==typeof e.type?"You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.":"Only elements types "+w.join(", ")+" are allowed. Helmet does not support rendering <"+e.type+"> elements. Refer to our API for more information."),u()(!t||"string"==typeof t||Array.isArray(t)&&!t.some((function(e){return"string"!=typeof e})),"Helmet expects a string as a child of <"+e.type+">. Did you forget to wrap your children in braces? ( <"+e.type+">{``}</"+e.type+"> ) Refer to our API for more information."),!0},n.mapChildrenToProps=function(e,t){var n=this,a={};return r.Children.forEach(e,(function(e){if(e&&e.props){var r=e.props,o=r.children,i=h(r,X),l=Object.keys(i).reduce((function(e,t){return e[k[t]||t]=i[t],e}),{}),s=e.type;switch("symbol"==typeof s?s=s.toString():n.warnOnInvalidChildren(e,o),s){case g.FRAGMENT:t=n.mapChildrenToProps(o,t);break;case g.LINK:case g.META:case g.NOSCRIPT:case g.SCRIPT:case g.STYLE:a=n.flattenArrayTypeChildren({child:e,arrayTypeChildren:a,newChildProps:l,nestedChildren:o});break;default:t=n.mapObjectTypeChildren({child:e,newProps:t,newChildProps:l,nestedChildren:o})}}})),this.mapArrayTypeChildrenToProps(a,t)},n.render=function(){var e=this.props,t=e.children,n=h(e,Z),a=f({},n),o=n.helmetData;return t&&(a=this.mapChildrenToProps(t,a)),!o||o instanceof U||(o=new U(o.context,o.instances)),o?r.createElement(Q,f({},a,{context:o.value,helmetData:void 0})):r.createElement(j.Consumer,null,(function(e){return r.createElement(Q,f({},a,{context:e}))}))},t}(r.Component);J.propTypes={base:o().object,bodyAttributes:o().object,children:o().oneOfType([o().arrayOf(o().node),o().node]),defaultTitle:o().string,defer:o().bool,encodeSpecialCharacters:o().bool,htmlAttributes:o().object,link:o().arrayOf(o().object),meta:o().arrayOf(o().object),noscript:o().arrayOf(o().object),onChangeClientState:o().func,script:o().arrayOf(o().object),style:o().arrayOf(o().object),title:o().string,titleAttributes:o().object,titleTemplate:o().string,prioritizeSeoTags:o().bool,helmetData:o().object},J.defaultProps={defer:!0,encodeSpecialCharacters:!0,prioritizeSeoTags:!1},J.displayName="Helmet"},2799:(e,t)=>{"use strict";var n="function"==typeof Symbol&&Symbol.for,r=n?Symbol.for("react.element"):60103,a=n?Symbol.for("react.portal"):60106,o=n?Symbol.for("react.fragment"):60107,i=n?Symbol.for("react.strict_mode"):60108,l=n?Symbol.for("react.profiler"):60114,s=n?Symbol.for("react.provider"):60109,u=n?Symbol.for("react.context"):60110,c=n?Symbol.for("react.async_mode"):60111,d=n?Symbol.for("react.concurrent_mode"):60111,f=n?Symbol.for("react.forward_ref"):60112,p=n?Symbol.for("react.suspense"):60113,m=n?Symbol.for("react.suspense_list"):60120,h=n?Symbol.for("react.memo"):60115,g=n?Symbol.for("react.lazy"):60116,b=n?Symbol.for("react.block"):60121,v=n?Symbol.for("react.fundamental"):60117,y=n?Symbol.for("react.responder"):60118,w=n?Symbol.for("react.scope"):60119;function E(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case r:switch(e=e.type){case c:case d:case o:case l:case i:case p:return e;default:switch(e=e&&e.$$typeof){case u:case f:case g:case h:case s:return e;default:return t}}case a:return t}}}function k(e){return E(e)===d}t.AsyncMode=c,t.ConcurrentMode=d,t.ContextConsumer=u,t.ContextProvider=s,t.Element=r,t.ForwardRef=f,t.Fragment=o,t.Lazy=g,t.Memo=h,t.Portal=a,t.Profiler=l,t.StrictMode=i,t.Suspense=p,t.isAsyncMode=function(e){return k(e)||E(e)===c},t.isConcurrentMode=k,t.isContextConsumer=function(e){return E(e)===u},t.isContextProvider=function(e){return E(e)===s},t.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===r},t.isForwardRef=function(e){return E(e)===f},t.isFragment=function(e){return E(e)===o},t.isLazy=function(e){return E(e)===g},t.isMemo=function(e){return E(e)===h},t.isPortal=function(e){return E(e)===a},t.isProfiler=function(e){return E(e)===l},t.isStrictMode=function(e){return E(e)===i},t.isSuspense=function(e){return E(e)===p},t.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===o||e===d||e===l||e===i||e===p||e===m||"object"==typeof e&&null!==e&&(e.$$typeof===g||e.$$typeof===h||e.$$typeof===s||e.$$typeof===u||e.$$typeof===f||e.$$typeof===v||e.$$typeof===y||e.$$typeof===w||e.$$typeof===b)},t.typeOf=E},4363:(e,t,n)=>{"use strict";e.exports=n(2799)},3259:(e,t,n)=>{"use strict";function r(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e.__proto__=t}function a(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(){return i=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},i.apply(this,arguments)}var l=n(6540),s=n(5556),u=[],c=[];function d(e){var t=e(),n={loading:!0,loaded:null,error:null};return n.promise=t.then((function(e){return n.loading=!1,n.loaded=e,e})).catch((function(e){throw n.loading=!1,n.error=e,e})),n}function f(e){var t={loading:!1,loaded:{},error:null},n=[];try{Object.keys(e).forEach((function(r){var a=d(e[r]);a.loading?t.loading=!0:(t.loaded[r]=a.loaded,t.error=a.error),n.push(a.promise),a.promise.then((function(e){t.loaded[r]=e})).catch((function(e){t.error=e}))}))}catch(r){t.error=r}return t.promise=Promise.all(n).then((function(e){return t.loading=!1,e})).catch((function(e){throw t.loading=!1,e})),t}function p(e,t){return l.createElement((n=e)&&n.__esModule?n.default:n,t);var n}function m(e,t){var d,f;if(!t.loading)throw new Error("react-loadable requires a `loading` component");var m=i({loader:null,loading:null,delay:200,timeout:null,render:p,webpack:null,modules:null},t),h=null;function g(){return h||(h=e(m.loader)),h.promise}return u.push(g),"function"==typeof m.webpack&&c.push((function(){if((0,m.webpack)().every((function(e){return void 0!==e&&void 0!==n.m[e]})))return g()})),f=d=function(t){function n(n){var r;return o(a(a(r=t.call(this,n)||this)),"retry",(function(){r.setState({error:null,loading:!0,timedOut:!1}),h=e(m.loader),r._loadModule()})),g(),r.state={error:h.error,pastDelay:!1,timedOut:!1,loading:h.loading,loaded:h.loaded},r}r(n,t),n.preload=function(){return g()};var i=n.prototype;return i.UNSAFE_componentWillMount=function(){this._loadModule()},i.componentDidMount=function(){this._mounted=!0},i._loadModule=function(){var e=this;if(this.context.loadable&&Array.isArray(m.modules)&&m.modules.forEach((function(t){e.context.loadable.report(t)})),h.loading){var t=function(t){e._mounted&&e.setState(t)};"number"==typeof m.delay&&(0===m.delay?this.setState({pastDelay:!0}):this._delay=setTimeout((function(){t({pastDelay:!0})}),m.delay)),"number"==typeof m.timeout&&(this._timeout=setTimeout((function(){t({timedOut:!0})}),m.timeout));var n=function(){t({error:h.error,loaded:h.loaded,loading:h.loading}),e._clearTimeouts()};h.promise.then((function(){return n(),null})).catch((function(e){return n(),null}))}},i.componentWillUnmount=function(){this._mounted=!1,this._clearTimeouts()},i._clearTimeouts=function(){clearTimeout(this._delay),clearTimeout(this._timeout)},i.render=function(){return this.state.loading||this.state.error?l.createElement(m.loading,{isLoading:this.state.loading,pastDelay:this.state.pastDelay,timedOut:this.state.timedOut,error:this.state.error,retry:this.retry}):this.state.loaded?m.render(this.state.loaded,this.props):null},n}(l.Component),o(d,"contextTypes",{loadable:s.shape({report:s.func.isRequired})}),f}function h(e){return m(d,e)}h.Map=function(e){if("function"!=typeof e.render)throw new Error("LoadableMap requires a `render(loaded, props)` function");return m(f,e)};var g=function(e){function t(){return e.apply(this,arguments)||this}r(t,e);var n=t.prototype;return n.getChildContext=function(){return{loadable:{report:this.props.report}}},n.render=function(){return l.Children.only(this.props.children)},t}(l.Component);function b(e){for(var t=[];e.length;){var n=e.pop();t.push(n())}return Promise.all(t).then((function(){if(e.length)return b(e)}))}o(g,"propTypes",{report:s.func.isRequired}),o(g,"childContextTypes",{loadable:s.shape({report:s.func.isRequired}).isRequired}),h.Capture=g,h.preloadAll=function(){return new Promise((function(e,t){b(u).then(e,t)}))},h.preloadReady=function(){return new Promise((function(e,t){b(c).then(e,e)}))},e.exports=h},2831:(e,t,n)=>{"use strict";n.d(t,{u:()=>i,v:()=>l});var r=n(6347),a=n(8168),o=n(6540);function i(e,t,n){return void 0===n&&(n=[]),e.some((function(e){var a=e.path?(0,r.B6)(t,e):n.length?n[n.length-1].match:r.Ix.computeRootMatch(t);return a&&(n.push({route:e,match:a}),e.routes&&i(e.routes,t,n)),a})),n}function l(e,t,n){return void 0===t&&(t={}),void 0===n&&(n={}),e?o.createElement(r.dO,n,e.map((function(e,n){return o.createElement(r.qh,{key:e.key||n,path:e.path,exact:e.exact,strict:e.strict,render:function(n){return e.render?e.render((0,a.A)({},n,{},t,{route:e})):o.createElement(e.component,(0,a.A)({},n,t,{route:e}))}})}))):null}},4625:(e,t,n)=>{"use strict";n.d(t,{Kd:()=>c,N_:()=>g,k2:()=>y});var r=n(6347),a=n(2892),o=n(6540),i=n(1513),l=n(8168),s=n(8587),u=n(1561),c=function(e){function t(){for(var t,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).history=(0,i.zR)(t.props),t}return(0,a.A)(t,e),t.prototype.render=function(){return o.createElement(r.Ix,{history:this.history,children:this.props.children})},t}(o.Component);o.Component;var d=function(e,t){return"function"==typeof e?e(t):e},f=function(e,t){return"string"==typeof e?(0,i.yJ)(e,null,null,t):e},p=function(e){return e},m=o.forwardRef;void 0===m&&(m=p);var h=m((function(e,t){var n=e.innerRef,r=e.navigate,a=e.onClick,i=(0,s.A)(e,["innerRef","navigate","onClick"]),u=i.target,c=(0,l.A)({},i,{onClick:function(e){try{a&&a(e)}catch(t){throw e.preventDefault(),t}e.defaultPrevented||0!==e.button||u&&"_self"!==u||function(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}(e)||(e.preventDefault(),r())}});return c.ref=p!==m&&t||n,o.createElement("a",c)}));var g=m((function(e,t){var n=e.component,a=void 0===n?h:n,c=e.replace,g=e.to,b=e.innerRef,v=(0,s.A)(e,["component","replace","to","innerRef"]);return o.createElement(r.XZ.Consumer,null,(function(e){e||(0,u.A)(!1);var n=e.history,r=f(d(g,e.location),e.location),s=r?n.createHref(r):"",h=(0,l.A)({},v,{href:s,navigate:function(){var t=d(g,e.location),r=(0,i.AO)(e.location)===(0,i.AO)(f(t));(c||r?n.replace:n.push)(t)}});return p!==m?h.ref=t||b:h.innerRef=b,o.createElement(a,h)}))})),b=function(e){return e},v=o.forwardRef;void 0===v&&(v=b);var y=v((function(e,t){var n=e["aria-current"],a=void 0===n?"page":n,i=e.activeClassName,c=void 0===i?"active":i,p=e.activeStyle,m=e.className,h=e.exact,y=e.isActive,w=e.location,E=e.sensitive,k=e.strict,S=e.style,x=e.to,_=e.innerRef,C=(0,s.A)(e,["aria-current","activeClassName","activeStyle","className","exact","isActive","location","sensitive","strict","style","to","innerRef"]);return o.createElement(r.XZ.Consumer,null,(function(e){e||(0,u.A)(!1);var n=w||e.location,i=f(d(x,n),n),s=i.pathname,A=s&&s.replace(/([.+*?=^!:${}()[\]|/\\])/g,"\\$1"),T=A?(0,r.B6)(n.pathname,{path:A,exact:h,sensitive:E,strict:k}):null,N=!!(y?y(T,n):T),O="function"==typeof m?m(N):m,L="function"==typeof S?S(N):S;N&&(O=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.filter((function(e){return e})).join(" ")}(O,c),L=(0,l.A)({},L,p));var P=(0,l.A)({"aria-current":N&&a||null,className:O,style:L,to:i},C);return b!==v?P.ref=t||_:P.innerRef=_,o.createElement(g,P)}))}))},6347:(e,t,n)=>{"use strict";n.d(t,{B6:()=>S,Ix:()=>w,W6:()=>P,XZ:()=>y,dO:()=>O,qh:()=>x,zy:()=>R});var r=n(2892),a=n(6540),o=n(5556),i=n.n(o),l=n(1513),s=n(1561),u=n(8168),c=n(8505),d=n.n(c),f=(n(4363),n(8587)),p=(n(4146),1073741823),m="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0!==n.g?n.g:{};function h(e){var t=[];return{on:function(e){t.push(e)},off:function(e){t=t.filter((function(t){return t!==e}))},get:function(){return e},set:function(n,r){e=n,t.forEach((function(t){return t(e,r)}))}}}var g=a.createContext||function(e,t){var n,o,l="__create-react-context-"+function(){var e="__global_unique_id__";return m[e]=(m[e]||0)+1}()+"__",s=function(e){function n(){for(var t,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).emitter=h(t.props.value),t}(0,r.A)(n,e);var a=n.prototype;return a.getChildContext=function(){var e;return(e={})[l]=this.emitter,e},a.componentWillReceiveProps=function(e){if(this.props.value!==e.value){var n,r=this.props.value,a=e.value;((o=r)===(i=a)?0!==o||1/o==1/i:o!=o&&i!=i)?n=0:(n="function"==typeof t?t(r,a):p,0!==(n|=0)&&this.emitter.set(e.value,n))}var o,i},a.render=function(){return this.props.children},n}(a.Component);s.childContextTypes=((n={})[l]=i().object.isRequired,n);var u=function(t){function n(){for(var e,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(e=t.call.apply(t,[this].concat(r))||this).observedBits=void 0,e.state={value:e.getValue()},e.onUpdate=function(t,n){0!=((0|e.observedBits)&n)&&e.setState({value:e.getValue()})},e}(0,r.A)(n,t);var a=n.prototype;return a.componentWillReceiveProps=function(e){var t=e.observedBits;this.observedBits=null==t?p:t},a.componentDidMount=function(){this.context[l]&&this.context[l].on(this.onUpdate);var e=this.props.observedBits;this.observedBits=null==e?p:e},a.componentWillUnmount=function(){this.context[l]&&this.context[l].off(this.onUpdate)},a.getValue=function(){return this.context[l]?this.context[l].get():e},a.render=function(){return(e=this.props.children,Array.isArray(e)?e[0]:e)(this.state.value);var e},n}(a.Component);return u.contextTypes=((o={})[l]=i().object,o),{Provider:s,Consumer:u}},b=function(e){var t=g();return t.displayName=e,t},v=b("Router-History"),y=b("Router"),w=function(e){function t(t){var n;return(n=e.call(this,t)||this).state={location:t.history.location},n._isMounted=!1,n._pendingLocation=null,t.staticContext||(n.unlisten=t.history.listen((function(e){n._pendingLocation=e}))),n}(0,r.A)(t,e),t.computeRootMatch=function(e){return{path:"/",url:"/",params:{},isExact:"/"===e}};var n=t.prototype;return n.componentDidMount=function(){var e=this;this._isMounted=!0,this.unlisten&&this.unlisten(),this.props.staticContext||(this.unlisten=this.props.history.listen((function(t){e._isMounted&&e.setState({location:t})}))),this._pendingLocation&&this.setState({location:this._pendingLocation})},n.componentWillUnmount=function(){this.unlisten&&(this.unlisten(),this._isMounted=!1,this._pendingLocation=null)},n.render=function(){return a.createElement(y.Provider,{value:{history:this.props.history,location:this.state.location,match:t.computeRootMatch(this.state.location.pathname),staticContext:this.props.staticContext}},a.createElement(v.Provider,{children:this.props.children||null,value:this.props.history}))},t}(a.Component);a.Component;a.Component;var E={},k=0;function S(e,t){void 0===t&&(t={}),("string"==typeof t||Array.isArray(t))&&(t={path:t});var n=t,r=n.path,a=n.exact,o=void 0!==a&&a,i=n.strict,l=void 0!==i&&i,s=n.sensitive,u=void 0!==s&&s;return[].concat(r).reduce((function(t,n){if(!n&&""!==n)return null;if(t)return t;var r=function(e,t){var n=""+t.end+t.strict+t.sensitive,r=E[n]||(E[n]={});if(r[e])return r[e];var a=[],o={regexp:d()(e,a,t),keys:a};return k<1e4&&(r[e]=o,k++),o}(n,{end:o,strict:l,sensitive:u}),a=r.regexp,i=r.keys,s=a.exec(e);if(!s)return null;var c=s[0],f=s.slice(1),p=e===c;return o&&!p?null:{path:n,url:"/"===n&&""===c?"/":c,isExact:p,params:i.reduce((function(e,t,n){return e[t.name]=f[n],e}),{})}}),null)}var x=function(e){function t(){return e.apply(this,arguments)||this}return(0,r.A)(t,e),t.prototype.render=function(){var e=this;return a.createElement(y.Consumer,null,(function(t){t||(0,s.A)(!1);var n=e.props.location||t.location,r=e.props.computedMatch?e.props.computedMatch:e.props.path?S(n.pathname,e.props):t.match,o=(0,u.A)({},t,{location:n,match:r}),i=e.props,l=i.children,c=i.component,d=i.render;return Array.isArray(l)&&function(e){return 0===a.Children.count(e)}(l)&&(l=null),a.createElement(y.Provider,{value:o},o.match?l?"function"==typeof l?l(o):l:c?a.createElement(c,o):d?d(o):null:"function"==typeof l?l(o):null)}))},t}(a.Component);function _(e){return"/"===e.charAt(0)?e:"/"+e}function C(e,t){if(!e)return t;var n=_(e);return 0!==t.pathname.indexOf(n)?t:(0,u.A)({},t,{pathname:t.pathname.substr(n.length)})}function A(e){return"string"==typeof e?e:(0,l.AO)(e)}function T(e){return function(){(0,s.A)(!1)}}function N(){}a.Component;var O=function(e){function t(){return e.apply(this,arguments)||this}return(0,r.A)(t,e),t.prototype.render=function(){var e=this;return a.createElement(y.Consumer,null,(function(t){t||(0,s.A)(!1);var n,r,o=e.props.location||t.location;return a.Children.forEach(e.props.children,(function(e){if(null==r&&a.isValidElement(e)){n=e;var i=e.props.path||e.props.from;r=i?S(o.pathname,(0,u.A)({},e.props,{path:i})):t.match}})),r?a.cloneElement(n,{location:o,computedMatch:r}):null}))},t}(a.Component);var L=a.useContext;function P(){return L(v)}function R(){return L(y).location}},8505:(e,t,n)=>{var r=n(4634);e.exports=p,e.exports.parse=o,e.exports.compile=function(e,t){return l(o(e,t),t)},e.exports.tokensToFunction=l,e.exports.tokensToRegExp=f;var a=new RegExp(["(\\\\.)","([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))"].join("|"),"g");function o(e,t){for(var n,r=[],o=0,i=0,l="",c=t&&t.delimiter||"/";null!=(n=a.exec(e));){var d=n[0],f=n[1],p=n.index;if(l+=e.slice(i,p),i=p+d.length,f)l+=f[1];else{var m=e[i],h=n[2],g=n[3],b=n[4],v=n[5],y=n[6],w=n[7];l&&(r.push(l),l="");var E=null!=h&&null!=m&&m!==h,k="+"===y||"*"===y,S="?"===y||"*"===y,x=n[2]||c,_=b||v;r.push({name:g||o++,prefix:h||"",delimiter:x,optional:S,repeat:k,partial:E,asterisk:!!w,pattern:_?u(_):w?".*":"[^"+s(x)+"]+?"})}}return i<e.length&&(l+=e.substr(i)),l&&r.push(l),r}function i(e){return encodeURI(e).replace(/[\/?#]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()}))}function l(e,t){for(var n=new Array(e.length),a=0;a<e.length;a++)"object"==typeof e[a]&&(n[a]=new RegExp("^(?:"+e[a].pattern+")$",d(t)));return function(t,a){for(var o="",l=t||{},s=(a||{}).pretty?i:encodeURIComponent,u=0;u<e.length;u++){var c=e[u];if("string"!=typeof c){var d,f=l[c.name];if(null==f){if(c.optional){c.partial&&(o+=c.prefix);continue}throw new TypeError('Expected "'+c.name+'" to be defined')}if(r(f)){if(!c.repeat)throw new TypeError('Expected "'+c.name+'" to not repeat, but received `'+JSON.stringify(f)+"`");if(0===f.length){if(c.optional)continue;throw new TypeError('Expected "'+c.name+'" to not be empty')}for(var p=0;p<f.length;p++){if(d=s(f[p]),!n[u].test(d))throw new TypeError('Expected all "'+c.name+'" to match "'+c.pattern+'", but received `'+JSON.stringify(d)+"`");o+=(0===p?c.prefix:c.delimiter)+d}}else{if(d=c.asterisk?encodeURI(f).replace(/[?#]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()})):s(f),!n[u].test(d))throw new TypeError('Expected "'+c.name+'" to match "'+c.pattern+'", but received "'+d+'"');o+=c.prefix+d}}else o+=c}return o}}function s(e){return e.replace(/([.+*?=^!:${}()[\]|\/\\])/g,"\\$1")}function u(e){return e.replace(/([=!:$\/()])/g,"\\$1")}function c(e,t){return e.keys=t,e}function d(e){return e&&e.sensitive?"":"i"}function f(e,t,n){r(t)||(n=t||n,t=[]);for(var a=(n=n||{}).strict,o=!1!==n.end,i="",l=0;l<e.length;l++){var u=e[l];if("string"==typeof u)i+=s(u);else{var f=s(u.prefix),p="(?:"+u.pattern+")";t.push(u),u.repeat&&(p+="(?:"+f+p+")*"),i+=p=u.optional?u.partial?f+"("+p+")?":"(?:"+f+"("+p+"))?":f+"("+p+")"}}var m=s(n.delimiter||"/"),h=i.slice(-m.length)===m;return a||(i=(h?i.slice(0,-m.length):i)+"(?:"+m+"(?=$))?"),i+=o?"$":a&&h?"":"(?="+m+"|$)",c(new RegExp("^"+i,d(n)),t)}function p(e,t,n){return r(t)||(n=t||n,t=[]),n=n||{},e instanceof RegExp?function(e,t){var n=e.source.match(/\((?!\?)/g);if(n)for(var r=0;r<n.length;r++)t.push({name:r,prefix:null,delimiter:null,optional:!1,repeat:!1,partial:!1,asterisk:!1,pattern:null});return c(e,t)}(e,t):r(e)?function(e,t,n){for(var r=[],a=0;a<e.length;a++)r.push(p(e[a],t,n).source);return c(new RegExp("(?:"+r.join("|")+")",d(n)),t)}(e,t,n):function(e,t,n){return f(o(e,n),t,n)}(e,t,n)}},5287:(e,t,n)=>{"use strict";var r=n(5228),a=60103,o=60106;t.Fragment=60107,t.StrictMode=60108,t.Profiler=60114;var i=60109,l=60110,s=60112;t.Suspense=60113;var u=60115,c=60116;if("function"==typeof Symbol&&Symbol.for){var d=Symbol.for;a=d("react.element"),o=d("react.portal"),t.Fragment=d("react.fragment"),t.StrictMode=d("react.strict_mode"),t.Profiler=d("react.profiler"),i=d("react.provider"),l=d("react.context"),s=d("react.forward_ref"),t.Suspense=d("react.suspense"),u=d("react.memo"),c=d("react.lazy")}var f="function"==typeof Symbol&&Symbol.iterator;function p(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var m={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},h={};function g(e,t,n){this.props=e,this.context=t,this.refs=h,this.updater=n||m}function b(){}function v(e,t,n){this.props=e,this.context=t,this.refs=h,this.updater=n||m}g.prototype.isReactComponent={},g.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error(p(85));this.updater.enqueueSetState(this,e,t,"setState")},g.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},b.prototype=g.prototype;var y=v.prototype=new b;y.constructor=v,r(y,g.prototype),y.isPureReactComponent=!0;var w={current:null},E=Object.prototype.hasOwnProperty,k={key:!0,ref:!0,__self:!0,__source:!0};function S(e,t,n){var r,o={},i=null,l=null;if(null!=t)for(r in void 0!==t.ref&&(l=t.ref),void 0!==t.key&&(i=""+t.key),t)E.call(t,r)&&!k.hasOwnProperty(r)&&(o[r]=t[r]);var s=arguments.length-2;if(1===s)o.children=n;else if(1<s){for(var u=Array(s),c=0;c<s;c++)u[c]=arguments[c+2];o.children=u}if(e&&e.defaultProps)for(r in s=e.defaultProps)void 0===o[r]&&(o[r]=s[r]);return{$$typeof:a,type:e,key:i,ref:l,props:o,_owner:w.current}}function x(e){return"object"==typeof e&&null!==e&&e.$$typeof===a}var _=/\/+/g;function C(e,t){return"object"==typeof e&&null!==e&&null!=e.key?function(e){var t={"=":"=0",":":"=2"};return"$"+e.replace(/[=:]/g,(function(e){return t[e]}))}(""+e.key):t.toString(36)}function A(e,t,n,r,i){var l=typeof e;"undefined"!==l&&"boolean"!==l||(e=null);var s=!1;if(null===e)s=!0;else switch(l){case"string":case"number":s=!0;break;case"object":switch(e.$$typeof){case a:case o:s=!0}}if(s)return i=i(s=e),e=""===r?"."+C(s,0):r,Array.isArray(i)?(n="",null!=e&&(n=e.replace(_,"$&/")+"/"),A(i,t,n,"",(function(e){return e}))):null!=i&&(x(i)&&(i=function(e,t){return{$$typeof:a,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}(i,n+(!i.key||s&&s.key===i.key?"":(""+i.key).replace(_,"$&/")+"/")+e)),t.push(i)),1;if(s=0,r=""===r?".":r+":",Array.isArray(e))for(var u=0;u<e.length;u++){var c=r+C(l=e[u],u);s+=A(l,t,n,c,i)}else if(c=function(e){return null===e||"object"!=typeof e?null:"function"==typeof(e=f&&e[f]||e["@@iterator"])?e:null}(e),"function"==typeof c)for(e=c.call(e),u=0;!(l=e.next()).done;)s+=A(l=l.value,t,n,c=r+C(l,u++),i);else if("object"===l)throw t=""+e,Error(p(31,"[object Object]"===t?"object with keys {"+Object.keys(e).join(", ")+"}":t));return s}function T(e,t,n){if(null==e)return e;var r=[],a=0;return A(e,r,"","",(function(e){return t.call(n,e,a++)})),r}function N(e){if(-1===e._status){var t=e._result;t=t(),e._status=0,e._result=t,t.then((function(t){0===e._status&&(t=t.default,e._status=1,e._result=t)}),(function(t){0===e._status&&(e._status=2,e._result=t)}))}if(1===e._status)return e._result;throw e._result}var O={current:null};function L(){var e=O.current;if(null===e)throw Error(p(321));return e}var P={ReactCurrentDispatcher:O,ReactCurrentBatchConfig:{transition:0},ReactCurrentOwner:w,IsSomeRendererActing:{current:!1},assign:r};t.Children={map:T,forEach:function(e,t,n){T(e,(function(){t.apply(this,arguments)}),n)},count:function(e){var t=0;return T(e,(function(){t++})),t},toArray:function(e){return T(e,(function(e){return e}))||[]},only:function(e){if(!x(e))throw Error(p(143));return e}},t.Component=g,t.PureComponent=v,t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=P,t.cloneElement=function(e,t,n){if(null==e)throw Error(p(267,e));var o=r({},e.props),i=e.key,l=e.ref,s=e._owner;if(null!=t){if(void 0!==t.ref&&(l=t.ref,s=w.current),void 0!==t.key&&(i=""+t.key),e.type&&e.type.defaultProps)var u=e.type.defaultProps;for(c in t)E.call(t,c)&&!k.hasOwnProperty(c)&&(o[c]=void 0===t[c]&&void 0!==u?u[c]:t[c])}var c=arguments.length-2;if(1===c)o.children=n;else if(1<c){u=Array(c);for(var d=0;d<c;d++)u[d]=arguments[d+2];o.children=u}return{$$typeof:a,type:e.type,key:i,ref:l,props:o,_owner:s}},t.createContext=function(e,t){return void 0===t&&(t=null),(e={$$typeof:l,_calculateChangedBits:t,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null}).Provider={$$typeof:i,_context:e},e.Consumer=e},t.createElement=S,t.createFactory=function(e){var t=S.bind(null,e);return t.type=e,t},t.createRef=function(){return{current:null}},t.forwardRef=function(e){return{$$typeof:s,render:e}},t.isValidElement=x,t.lazy=function(e){return{$$typeof:c,_payload:{_status:-1,_result:e},_init:N}},t.memo=function(e,t){return{$$typeof:u,type:e,compare:void 0===t?null:t}},t.useCallback=function(e,t){return L().useCallback(e,t)},t.useContext=function(e,t){return L().useContext(e,t)},t.useDebugValue=function(){},t.useEffect=function(e,t){return L().useEffect(e,t)},t.useImperativeHandle=function(e,t,n){return L().useImperativeHandle(e,t,n)},t.useLayoutEffect=function(e,t){return L().useLayoutEffect(e,t)},t.useMemo=function(e,t){return L().useMemo(e,t)},t.useReducer=function(e,t,n){return L().useReducer(e,t,n)},t.useRef=function(e){return L().useRef(e)},t.useState=function(e){return L().useState(e)},t.version="17.0.2"},6540:(e,t,n)=>{"use strict";e.exports=n(5287)},7463:(e,t)=>{"use strict";var n,r,a,o;if("object"==typeof performance&&"function"==typeof performance.now){var i=performance;t.unstable_now=function(){return i.now()}}else{var l=Date,s=l.now();t.unstable_now=function(){return l.now()-s}}if("undefined"==typeof window||"function"!=typeof MessageChannel){var u=null,c=null,d=function(){if(null!==u)try{var e=t.unstable_now();u(!0,e),u=null}catch(n){throw setTimeout(d,0),n}};n=function(e){null!==u?setTimeout(n,0,e):(u=e,setTimeout(d,0))},r=function(e,t){c=setTimeout(e,t)},a=function(){clearTimeout(c)},t.unstable_shouldYield=function(){return!1},o=t.unstable_forceFrameRate=function(){}}else{var f=window.setTimeout,p=window.clearTimeout;if("undefined"!=typeof console){var m=window.cancelAnimationFrame;"function"!=typeof window.requestAnimationFrame&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills"),"function"!=typeof m&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills")}var h=!1,g=null,b=-1,v=5,y=0;t.unstable_shouldYield=function(){return t.unstable_now()>=y},o=function(){},t.unstable_forceFrameRate=function(e){0>e||125<e?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):v=0<e?Math.floor(1e3/e):5};var w=new MessageChannel,E=w.port2;w.port1.onmessage=function(){if(null!==g){var e=t.unstable_now();y=e+v;try{g(!0,e)?E.postMessage(null):(h=!1,g=null)}catch(n){throw E.postMessage(null),n}}else h=!1},n=function(e){g=e,h||(h=!0,E.postMessage(null))},r=function(e,n){b=f((function(){e(t.unstable_now())}),n)},a=function(){p(b),b=-1}}function k(e,t){var n=e.length;e.push(t);e:for(;;){var r=n-1>>>1,a=e[r];if(!(void 0!==a&&0<_(a,t)))break e;e[r]=t,e[n]=a,n=r}}function S(e){return void 0===(e=e[0])?null:e}function x(e){var t=e[0];if(void 0!==t){var n=e.pop();if(n!==t){e[0]=n;e:for(var r=0,a=e.length;r<a;){var o=2*(r+1)-1,i=e[o],l=o+1,s=e[l];if(void 0!==i&&0>_(i,n))void 0!==s&&0>_(s,i)?(e[r]=s,e[l]=n,r=l):(e[r]=i,e[o]=n,r=o);else{if(!(void 0!==s&&0>_(s,n)))break e;e[r]=s,e[l]=n,r=l}}}return t}return null}function _(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}var C=[],A=[],T=1,N=null,O=3,L=!1,P=!1,R=!1;function I(e){for(var t=S(A);null!==t;){if(null===t.callback)x(A);else{if(!(t.startTime<=e))break;x(A),t.sortIndex=t.expirationTime,k(C,t)}t=S(A)}}function M(e){if(R=!1,I(e),!P)if(null!==S(C))P=!0,n(D);else{var t=S(A);null!==t&&r(M,t.startTime-e)}}function D(e,n){P=!1,R&&(R=!1,a()),L=!0;var o=O;try{for(I(n),N=S(C);null!==N&&(!(N.expirationTime>n)||e&&!t.unstable_shouldYield());){var i=N.callback;if("function"==typeof i){N.callback=null,O=N.priorityLevel;var l=i(N.expirationTime<=n);n=t.unstable_now(),"function"==typeof l?N.callback=l:N===S(C)&&x(C),I(n)}else x(C);N=S(C)}if(null!==N)var s=!0;else{var u=S(A);null!==u&&r(M,u.startTime-n),s=!1}return s}finally{N=null,O=o,L=!1}}var F=o;t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_continueExecution=function(){P||L||(P=!0,n(D))},t.unstable_getCurrentPriorityLevel=function(){return O},t.unstable_getFirstCallbackNode=function(){return S(C)},t.unstable_next=function(e){switch(O){case 1:case 2:case 3:var t=3;break;default:t=O}var n=O;O=t;try{return e()}finally{O=n}},t.unstable_pauseExecution=function(){},t.unstable_requestPaint=F,t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=O;O=e;try{return t()}finally{O=n}},t.unstable_scheduleCallback=function(e,o,i){var l=t.unstable_now();switch("object"==typeof i&&null!==i?i="number"==typeof(i=i.delay)&&0<i?l+i:l:i=l,e){case 1:var s=-1;break;case 2:s=250;break;case 5:s=1073741823;break;case 4:s=1e4;break;default:s=5e3}return e={id:T++,callback:o,priorityLevel:e,startTime:i,expirationTime:s=i+s,sortIndex:-1},i>l?(e.sortIndex=i,k(A,e),null===S(C)&&e===S(A)&&(R?a():R=!0,r(M,i-l))):(e.sortIndex=s,k(C,e),P||L||(P=!0,n(D))),e},t.unstable_wrapCallback=function(e){var t=O;return function(){var n=O;O=t;try{return e.apply(this,arguments)}finally{O=n}}}},9982:(e,t,n)=>{"use strict";e.exports=n(7463)},2833:e=>{e.exports=function(e,t,n,r){var a=n?n.call(r,e,t):void 0;if(void 0!==a)return!!a;if(e===t)return!0;if("object"!=typeof e||!e||"object"!=typeof t||!t)return!1;var o=Object.keys(e),i=Object.keys(t);if(o.length!==i.length)return!1;for(var l=Object.prototype.hasOwnProperty.bind(t),s=0;s<o.length;s++){var u=o[s];if(!l(u))return!1;var c=e[u],d=t[u];if(!1===(a=n?n.call(r,c,d,u):void 0)||void 0===a&&c!==d)return!1}return!0}},1063:(e,t,n)=>{"use strict";var r=n(6540);var a="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},o=r.useState,i=r.useEffect,l=r.useLayoutEffect,s=r.useDebugValue;function u(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!a(e,n)}catch(r){return!0}}var c="undefined"==typeof window||void 0===window.document||void 0===window.document.createElement?function(e,t){return t()}:function(e,t){var n=t(),r=o({inst:{value:n,getSnapshot:t}}),a=r[0].inst,c=r[1];return l((function(){a.value=n,a.getSnapshot=t,u(a)&&c({inst:a})}),[e,n,t]),i((function(){return u(a)&&c({inst:a}),e((function(){u(a)&&c({inst:a})}))}),[e]),s(n),n};void 0!==r.useSyncExternalStore&&r.useSyncExternalStore},9888:(e,t,n)=>{"use strict";n(1063)},4784:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r={title:"GraphQL ASP.NET",tagline:"v1.0.0",url:"https://graphql-aspnet.github.io",baseUrl:"/",onBrokenLinks:"throw",onBrokenMarkdownLinks:"warn",favicon:"img/favicon.ico",organizationName:"graphql-aspnet",projectName:"graphql-aspnet.github.io",trailingSlash:!1,i18n:{defaultLocale:"en",locales:["en"],path:"i18n",localeConfigs:{}},scripts:[{src:"https://buttons.github.io/buttons.js",async:!0,type:"text/javascript"}],presets:[["classic",{docs:{sidebarPath:"/home/runner/work/graphql-aspnet.github.io/graphql-aspnet.github.io/sidebars.js",sidebarCollapsible:!1},theme:{customCss:"/home/runner/work/graphql-aspnet.github.io/graphql-aspnet.github.io/src/css/custom.css"}}]],themeConfig:{colorMode:{defaultMode:"dark",disableSwitch:!1,respectPrefersColorScheme:!0},navbar:{title:"GraphQL ASP.NET",logo:{alt:"My Site Logo",src:"img/logo-128.png"},items:[{to:"https://github.com/graphql-aspnet/graphql-aspnet",position:"right",className:"header-github-link","aria-label":"GitHub repository"}],hideOnScroll:!1},footer:{links:[{title:"Docs",items:[{label:"Made for ASP.NET developers",to:"/docs/introduction/made-for-aspnet-developers"},{label:"Code Examples",to:"/docs/quick/code-examples"}]},{title:" ",items:[]},{title:"More",items:[{html:'\n <a \n class="github-button" \n href="https://github.com/graphql-aspnet/graphql-aspnet" \n data-size="large" \n data-show-count="true" \n aria-label="Star graphql-aspnet/graphql-aspnet on GitHub">\n Star on Github\n </a>\n '},{html:'\n <a \n class="footer__link-item"\n href="https://graphql.org" \n target="_blank" \n rel="noreferrer noopener" \n aria-label="GraphQl Org">\n GraphQL.org\n </a>\n '}]}],copyright:"Copyright \xa9 2025 GraphQL ASP.NET",style:"light"},prism:{darkTheme:{plain:{color:"#bfc7d5",backgroundColor:"#292d3e"},styles:[{types:["comment"],style:{color:"rgb(105, 112, 152)",fontStyle:"italic"}},{types:["string","inserted"],style:{color:"rgb(195, 232, 141)"}},{types:["number"],style:{color:"rgb(247, 140, 108)"}},{types:["builtin","char","constant","function"],style:{color:"rgb(130, 170, 255)"}},{types:["punctuation","selector"],style:{color:"rgb(199, 146, 234)"}},{types:["variable"],style:{color:"rgb(191, 199, 213)"}},{types:["class-name","attr-name"],style:{color:"rgb(255, 203, 107)"}},{types:["tag","deleted"],style:{color:"rgb(255, 85, 114)"}},{types:["operator"],style:{color:"rgb(137, 221, 255)"}},{types:["boolean"],style:{color:"rgb(255, 88, 116)"}},{types:["keyword"],style:{fontStyle:"italic"}},{types:["doctype"],style:{color:"rgb(199, 146, 234)",fontStyle:"italic"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)"}},{types:["url"],style:{color:"rgb(221, 221, 221)"}}]},theme:{plain:{color:"#393A34",backgroundColor:"#f6f8fa"},styles:[{types:["comment","prolog","doctype","cdata"],style:{color:"#999988",fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}},{types:["string","attr-value"],style:{color:"#e3116c"}},{types:["punctuation","operator"],style:{color:"#393A34"}},{types:["entity","url","symbol","number","boolean","variable","constant","property","regex","inserted"],style:{color:"#36acaa"}},{types:["atrule","keyword","attr-name","selector"],style:{color:"#00a4db"}},{types:["function","deleted","tag"],style:{color:"#d73a49"}},{types:["function-variable"],style:{color:"#6f42c1"}},{types:["tag","selector","keyword"],style:{color:"#00009f"}}]},additionalLanguages:["csharp","powershell"],magicComments:[{className:"theme-code-block-highlighted-line",line:"highlight-next-line",block:{start:"highlight-start",end:"highlight-end"}}]},docs:{sidebar:{autoCollapseCategories:!1,hideable:!1},versionPersistence:"localStorage"},metadata:[],tableOfContents:{minHeadingLevel:2,maxHeadingLevel:3}},baseUrlIssueBanner:!0,onDuplicateRoutes:"warn",staticDirectories:["static"],customFields:{},plugins:[],themes:[],headTags:[],stylesheets:[],clientModules:[],titleDelimiter:"|",noIndex:!1,markdown:{mermaid:!1}}},8168:(e,t,n)=>{"use strict";function r(){return r=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},r.apply(null,arguments)}n.d(t,{A:()=>r})},2892:(e,t,n)=>{"use strict";function r(e,t){return r=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},r(e,t)}function a(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,r(e,t)}n.d(t,{A:()=>a})},8587:(e,t,n)=>{"use strict";function r(e,t){if(null==e)return{};var n={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(-1!==t.indexOf(r))continue;n[r]=e[r]}return n}n.d(t,{A:()=>r})},1561:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});var r="Invariant failed";function a(e,t){if(!e)throw new Error(r)}},2654:e=>{"use strict";e.exports={}},4054:e=>{"use strict";e.exports=JSON.parse('{"/docs-79c":{"__comp":"1be78505","__context":{"plugin":"6a8cb708"},"versionMetadata":"935f2afb"},"/docs/advanced/custom-scalars-7ba":{"__comp":"17896441","content":"6499ffa8"},"/docs/advanced/directives-7e4":{"__comp":"17896441","content":"40dc0bc8"},"/docs/advanced/graph-action-results-c3d":{"__comp":"17896441","content":"575dc170"},"/docs/advanced/multi-schema-support-594":{"__comp":"17896441","content":"2b3b1ca0"},"/docs/advanced/subscriptions-f2e":{"__comp":"17896441","content":"cc10add0"},"/docs/advanced/type-expressions-ee8":{"__comp":"17896441","content":"05cffa07"},"/docs/controllers/actions-74c":{"__comp":"17896441","content":"e844e6f9"},"/docs/controllers/authorization-ec3":{"__comp":"17896441","content":"34c61599"},"/docs/controllers/batch-operations-98a":{"__comp":"17896441","content":"609dc67a"},"/docs/controllers/field-paths-395":{"__comp":"17896441","content":"7f4a28f4"},"/docs/controllers/model-state-38a":{"__comp":"17896441","content":"cfce05e4"},"/docs/controllers/type-extensions-72f":{"__comp":"17896441","content":"878c0d65"},"/docs/development/debugging-c34":{"__comp":"17896441","content":"b540f618"},"/docs/development/entity-framework-f77":{"__comp":"17896441","content":"7966db6c"},"/docs/development/unit-testing-5da":{"__comp":"17896441","content":"0536d272"},"/docs/execution/malicious-queries-233":{"__comp":"17896441","content":"39a1aa48"},"/docs/execution/metrics-d1a":{"__comp":"17896441","content":"99037cd0"},"/docs/introduction/made-for-aspnet-developers-91c":{"__comp":"17896441","content":"ec78a8f8"},"/docs/introduction/what-is-graphql-844":{"__comp":"17896441","content":"6c218552"},"/docs/logging/standard-events-018":{"__comp":"17896441","content":"5c71343a"},"/docs/logging/structured-logging-164":{"__comp":"17896441","content":"de26b084"},"/docs/logging/subscription-events-e4c":{"__comp":"17896441","content":"49d4cdfe"},"/docs/quick/code-examples-bfa":{"__comp":"17896441","content":"3ebc953e"},"/docs/quick/create-app-bf4":{"__comp":"17896441","content":"ef5435f4"},"/docs/quick/overview-70e":{"__comp":"17896441","content":"b19420f8"},"/docs/reference/attributes-bf8":{"__comp":"17896441","content":"37bd4da2"},"/docs/reference/demo-projects-fbd":{"__comp":"17896441","content":"1ae49e3a"},"/docs/reference/global-configuration-25f":{"__comp":"17896441","content":"0ee64cde"},"/docs/reference/graph-controller-e7f":{"__comp":"17896441","content":"2f931191"},"/docs/reference/graph-directive-a0b":{"__comp":"17896441","content":"52b17a27"},"/docs/reference/how-it-works-1ad":{"__comp":"17896441","content":"c8cba33e"},"/docs/reference/http-processor-c1a":{"__comp":"17896441","content":"522ea0c7"},"/docs/reference/middleware-b99":{"__comp":"17896441","content":"d99d9121"},"/docs/reference/performance-feb":{"__comp":"17896441","content":"010df87a"},"/docs/reference/query-cache-fb3":{"__comp":"17896441","content":"e9cf564a"},"/docs/reference/schema-configuration-13c":{"__comp":"17896441","content":"a79310a1"},"/docs/reference/vocabulary-a70":{"__comp":"17896441","content":"b8ee5ddb"},"/docs/server-extensions/multipart-requests-764":{"__comp":"17896441","content":"1c85f73a"},"/docs/types/enums-dca":{"__comp":"17896441","content":"1a387cfc"},"/docs/types/input-objects-6af":{"__comp":"17896441","content":"d1dae560"},"/docs/types/interfaces-0d8":{"__comp":"17896441","content":"9f241a4b"},"/docs/types/list-non-null-40d":{"__comp":"17896441","content":"c8b5e9da"},"/docs/types/objects-e6d":{"__comp":"17896441","content":"fed34737"},"/docs/types/scalars-f83":{"__comp":"17896441","content":"fefbe673"},"/docs/types/unions-49a":{"__comp":"17896441","content":"43b0182c"},"/-341":{"__comp":"c4f5d8e4","__context":{"plugin":"61e865ea"},"config":"5e9f5e1a"}}')}},e=>{e.O(0,[1869],(()=>{return t=5660,e(e.s=t);var t}));e.O()}]); \ No newline at end of file diff --git a/assets/js/main.3f1da62e.js.LICENSE.txt b/assets/js/main.3f1da62e.js.LICENSE.txt new file mode 100644 index 0000000..eb75d69 --- /dev/null +++ b/assets/js/main.3f1da62e.js.LICENSE.txt @@ -0,0 +1,63 @@ +/* +object-assign +(c) Sindre Sorhus +@license MIT +*/ + +/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress + * @license MIT */ + +/** + * @license React + * use-sync-external-store-shim.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Prism: Lightweight, robust, elegant syntax highlighting + * + * @license MIT <https://opensource.org/licenses/MIT> + * @author Lea Verou <https://lea.verou.me> + * @namespace + * @public + */ + +/** @license React v0.20.2 + * scheduler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** @license React v16.13.1 + * react-is.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** @license React v17.0.2 + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** @license React v17.0.2 + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ diff --git a/assets/js/runtime~main.bd51e6f4.js b/assets/js/runtime~main.bd51e6f4.js new file mode 100644 index 0000000..e641365 --- /dev/null +++ b/assets/js/runtime~main.bd51e6f4.js @@ -0,0 +1 @@ +(()=>{"use strict";var e,a,c,t,r,f={},d={};function o(e){var a=d[e];if(void 0!==a)return a.exports;var c=d[e]={exports:{}};return f[e].call(c.exports,c,c.exports,o),c.exports}o.m=f,e=[],o.O=(a,c,t,r)=>{if(!c){var f=1/0;for(i=0;i<e.length;i++){c=e[i][0],t=e[i][1],r=e[i][2];for(var d=!0,b=0;b<c.length;b++)(!1&r||f>=r)&&Object.keys(o.O).every((e=>o.O[e](c[b])))?c.splice(b--,1):(d=!1,r<f&&(f=r));if(d){e.splice(i--,1);var n=t();void 0!==n&&(a=n)}}return a}r=r||0;for(var i=e.length;i>0&&e[i-1][2]>r;i--)e[i]=e[i-1];e[i]=[c,t,r]},o.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return o.d(a,{a:a}),a},c=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if("object"==typeof e&&e){if(4&t&&e.__esModule)return e;if(16&t&&"function"==typeof e.then)return e}var r=Object.create(null);o.r(r);var f={};a=a||[null,c({}),c([]),c(c)];for(var d=2&t&&e;"object"==typeof d&&!~a.indexOf(d);d=c(d))Object.getOwnPropertyNames(d).forEach((a=>f[a]=()=>e[a]));return f.default=()=>e,o.d(r,f),r},o.d=(e,a)=>{for(var c in a)o.o(a,c)&&!o.o(e,c)&&Object.defineProperty(e,c,{enumerable:!0,get:a[c]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((a,c)=>(o.f[c](e,a),a)),[])),o.u=e=>"assets/js/"+({61:"609dc67a",275:"40dc0bc8",570:"37bd4da2",612:"010df87a",657:"05cffa07",1294:"5c71343a",1442:"a79310a1",2053:"c8cba33e",2286:"b8ee5ddb",2477:"34c61599",2552:"3ebc953e",2634:"c4f5d8e4",2692:"0536d272",2892:"7966db6c",2980:"52b17a27",3040:"1ae49e3a",3515:"1a387cfc",3603:"d1dae560",3715:"b540f618",4055:"ef5435f4",4206:"c8b5e9da",4209:"0ee64cde",4231:"b19420f8",4303:"2f931191",4338:"cfce05e4",4368:"43b0182c",4531:"1c85f73a",5176:"49d4cdfe",5227:"fefbe673",5292:"d99d9121",5436:"de26b084",5731:"ec78a8f8",5749:"7f4a28f4",6593:"575dc170",6667:"2b3b1ca0",6792:"6499ffa8",7045:"6c218552",7219:"39a1aa48",7678:"e9cf564a",7742:"fed34737",7776:"99037cd0",7937:"cc10add0",7978:"61e865ea",8401:"17896441",8581:"935f2afb",8700:"878c0d65",8714:"1be78505",8956:"9f241a4b",9182:"e844e6f9",9644:"522ea0c7",9694:"6a8cb708"}[e]||e)+"."+{61:"41b76dc6",275:"8c2db3a2",570:"dec9a7e0",612:"3af7c1fe",657:"53c5d558",1294:"0dc5b22e",1442:"76f167be",1774:"a7cc4885",2053:"40089fc5",2286:"89a51733",2477:"65b67f00",2552:"631dc830",2634:"c84b1214",2692:"aaf8898e",2892:"cad9283f",2980:"9f82953e",3040:"c9c49699",3515:"9c6040f9",3603:"52f085f0",3715:"230cbcd1",4055:"bff8a87b",4206:"d94a376d",4209:"ebc10e9b",4231:"a3de76e7",4303:"79719bcb",4338:"30782168",4368:"3fd2562b",4531:"6cae0578",5176:"175849e0",5227:"ec47657c",5292:"2e6f91bb",5436:"ad69191d",5731:"e83fc98d",5749:"9e54798a",6593:"ade35cc6",6667:"93ca4834",6792:"ba0c0a55",7045:"ea014484",7219:"a18be5c1",7678:"17ce7feb",7742:"dbbb9a21",7776:"ed44f196",7937:"ae40f07c",7964:"c1ffc5a7",7978:"0f52cb2d",8401:"aa8b80cd",8581:"d4075e24",8700:"6d2cf3cd",8714:"cf141f73",8956:"939c1f9b",9182:"f4adb037",9644:"ed2454d0",9694:"912ce0ef"}[e]+".js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),t={},r="my-website:",o.l=(e,a,c,f)=>{if(t[e])t[e].push(a);else{var d,b;if(void 0!==c)for(var n=document.getElementsByTagName("script"),i=0;i<n.length;i++){var u=n[i];if(u.getAttribute("src")==e||u.getAttribute("data-webpack")==r+c){d=u;break}}d||(b=!0,(d=document.createElement("script")).charset="utf-8",d.timeout=120,o.nc&&d.setAttribute("nonce",o.nc),d.setAttribute("data-webpack",r+c),d.src=e),t[e]=[a];var l=(a,c)=>{d.onerror=d.onload=null,clearTimeout(s);var r=t[e];if(delete t[e],d.parentNode&&d.parentNode.removeChild(d),r&&r.forEach((e=>e(c))),a)return a(c)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=l.bind(null,d.onerror),d.onload=l.bind(null,d.onload),b&&document.head.appendChild(d)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.p="/",o.gca=function(e){return e={17896441:"8401","609dc67a":"61","40dc0bc8":"275","37bd4da2":"570","010df87a":"612","05cffa07":"657","5c71343a":"1294",a79310a1:"1442",c8cba33e:"2053",b8ee5ddb:"2286","34c61599":"2477","3ebc953e":"2552",c4f5d8e4:"2634","0536d272":"2692","7966db6c":"2892","52b17a27":"2980","1ae49e3a":"3040","1a387cfc":"3515",d1dae560:"3603",b540f618:"3715",ef5435f4:"4055",c8b5e9da:"4206","0ee64cde":"4209",b19420f8:"4231","2f931191":"4303",cfce05e4:"4338","43b0182c":"4368","1c85f73a":"4531","49d4cdfe":"5176",fefbe673:"5227",d99d9121:"5292",de26b084:"5436",ec78a8f8:"5731","7f4a28f4":"5749","575dc170":"6593","2b3b1ca0":"6667","6499ffa8":"6792","6c218552":"7045","39a1aa48":"7219",e9cf564a:"7678",fed34737:"7742","99037cd0":"7776",cc10add0:"7937","61e865ea":"7978","935f2afb":"8581","878c0d65":"8700","1be78505":"8714","9f241a4b":"8956",e844e6f9:"9182","522ea0c7":"9644","6a8cb708":"9694"}[e]||e,o.p+o.u(e)},(()=>{var e={5354:0,1869:0};o.f.j=(a,c)=>{var t=o.o(e,a)?e[a]:void 0;if(0!==t)if(t)c.push(t[2]);else if(/^(1869|5354)$/.test(a))e[a]=0;else{var r=new Promise(((c,r)=>t=e[a]=[c,r]));c.push(t[2]=r);var f=o.p+o.u(a),d=new Error;o.l(f,(c=>{if(o.o(e,a)&&(0!==(t=e[a])&&(e[a]=void 0),t)){var r=c&&("load"===c.type?"missing":c.type),f=c&&c.target&&c.target.src;d.message="Loading chunk "+a+" failed.\n("+r+": "+f+")",d.name="ChunkLoadError",d.type=r,d.request=f,t[1](d)}}),"chunk-"+a,a)}},o.O.j=a=>0===e[a];var a=(a,c)=>{var t,r,f=c[0],d=c[1],b=c[2],n=0;if(f.some((a=>0!==e[a]))){for(t in d)o.o(d,t)&&(o.m[t]=d[t]);if(b)var i=b(o)}for(a&&a(c);n<f.length;n++)r=f[n],o.o(e,r)&&e[r]&&e[r][0](),e[r]=0;return o.O(i)},c=self.webpackChunkmy_website=self.webpackChunkmy_website||[];c.forEach(a.bind(null,0)),c.push=a.bind(null,c.push.bind(c))})()})(); \ No newline at end of file diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index e00595d..0000000 --- a/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [require.resolve('@docusaurus/core/lib/babel/preset')], -}; diff --git a/docs/advanced/_category_.json b/docs/advanced/_category_.json deleted file mode 100644 index 0a8c1de..0000000 --- a/docs/advanced/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Advanced", - "position": 4, - "collapsed": false -} \ No newline at end of file diff --git a/docs/advanced/custom-scalars.html b/docs/advanced/custom-scalars.html new file mode 100644 index 0000000..86c4707 --- /dev/null +++ b/docs/advanced/custom-scalars.html @@ -0,0 +1,24 @@ +<!doctype html> +<html lang="en" dir="ltr" class="docs-wrapper docs-doc-page docs-version-current plugin-docs plugin-id-default docs-doc-id-advanced/custom-scalars"> +<head> +<meta charset="UTF-8"> +<meta name="generator" content="Docusaurus v2.4.0"> +<title data-rh="true">Custom Scalars | GraphQL ASP.NET + + + + +
+

Custom Scalars

Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being enums).

Enums, being a type of their own, are very straight forward in .NET. Scalars, however; can be anything. For instance, the Uri scalar is represented in GraphQL by a string. On the server though, we convert it into a System.Uri object, with all the extra features that go along with it.

This can be done for any value that can be represented as a simple set of characters. When you create a scalar you declare its .NET type, provide a value resolver that accepts raw data from a query (a ReadOnlySpan<char>) and returns the instantiated scalar value.

Lets say we wanted to build a scalar called Money that can handle both an amount and currency symbol (e.g. "$23.45"). We might accept it in a query like this:

Declaring a Money Scalar
public class InventoryController : GraphController
{
[QueryRoot("search")]
public IEnumerable<Product> Search(Money minPrice)
{
return _service.RetrieveProducts(
minPrice.Symbol,
minPrice.Price);

}
}

public class Money
{
public Money(char symbol, decimal price)
{
this.Symbol = symbol;
this.Price = price;
}

public char Symbol { get; }
public decimal Price { get; }
}
Using the Money Scalar
query {
search(minPrice: "$18.45"){
id
name
}
}

The query supplies the data as a quoted string, "$18.45", but our action method receives a Money object. Internally, GraphQL senses that the supplied string value should be Money from the schema definition and invokes the correct resolver to parse the value and generate the .NET object that can be passed to our action method.

Implement IScalarGraphType

To create a scalar we need to implement IScalarGraphType and register it with GraphQL. The methods and properties of IScalarGraphType are as follows:

IScalarGraphType.cs
public interface IScalarGraphType
{
string Name { get; }
string InternalName { get; }
string Description { get; }
string SpecifiedByUrl { get; }
TypeKind Kind { get; }
bool Publish { get; }
ScalarValueType ValueType { get; }
Type ObjectType { get; }
TypeCollection OtherKnownTypes { get; }
ILeafValueResolver SourceResolver { get; }

object Serialize(object item);
string SerializeToQueryLanguage(object item);
bool ValidateObject(object item);
}

public interface ILeafValueResolver
{
object Resolve(ReadOnlySpan<char> data);
}

IScalarGraphType Members

  • Name: The unique name of this scalar. This name must be used when declaring a variable.
  • InternalName: An alternate name representing the scalar in server side code. This name is commonly used in logging messages and exceptions to identify the scalar in terms of the server definitions. Its common to use the fully qualified name, i.e. "MyNameSpace.Money".
  • Description: The phrase that will used to describe the scalar in introspection queries.
  • Kind: Scalars must always be declared as TypeKind.SCALAR.
  • Publish: Indicates if the scalar should be published for introspection queries. Unless there is a very strong reason not to, scalars should always be published. Set this value to true.
  • ValueType: A set of flags indicating what type of source data, read from a query, this scalar is capable of processing (string, number or boolean). GraphQL will do a preemptive check and if the query document does not supply the data in the correct format it will not attempt to resolve the scalar. Most custom scalars will use ScalarValueType.String.
  • SpecifiedByUrl: A url, formatted as a string, pointing to information or the specification that defines this scalar. (optional, can be null)
  • ObjectType: The primary, internal type representing the scalar in .NET. In our example above we would set this to typeof(Money).
  • OtherKnownTypes: A collection of other potential types that could be used to represent the scalar in a controller class. For instance, integers can be expressed as int or int?. Most scalars will provide an empty list (e.g. TypeCollection.Empty).
  • SourceResolver: An object that implements ILeafValueResolver which can convert raw input data into the scalar's primary ObjectType.
  • Serialize(object): A method that converts an instance of your scalar to a leaf value that is serializable in a query response
    • This method must return a number, string, bool or null.
    • When converting to a number this method can return any C# number value type (int, float, decimal etc.).
  • SerializeToQueryLanguage(object): A method that converts an instance of your scalar to a string representing it if it were declared as part of a schema language type definition.
    • This method is used when generated default values for field arguments and input object fields via introspection queries.
    • This method must return a value exactly as it would appear in a schema type definition For example, strings must be surrounded by quotes.
  • ValidateObject(object): A method used when validating data received from a a field resolver. GraphQL will call this method and provide an object instance to determine if its acceptable and can be used in a query result.
note

ValidateObject(object) should not attempt to enforce nullability rules. In general, all scalars "could be null" depending on their usage in a schema. All scalars should return true for a validation result if the provided object is null. A field's type expression, enforced by graphql, will decide if null is acceptable on an individual field.

ILeafValueResolver

ILeafValueResolver contains a single method:

  • Resolve(ReadOnlySpan<char>): A resolver function used for converting an array of characters into the internal representation of the scalar.

Dealing with Escaped Strings

The span provided to ILeafValueResolver.Resolve will be the raw data read from the query document. If the data represents a string, it will be provided in its delimited format. This means being surrounded by quotes as well as containing escaped characters (including escaped unicode characters):

Example data:

  • "quoted string"
  • """triple quoted string"""
  • "With \"\u03A3scaped ch\u03B1racters\"";

The static method GraphQLStrings.UnescapeAndTrimDelimiters provides a handy way for unescaping the data if you don't need to do anything special with it.

Calling GraphQLStrings.UnescapeAndTrimDelimiters with the previous examples produces:

  • quoted string
  • triple quoted string
  • With "Σscaped chαracters"

Indicating an Error

When resolving incoming values with Resolve(), if the provided value is not usable and must be rejected then the entire query document must be rejected. For instance, if a document contained the value "$15.R0" for our money scalar it would need to be rejected because 15.R0 cannot be converted to a decimal.

Throw an exception when this happens and GraphQL will automatically generate an appropriate response with the correct origin information indicating the line and column in the query document where the error occurred. However, like with any other encounterd exception, the library will obfuscate it to a generic message and only expose your exception details if allowed by the schema configuration.

Pro Tip!

If you throw the special UnresolvedValueException your error message will be delivered verbatim to the requestor as part of the response message instead of being obfuscated.

Example: Money Scalar

The completed Money custom scalar type


public class MoneyScalarType : IScalarGraphType
{
public string Name => "Money";

public string InternalName => typeof(Money).FullName;

public string Description => string.Empty;

public TypeKind Kind => TypeKind.SCALAR;

public bool Publish => true;

public ScalarValueType ValueType => ScalarValueType.String;

public Type ObjectType => typeof(Money);

public TypeCollection OtherKnownTypes => TypeCollection.Empty;

public ILeafValueResolver SourceResolver { get; } = new MoneyValueResolver();

public object Serialize(object item)
{
if (item == null)
return item;

var money = (Money)item;
return $"{money.Symbol}{money.Price}";
}

public string SerializeToQueryLanguage(object item)
{
// convert to a string first
var serialized = this.Serialize(item);
if (serialized == null)
return "null";

// return value as quoted
return $"\"{serialized}\"";
}

public bool ValidateObject(object item)
{
if (item == null)
return true;

return item.GetType() == typeof(Money);
}
}

public class MoneyValueResolver : ILeafValueResolver
{
public object Resolve(ReadOnlySpan<char> data)
{
// example only, more validation code is needed to fully validate
// the data
var sanitizedMoney = GraphQLStrings.UnescapeAndTrimDelimiters(data);
if(sanitizedMoney == null || sanitizedMoney.Length < 2)
throw new UnresolvedValueException("Money must be at least 2 characters");

return new Money(sanitizedMoney[0], Decimal.Parse(sanitizedMoney.Substring(1)));
}
}

Registering A Scalar

The last step in declaring a scalar is to register it with the runtime. Scalars are schema agnostic. They sit outside of any dependency injection context and must be registered directly with GraphQL.

Register The Money Scalar
// register the scalar type to the global provider
// BEFORE calling .AddGraphQL()
GraphQLProviders.ScalarProvider.RegisterCustomScalar(typeof(MoneyScalarType));

services.AddGraphQL();
info

Since our scalar is represented by a .NET class, if we don't pre-register it, GraphQL will attempt to parse the Money class as an input object graph type. Once registered as a scalar, any attempt to use Money as an object graph type will cause an exception.

@specifiedBy Directive

GraphQL provides a special, built-in directive called @specifiedBy that allows you to supply a URL pointing to a the specification for your custom scalar. This url is used by various tools to link to additional data for you or your customers so they know how to interact with your scalar type. It is entirely optional.

The @specifiedBy directive can be applied to a scalar in all the same ways as other type system directives or by use of the special [SpecifiedBy] attribute.

Applying the @specifiedBy
// apply the directive to a single schema at startup
services.AddGraphQL(o => {
o.ApplyDirective("@specifiedBy")
.WithArguments("https://myurl.com")
.ToItems(item => item.Name == "Money");
});

// via the [ApplyDirective] attribute
// for all schemas
[ApplyDirective("@specifiedBy", "https://myurl.com")]
public class MoneyScalarType : IScalarGraphType
{}

// via the special [SpecifiedBy] attribute
// for all schemas
[SpecifiedBy("https://myurl.com")]
public class MoneyScalarType : IScalarGraphType
{}

// as part of the contructor
// for all schemas
public class MoneyScalarType : IScalarGraphType
{
public MoneyScalarType()
{
this.SpecifiedByUrl = "https://myurl.com";
}
}

Tips When Developing a Scalar

A few points about designing your scalar:

  • Looking through the built in scalars can be helpful when designing your own.
  • Scalar types are expected to be thread safe.
  • The runtime will pass a new instance of your scalar graph type to each registered schema. It must be declared with a public, parameterless constructor.
  • Scalar types should be simple and work in isolation.
  • The ReadOnlySpan<char> provided to ILeafValueResolver.Resolve should be all the data needed to generate a value, there should be no need to perform side effects or fetch additional data.
  • Scalar types should not track any state, depend on any stateful objects, or attempt to use any sort of dependency injection.
  • ILeafValueResolver.Resolve must be FAST! Since your resolver is used to construct an initial query plan from the raw query text, it'll be called many orders of magnitude more often than any other method. Taking the time and performing various micro-optimizations are appropriate for this method.

Aim for Fewer Scalars

Avoid the urge to start declaring a lot of custom scalars. In fact, chances are that you'll never need to create one. In our example we could have represented our money scalar as an INPUT_OBJECT graph type:

Money as an Input Object Graph Type
public class InventoryController : GraphController
{
[QueryRoot("search")]
public IEnumerable<Product> Search(Money minPrice)
{
return _service.RetrieveProducts(
minPrice.Symbol,
minPrice.Price);
}

}

public class Money
{
public string Symbol { get; set; }
public decimal Price { get; set; }
}
Using the Money Input Object
query {
search(minPrice: {symbol: "$" price: 18.45}){
id
name
}
}

This is a lot more flexible. We can add more properties to Money when needed and not break existing queries. Whereas with a scalar if we change the acceptable format of the string data any existing applications using our graph may need to be updated. It is almost always better to represent your data as an input object rather than a custom scalar.

Be Careful

Creating a custom scalar should be a last resort, not a first option.

+ + + + \ No newline at end of file diff --git a/docs/advanced/custom-scalars.md b/docs/advanced/custom-scalars.md deleted file mode 100644 index 8ecc3a8..0000000 --- a/docs/advanced/custom-scalars.md +++ /dev/null @@ -1,321 +0,0 @@ ---- -id: custom-scalars -title: Custom Scalars -sidebar_label: Custom Scalars -sidebar_position: 3 ---- - -Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being [enums](../types/enums)). - -Enums, being a type of their own, are very straight forward in .NET. Scalars, however; can be anything. For instance, the `Uri` scalar is represented in GraphQL by a string. On the server though, we convert it into a `System.Uri` object, with all the extra features that go along with it. - -This can be done for any value that can be represented as a simple set of characters. When you create a scalar you declare its .NET type, provide a value resolver that accepts raw data from a query (a `ReadOnlySpan`) and returns the instantiated scalar value. - -Lets say we wanted to build a scalar called `Money` that can handle both an amount and currency symbol (e.g. "$23.45"). We might accept it in a query like this: - -```csharp title="Declaring a Money Scalar" -public class InventoryController : GraphController -{ - [QueryRoot("search")] - // highlight-next-line - public IEnumerable Search(Money minPrice) - { - return _service.RetrieveProducts( - minPrice.Symbol, - minPrice.Price); - - } -} - -public class Money -{ - public Money(char symbol, decimal price) - { - this.Symbol = symbol; - this.Price = price; - } - - public char Symbol { get; } - public decimal Price { get; } -} -``` - -```graphql title="Using the Money Scalar" -query { - # highlight-next-line - search(minPrice: "$18.45"){ - id - name - } -} -``` - - -The query supplies the data as a quoted string, `"$18.45"`, but our action method receives a `Money` object. Internally, GraphQL senses that the supplied string value should be `Money` from the schema definition and invokes the correct resolver to parse the value and generate the .NET object that can be passed to our action method. - -## Implement IScalarGraphType - -To create a scalar we need to implement `IScalarGraphType` and register it with GraphQL. The methods and properties of `IScalarGraphType` are as follows: - -```csharp title="IScalarGraphType.cs" -public interface IScalarGraphType -{ - string Name { get; } - string InternalName { get; } - string Description { get; } - string SpecifiedByUrl { get; } - TypeKind Kind { get; } - bool Publish { get; } - ScalarValueType ValueType { get; } - Type ObjectType { get; } - TypeCollection OtherKnownTypes { get; } - ILeafValueResolver SourceResolver { get; } - - object Serialize(object item); - string SerializeToQueryLanguage(object item); - bool ValidateObject(object item); -} - -public interface ILeafValueResolver -{ - object Resolve(ReadOnlySpan data); -} -``` - -### IScalarGraphType Members - -- `Name`: The unique name of this scalar. This name must be used when declaring a variable. -- `InternalName`: An alternate name representing the scalar in server side code. This name is commonly used in logging messages and exceptions to identify the scalar in terms of the server definitions. Its common to use the fully qualified name, i.e. `"MyNameSpace.Money"`. -- `Description`: The phrase that will used to describe the scalar in introspection queries. -- `Kind`: Scalars must always be declared as `TypeKind.SCALAR`. -- `Publish`: Indicates if the scalar should be published for introspection queries. Unless there is a very strong reason not to, scalars should always be published. Set this value to `true`. -- `ValueType`: A set of flags indicating what type of source data, read from a query, this scalar is capable of processing (string, number or boolean). GraphQL will do a preemptive check and if the query document does not supply the data in the correct format it will not attempt to resolve the scalar. Most custom scalars will use `ScalarValueType.String`. -- `SpecifiedByUrl`: A url, formatted as a string, pointing to information or the specification that defines this scalar. (optional, can be null) -- `ObjectType`: The primary, internal type representing the scalar in .NET. In our example above we would set this to `typeof(Money)`. -- `OtherKnownTypes`: A collection of other potential types that could be used to represent the scalar in a controller class. For instance, integers can be expressed as `int` or `int?`. Most scalars will provide an empty list (e.g. `TypeCollection.Empty`). -- `SourceResolver`: An object that implements `ILeafValueResolver` which can convert raw input data into the scalar's primary `ObjectType`. -- `Serialize(object)`: A method that converts an instance of your scalar to a leaf value that is serializable in a query response - - This method **must** return a `number`, `string`, `bool` or `null`. - - When converting to a number this method can return any C# number value type (int, float, decimal etc.). -- `SerializeToQueryLanguage(object)`: A method that converts an instance of your scalar to a string representing it if it were declared as part of a schema language type definition. - - This method is used when generated default values for field arguments and input object fields via introspection queries. - - This method must return a value exactly as it would appear in a schema type definition For example, strings must be surrounded by quotes. - -- `ValidateObject(object)`: A method used when validating data received from a a field resolver. GraphQL will call this method and provide an object instance to determine if its acceptable and can be used in a query result. - -:::note - `ValidateObject(object)` should not attempt to enforce nullability rules. In general, all scalars "could be null" depending on their usage in a schema. All scalars should return `true` for a validation result if the provided object is `null`. A field's type expression, enforced by graphql, will decide if null is acceptable on an individual field. -::: - -### ILeafValueResolver - -ILeafValueResolver contains a single method: - -- `Resolve(ReadOnlySpan)`: A resolver function used for converting an array of characters into the internal representation of the scalar. - -#### Dealing with Escaped Strings - -The span provided to `ILeafValueResolver.Resolve` will be the raw data read from the query document. If the data represents a string, it will be provided in its delimited format. This means being surrounded by quotes as well as containing escaped characters (including escaped unicode characters): - -Example data: - -- `"quoted string"` -- `"""triple quoted string"""` -- `"With \"\u03A3scaped ch\u03B1racters\""`; - -The static method `GraphQLStrings.UnescapeAndTrimDelimiters` provides a handy way for unescaping the data if you don't need to do anything special with it. - -Calling `GraphQLStrings.UnescapeAndTrimDelimiters` with the previous examples produces: - -- `quoted string` -- `triple quoted string` -- `With "Σscaped chαracters"` - -#### Indicating an Error - -When resolving incoming values with `Resolve()`, if the provided value is not usable and must be rejected then the entire query document must be rejected. For instance, if a document contained the value `"$15.R0"` for our money scalar it would need to be rejected because `15.R0` cannot be converted to a decimal. - -Throw an exception when this happens and GraphQL will automatically generate an appropriate response with the correct origin information indicating the line and column in the query document where the error occurred. However, like with any other encounterd exception, the library will obfuscate it to a generic message and only expose your exception details if allowed by the [schema configuration](../reference/schema-configuration). - -:::tip Pro Tip! -If you throw the special `UnresolvedValueException` your error message will be delivered verbatim to the requestor as part of the response message instead of being obfuscated. -::: - -### Example: Money Scalar -The completed Money custom scalar type - -```csharp - - public class MoneyScalarType : IScalarGraphType - { - public string Name => "Money"; - - public string InternalName => typeof(Money).FullName; - - public string Description => string.Empty; - - public TypeKind Kind => TypeKind.SCALAR; - - public bool Publish => true; - - public ScalarValueType ValueType => ScalarValueType.String; - - public Type ObjectType => typeof(Money); - - public TypeCollection OtherKnownTypes => TypeCollection.Empty; - - public ILeafValueResolver SourceResolver { get; } = new MoneyValueResolver(); - - public object Serialize(object item) - { - if (item == null) - return item; - - var money = (Money)item; - return $"{money.Symbol}{money.Price}"; - } - - public string SerializeToQueryLanguage(object item) - { - // convert to a string first - var serialized = this.Serialize(item); - if (serialized == null) - return "null"; - - // return value as quoted - return $"\"{serialized}\""; - } - - public bool ValidateObject(object item) - { - if (item == null) - return true; - - return item.GetType() == typeof(Money); - } - } - - public class MoneyValueResolver : ILeafValueResolver - { - public object Resolve(ReadOnlySpan data) - { - // example only, more validation code is needed to fully validate - // the data - var sanitizedMoney = GraphQLStrings.UnescapeAndTrimDelimiters(data); - if(sanitizedMoney == null || sanitizedMoney.Length < 2) - throw new UnresolvedValueException("Money must be at least 2 characters"); - - return new Money(sanitizedMoney[0], Decimal.Parse(sanitizedMoney.Substring(1))); - } - } -``` - -## Registering A Scalar - -The last step in declaring a scalar is to register it with the runtime. Scalars are schema agnostic. They sit outside of any dependency injection context and must be registered directly with GraphQL. - -```csharp title="Register The Money Scalar " -// register the scalar type to the global provider -// BEFORE calling .AddGraphQL() -GraphQLProviders.ScalarProvider.RegisterCustomScalar(typeof(MoneyScalarType)); - -services.AddGraphQL(); -``` - -:::info -Since our scalar is represented by a .NET class, if we don't pre-register it, GraphQL will attempt to parse the `Money` class as an input object graph type. Once registered as a scalar, any attempt to use `Money` as an object graph type will cause an exception. -::: - -## @specifiedBy Directive - -GraphQL provides a special, built-in directive called `@specifiedBy` that allows you to supply a URL pointing to a the specification for your custom scalar. This url is used by various tools to link to additional data for you or your customers so they know how to interact with your scalar type. It is entirely optional. - -The @specifiedBy directive can be applied to a scalar in all the same ways as other type system directives or by use of the special `[SpecifiedBy]` attribute. - -```csharp title="Applying the @specifiedBy" -// apply the directive to a single schema at startup -services.AddGraphQL(o => { -// highlight-start - o.ApplyDirective("@specifiedBy") - .WithArguments("https://myurl.com") - .ToItems(item => item.Name == "Money"); -// highlight-end -}); - -// via the [ApplyDirective] attribute -// for all schemas -// highlight-next-line -[ApplyDirective("@specifiedBy", "https://myurl.com")] -public class MoneyScalarType : IScalarGraphType -{} - -// via the special [SpecifiedBy] attribute -// for all schemas -// highlight-next-line -[SpecifiedBy("https://myurl.com")] -public class MoneyScalarType : IScalarGraphType -{} - -// as part of the contructor -// for all schemas -public class MoneyScalarType : IScalarGraphType -{ - public MoneyScalarType() - { - // highlight-next-line - this.SpecifiedByUrl = "https://myurl.com"; - } -} -``` - -## Tips When Developing a Scalar - -A few points about designing your scalar: - -- Looking through the [built in scalars](https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Schemas/TypeSystem/Scalars) can be helpful when designing your own. -- Scalar types are expected to be thread safe. -- The runtime will pass a new instance of your scalar graph type to each registered schema. It must be declared with a public, parameterless constructor. -- Scalar types should be simple and work in isolation. -- The `ReadOnlySpan` provided to `ILeafValueResolver.Resolve` should be all the data needed to generate a value, there should be no need to perform side effects or fetch additional data. -- Scalar types should not track any state, depend on any stateful objects, or attempt to use any sort of dependency injection. -- `ILeafValueResolver.Resolve` must be **FAST**! Since your resolver is used to construct an initial query plan from the raw query text, it'll be called many orders of magnitude more often than any other method. Taking the time and performing various micro-optimizations are appropriate for this method. - -### Aim for Fewer Scalars - -Avoid the urge to start declaring a lot of custom scalars. In fact, chances are that you'll never need to create one. In our example we could have represented our money scalar as an INPUT_OBJECT graph type: - -```csharp title="Money as an Input Object Graph Type" -public class InventoryController : GraphController -{ - [QueryRoot("search")] - public IEnumerable Search(Money minPrice) - { - return _service.RetrieveProducts( - minPrice.Symbol, - minPrice.Price); - } - -} - -public class Money -{ - public string Symbol { get; set; } - public decimal Price { get; set; } -} -``` - -```graphql title="Using the Money Input Object" -query { - search(minPrice: {symbol: "$" price: 18.45}){ - id - name - } -} -``` - - -This is a lot more flexible. We can add more properties to `Money` when needed and not break existing queries. Whereas with a scalar if we change the acceptable format of the string data any existing applications using our graph may need to be updated. It is almost always better to represent your data as an input object rather than a custom scalar. - -:::caution Be Careful -Creating a custom scalar should be a last resort, not a first option. -::: diff --git a/docs/advanced/directives.html b/docs/advanced/directives.html new file mode 100644 index 0000000..3b5582e --- /dev/null +++ b/docs/advanced/directives.html @@ -0,0 +1,26 @@ + + + + + +Directives | GraphQL ASP.NET + + + + +
+

Directives

What is a Directive?

Directives decorate parts of your schema or a query document to perform some sort of custom logic. What that logic is, is entirely up to you. There are several directives built into graphql:

  • @include : An execution directive that conditionally includes a field or fragment in the results of a graphql query
  • @skip : An execution directive that conditionally excludes a field or fragment from the results of a graphql query
  • @deprecated : A type system directive that marks a field definition or enum value as deprecated, indicating that it may be removed in a future release of your graph.
  • @specifiedBy : A type system directive for a custom scalar that adds a URL pointing to documentation about how the scalar is used. This url is returned as part of an introspection query.

Beyond this you can create directives to perform any sort of action against your graph or query document as seems fit to your use case.

Anatomy of a Directive

Directives are implemented in much the same way as a GraphController but where you'd indicate an action method as being for a query or mutation, directive action methods must indicate the location(s) they can be applied in either a query document or the type system.

SkipDirective.cs
public sealed class SkipDirective : GraphDirective
{
[DirectiveLocations(DirectiveLocation.FIELD | DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)]
public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument)
{
if (this.DirectiveTarget is IIncludeableDocumentPart docPart)
docPart.IsIncluded = !ifArgument;

return this.Ok();
}
}
Quring using @skip
# skip including flavor
query {
donut(id: 15) {
id
name
flavor @skip(if: true)
}
}

✅ All directives must:

  • Inherit from GraphQL.AspNet.Directives.GraphDirective
  • Provide at least one action method that indicates at least 1 valid DirectiveLocation.

✅ All directive action methods must:

  • Share the same method signature
  • The input arguments must match exactly in type, name, casing and declaration order.
  • Return a IGraphActionResult or Task<IGraphActionResult>

Action Results

Directives have two built in action results that can be returned:

  • this.Ok()
    • Indicates that the directive completed successfully and processing should continue.
  • this.Cancel()
    • Indicates that the directive did NOT complete successfully and processing should stop.
    • If this is a type system directive, the target schema will not be generated and the server will fail to start.
    • If this is an execution directive, the query will be abandoned and the caller will receive an error result.

Helpful Properties

The following properties are available to all directive action methods:

  • this.DirectiveTarget - The ISchemaItem or IDocumentPart to which the directive is being applied.
  • this.Request - The directive invocation request for the currently executing directive. Contains lots of advanced information such as execution phase, the directive type declared on the schema etc.

Directive Arguments

Directives may contain input arguments just like fields. However, its important to note that while a directive may declare multiple action methods for different locations to seperate your logic better, it is only a single entity in the schema. ALL action methods must share a common signature. The runtime will throw an exception while creating your schema if the signatures of the action methods differ.

Arguments for Directives
public class MyValidDirective : GraphDirective
{
[DirectiveLocations(DirectiveLocation.FIELD)]
public IGraphActionResult ExecuteField(int arg1, string arg2) { /.../ }

[DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD)]
public Task<IGraphActionResult> ExecuteFragSpread(int arg1, string arg2) { /.../ }
}

public class MyInvalidDirective : GraphDirective
{
[DirectiveLocations(DirectiveLocation.FIELD)]
public IGraphActionResult ExecuteField(int arg1, int arg2) { /.../ }

// method parameters MUST match for all directive action methods.
[DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD)]
public IGraphActionResult ExecuteFragSpread(int arg1, string arg2) { /.../ }
}
info

Directive arguments must match in name, data type and position for all action methods. Being able to use different methods for different locations is a convenience; to GraphQL there is only one directive with one set of parameters.

Execution Directives

(a.k.a. Operation Directives)

Execution Directives are applied to query documents and executed only on the request in which they are encountered.

Example: @include

This is the code for the built in @include directive:

[GraphType("include")]
public sealed class IncludeDirective : GraphDirective
{
[DirectiveLocations(DirectiveLocation.FIELD | DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)]
public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument)
{
if (this.DirectiveTarget is IIncludeableDocumentPart idp)
idp.IsIncluded = ifArgument;

return this.Ok();
}
}

This Directive:

  • Declares its name using the [GraphType] attribute
    • The name will be derived from the class name if the attribute is omitted
  • Declares that it can be applied to a query document at all field selection locations using the [DirectiveLocations] attribute
  • Uses the [FromGraphQL] attribute to declare the input argument's name in the schema
    • This is because if is a keyword in C# and we don't want the argument being named ifArgument in the schema.
  • Is executed once for each field, fragment spread or inline fragment to which its applied in a query document.

The action method name Execute in this example is arbitrary. Method names can be whatever makes the most sense to you.

Directive Execution Order

When more than one directive is encountered for a single location, they are executed in the order encountered, from left to right, in the source text.

In this example :

Using Multiple Execution Directives
query {
bakery {
allPastries{
id @directiveA @directiveB
name
}
}
}

The directives attached to the id field are executed in order from left to right:

  1. @directiveA
  2. @directiveB

Influencing Field Resolution

Execution directives are applied to document parts, not schema items. As a result they aren't directly involved in resolving fields but instead influence the document that is eventually translated into a query plan and executed. However, one common use case for execution directives includes augmenting the results of a field after its resolved. For instance, perhaps you had a directive that could conditionally turn a string field into an upper case string when applied (i.e. @toUpper).

For this reason it is possible to apply a 'PostResolver' directly to an IFieldDocumentPart. This post resolver is executed immediately after the primary field resolver is executed.

ToUpperDirective.cs
public class ToUpperDirective : GraphDirective
{
[DirectiveLocations(DirectiveLocation.FIELD)]
public IGraphActionResult UpdateResolver()
{
if (this.DirectiveTarget is IFieldDocumentPart fieldPart)
{
//
if (fieldPart.Field?.ObjectType != typeof(string))
throw new GraphExecutionException("ONLY STRINGS!"); // - hulk

// add a post resolver to the target field document
// part to perform the conversion when the query is
// ran
fieldPart.PostResolver = ConvertToUpper;
}

return this.Ok();
}

private static Task ConvertToUpper(
FieldResolutionContext context,
CancellationToken token)
{
if (context.Result is string)
context.Result = context.Result?.ToString().ToUpperInvariant();

return Task.CompletedTask;
}
}
Using @toUpper
query {
bakery {
allPastries{
id
name @toUpper
}
}
}

Working with Batch Extensions

Batch extensions work differently than standard field resolvers; they don't resolve a single item at a time. This means our @toUpper example above won't work as context.Result won't be a string. Should you employ a post resolver that may be applied to a batch extension you'll need to handle the resultant dictionary differently than you would a single field value. The dictionary will always be of the format IDictionary<TSource, TResult> where TSource is the data type of the field that owns the field the directive was applied to and TResult is the data type or an IEnumerable of the data type the target field returns. The dictionary is always keyed by source item reference.

Be Careful with Batch Type Extensions

Batch Extensions will return a dictionary of data not a single item. Your post resolver must be able to handle this dictionary if applied to a field that is a [BatchExtensionType].

Type System Directives

(a.k.a. Schema Directives)

Type System directives are applied to schema items and executed at start up while the schema is being created.

Example: @toLower

This directive will extend the resolver of a field, as its declared in the schema, to turn any strings into lower case letters.

Example: ToLowerDirective.cs
public class ToLowerDirective : GraphDirective
{
[DirectiveLocations(DirectiveLocation.FIELD_DEFINITION)]
public IGraphActionResult Execute()
{
// ensure we are working with a graph field definition and that it returns a string
if (this.DirectiveTarget is IGraphField field)
{
// ObjectType represents the .NET Type of the data returned by the field
if (field.ObjectType != typeof(string))
throw new Exception("This directive can only be applied to string fields");

// update the resolver to execute the orignal
// resolver then apply lower casing to the string result
var resolver = field.Resolver.Extend(ConvertToLower);
field.UpdateResolver(resolver);
}

return this.Ok();
}

private static Task ConvertToLower(FieldResolutionContext context, CancellationToken token)
{
if (context.Result is string)
context.Result = context.Result?.ToString().ToLower();

return Task.CompletedTask;
}
}

This Directive:

  • Targets a FIELD_DEFINITION.
  • Ensures that the target field returns a string.
  • Extends the field's resolver to convert the result to a lower-case string.
  • The directive is executed once per field definition its applied to when the schema is created. The extended resolver method is executed on every field resolution.
Type System Directives

Notice the difference in this type system directive vs. the @toUpper execution directive above. Where as toUpper was declared as a PostResolver on the document part, this directive extends the primary resolver of an IGraphField and affects ALL queries that request this field.

Example: @deprecated

The @deprecated directive is a built in type system directive provided by graphql to indicate deprecation on a field definition or enum value. Below is the code for its implementation.

public sealed class DeprecatedDirective : GraphDirective
{
[DirectiveLocations(DirectiveLocation.FIELD_DEFINITION | DirectiveLocation.ENUM_VALUE)]
public IGraphActionResult Execute([FromGraphQL("reason")] string reason = "No longer supported")
{
if (this.DirectiveTarget is IGraphField field)
{
field.IsDeprecated = true;
field.DeprecationReason = reason;
}
else if (this.DirectiveTarget is IEnumValue enumValue)
{
enumValue.IsDeprecated = true;
enumValue.DeprecationReason = reason;
}

return this.Ok();
}
}

This Directive:

  • Targets a FIELD_DEFINITION or ENUM_VALUE.
  • Marks the field or enum value as deprecated and attaches the provided deprecation reason
  • The directive is executed once per field definition and enum value its applied to when the schema is created.

Applying Type System Directives

Using the [ApplyDirective] attribute

If you have access to the source code of a given type you can use the [ApplyDirective] attribute:

Person.cs
public class Person
{
[ApplyDirective(typeof(ToLowerDirective))]
public string Name{ get; set; }
}
Person Type Definition
type Person  {
name: String @toLower
}

If different schemas on your server will use different implementations of the directive you can also specify the directive by name. This name is case sensitive and must match the name of the registered directive in the target schema. At runtime, the concrete class declared as the directive in each schema will be instantiated and used.

Apply a Directive By Name
[ApplyDirective("monitor")]
public class Person
{
public string Name{ get; set; }
}
Person Type Definition
type Person @monitor  {
name: String
}

Adding Argument Values with [ApplyDirective]

Arguments added to the apply directive attribute will be passed to the directive in the order they are encountered. The supplied values must be coercable into the expected data types for any input parameters.

Applying Directive Arguments
public class Person
{
[ApplyDirective("deprecated", "Names don't matter")]
public string Name{ get; set; }
}
Person Type Definition
type Person  {
name: String @deprecated("Names don't matter")
}

Using Schema Options

Alternatively, instead of using attributes to apply directives you can apply directives during schema configuration:

Apply Directives at Startup
services.AddGraphQL(options =>
{
options.AddGraphType<Person>();

// mark Person.Name as deprecated
options.ApplyDirective("monitor")
.ToItems(schemaItem => schemaItem.IsObjectGraphType<Person>());
}
Person Type Definition
type Person  @monitor {
name: String
}

The ToItems filter can be invoked multiple times. A schema item must match all filter criteria in order for the directive to be applied.

Type system directives are applied in order of declaration with the [ApplyDirective] attributes taking precedence over the .ApplyDirective() method.

Adding arguments via .ApplyDirective()

Adding Arguments via schema options is a lot more flexible than via attributes. Use the .WithArguments method to supply either a static set of arguments for all matched schema items +or a Func<ISchemaItem, object[]> that returns a collection of any parameters you want on a per item basis.

Apply Directives at Startup With Arguments
// startup code
services.AddGraphQL(options =>
{
options.AddGraphType<Person>();
options.ApplyDirective("@deprecated")
.WithArguments("Names don't matter")
.ToItems(schemaItem => schemaItem.IsGraphField<Person>("name"));
}
Person Type Definition
type Person  {
name: String @deprecated("Names don't matter")
}

Repeatable Directives

GraphQL ASP.NET supports repeatable type system directives. Sometimes it can be helpful to apply your directive to an schema item more than once, especially if you supply different parameters on each application.

Add the [Repeatable] attribute to the directive definition and you can then apply it multiple times using the standard methods. GraphQL tools that support this the repeatable syntax will be able to properly interprete your schema.

Repeatable Directives
[Repeatable]
public sealed class ScanItemDirective : GraphDirective
{
[DirectiveLocations(DirectiveLocation.OBJECT)]
public IGraphActionResult Execute(string scanType)
{ /* ... */}
}

// Option 1: Apply the directive to the class directly
[ApplyDirective("@scanItem", "medium")]
[ApplyDirective("@scanItem", "high")]
public class Person
{}

// Option 2: Apply the directive at startup
services.AddGraphQL(o => {
// ...
o.ApplyDirective("@scanItem")
.WithArguments("medium")
.ToItems(item => item.IsObjectGraphType<Person>());
o.ApplyDirective("@scanItem")
.WithArguments("high")
.ToItems(item => item.IsObjectGraphType<Person>());
});
Person Type Definition
type Person @scanItem("medium") @scanItem("high") {
name: String
}

Understanding the Type System

GraphQL ASP.NET builds your schema and all of its types from your controllers and objects. In general, this is done behind the scenes and you do not need to interact with it. However, when applying type system directives you are affecting the generated schema and need to understand the various parts of it. If you have a question don't be afraid to ask on github.

UML Diagrams

These uml diagrams detail the major interfaces and their most useful properties of the type system. However, these diagrams are not exaustive. Look at the source code for the full definitions.

Helpful Extensions

There are a robust set of of built in extensions for ISchemaItem that can help you filter your data when applying directives. See the full source code for details.

Directives as Services

Directives are invoked as services through your DI container when they are executed. When you add types to your schema during its initial configuration, GraphQL ASP.NET will automatically register any directives it finds attached to your entities as services in your IServiceCollection instance. However, there are times when it cannot do this, such as when you apply a directive by its string declared name. These late-bound directives may still be discoverable later and graphql will attempt to add them to your schema whenever it can. However, it may do this after the opportunity to register them with the DI container has passed.

When this occurs, if your directive contains a public, parameterless constructor graphql will still instantiate and use your directive as normal. If the directive contains dependencies in the constructor that it can't resolve, execution of that directive will fail and an exception will be thrown. To be safe, make sure to add any directives you may use to your schema during the .AddGraphQL() configuration method. Directives are directly discoverable and will be included via the options.AddAssembly() helper method as well.

The benefit of ensuring your directives are part of your IServiceCollection should be apparent:

  • The directive instance will obey lifetime scopes (e.g. transient, scoped, singleton).
  • The directive can be instantiated with any dependencies or services you wish; making for a much richer experience.

Directive Security

Directives can be secured like controller actions. However, where a controller action represents a field in the graph, a directive action does not. Regardless of the number of action methods, there is only one directive definition in your schema. As a result, the directive is secured at the class level not the method level. Any applied security parameters effect ALL action methods equally.

Take for example that the graph schema included a field of data that, by default, was always rendered in a redacted state (meaning it was obsecured) such as social security number. You could have a directive that, when supplied by the requestor, would unredact the field and allow the value to be displayed.

Applying Authorization to Directives
[Authorize(Policy = "admin")]
public sealed class UnRedactDirective : GraphDirective
{
[DirectiveLocations(DirectiveLocation.FIELD)]
public IGraphActionResult Execute()
{ /* ... */}
}

A user must adhere to the requirements of the admin policy in order to apply the @unRedact directive to a field. If the user is not part of this policy and they attempt to apply the directive, the query will be rejected.

Security Scenarios

  • Execution Directives - These directives execute using the same security context and ClaimsPrincipal applied to the HTTP request; such as an oAuth token. Execution directives are evaluated against the source document while its being constructed, BEFORE it is executed. As a result, if an execution directive fails authorization, the document fails to be constructed and no fields are resolved. This is true regardless of the authorization method assigned to the schema.

  • Type System Directives - These directives are executed during server startup, WITHOUT a ClaimsPrincipal, while the schema is being built. As a result, type system directives should not contain any security requirements, they will fail to execute if any security parameters are defined.

Since type system directives execute outside of a specific user context, only apply type system directives that you trust.

Demo Project

See the Demo Projects page for a demonstration on creating a type system directive for extending a field resolver and an execution directives +that manipulates a string field result at runtime.

+ + + + \ No newline at end of file diff --git a/docs/advanced/directives.md b/docs/advanced/directives.md deleted file mode 100644 index 44c9924..0000000 --- a/docs/advanced/directives.md +++ /dev/null @@ -1,510 +0,0 @@ ---- -id: directives -title: Directives -sidebar_label: Directives -sidebar_position: 2 ---- - -## What is a Directive? - -Directives decorate parts of your schema or a query document to perform some sort of custom logic. What that logic is, is entirely up to you. There are several directives built into graphql: - -- `@include` : An execution directive that conditionally includes a field or fragment in the results of a graphql query -- `@skip` : An execution directive that conditionally excludes a field or fragment from the results of a graphql query -- `@deprecated` : A type system directive that marks a field definition or enum value as deprecated, indicating that it may be removed in a future release of your graph. -- `@specifiedBy` : A type system directive for a custom scalar that adds a URL pointing to documentation about how the scalar is used. This url is returned as part of an introspection query. - -Beyond this you can create directives to perform any sort of action against your graph or query document as seems fit to your use case. - -## Anatomy of a Directive - -Directives are implemented in much the same way as a `GraphController` but where you'd indicate an action method as being for a query or mutation, directive action methods must indicate the location(s) they can be applied in either a query document or the type system. - -```csharp title="SkipDirective.cs" -public sealed class SkipDirective : GraphDirective -{ - [DirectiveLocations(DirectiveLocation.FIELD | DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)] - public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument) - { - if (this.DirectiveTarget is IIncludeableDocumentPart docPart) - docPart.IsIncluded = !ifArgument; - - return this.Ok(); - } -} -``` - -```graphql title="Quring using @skip" -# skip including flavor -query { - donut(id: 15) { - id - name - flavor @skip(if: true) - } -} -``` - -✅ All directives must: - -- Inherit from `GraphQL.AspNet.Directives.GraphDirective` -- Provide at least one action method that indicates at least 1 valid `DirectiveLocation`. - -✅ All directive action methods must: - -- Share the same method signature -- The input arguments must match exactly in type, name, casing and declaration order. -- Return a `IGraphActionResult` or `Task` - -### Action Results - -Directives have two built in action results that can be returned: - -- `this.Ok()` - - Indicates that the directive completed successfully and processing should continue. -- `this.Cancel()` - - Indicates that the directive did NOT complete successfully and processing should stop. - - If this is a type system directive, the target schema will not be generated and the server will fail to start. - - If this is an execution directive, the query will be abandoned and the caller will receive an error result. - -### Helpful Properties - -The following properties are available to all directive action methods: - -- `this.DirectiveTarget` - The `ISchemaItem` or `IDocumentPart` to which the directive is being applied. -- `this.Request` - The directive invocation request for the currently executing directive. Contains lots of advanced information such as execution phase, the directive type declared on the schema etc. - -### Directive Arguments - -Directives may contain input arguments just like fields. However, its important to note that while a directive may declare multiple action methods for different locations to seperate your logic better, it is only a single entity in the schema. ALL action methods must share a common signature. The runtime will throw an exception while creating your schema if the signatures of the action methods differ. - -```csharp title="Arguments for Directives" -public class MyValidDirective : GraphDirective -{ - [DirectiveLocations(DirectiveLocation.FIELD)] - public IGraphActionResult ExecuteField(int arg1, string arg2) { /.../ } - - [DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD)] - public Task ExecuteFragSpread(int arg1, string arg2) { /.../ } -} - -public class MyInvalidDirective : GraphDirective -{ - [DirectiveLocations(DirectiveLocation.FIELD)] - // highlight-next-line - public IGraphActionResult ExecuteField(int arg1, int arg2) { /.../ } - - // method parameters MUST match for all directive action methods. - [DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD)] - // highlight-next-line - public IGraphActionResult ExecuteFragSpread(int arg1, string arg2) { /.../ } -} -``` - -:::info - Directive arguments must match in name, data type and position for all action methods. Being able to use different methods for different locations is a convenience; to GraphQL there is only one directive with one set of parameters. -::: - -## Execution Directives - -(_**a.k.a. Operation Directives**_) - -Execution Directives are applied to query documents and executed only on the request in which they are encountered. - -### Example: @include - -This is the code for the built in `@include` directive: - -```csharp -[GraphType("include")] -public sealed class IncludeDirective : GraphDirective -{ - [DirectiveLocations(DirectiveLocation.FIELD | DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)] - public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument) - { - if (this.DirectiveTarget is IIncludeableDocumentPart idp) - idp.IsIncluded = ifArgument; - - return this.Ok(); - } -} -``` - -This Directive: - -- Declares its name using the `[GraphType]` attribute - - The name will be derived from the class name if the attribute is omitted -- Declares that it can be applied to a query document at all field selection locations using the `[DirectiveLocations]` attribute -- Uses the `[FromGraphQL]` attribute to declare the input argument's name in the schema - - This is because `if` is a keyword in C# and we don't want the argument being named `ifArgument` in the schema. -- Is executed once for each field, fragment spread or inline fragment to which its applied in a query document. - -> The action method name `Execute` in this example is arbitrary. Method names can be whatever makes the most sense to you. - -### Directive Execution Order - -When more than one directive is encountered for a single location, they are executed in the order encountered, from left to right, in the source text. - -In this example : - -```graphql title="Using Multiple Execution Directives" -query { - bakery { - allPastries{ - id @directiveA @directiveB - name - } - } -} -``` - -The directives attached to the `id` field are executed in order from left to right: - -1. @directiveA -2. @directiveB - -### Influencing Field Resolution - -Execution directives are applied to document parts, not schema items. As a result they aren't directly involved in resolving fields but instead influence the document that is eventually translated into a query plan and executed. However, one common use case for execution directives includes augmenting the results of a field after its resolved. For instance, perhaps you had a directive that could conditionally turn a string field into an upper case string when applied (i.e. `@toUpper`). - -For this reason it is possible to apply a 'PostResolver' directly to an `IFieldDocumentPart`. This post resolver is executed immediately after the primary field resolver is executed. - -```csharp title="ToUpperDirective.cs" -public class ToUpperDirective : GraphDirective -{ - [DirectiveLocations(DirectiveLocation.FIELD)] - public IGraphActionResult UpdateResolver() - { - if (this.DirectiveTarget is IFieldDocumentPart fieldPart) - { - // - if (fieldPart.Field?.ObjectType != typeof(string)) - throw new GraphExecutionException("ONLY STRINGS!"); // - hulk - - // add a post resolver to the target field document - // part to perform the conversion when the query is - // ran - // highlight-next-line - fieldPart.PostResolver = ConvertToUpper; - } - - return this.Ok(); - } - - private static Task ConvertToUpper( - FieldResolutionContext context, - CancellationToken token) - { - if (context.Result is string) - context.Result = context.Result?.ToString().ToUpperInvariant(); - - return Task.CompletedTask; - } -} -``` - -```graphql title="Using @toUpper" -query { - bakery { - allPastries{ - id - name @toUpper - } - } -} -``` - -#### Working with Batch Extensions - -Batch extensions work differently than standard field resolvers; they don't resolve a single item at a time. This means our `@toUpper` example above won't work as `context.Result` won't be a string. Should you employ a post resolver that may be applied to a batch extension you'll need to handle the resultant dictionary differently than you would a single field value. The dictionary will always be of the format `IDictionary` where `TSource` is the data type of the field that owns the field the directive was applied to and `TResult` is the data type or an `IEnumerable` of the data type the target field returns. The dictionary is always keyed by source item reference. - -:::caution Be Careful with Batch Type Extensions - Batch Extensions will return a dictionary of data not a single item. Your post resolver must be able to handle this dictionary if applied to a field that is a `[BatchExtensionType]`. -::: - -## Type System Directives - -(_**a.k.a. Schema Directives**_) - -Type System directives are applied to schema items and executed at start up while the schema is being created. - -### Example: @toLower - -This directive will extend the resolver of a field, as its declared **in the schema**, to turn any strings into lower case letters. - -```csharp title="Example: ToLowerDirective.cs" -public class ToLowerDirective : GraphDirective -{ - [DirectiveLocations(DirectiveLocation.FIELD_DEFINITION)] - public IGraphActionResult Execute() - { - // ensure we are working with a graph field definition and that it returns a string - if (this.DirectiveTarget is IGraphField field) - { - // ObjectType represents the .NET Type of the data returned by the field - if (field.ObjectType != typeof(string)) - throw new Exception("This directive can only be applied to string fields"); - - // update the resolver to execute the orignal - // resolver then apply lower casing to the string result - var resolver = field.Resolver.Extend(ConvertToLower); - field.UpdateResolver(resolver); - } - - return this.Ok(); - } - - private static Task ConvertToLower(FieldResolutionContext context, CancellationToken token) - { - if (context.Result is string) - context.Result = context.Result?.ToString().ToLower(); - - return Task.CompletedTask; - } -} -``` - -This Directive: - -- Targets a FIELD_DEFINITION. -- Ensures that the target field returns a string. -- Extends the field's resolver to convert the result to a lower-case string. -- The directive is executed once per field definition its applied to when the schema is created. The extended resolver method is executed on every field resolution. - -:::info Type System Directives - Notice the difference in this type system directive vs. the `@toUpper` execution directive above. Where as toUpper was declared as a PostResolver on the document part, this directive extends the primary resolver of an `IGraphField` and affects ALL queries that request this field. -::: - -### Example: @deprecated - -The `@deprecated` directive is a built in type system directive provided by graphql to indicate deprecation on a field definition or enum value. Below is the code for its implementation. - -```csharp -public sealed class DeprecatedDirective : GraphDirective -{ - [DirectiveLocations(DirectiveLocation.FIELD_DEFINITION | DirectiveLocation.ENUM_VALUE)] - public IGraphActionResult Execute([FromGraphQL("reason")] string reason = "No longer supported") - { - if (this.DirectiveTarget is IGraphField field) - { - field.IsDeprecated = true; - field.DeprecationReason = reason; - } - else if (this.DirectiveTarget is IEnumValue enumValue) - { - enumValue.IsDeprecated = true; - enumValue.DeprecationReason = reason; - } - - return this.Ok(); - } -} -``` - -This Directive: - -- Targets a FIELD_DEFINITION or ENUM_VALUE. -- Marks the field or enum value as deprecated and attaches the provided deprecation reason -- The directive is executed once per field definition and enum value its applied to when the schema is created. - -### Applying Type System Directives - -#### Using the `[ApplyDirective]` attribute - -If you have access to the source code of a given type you can use the `[ApplyDirective]` attribute: - - -```csharp title="Person.cs" -public class Person -{ - [ApplyDirective(typeof(ToLowerDirective))] - public string Name{ get; set; } -} -``` - -```graphql title="Person Type Definition" -type Person { - name: String @toLower -} -``` - -If different schemas on your server will use different implementations of the directive you can also specify the directive by name. This name is case sensitive and must match the name of the registered directive in the target schema. At runtime, the concrete class declared as the directive in each schema will be instantiated and used. - -```csharp title="Apply a Directive By Name" -[ApplyDirective("monitor")] -public class Person -{ - public string Name{ get; set; } -} -``` -```graphql title="Person Type Definition" -type Person @monitor { - name: String -} -``` -**Adding Argument Values with [ApplyDirective]** - -Arguments added to the apply directive attribute will be passed to the directive in the order they are encountered. The supplied values must be coercable into the expected data types for any input parameters. - - -```csharp title="Applying Directive Arguments" -public class Person -{ - [ApplyDirective("deprecated", "Names don't matter")] - public string Name{ get; set; } -} -``` - -```graphql title="Person Type Definition" -type Person { - name: String @deprecated("Names don't matter") -} -``` - -#### Using Schema Options - -Alternatively, instead of using attributes to apply directives you can apply directives during schema configuration: - -```csharp title="Apply Directives at Startup" -services.AddGraphQL(options => -{ - options.AddGraphType(); - - // mark Person.Name as deprecated - options.ApplyDirective("monitor") - .ToItems(schemaItem => schemaItem.IsObjectGraphType()); -} -``` - -```graphql title="Person Type Definition" -type Person @monitor { - name: String -} -``` - -> The `ToItems` filter can be invoked multiple times. A schema item must match all filter criteria in order for the directive to be applied. - -> Type system directives are applied in order of declaration with the `[ApplyDirective]` attributes taking precedence over the `.ApplyDirective()` method. - -**Adding arguments via .ApplyDirective()** - -Adding Arguments via schema options is a lot more flexible than via attributes. Use the `.WithArguments` method to supply either a static set of arguments for all matched schema items -or a `Func` that returns a collection of any parameters you want on a per item basis. - - -```csharp title="Apply Directives at Startup With Arguments" -// startup code -services.AddGraphQL(options => -{ - options.AddGraphType(); - // highlight-start - options.ApplyDirective("@deprecated") - .WithArguments("Names don't matter") - .ToItems(schemaItem => schemaItem.IsGraphField("name")); - // highlight-end -} -``` - -```graphql title="Person Type Definition" -type Person { - name: String @deprecated("Names don't matter") -} -``` - -### Repeatable Directives - -GraphQL ASP.NET supports repeatable type system directives. Sometimes it can be helpful to apply your directive to an schema item more than once, especially if you supply different parameters on each application. - -Add the `[Repeatable]` attribute to the directive definition and you can then apply it multiple times using the standard methods. GraphQL tools that support this the repeatable syntax will be able to properly interprete your schema. - -```csharp title="Repeatable Directives" -[Repeatable] -public sealed class ScanItemDirective : GraphDirective -{ - [DirectiveLocations(DirectiveLocation.OBJECT)] - public IGraphActionResult Execute(string scanType) - { /* ... */} -} - -// Option 1: Apply the directive to the class directly -// highlight-start -[ApplyDirective("@scanItem", "medium")] -[ApplyDirective("@scanItem", "high")] -// highlight-end -public class Person -{} - -// Option 2: Apply the directive at startup -services.AddGraphQL(o => { - // ... - // highlight-start - o.ApplyDirective("@scanItem") - .WithArguments("medium") - .ToItems(item => item.IsObjectGraphType()); - o.ApplyDirective("@scanItem") - .WithArguments("high") - .ToItems(item => item.IsObjectGraphType()); - // highlight-end -}); -``` - -```graphql title="Person Type Definition" -type Person @scanItem("medium") @scanItem("high") { - name: String -} -``` - -### Understanding the Type System - -GraphQL ASP.NET builds your schema and all of its types from your controllers and objects. In general, this is done behind the scenes and you do not need to interact with it. However, when applying type system directives you are affecting the generated schema and need to understand the various parts of it. If you have a question don't be afraid to ask on [github](https://github.com/graphql-aspnet/graphql-aspnet). - -**UML Diagrams** - -These [uml diagrams](../assets/2022-10-graphql-aspnet-structural-diagrams.pdf) detail the major interfaces and their most useful properties of the type system. However, these diagrams are not exaustive. Look at the [source code](https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Interfaces/Schema) for the full definitions. - -**Helpful Extensions** - -There are a robust set of of built in extensions for `ISchemaItem` that can help you filter your data when applying directives. See the [full source code](https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet/Configuration/SchemaItemFilterExtensions.cs) for details. - -## Directives as Services - -Directives are invoked as services through your DI container when they are executed. When you add types to your schema during its initial configuration, GraphQL ASP.NET will automatically register any directives it finds attached to your entities as services in your `IServiceCollection` instance. However, there are times when it cannot do this, such as when you apply a directive by its string declared name. These late-bound directives may still be discoverable later and graphql will attempt to add them to your schema whenever it can. However, it may do this after the opportunity to register them with the DI container has passed. - -When this occurs, if your directive contains a public, parameterless constructor graphql will still instantiate and use your directive as normal. If the directive contains dependencies in the constructor that it can't resolve, execution of that directive will fail and an exception will be thrown. To be safe, make sure to add any directives you may use to your schema during the `.AddGraphQL()` configuration method. Directives are directly discoverable and will be included via the `options.AddAssembly()` helper method as well. - -The benefit of ensuring your directives are part of your `IServiceCollection` should be apparent: - -- The directive instance will obey lifetime scopes (e.g. transient, scoped, singleton). -- The directive can be instantiated with any dependencies or services you wish; making for a much richer experience. - -## Directive Security - -Directives can be secured like controller actions. However, where a controller action represents a field in the graph, a directive action does not. Regardless of the number of action methods, there is only one directive definition in your schema. As a result, the directive is secured at the class level not the method level. Any applied security parameters effect ALL action methods equally. - -Take for example that the graph schema included a field of data that, by default, was always rendered in a redacted state (meaning it was obsecured) such as social security number. You could have a directive that, when supplied by the requestor, would unredact the field and allow the value to be displayed. - -```csharp title="Applying Authorization to Directives" -// highlight-next-line -[Authorize(Policy = "admin")] -public sealed class UnRedactDirective : GraphDirective -{ - [DirectiveLocations(DirectiveLocation.FIELD)] - public IGraphActionResult Execute() - { /* ... */} -} -``` - -> A user must adhere to the requirements of the `admin` policy in order to apply the `@unRedact` directive to a field. If the user is not part of this policy and they attempt to apply the directive, the query will be rejected. - -### Security Scenarios - -- **Execution Directives** - These directives execute using the same security context and `ClaimsPrincipal` applied to the HTTP request; such as an oAuth token. Execution directives are evaluated against the source document while its being constructed, BEFORE it is executed. As a result, if an execution directive fails authorization, the document fails to be constructed and no fields are resolved. This is true regardless of the authorization method assigned to the schema. - -- **Type System Directives** - These directives are executed during server startup, WITHOUT a `ClaimsPrincipal`, while the schema is being built. As a result, type system directives should not contain any security requirements, they will fail to execute if any security parameters are defined. - -> Since type system directives execute outside of a specific user context, only apply type system directives that you trust. - -## Demo Project - -See the [Demo Projects](../reference/demo-projects.md) page for a demonstration on creating a type system directive for extending a field resolver and an execution directives -that manipulates a string field result at runtime. diff --git a/docs/advanced/graph-action-results.html b/docs/advanced/graph-action-results.html new file mode 100644 index 0000000..fbc90c0 --- /dev/null +++ b/docs/advanced/graph-action-results.html @@ -0,0 +1,24 @@ + + + + + +Action Results | GraphQL ASP.NET + + + + +
+

Action Results

What is an Action Result?

In ASP.NET you may do things like this:

Return an Action Result
public class BakeryController : Controller
{
[HttpGet("donuts/{id}")]
public IActionResult RetrieveDonut(int id)
{
Donut donut = null;
// ...

// return the donut and indicate success
return this.Ok(donut);
}
}
Return an Object
public class BakeryController : Controller
{
[HttpGet("donuts/{id}")]
public Donut RetrieveDonut(int id)
{
Donut donut = null;
// ...

// return the donut directly!
return donut;
}
}

You can either return the data itself or some alternate IActionResult to tell ASP.NET how to render a response.

Some common ASP.NET action results:

  • this.Ok() : Everything worked fine, return status 200.
  • this.NotFound() : The item doesn't exist, return status 404.
  • this.File(): Return status 200 and stream the file to the client.
  • this.View(): Render a razor view and send the HTML to the client.

This works the same way in GraphQL ASP.NET. The available actions are slightly different (e.g. GraphQL won't stream files) but the usage is the same. You can even write your own action results.

Controller Action Results

Instead of IActionResult we use IGraphActionResult from a controller action method. Both directives and controller action methods can return action results.

Built in Controller Action Methods:

  • this.Ok(fieldValue) : Return fieldValue as the resolved value of the field and indicate to the runtime that everything completed successfully.
  • this.Error(message): Indicates a problem. The field will resolve to a null value automatically. Child fields are not processed and an error message with the given text and error code is added to the response payload.
  • this.StartBatch() : Initiates the start a of a new batch. See batch operations for details.
  • this.Unauthorized(): Indicate the user is not authorized to request the field. A message telling them as such will be added to the result and no child fields will be processed. The field will resolve to a null value automatically. This is sometimes necessary for data-level validation that can't be readily determined from an [Authorize] attribute or query level validation.
  • this.BadRequest(): Commonly used in conjunction with this.ModelState. This result indicates the data supplied to the method is not valid for the operation. If given a model state collection, an error for each validation error is rendered.
  • this.InternalServerError(): Indicates an unintended error, such as an exception occurred. The supplied message will be added to the response and no child fields will be resolved.

Directive Action Results

Directives have two built in Action Results:

  • this.Ok(): Indicates that the directive completed its expected operation successfully and processing can continue.
  • this.Cancel(): Indicates that the directive did NOT complete its operation successfully.
    • If this is a type system directive, the schema will fail to complete and the server will not start.
    • If this is an execution directive, the query will be abandoned and the user will receive an error message.

Using an IGraphActionResult

Using a graph action result is straight forward. Use it like you would a regular action result with a REST query:

public class BakeryController : GraphController
{
[Query("donut", typeof(Donut))]
public IGraphActionResult RetrieveDonut(int id)
{
if(id < 0)
return this.Error("Invalid Id");

Donut donut = new Donut(id);
return this.Ok(donut);
}
}

Notice, however; that we had to declare the return type of the donut field in the [Query] attribute. This is because the actual return type is hidden by the use of IGraphActionResult. This is the trade off to the extra functionality provided by action results. Since GraphQL is a statically typed language all field return types must be known at startup.

info

Using a graph action result requires you to declare the return type of your action method elsewhere, usually in the [Query] or [Mutation] attribute.

Custom Graph Action Results

You can add your own custom action results. This can be particularly useful on larger teams where you want a uniform field response or error message contents for a given situation.

To create a custom result, implement IGraphActionResult, which defines a single method:

IGraphActionResult.cs
public interface IGraphActionResult
{
Task CompleteAsync(ResolutionContext context);
}

ResolutionContext is the data context used to resolve the field or directive. For controller actions this can be cast to FieldResolutionContext to obtain access to the Result property. Setting this property sets the resolved value for the field.

info

FieldResolutionContext contains a Result property which indicates the final resolved value for the field.

Looking at the UnauthorizedGraphActionResult is a great example of how to implement your own:

UnauthorizedGraphActionResult.cs
    public class UnauthorizedGraphActionResult : IGraphActionResult
{
private readonly string _errorCode;
private readonly string _errorMessage;

public UnauthorizedGraphActionResult(
string errorMessage = "",
string errorCode = Constants.ErrorCodes.ACCESS_DENIED)
{
_errorMessage = errorMessage ?? "Unauthorized Access";
_errorCode = errorCode ?? Constants.ErrorCodes.ACCESS_DENIED;
}

public Task CompleteAsync(ResolutionContext context)
{
// add an error message to the response
context.Messages.Critical(
_errorMessage,
_errorCode,
context.Request.Origin);

// instruct graphql to stop processing this field
// and its children
context.Cancel();
return Task.CompletedTask;
}
}

The result takes in an optional error message and code, providing defaults if not supplied. Then on CompleteAsync it adds the message to the context and cancels its execution.

+ + + + \ No newline at end of file diff --git a/docs/advanced/graph-action-results.md b/docs/advanced/graph-action-results.md deleted file mode 100644 index 95ff0db..0000000 --- a/docs/advanced/graph-action-results.md +++ /dev/null @@ -1,157 +0,0 @@ ---- -id: graph-action-results -title: Action Results -sidebar_label: Action Results -sidebar_position: 4 ---- - -## What is an Action Result? - -In ASP.NET you may do things like this: - -```csharp title="Return an Action Result" -public class BakeryController : Controller -{ - [HttpGet("donuts/{id}")] - public IActionResult RetrieveDonut(int id) - { - Donut donut = null; - // ... - - // highlight-start - // return the donut and indicate success - return this.Ok(donut); - // highlight-end - } -} -``` - -```csharp title="Return an Object" -public class BakeryController : Controller -{ - [HttpGet("donuts/{id}")] - public Donut RetrieveDonut(int id) - { - Donut donut = null; - // ... - - // highlight-start - // return the donut directly! - return donut; - // highlight-end - } -} -``` - -> You can either return the data itself or some alternate `IActionResult` to tell ASP.NET how to render a response. - -Some common ASP.NET action results: - -- `this.Ok()` : Everything worked fine, return status 200. -- `this.NotFound()` : The item doesn't exist, return status 404. -- `this.File()`: Return status 200 and stream the file to the client. -- `this.View()`: Render a razor view and send the HTML to the client. - -This works the same way in GraphQL ASP.NET. The available actions are slightly different (e.g. GraphQL won't stream files) but the usage is the same. You can even write your own action results. - -## Controller Action Results - -Instead of `IActionResult` we use `IGraphActionResult` from a controller action method. Both [directives](./directives) and controller [action methods](../controllers/actions) can return action results. - -Built in Controller Action Methods: - -- `this.Ok(fieldValue)` : Return _fieldValue_ as the resolved value of the field and indicate to the runtime that everything completed successfully. -- `this.Error(message)`: Indicates a problem. The field will resolve to a `null` value automatically. Child fields are not processed and an error message with the given text and error code is added to the response payload. -- `this.StartBatch()` : Initiates the start a of a new batch. See [batch operations](../controllers/batch-operations.md) for details. -- `this.Unauthorized()`: Indicate the user is not authorized to request the field. A message telling them as such will be added to the result and no child fields will be processed. The field will resolve to a `null` value automatically. This is sometimes necessary for data-level validation that can't be readily determined from an `[Authorize]` attribute or query level validation. -- `this.BadRequest()`: Commonly used in conjunction with `this.ModelState`. This result indicates the data supplied to the method is not valid for the operation. If given a model state collection, an error for each validation error is rendered. -- `this.InternalServerError()`: Indicates an unintended error, such as an exception occurred. The supplied message will be added to the response and no child fields will be resolved. - -### Directive Action Results -[Directives](./directives) have two built in Action Results: - -- `this.Ok()`: Indicates that the directive completed its expected operation successfully and processing can continue. -- `this.Cancel()`: Indicates that the directive did NOT complete its operation successfully. - - If this is a type system directive, the schema will fail to complete and the server will not start. - - If this is an execution directive, the query will be abandoned and the user will receive an error message. - -## Using an IGraphActionResult - -Using a graph action result is straight forward. Use it like you would a regular action result with a REST query: - -```csharp -public class BakeryController : GraphController -{ - [Query("donut", typeof(Donut))] - public IGraphActionResult RetrieveDonut(int id) - { - if(id < 0) - // highlight-next-line - return this.Error("Invalid Id"); - - Donut donut = new Donut(id); - // highlight-next-line - return this.Ok(donut); - } -} -``` - -Notice, however; that we had to declare the return type of the donut field in the `[Query]` attribute. This is because the actual return type is hidden by the use of `IGraphActionResult`. This is the trade off to the extra functionality provided by action results. Since GraphQL is a statically typed language all field return types must be known at startup. - -:::info -Using a graph action result requires you to declare the return type of your action method elsewhere, usually in the `[Query]` or `[Mutation]` attribute. -::: - - -## Custom Graph Action Results - -You can add your own custom action results. This can be particularly useful on larger teams where you want a uniform field response or error message contents for a given situation. - -To create a custom result, implement `IGraphActionResult`, which defines a single method: - -```csharp title="IGraphActionResult.cs" -public interface IGraphActionResult -{ - Task CompleteAsync(ResolutionContext context); -} -``` - -`ResolutionContext` is the data context used to resolve the field or directive. For controller actions this can be cast to `FieldResolutionContext` to obtain access to the `Result` property. Setting this property sets the resolved value for the field. - -:::info -`FieldResolutionContext` contains a `Result` property which indicates the final resolved value for the field. -::: - -Looking at the `UnauthorizedGraphActionResult` is a great example of how to implement your own: - -```csharp title="UnauthorizedGraphActionResult.cs" - public class UnauthorizedGraphActionResult : IGraphActionResult - { - private readonly string _errorCode; - private readonly string _errorMessage; - - public UnauthorizedGraphActionResult( - string errorMessage = "", - string errorCode = Constants.ErrorCodes.ACCESS_DENIED) - { - _errorMessage = errorMessage ?? "Unauthorized Access"; - _errorCode = errorCode ?? Constants.ErrorCodes.ACCESS_DENIED; - } - - public Task CompleteAsync(ResolutionContext context) - { - // add an error message to the response - context.Messages.Critical( - _errorMessage, - _errorCode, - context.Request.Origin); - - // instruct graphql to stop processing this field - // and its children - context.Cancel(); - return Task.CompletedTask; - } - } -``` - -The result takes in an optional error message and code, providing defaults if not supplied. Then on `CompleteAsync` it adds the message to the context and cancels its execution. diff --git a/docs/advanced/multi-schema-support.html b/docs/advanced/multi-schema-support.html new file mode 100644 index 0000000..52fef1b --- /dev/null +++ b/docs/advanced/multi-schema-support.html @@ -0,0 +1,24 @@ + + + + + +Multi-Schema Support | GraphQL ASP.NET + + + + +
+

Multi-Schema Support

GraphQL ASP.NET supports multiple schemas on the same server out of the box. Each schema is recognized by its concrete .NET type.

Create a Custom Schema

To register multiple schemas you'll need to create your own class that implements ISchema. While it is possible to implement ISchema directly, if you don't require any extra functionality in your schema its easier to just inherit from the default GraphSchema. Updating the Name and Description is highly encouraged as the information is referenced in several different messages and can be very helpful in debugging.

Declaring Custom Schemas
public class EmployeeSchema : GraphSchema
{
// The schema name may be referenced in some error messages
// and log entries.
public override string Name => "Employee Schema";

// The description is publically available via introspection queries.
public override string Description => "Employee Related Data";
}

public class CustomerSchema : GraphSchema
{
public override string Name => "Customer Schema";
public override string Description => "Customer Related Data";
}

Implementing ISchema and its dependencies from scratch is not a trivial task and is beyond the scope of this documentation.

Register Each Schema

Each schema can be registered using an overload of .AddGraphQL() during startup.

By default, the query handler will attempt to register a schema to /graphql as its URL. You'll want to ensure that each schema has its own endpoint by updating individual routes as necessary.

Adding Multiple Schemas
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGraphQL<EmployeeSchema>((options) =>
{
options.QueryHandler.Route = "/graphql_employees";
// add assembly or graph type references here
});

builder.Services.AddGraphQL<CustomerSchema>((options) =>
{
options.QueryHandler.Route = "/graphql_customers";
// add assembly or graph type references here
});

var app = builder.Build();

app.UseGraphQL();
app.Run();
note

Each schema must be configured to use its own endpoint.

Disable Local Graph Entity Registration

(optional) You may want to disable the registering of local graph entities (the entities in the startup assembly) on one or both schemas lest you want each schema to contain the same controllers and graph types.

Startup Code
// Optionally Disable Local Entity Registration
services.AddGraphQL<EmployeeSchema>(o =>
{
o.AutoRegisterLocalEntities = false;
});
+ + + + \ No newline at end of file diff --git a/docs/advanced/multiple-schema.md b/docs/advanced/multiple-schema.md deleted file mode 100644 index f49106d..0000000 --- a/docs/advanced/multiple-schema.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -id: multi-schema-support -title: Multi-Schema Support -sidebar_label: Multi-Schema Support -sidebar_position: 5 ---- - -GraphQL ASP.NET supports multiple schemas on the same server out of the box. Each schema is recognized by its concrete .NET type. - -## Create a Custom Schema - -To register multiple schemas you'll need to create your own class that implements `ISchema`. While it is possible to implement `ISchema` directly, if you don't require any extra functionality in your schema its easier to just inherit from the default `GraphSchema`. Updating the `Name` and `Description` is highly encouraged as the information is referenced in several different messages and can be very helpful in debugging. - -```csharp title="Declaring Custom Schemas" -// highlight-next-line -public class EmployeeSchema : GraphSchema -{ - // The schema name may be referenced in some error messages - // and log entries. - public override string Name => "Employee Schema"; - - // The description is publically available via introspection queries. - public override string Description => "Employee Related Data"; -} - -// highlight-next-line -public class CustomerSchema : GraphSchema -{ - public override string Name => "Customer Schema"; - public override string Description => "Customer Related Data"; -} -``` - -> Implementing `ISchema` and its dependencies from scratch is not a trivial task and is beyond the scope of this documentation. - - -## Register Each Schema - -Each schema can be registered using an overload of `.AddGraphQL()` during startup. - -By default, the query handler will attempt to register a schema to `/graphql` as its URL. You'll want to ensure that each schema has its own endpoint by updating individual routes as necessary. - -```csharp title="Adding Multiple Schemas" -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddGraphQL((options) => -{ - // highlight-next-line - options.QueryHandler.Route = "/graphql_employees"; - // add assembly or graph type references here -}); - -builder.Services.AddGraphQL((options) => -{ - // highlight-next-line - options.QueryHandler.Route = "/graphql_customers"; - // add assembly or graph type references here -}); - -var app = builder.Build(); - -// highlight-next-line -app.UseGraphQL(); -app.Run(); -``` - -:::note -Each schema **must** be configured to use its own endpoint. -::: - -## Disable Local Graph Entity Registration - -(optional) You may want to disable the registering of local graph entities (the entities in the startup assembly) on one or both schemas lest you want each schema to contain the same controllers and graph types. - -```csharp title="Startup Code" -// Optionally Disable Local Entity Registration -services.AddGraphQL(o => -{ - // highlight-next-line - o.AutoRegisterLocalEntities = false; -}); -``` \ No newline at end of file diff --git a/docs/advanced/subscriptions.html b/docs/advanced/subscriptions.html new file mode 100644 index 0000000..f4db2c8 --- /dev/null +++ b/docs/advanced/subscriptions.html @@ -0,0 +1,25 @@ + + + + + +Subscriptions | GraphQL ASP.NET + + + + +
+

Subscriptions

Initial Setup

Successfully handling subscriptions in your GraphQL server can be straight forward for single server environments or very complicated for multi-server and scalable solutions. First we'll look at adding subscriptions for a single server.

Install the Subscriptions Package

The first step to using subscriptions is to install the subscription server package.

Install The Library
# Using the dotnet CLI
> dotnet add package GraphQL.AspNet.Subscriptions

# using Package Manager Console
> Install-Package GraphQL.AspNet.Subscriptions

This adds the necessary components to create a subscription server for a given schema such as communicating with web sockets, parsing subscription queries and responding to events.

Configure the Server Instance

You must configure web socket support for your Asp.Net server instance separately. The ways in which you perform this configuration will vary widely depending on your CORS requirements, keep-alive support and other needs.

After web sockets are added to your server, add subscription support to the graphql registration.

Add Subscription Support at Startup
// configuring services at startup
// ---------------
services.AddWebSockets(/*...*/);

services.AddGraphQL()
.AddSubscriptions();

// building the application pipeline
// ---------------
app.UseWebSockets();
app.UseGraphQL();
tip

Don't forget to call .UseWebsockets() before calling .UseGraphQL().

Create a Subscription

Declaring a subscription is the same as declaring a query or mutation on a controller but with [Subscription] and [SubscriptionRoot] attributes. Feel free to mix subscriptions with your queries and mutations. They do not need to be kept seperate.

SubscriptionController.cs
public class SubscriptionController : GraphController
{
// other code not shown for brevity

[SubscriptionRoot("onWidgetChanged", typeof(Widget), EventName = "WIDGET_CHANGED")]
public IGraphActionResult OnWidgetChanged(Widget eventData, string filter)
{
if(eventData.Name.StartsWith(filter))
{
// send the data down to the listening client
return this.Ok(eventData);
}
else
{
// use SkipSubscriptionEvent() to disregard the data
// and not communicate anything to the listening client
return this.SkipSubscriptionEvent();
}
}
}

Here we've declared a new subscription, one that takes in a filter parameter to restrict the data that any subscribers receive.

A query to invoke this subscription may look like this:

Sample Subscription Query
subscription {
onWidgetChanged(filter: "Big"){
id
name
description
}
}

Any updated widgets that start with the phrase "Big" will then be sent to the requestor as they are changed on the server. Any other changed widgets will be skipped/dropped and no data will be sent to the client.

Publish a Subscription Event

In order for the subscription server to send data to any subscribers it has to be notified when its time to do so. It does this via named Subscription Events. These are internal, schema-unique keys (strings) that identify when something happened, usually via a mutation. Once the mutation publishes an event, the subscription server will execute the appropriate action method for any subscribers, using the supplied data, and deliver the results to the client.

MutationController.cs
public class MutationController : GraphController
{
// other code not shown for brevity

[MutationRoot("updateWidget", typeof(Widget))]
public async Task<IGraphActionResult> OnWidgetChanged(int id, string name){
var widget = _service.RetrieveWidget(id);
widget.Name = name;

await _service.UpdateWidget(widget);

// publish a new event to let any subscribers know
// something changed
this.PublishSubscriptionEvent("WIDGET_CHANGED", widget);
return this.Ok(widget);
}
}
Event Names Must Match

Notice that the event name used in PublishSubscriptionEvent() is the same as the EventName property on the [SubscriptionRoot] attribute above. This is how the subscription server knows what published event is tied to which subscription. This value is case-sensitive.

Subscription Event Data Source

In the example above, the data sent with PublishSubscriptionEvent() is the same as the first input parameter, called eventData, on the subscription method, which is the same as the return type of the subscription method. By default, GraphQL will look for a parameter with the same data type as the method's own return type and use that as the event data source. It will automatically populate this field with the data from PublishSubscriptionEvent(); this argument is not exposed in the object graph.

You can explicitly flag a different parameter, or a parameter of a different data type to be the expected event source with the [SubscriptionSource] attribute.

Custom Event Data Source
public class SubscriptionController : GraphController
{
[SubscriptionRoot("onWidgetChanged", typeof(Widget), EventName = "WIDGET_CHANGED")]
public IGraphActionResult OnWidgetChanged(
[SubscriptionSource] WidgetInternal eventData,
string filter)
{
if(eventData.Name.StartsWith(filter))
return this.Ok(eventData.ToWidget());
return this.SkipSubscriptionEvent();
}
}

Here the subscription expects that an event is published using a WidgetInternal object that it will convert to a Widget and send to any subscribers. This can be useful if you wish to share internal objects between your mutations and subscriptions that you don't want publicly exposed.

Event Data Objects Must Match

The data object published with PublishSubscriptionEvent() must have the same type as the [SubscriptionSource] on the subscription field.

Summary

That's all there is for a basic subscription server setup:

  1. Add the package reference and update your startup code.
  2. Declare a new subscription on a controller using [Subscription] or [SubscriptionRoot].
  3. Publish an event (usually from a mutation).

Apollo Client Example

📌 A complete example of single instance subscription server including a react app that utilizes the Apollo Client is available in the demo projects section.

Subscription Action Results

You saw above the special action result SkipSubscriptionEvent() used to instruct graphql to skip the received event and not tell the client about it; this can be very useful in scenarios where the subscription supplies filter data to only receive some very specific data and not all items published via a specific event.

Here is a complete list of the various "subscription specific" action results:

  • SkipSubscriptionEvent() - Instructs the server to skip the raised event, the client will not receive any data.
  • OkAndComplete(data) - Works just like this.Ok() but ends the subscription after the event is completed. The client is informed that no additional data will be sent and that the server is closing the subscription permanently. This, however; does not close the underlying websocket connection.
Be Careful With Sensitive Data

All active subscriptions, from all connected users, have an opportunity to handle data published via PublishSubscriptionEvent().

If there are scenarios where an event payload should not be shared with a user, be sure to enforce that business logic in your subscription method and use SkipSubscriptionEvent() for a given payload.

Scaling Subscription Servers

Using web sockets has a natural limitation in that any single server instance has a maximum number of socket connections that it can realistically handle before being overloaded. Additionally, all cloud providers impose an artifical limit for many of their pricing tiers. Once that limit is reached no additional clients can connect, even if the server has additional capacity.

Ok no problem, just scale horizontally, right? Spin up additional server instances, add a load balancer and have the new requests open a web socket connection to these additional server instances...Not so fast!

With the examples above, events published by any mutation using PublishSubscriptionEvent() are routed internally, directly to the local subscription server meaning only those clients connected to the server instance where the event was raised will receive it. Clients connected to other server instances will never know the event occured. This represents a big problem for large scale websites, so what do we do?

This diagram shows a high level difference between the default, single server configuration and a custom scalable solution.

Custom Event Publishing

Instead of publishing events internally, within the server instance, we need to publish our events to some intermediate source such that any server can be notified of the change. There are a variety of technologies to handle this scenario; be it a common database or messaging technologies like RabbitMQ, Azure Service Bus etc.

Implement ISubscriptionEventPublisher

Whatever your technology of choice the first step is to create and register a custom publisher that implements ISubscriptionEventPublisher. How your publisher class functions will vary widely depending on your implementation.

ISubscriptionEventPublisher.cs
public interface ISubscriptionEventPublisher
{
Task PublishEventAsync(SubscriptionEvent eventData);
}

Register your publisher with the DI container BEFORE calling .AddGraphQL() and GraphQL ASP.NET will use your registered publisher instead of its default, internal publisher.

Register Your Event Publisher
services.AddSingleton<ISubscriptionEventPublisher, MyEventPublisher>();

services.AddGraphQL()
.AddSubscriptions();
caution

Publishing Subscription Events externally is not trivial. You'll have to deal with concerns like data serialization, package size etc. All of which are specific to your application and environment.

Consuming Published Events

At this point, we've successfully published our events to some external data source. Now we need to consume them. How that occurs is, again, implementation specific. Perhaps you run a background hosted service to watch for messages on an Azure Service Bus topic or perhaps you periodically pole a database table to look for new events. The ways in which data may be shared is endless.

Once you rematerialize a SubscriptionEvent you need to let GraphQL know that it occurred. this is done using the ISubscriptionEventRouter. In general, you won't need to implement your own router, just inject it into your listener then call RaisePublishedEvent and GraphQL will take it from there.

The router will take care of the details in figuring out which schema the event is destined for, which clients have active subscriptions etc. and forward it accordingly.

Example Custom Event Listener Service
 public class MyListenerService : BackgroundService
{
private readonly ISubscriptionEventRouter _router;
private bool _notStopped = true;

public MyListenerService(ISubscriptionEventRouter router)
{
_router = router;
}

protected override async Task ExecuteAsync(CancellationToken cancelToken)
{
while (_notStopped)
{
SubscriptionEvent eventData = /* fetch next event */;
_router.RaisePublishedEvent(eventData);
}
}
}
info

The above example is made using Background Services. A feature of the modern ASP.NET stack.

Azure Service Bus Example

📌 A functional example, including serialization and deserialization using the Azure Service Bus is available in the demo projects section.

note

The Azure Service Bus demo project represents a functional starting point and lacks a lot of the error handling and resilency needs of a production environment.

Partial Server Configuration

When using the .AddSubscriptions() extension method two seperate operations occur:

  1. The subscription server components are registered to the DI container, the graphql execution pipeline is modified to support clients registering subscriptions and a middleware component is appended to the ASP.NET pipeline to intercept and communicate with web socket connections.

  2. A middleware component is appended to the end of the graphql execution pipeline to formally publish any events staged via PublishSubscriptionEvent()

Some applications may wish to split these operations in different server instances for managing load or just splitting different types of traffic. For example, having one set of servers dedicated to query/mutation operations (stateless requests) and others dedicated to handling subscriptions and websockets (stateful requests).

The following granular configuration options may be useful:

  • .AddSubscriptionServer() :: Only configures the ASP.NET pipeline to intercept websockets and adds the subscription server components to the DI container. Mutations found on this server instance will NOT be successful in publishing events.

  • .AddSubscriptionPublishing() :: Only configures the graphql execution pipeline to publish events. Subscription creation and websocket support is NOT enabled.

Security & Query Authorization

Because subscriptions are long running, consume server resources and registered before any data is processed, the subscription server requires a query authorization method of PerRequest. This allows the subscription to be fully validated before its registered with the server and ensure that any queries can actually be processed when needed. This authorization method is set at startup and will apply to queries and mutations as well. It is because of this that it may be useful to split your mutations and subscriptions into different server instances for larger applications, as mentioned above, depending on your reliance on partial query resolution.

This is different than the default behavior when subscriptions are not enabled. Queries and mutations, by default, will follow a PerField method allowing for partial query resolutions.

caution

Adding Subscriptions to your server will force the use of PerRequest Authorization

Query Timeouts

By default, the library does not define a timeout for an executed query. The query will run as long as the underlying connection is open. This is true for subscriptions as well. Given that the websocket connection is never closed while the end user is connected, any query executed through the websocket will be allowed to run for an infinite amount of time which can have some unintended side effects and consume resources unecessarily.

Optionally, you can define a query timeout for a given schema, which the subscription server will obey:

Adding a Query Timeout
// startup code
services.AddGraphQL(o =>
{
// define a 2 minute timeout per query or subscription event executed.
o.ExecutionOptions.QueryTimeout = TimeSpan.FromMinutes(2);
})

Websocket Protocols

Out of the box, the library supports subscriptions over websockets using graphql-transport-ws, the modern protocol used by many client libraries. It also provides support for the legacy protocol graphql-ws (originally maintained by Apollo). A client requesting either protocol over a websocket will work with no additional configuration.

Supported Protocols

Creating Custom Protocols

If you wish to add support for your own websocket messaging protocol you need to implement two interfaces:

  • ISubscriptionClientProxy<TSchema> wraps a client connection, processes events and performs all necessary communications.
  • ISubscriptionClientProxyFactory which is used create client proxy instances for your protocol.
ISubscriptionClientProxyFactory.cs
public interface ISubscriptionClientProxyFactory
{
// Create client proxy instances that
// act as an intermediary to communicate server-side events
// to the client connection
Task<ISubscriptionClientProxy<TSchema>> CreateClient<TSchema>(IClientConnection connection)
where TSchema : class, ISchema;

// The unique name of your sub-protocol. A client connection requesting your
// protocol, by name, will be handed to this
// factory to create the appropriate client proxy.
string Protocol { get; }
}

Inject the factory into your DI container before calling AddGraphQL:

Register Your Protocol Client Factory
// startup
services.AddSingleton<ISubscriptionClientProxyFactory, MyClientProxyFactory>();

services.AddGraphQL()
.AddSubscriptions();
Create a Singleton Factory

ISubscriptionClientProxyFactory is expected to be a singleton; it is instantiated once when the server first comes online. The ISubscriptionClientProxy<TSchema>instances it creates should be unique per IClientConnection instance.

The server will listen for subscription registrations from your client proxy and send back published events when new data is available. It is up to your proxy to interprete these events, generate an appropriate result (including executing queries against the runtime), serialize the data and send it to the connected client on the other end.

Custom Protocols are Difficult

The complete details of implementing a custom graphql client proxy are beyond the scope of this documentation. Take a peek at the subscription library source code for some clues on how to get started.

Other Communication Options

While websockets is the primary medium for persistant connections its not the only option. Internally, the library supplies an IClientConnection interface which encapsulates a raw websocket received from ASP.NET. This interface is internally implemented as a WebSocketClientConnection which is responsible for reading and writing raw bytes to the socket. Its not a stretch of the imagination to implement your own custom client connection, invent a way to capture said connections and basically rewrite the entire communications layer of the subscriptions module.

Please do a deep dive into the subscription code base to learn about all the intricacies of building your own communications layer and how you might go about registering it with the runtime. If you do try to tackle this very large effort don't hesitate to reach out. We're happy to partner with you and meet you half way on a solution if it makes sense for the rest of the community.

Performance Considerations

In a production app, its very possible that you may have lots of subscription events fired and communicated to a lot of connected clients in short succession. Its important to understand how the server will process those events and plan accordingly.

When the router receives an event it looks to see which clients are subscribed to that event and queues up a work item for each one. If there are 5 clients registered to the single event, then 5 work items are queued. Internally, this work is processed asyncronously to a server-configured maximum. Once this maximum is reached, new work will only begin as other work finishes up.

Each work item is, for the most part, a single query execution. Though, if a client registers to a subscription multiple times each registration is executed as its own query. With lots of events being delivered on a server saturated with clients, each potentially having multiple subscriptions, along with regular queries and mutations executing...limits must be imposed otherwise CPU utilization could unreasonably spike...and it may spike regardless in some use cases.

By default, the max number of work items the router will deliver simultaniously is 500. This is a global, server-wide pool, shared amongst all registered schemas. You can control this value by changing it prior to calling .AddGraphQL(). This value defaults to a low number on purpose, use it as a starting point to dial up the max concurrency to a level you feel comfortable with in terms of performance and cost. The only limit here is server resources and other environment limitations outside the control of graphql.

Set A Receiver Throttle During Startup
// Adjust the max concurrent communications value
// BEFORE calling .AddGraphQL()
GraphQLSubscriptionServerSettings.MaxConcurrentReceiverCount = 500;

services.AddGraphQL()
.AddSubscriptions();
note

The max receiver count can easily be set in the 1000s or higher. There is no magic bullet in choosing an appropriate value. It all depends on the number of events you are expecting, the number of subscribers, the workload of each event and the hardware available to your application.

Event Multiplication

Think carefully about your production scenarios when you introduce subscriptions into your application. As mentioned above, for each event raised, each subscription monitoring that event must execute a standard graphql query, with the supplied event data, to generate a result and send it to its connected client.

If, for instance, you have 200 clients connected to a single server, each with a subscription against the same field, thats 200 individual queries that must be executed to process a single event completely. Even if you call SkipSubscriptionEvent to drop the event and send no send data to a client, the query must still be executed to determine if the subscriber is not interested in the data. If you execute any database operations in your [Subcription] method, its going to be run 200 times. Suppose your server receives 5 mutations in rapid succession, all of which raise the event, thats a spike of 1,000 queries, instantaneously, that the server must process.

Balancing the load can be difficult. Luckily there are some throttling levers you can adjust.

Know Your User Traffic

Raising subscription events can exponentially increase the load on each of your servers if not planned correctly. Think carefully when you deploy subscriptions to your application.

Dispatch Queue Monitoring

Internally, whenever a subscription server instance receives an event, the router checks to see which of the currently connected clients need to process that event. The client/event combination is then put into a dispatch queue that is continually processed via an internal, background service according to the throttling limits you've specified. If events are received faster than they can be dispatched they are queued, in memory, until resources are freed up.

There is a built-in monitoring of this queue that will automatically record a log event when a given threshold is reached.

Default Event Alert Threshold

This log event is recorded at a Critical level when the queue reaches 10,000 events. This alert is then re-recorded once every 5 minutes if the +queue remains above 10,000 events.

Custom Event Alert Thresholds

In some high volume scenarios, its not uncommon or unexpected for the dispatch queue to spike beyond the default monitoring levels from time to time. If you need more granular control of the notifications, register an instance of ISubscriptionClientDispatchQueueAlertSettings to your DI container before adding GraphQL and your settings will be used instead.

In the example below, if the queue reaches 1,000 events, the debug level alert will be recorded. If 30 seconds pass and the queue is still above 1000 events, the debug level alert will be recorded again. However, if the queue crosses 10,000 events then the warning level alert will be recorded (the debug alert is then ignored). If the queue reaches 100k events then a critical level alert will be recorded every 15 seconds until it drops below 100k.

Lower level thresholds (as determined by number of queued events) will not be triggered if a higher level is on active cool down.

Custom Dispatch Queue Monitoring
// startup configuration

var thresholds = new SubscriptionClientDispatchQueueAlertSettings();
thresholds.AddThreshold(
LogLevel.Debug,
1000,
TimeSpan.FromSeconds(30));

thresholds.AddThreshold(
LogLevel.Warning,
10000,
TimeSpan.FromSeconds(120));

thresholds.AddThreshold(
LogLevel.Critical,
100000,
TimeSpan.FromSeconds(15));

// register as a singleton
services.AddSingleton<ISubscriptionClientDispatchQueueAlertSettings>(alerts);

// normal graphql configuration
services.AddGraphQL()
.AddSubscriptions();
tip

Consider using the built in SubscriptionClientDispatchQueueAlertSettings object for a standard implementation of the required interface.

General Tips

DO exit a subscription with this.SkipSubscriptionEvent() as soon as possible.

DO secure your business objects. Subscriptions will receive every event raised against the field they are subscribed to, regardless of the data.

🧨 DON'T rely on database queries or other IO to determine if an event should be skipped.

+ + + + \ No newline at end of file diff --git a/docs/advanced/subscriptions.md b/docs/advanced/subscriptions.md deleted file mode 100644 index 9f099f9..0000000 --- a/docs/advanced/subscriptions.md +++ /dev/null @@ -1,457 +0,0 @@ ---- -id: subscriptions -title: Subscriptions -sidebar_label: Subscriptions -sidebar_position: 0 ---- - -## Initial Setup - -Successfully handling subscriptions in your GraphQL server can be straight forward for single server environments or very complicated for multi-server and scalable solutions. First we'll look at adding subscriptions for a single server. - -### Install the Subscriptions Package - -The first step to using subscriptions is to install the subscription server package. - -```powershell title="Install The Library" -# Using the dotnet CLI -> dotnet add package GraphQL.AspNet.Subscriptions - -# using Package Manager Console -> Install-Package GraphQL.AspNet.Subscriptions -``` - -This adds the necessary components to create a subscription server for a given schema such as communicating with web sockets, parsing subscription queries and responding to events. - -### Configure the Server Instance - -You must configure web socket support for your Asp.Net server instance separately. The ways in which you perform this configuration will vary widely depending on your CORS requirements, keep-alive support and other needs. - -After web sockets are added to your server, add subscription support to the graphql registration. - -```csharp title="Add Subscription Support at Startup" -// configuring services at startup -// --------------- -services.AddWebSockets(/*...*/); - -services.AddGraphQL() -// highlight-next-line - .AddSubscriptions(); - -// building the application pipeline -// --------------- -app.UseWebSockets(); -// highlight-next-line -app.UseGraphQL(); -``` - -:::tip - Don't forget to call `.UseWebsockets()` before calling `.UseGraphQL()`. -::: - -### Create a Subscription - -Declaring a subscription is the same as declaring a query or mutation on a controller but with `[Subscription]` and `[SubscriptionRoot]` attributes. Feel free to mix subscriptions with your queries and mutations. They do not need to be kept seperate. - -```csharp title="SubscriptionController.cs" -public class SubscriptionController : GraphController -{ - // other code not shown for brevity - - // highlight-next-line - [SubscriptionRoot("onWidgetChanged", typeof(Widget), EventName = "WIDGET_CHANGED")] - public IGraphActionResult OnWidgetChanged(Widget eventData, string filter) - { - if(eventData.Name.StartsWith(filter)) - { - // send the data down to the listening client - return this.Ok(eventData); - } - else - { - // use SkipSubscriptionEvent() to disregard the data - // and not communicate anything to the listening client - return this.SkipSubscriptionEvent(); - } - } -} -``` -Here we've declared a new subscription, one that takes in a `filter` parameter to restrict the data that any subscribers receive. - -A query to invoke this subscription may look like this: - -```graphql title="Sample Subscription Query" -subscription { - onWidgetChanged(filter: "Big"){ - id - name - description - } -} -``` - -Any updated widgets that start with the phrase "Big" will then be sent to the requestor as they are changed on the server. Any other changed widgets will be skipped/dropped and no data will be sent to the client. - -### Publish a Subscription Event - -In order for the subscription server to send data to any subscribers it has to be notified when its time to do so. It does this via named Subscription Events. These are internal, schema-unique keys (strings) that identify when something happened, usually via a mutation. Once the mutation publishes an event, the subscription server will execute the appropriate action method for any subscribers, using the supplied data, and deliver the results to the client. - -```csharp title="MutationController.cs" -public class MutationController : GraphController -{ - // other code not shown for brevity - - [MutationRoot("updateWidget", typeof(Widget))] - public async Task OnWidgetChanged(int id, string name){ - var widget = _service.RetrieveWidget(id); - widget.Name = name; - - await _service.UpdateWidget(widget); - - // publish a new event to let any subscribers know - // something changed - // highlight-next-line - this.PublishSubscriptionEvent("WIDGET_CHANGED", widget); - return this.Ok(widget); - } -} -``` - -:::info Event Names Must Match - Notice that the event name used in `PublishSubscriptionEvent()` is the same as the `EventName` property on the `[SubscriptionRoot]` attribute above. This is how the subscription server knows what published event is tied to which subscription. This value is case-sensitive. -::: - -### Subscription Event Data Source - -In the example above, the data sent with `PublishSubscriptionEvent()` is the same as the first input parameter, called `eventData`, on the subscription method, which is the same as the return type of the subscription method. By default, GraphQL will look for a parameter with the same data type as the method's own return type and use that as the event data source. It will automatically populate this field with the data from `PublishSubscriptionEvent()`; this argument is not exposed in the object graph. - -You can explicitly flag a different parameter, or a parameter of a different data type to be the expected event source with the `[SubscriptionSource]` attribute. - -```csharp title="Custom Event Data Source" -public class SubscriptionController : GraphController -{ - [SubscriptionRoot("onWidgetChanged", typeof(Widget), EventName = "WIDGET_CHANGED")] - public IGraphActionResult OnWidgetChanged( - // highlight-next-line - [SubscriptionSource] WidgetInternal eventData, - string filter) - { - if(eventData.Name.StartsWith(filter)) - return this.Ok(eventData.ToWidget()); - return this.SkipSubscriptionEvent(); - } -} -``` - -Here the subscription expects that an event is published using a `WidgetInternal` object that it will convert to a `Widget` and send to any subscribers. This can be useful if you wish to share internal objects between your mutations and subscriptions that you don't want publicly exposed. - -:::info Event Data Objects Must Match - The data object published with `PublishSubscriptionEvent()` must have the same type as the `[SubscriptionSource]` on the subscription field. -::: - -### Summary - -That's all there is for a basic subscription server setup: - -1. Add the package reference and update your startup code. -2. Declare a new subscription on a controller using `[Subscription]` or `[SubscriptionRoot]`. -3. Publish an event (usually from a mutation). - -### Apollo Client Example - -📌 A complete example of single instance subscription server including a react app that utilizes the Apollo Client is available in the [demo projects](../reference/demo-projects) section. - -## Subscription Action Results - -You saw above the special action result `SkipSubscriptionEvent()` used to instruct graphql to skip the received event and not tell the client about it; this can be very useful in scenarios where the subscription supplies filter data to only receive some very specific data and not all items published via a specific event. - -Here is a complete list of the various "subscription specific" action results: - -* `SkipSubscriptionEvent()` - Instructs the server to skip the raised event, the client will not receive any data. -* `OkAndComplete(data)` - Works just like `this.Ok()` but ends the subscription after the event is completed. The client is informed that no additional data will be sent and that the server is closing the subscription permanently. This, however; does not close the underlying websocket connection. - -:::danger Be Careful With Sensitive Data -All active subscriptions, from all connected users, have an opportunity to handle data published via `PublishSubscriptionEvent()`. - -If there are scenarios where an event payload should not be shared with a user, be sure to enforce that business logic in your subscription method and use `SkipSubscriptionEvent()` for a given payload. -::: - -## Scaling Subscription Servers - -Using web sockets has a natural limitation in that any single server instance has a maximum number of socket connections that it can realistically handle before being overloaded. Additionally, all cloud providers impose an artifical limit for many of their pricing tiers. Once that limit is reached no additional clients can connect, even if the server has additional capacity. - -Ok no problem, just scale horizontally, right? Spin up additional server instances, add a load balancer and have the new requests open a web socket connection to these additional server instances...Not so fast! - -With the examples above, events published by any mutation using `PublishSubscriptionEvent()` are routed internally, directly to the local subscription server meaning only those clients connected to the server instance where the event was raised will receive it. Clients connected to other server instances will never know the event occured. This represents a big problem for large scale websites, so what do we do? - -> [This diagram](../assets/2023-01-subscription-server.pdf) shows a high level difference between the default, single server configuration and a custom scalable solution. - -### Custom Event Publishing - -Instead of publishing events internally, within the server instance, we need to publish our events to some intermediate source such that any server can be notified of the change. There are a variety of technologies to handle this scenario; be it a common database or messaging technologies like RabbitMQ, Azure Service Bus etc. - -#### Implement `ISubscriptionEventPublisher` - -Whatever your technology of choice the first step is to create and register a custom publisher that implements `ISubscriptionEventPublisher`. How your publisher class functions will vary widely depending on your implementation. - -```csharp title="ISubscriptionEventPublisher.cs" -public interface ISubscriptionEventPublisher -{ - Task PublishEventAsync(SubscriptionEvent eventData); -} -``` - -Register your publisher with the DI container BEFORE calling `.AddGraphQL()` and GraphQL ASP.NET will use your registered publisher instead of its default, internal publisher. - -```csharp title="Register Your Event Publisher" -// highlight-next-line -services.AddSingleton(); - -services.AddGraphQL() - .AddSubscriptions(); -``` - - -:::caution -Publishing Subscription Events externally is not trivial. You'll have to deal with concerns like data serialization, package size etc. All of which are specific to your application and environment. -::: - - -### Consuming Published Events - -At this point, we've successfully published our events to some external data source. Now we need to consume them. How that occurs is, again, implementation specific. Perhaps you run a background hosted service to watch for messages on an Azure Service Bus topic or perhaps you periodically pole a database table to look for new events. The ways in which data may be shared is endless. - -Once you rematerialize a `SubscriptionEvent` you need to let GraphQL know that it occurred. this is done using the `ISubscriptionEventRouter`. In general, you won't need to implement your own router, just inject it into your listener then call `RaisePublishedEvent` and GraphQL will take it from there. - -The router will take care of the details in figuring out which schema the event is destined for, which clients have active subscriptions etc. and forward it accordingly. - -```csharp title="Example Custom Event Listener Service" - public class MyListenerService : BackgroundService - { - private readonly ISubscriptionEventRouter _router; - private bool _notStopped = true; - - public MyListenerService(ISubscriptionEventRouter router) - { - _router = router; - } - - protected override async Task ExecuteAsync(CancellationToken cancelToken) - { - while (_notStopped) - { - SubscriptionEvent eventData = /* fetch next event */; - // highlight-next-line - _router.RaisePublishedEvent(eventData); - } - } - } -``` - -:::info -The above example is made using [`Background Services`](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-7.0&tabs=visual-studio). A feature of the modern ASP.NET stack. -::: - - - -### Azure Service Bus Example - -📌 A functional example, including serialization and deserialization using the Azure Service Bus is available in the [demo projects](../reference/demo-projects) section. - -:::note - The Azure Service Bus demo project represents a functional starting point and lacks a lot of the error handling and resilency needs of a production environment. -::: - -## Partial Server Configuration - -When using the `.AddSubscriptions()` extension method two seperate operations occur: - -1. The subscription server components are registered to the DI container, the graphql execution pipeline is modified to support clients registering subscriptions and a middleware component is appended to the ASP.NET pipeline to intercept and communicate with web socket connections. - -2. A middleware component is appended to the end of the graphql execution pipeline to formally publish any events staged via `PublishSubscriptionEvent()` - -Some applications may wish to split these operations in different server instances for managing load or just splitting different types of traffic. For example, having one set of servers dedicated to query/mutation operations (stateless requests) and others dedicated to handling subscriptions and websockets (stateful requests). - -The following granular configuration options may be useful: - -- `.AddSubscriptionServer()` :: Only configures the ASP.NET pipeline to intercept websockets and adds the subscription server components to the DI container. Mutations found on this server instance will **NOT** be successful in publishing events. - -- `.AddSubscriptionPublishing()` :: Only configures the graphql execution pipeline to publish events. Subscription creation and websocket support is **NOT** enabled. - -## Security & Query Authorization - -Because subscriptions are long running, consume server resources and registered before any data is processed, the subscription server requires a [query authorization method](../reference/schema-configuration#authorization-options) of `PerRequest`. This allows the subscription to be fully validated before its registered with the server and ensure that any queries can actually be processed when needed. This authorization method is set at startup and will apply to queries and mutations as well. It is because of this that it may be useful to split your mutations and subscriptions into different server instances for larger applications, as mentioned above, depending on your reliance on partial query resolution. - -This is different than the default behavior when subscriptions are not enabled. Queries and mutations, by default, will follow a `PerField` method allowing for partial query resolutions. - -:::caution - Adding Subscriptions to your server will force the use of `PerRequest` Authorization -::: - -## Query Timeouts - -By default, the library does not define a timeout for an executed query. The query will run as long as the underlying connection is open. This is true for subscriptions as well. Given that the websocket connection is never closed while the end user is connected, any query executed through the websocket will be allowed to run for an infinite amount of time which can have some unintended side effects and consume resources unecessarily. - -Optionally, you can define a query timeout for a given schema, which the subscription server will obey: - -```csharp title="Adding a Query Timeout" -// startup code -services.AddGraphQL(o => -{ - // define a 2 minute timeout per query or subscription event executed. - o.ExecutionOptions.QueryTimeout = TimeSpan.FromMinutes(2); -}) -``` - -## Websocket Protocols - -Out of the box, the library supports subscriptions over websockets using `graphql-transport-ws`, the modern protocol used by many client libraries. It also provides support for the legacy protocol `graphql-ws` (originally maintained by Apollo). A client requesting either protocol over a websocket will work with no additional configuration. - -### Supported Protocols - -- [graphql-transport-ws](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) -- [graphql-ws](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md) (_legacy_) - -### Creating Custom Protocols - -If you wish to add support for your own websocket messaging protocol you need to implement two interfaces: - -- `ISubscriptionClientProxy` wraps a client connection, processes events and performs all necessary communications. -- `ISubscriptionClientProxyFactory` which is used create client proxy instances for your protocol. - - -```csharp title="ISubscriptionClientProxyFactory.cs" -public interface ISubscriptionClientProxyFactory -{ - // Create client proxy instances that - // act as an intermediary to communicate server-side events - // to the client connection - Task> CreateClient(IClientConnection connection) - where TSchema : class, ISchema; - - // The unique name of your sub-protocol. A client connection requesting your - // protocol, by name, will be handed to this - // factory to create the appropriate client proxy. - string Protocol { get; } -} -``` - -Inject the factory into your DI container before calling AddGraphQL: - -```csharp title="Register Your Protocol Client Factory" -// startup -services.AddSingleton(); - -services.AddGraphQL() - .AddSubscriptions(); -``` - -:::caution Create a Singleton Factory - `ISubscriptionClientProxyFactory` is expected to be a singleton; it is instantiated once when the server first comes online. The `ISubscriptionClientProxy`instances it creates should be unique per `IClientConnection` instance. -::: - -The server will listen for subscription registrations from your client proxy and send back published events when new data is available. It is up to your proxy to interprete these events, generate an appropriate result (including executing queries against the runtime), serialize the data and send it to the connected client on the other end. - -:::info Custom Protocols are Difficult -The complete details of implementing a custom graphql client proxy are beyond the scope of this documentation. Take a peek at the subscription library source code for some clues on how to get started. -::: - -## Other Communication Options - -While websockets is the primary medium for persistant connections its not the only option. Internally, the library supplies an `IClientConnection` interface which encapsulates a raw websocket received from ASP.NET. This interface is internally implemented as a `WebSocketClientConnection` which is responsible for reading and writing raw bytes to the socket. Its not a stretch of the imagination to implement your own custom client connection, invent a way to capture said connections and basically rewrite the entire communications layer of the subscriptions module. - -Please do a deep dive into the subscription code base to learn about all the intricacies of building your own communications layer and how you might go about registering it with the runtime. If you do try to tackle this very large effort don't hesitate to reach out. We're happy to partner with you and meet you half way on a solution if it makes sense for the rest of the community. - -## Performance Considerations - -In a production app, its very possible that you may have lots of subscription events fired and communicated to a lot of connected clients in short succession. Its important to understand how the server will process those events and plan accordingly. - -When the router receives an event it looks to see which clients are subscribed to that event and queues up a work item for each one. If there are 5 clients registered to the single event, then 5 work items are queued. Internally, this work is processed asyncronously to a server-configured maximum. Once this maximum is reached, new work will only begin as other work finishes up. - -Each work item is, for the most part, a single query execution. Though, if a client registers to a subscription multiple times each registration is executed as its own query. With lots of events being delivered on a server saturated with clients, each potentially having multiple subscriptions, along with regular queries and mutations executing...limits must be imposed otherwise CPU utilization could unreasonably spike...and it may spike regardless in some use cases. - -By default, the max number of work items the router will deliver simultaniously is `500`. This is a global, server-wide pool, shared amongst all registered schemas. You can control this value by changing it prior to calling `.AddGraphQL()`. This value defaults to a low number on purpose, use it as a starting point to dial up the max concurrency to a level you feel comfortable with in terms of performance and cost. The only limit here is server resources and other environment limitations outside the control of graphql. - -```csharp title="Set A Receiver Throttle During Startup" -// Adjust the max concurrent communications value -// BEFORE calling .AddGraphQL() -// highlight-next-line -GraphQLSubscriptionServerSettings.MaxConcurrentReceiverCount = 500; - -services.AddGraphQL() - .AddSubscriptions(); -``` - -:::note -The max receiver count can easily be set in the 1000s or higher. There is no magic bullet in choosing an appropriate value. It all depends on the number of events you are expecting, the number of subscribers, the workload of each event and the hardware available to your application. -::: - -### Event Multiplication - -Think carefully about your production scenarios when you introduce subscriptions into your application. As mentioned above, for each event raised, each subscription monitoring that event must execute a standard graphql query, with the supplied event data, to generate a result and send it to its connected client. - -If, for instance, you have `200 clients` connected to a single server, each with a subscription against the same field, thats `200 individual queries` that must be executed to process a _single event_ completely. Even if you call `SkipSubscriptionEvent` to drop the event and send no send data to a client, the query must still be executed to determine if the subscriber is not interested in the data. If you execute any database operations in your `[Subcription]` method, its going to be run 200 times. Suppose your server receives 5 mutations in rapid succession, all of which raise the event, thats a spike of `1,000 queries`, instantaneously, that the server must process. - -Balancing the load can be difficult. Luckily there are some [throttling levers](/docs/reference/global-configuration#subscriptions) you can adjust. - -:::danger Know Your User Traffic - Raising subscription events can exponentially increase the load on each of your servers if not planned correctly. Think carefully when you deploy subscriptions to your application. -::: - -### Dispatch Queue Monitoring - -Internally, whenever a subscription server instance receives an event, the router checks to see which of the currently connected clients need to process that event. The client/event combination is then put into a dispatch queue that is continually processed via an internal, background service according to the throttling limits you've specified. If events are received faster than they can be dispatched they are queued, in memory, until resources are freed up. - -There is a built-in monitoring of this queue that will automatically [record a log event](../logging/subscription-events.md#subscription-event-dispatch-queue-alert) when a given threshold is reached. - -#### Default Event Alert Threshold -This log event is recorded at a `Critical` level when the queue reaches `10,000 events`. This alert is then re-recorded once every 5 minutes if the -queue remains above 10,000 events. - -#### Custom Event Alert Thresholds - -In some high volume scenarios, its not uncommon or unexpected for the dispatch queue to spike beyond the default monitoring levels from time to time. If you need more granular control of the notifications, register an instance of `ISubscriptionClientDispatchQueueAlertSettings` to your DI container before adding GraphQL and your settings will be used instead. - -In the example below, if the queue reaches 1,000 events, the debug level alert will be recorded. If 30 seconds pass and the queue is still above 1000 events, the debug level alert will be recorded again. However, if the queue crosses 10,000 events then the warning level alert will be recorded (the debug alert is then ignored). If the queue reaches 100k events then a critical level alert will be recorded every 15 seconds until it drops below 100k. - - Lower level thresholds (as determined by number of queued events) will not be triggered if a higher level is on active cool down. - -```csharp title="Custom Dispatch Queue Monitoring" -// startup configuration - -var thresholds = new SubscriptionClientDispatchQueueAlertSettings(); -thresholds.AddThreshold( - LogLevel.Debug, - 1000, - TimeSpan.FromSeconds(30)); - -thresholds.AddThreshold( - LogLevel.Warning, - 10000, - TimeSpan.FromSeconds(120)); - -thresholds.AddThreshold( - LogLevel.Critical, - 100000, - TimeSpan.FromSeconds(15)); - -// register as a singleton -// highlight-next-line -services.AddSingleton(alerts); - -// normal graphql configuration -services.AddGraphQL() - .AddSubscriptions(); -``` - -:::tip - Consider using the built in `SubscriptionClientDispatchQueueAlertSettings` object for a standard implementation of the required interface. -::: - -## General Tips - -✅ **DO** exit a subscription with `this.SkipSubscriptionEvent()` as soon as possible.
- -✅ **DO** secure your business objects. Subscriptions will receive every event raised against the field they are subscribed to, regardless of the data.
- -🧨 **DON'T** rely on database queries or other IO to determine if an event should be skipped. diff --git a/docs/advanced/type-expressions.html b/docs/advanced/type-expressions.html new file mode 100644 index 0000000..6f9cc6f --- /dev/null +++ b/docs/advanced/type-expressions.html @@ -0,0 +1,24 @@ + + + + + +Type Expressions | GraphQL ASP.NET + + + + +
+

Type Expressions

The GraphQL specification states that when a field resolves a value that doesn't conform to the expected type expression of the field that the value is rejected, converted to null and an error added to the response.

When the library builds a schema it makes as few assumptions as possible about the data returned from your fields to result in as few errors as possible.

These assumptions are:

  • Fields that return reference types can be null
  • Fields that return primatives or value types (including structs) cannot be null
  • Fields that return Nullable primatives or value types (e.g. int?) can be be null.
  • When a field returns an object that implements IEnumerable<TType> it will be presented to GraphQL as a "list of TType".

Basically, if your method is able to return a value...then its valid as far as GraphQL is concerned.

Lets look at an example:

BakeryController.cs
[GraphRoute("bakery")]
public class BakeryController : GraphController
{
[Query("donut")]
public Donut RetrieveDonut(int id)
{/*...*/}
}
Sample Query
query {
bakery {
donut(id: 15){
name
flavor
}
}
}

Assuming Donut was a class (a reference type), this action method could return a donut object or null. But should the donut field, from a GraphQL perspective, allow a null return value? The code certainly does and the rules above say fields that return a reference type can be null...but that's not what's important. Its ultimately your decision to decide if a "null donut" is allowed, not the C# compiler and not the assumptions made by the library.

On one hand, if a null value is returned, regardless of it being valid, the outcome of the field is the same. When we return a null no child fields are processed. On the other hand, if null is not allowed we need to tell someone, let them know its nulled out not because it simply is null but because a schema violation occurred.

Field Type Expressions

You can add more specificity to your fields by using the TypeExpression property of the various field declaration attributes.

Example Custom Type Expressions
// Declare that a donut MUST be returned (null is invalid)
// ----
// Final Schema Syntax: Donut!
[Query("donut", TypeExpression = "Type!")]
public Donut RetrieveDonut(string id)
{/*...*/}


// Declare that a list must be returned but the elements of the list
// could be null:
// valid: [donut1, null, donut2, donut3]
// valid: []
// invalid: null
// ----
// Final Schema Syntax: [Donut]!
[Query("donut", TypeExpression = "[Type]!")]
public IEnumerable<Donut> RetrieveDonut(string id)
{/*...*/}


// Declare that a list must be returned AND the elements of the list
// must not be null:
// valid: [donut1, donut2, donut3]
// valid: []
// invalid: [donut1, null, donut2]
// invalid: null
// ----
// Final Schema Syntax: [Donut!]!
[Query("donut", TypeExpression = "[Type!]!")]
public IEnumerable<Donut> RetrieveDonut(string id)
{/*...*/}
Type is a place holder

The type name used in the examples (e.g. Type) is arbitrary and can be any valid string. The correct type name for the target field will be used in its place at runtime.

Input Argument Type Expressions

Similar to fields, you can use the TypeExpression property on [FromGraphQL] to add more specificity to your input arguments.

Type Expression on an Argument
// Force the argument "id" to supply a string (it cannot be supplied as null)
// -----------------
// Final Type Expression of the 'id' arg: String!
[Query]
public Donut RetrieveDonut([FromGraphQL(TypeExpression = "Type!")] string id)
{/*...*/}

Runtime Type Validation

Note that the library will accept your type string even if it would be impossible, from a C# perspective, to return data that would match.

Data and Type Expression Mismatch
// QUERY EXECUTION ERROR
// GraphQL will attempt to process a single Donut as a list and will fail
[Query("donut", TypeExpression ="[Type]")]
public Donut RetrieveDonut(string id)
{/*...*/}

When executing a query and resolving a field, should one of your action methods (or even your object properties) not return data conforming to the type expression that's defined for it, GraphQL will reject the data. The value is set to null and an error is registered in the response for the field in question. The runtime will not attempt to resolve any referenced child fields for a rejected value.

If the rejected field does not allow nulls, the error is propagated up to its parent, which is then also set to null. If that parent field can't return a null value the error continues up until it reaches a field that can be null or the entire field collection is nulled out. [Spec § 6.4.4]

danger

When declared, the runtime will use your TypeExpression as law for any field declarations; skipping its internal checks. You can setup a scenario where by you could return data that the runtime could never validate as being correct and GraphQL will happily process it and return an error every time.

"With great power comes great responsibility" -Uncle Ben

+ + + + \ No newline at end of file diff --git a/docs/advanced/type-expressions.md b/docs/advanced/type-expressions.md deleted file mode 100644 index acd7d69..0000000 --- a/docs/advanced/type-expressions.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -id: type-expressions -title: Type Expressions -sidebar_label: Type Expressions -sidebar_position: 1 ---- - -The GraphQL specification states that when a field resolves a value that doesn't conform to the expected type expression of the field that the value is rejected, converted to null and an error added to the response. - -When the library builds a schema it makes as few assumptions as possible about the data returned from your fields to result in as few errors as possible. - -These assumptions are: - -- Fields that return reference types **can be** null -- Fields that return primatives or value types (including structs) **cannot be** null -- Fields that return Nullable primatives or value types (e.g. `int?`) **can be** be null. -- When a field returns an object that implements `IEnumerable` it will be presented to GraphQL as a "list of `TType`". - -Basically, if your method is able to return a value...then its valid as far as GraphQL is concerned. - -Lets look at an example: - -```csharp title="BakeryController.cs" -[GraphRoute("bakery")] -public class BakeryController : GraphController -{ - [Query("donut")] - public Donut RetrieveDonut(int id) - {/*...*/} -} -``` -```graphql title="Sample Query" -query { - bakery { - donut(id: 15){ - name - flavor - } - } -} -``` - -Assuming `Donut` was a class (a reference type), this action method could return a donut object or `null`. But should the donut field, from a GraphQL perspective, allow a null return value? The code certainly does and the rules above say fields that return a reference type can be null...but that's not what's important. Its ultimately your decision to decide if a "null donut" is allowed, not the C# compiler and not the assumptions made by the library. - -On one hand, if a null value is returned, regardless of it being valid, the _outcome_ of the field is the same. When we return a null no child fields are processed. On the other hand, if null is not allowed we need to tell someone, let them know its nulled out not because it simply _is_ null but because a schema violation occurred. - -## Field Type Expressions - -You can add more specificity to your fields by using the `TypeExpression` property of the various field declaration attributes. - -```csharp title="Example Custom Type Expressions" -// Declare that a donut MUST be returned (null is invalid) -// ---- -// Final Schema Syntax: Donut! -// highlight-next-line -[Query("donut", TypeExpression = "Type!")] -public Donut RetrieveDonut(string id) -{/*...*/} - - -// Declare that a list must be returned but the elements of the list -// could be null: -// valid: [donut1, null, donut2, donut3] -// valid: [] -// invalid: null -// ---- -// Final Schema Syntax: [Donut]! -// highlight-next-line -[Query("donut", TypeExpression = "[Type]!")] -public IEnumerable RetrieveDonut(string id) -{/*...*/} - - -// Declare that a list must be returned AND the elements of the list -// must not be null: -// valid: [donut1, donut2, donut3] -// valid: [] -// invalid: [donut1, null, donut2] -// invalid: null -// ---- -// Final Schema Syntax: [Donut!]! -// highlight-next-line -[Query("donut", TypeExpression = "[Type!]!")] -public IEnumerable RetrieveDonut(string id) -{/*...*/} -``` - -:::info `Type` is a place holder -The type name used in the examples (e.g. `Type`) is arbitrary and can be any valid string. The correct type name for the target field will be used in its place at runtime. -::: - -## Input Argument Type Expressions - -Similar to fields, you can use the `TypeExpression` property on `[FromGraphQL]` to add more specificity to your input arguments. - -```csharp title="Type Expression on an Argument" -// Force the argument "id" to supply a string (it cannot be supplied as null) -// ----------------- -// Final Type Expression of the 'id' arg: String! -[Query] -// highlight-next-line -public Donut RetrieveDonut([FromGraphQL(TypeExpression = "Type!")] string id) -{/*...*/} -``` - - -## Runtime Type Validation - -Note that the library will accept your type string even if it would be impossible, from a C# perspective, to return data that would match. - -```csharp title="Data and Type Expression Mismatch" -// QUERY EXECUTION ERROR -// GraphQL will attempt to process a single Donut as a list and will fail -// highlight-next-line -[Query("donut", TypeExpression ="[Type]")] -public Donut RetrieveDonut(string id) -{/*...*/} -``` - -When executing a query and resolving a field, should one of your action methods (or even your object properties) not return data conforming to the type expression that's defined for it, GraphQL will reject the data. The value is set to null and an error is registered in the response for the field in question. The runtime will not attempt to resolve any referenced child fields for a rejected value. - -If the rejected field does not allow nulls, the error is propagated up to its parent, which is then also set to null. If that parent field can't return a null value the error continues up until it reaches a field that can be null or the entire field collection is nulled out. \[Spec § [6.4.4](https://graphql.github.io/graphql-spec/October2021/#sec-Errors-and-Non-Nullability)\] - -:::danger -When declared, the runtime will use your `TypeExpression` as law for any field declarations; skipping its internal checks. You can setup a scenario where by you could return data that the runtime could never validate as being correct and GraphQL will happily process it and return an error every time. -::: - -> "With great power comes great responsibility" -Uncle Ben - diff --git a/docs/assets/2021-01-graphql-aspnet-execution-diagrams.pdf b/docs/assets/2021-01-graphql-aspnet-execution-diagrams.pdf deleted file mode 100644 index b5e6747..0000000 Binary files a/docs/assets/2021-01-graphql-aspnet-execution-diagrams.pdf and /dev/null differ diff --git a/docs/assets/2021-01-graphql-aspnet-structural-diagrams.pdf b/docs/assets/2021-01-graphql-aspnet-structural-diagrams.pdf deleted file mode 100644 index d22d43b..0000000 Binary files a/docs/assets/2021-01-graphql-aspnet-structural-diagrams.pdf and /dev/null differ diff --git a/docs/assets/2022-05-graphql-aspnet-type-system-interface-diagrams.pdf b/docs/assets/2022-05-graphql-aspnet-type-system-interface-diagrams.pdf deleted file mode 100644 index 6d665f7..0000000 Binary files a/docs/assets/2022-05-graphql-aspnet-type-system-interface-diagrams.pdf and /dev/null differ diff --git a/docs/assets/2022-10-subscription-server.pdf b/docs/assets/2022-10-subscription-server.pdf deleted file mode 100644 index 11f9256..0000000 Binary files a/docs/assets/2022-10-subscription-server.pdf and /dev/null differ diff --git a/docs/assets/authorization-flow.png b/docs/assets/authorization-flow.png deleted file mode 100644 index 1fcc85e..0000000 Binary files a/docs/assets/authorization-flow.png and /dev/null differ diff --git a/docs/assets/benchmarks-v040beta.png b/docs/assets/benchmarks-v040beta.png deleted file mode 100644 index f1ea39a..0000000 Binary files a/docs/assets/benchmarks-v040beta.png and /dev/null differ diff --git a/docs/assets/how-it-works-2.png b/docs/assets/how-it-works-2.png deleted file mode 100644 index 8f9680a..0000000 Binary files a/docs/assets/how-it-works-2.png and /dev/null differ diff --git a/docs/assets/quick-start-1-choose-api.png b/docs/assets/quick-start-1-choose-api.png deleted file mode 100644 index ed5d4aa..0000000 Binary files a/docs/assets/quick-start-1-choose-api.png and /dev/null differ diff --git a/docs/assets/quick-start-2-package-manager.png b/docs/assets/quick-start-2-package-manager.png deleted file mode 100644 index 305b091..0000000 Binary files a/docs/assets/quick-start-2-package-manager.png and /dev/null differ diff --git a/docs/assets/quick-start-5-altair-results.png b/docs/assets/quick-start-5-altair-results.png deleted file mode 100644 index 187272c..0000000 Binary files a/docs/assets/quick-start-5-altair-results.png and /dev/null differ diff --git a/docs/controllers/_category_.json b/docs/controllers/_category_.json deleted file mode 100644 index b39980f..0000000 --- a/docs/controllers/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Controllers & Actions", - "position": 2, - "collapsed": false -} \ No newline at end of file diff --git a/docs/controllers/actions.html b/docs/controllers/actions.html new file mode 100644 index 0000000..f84c86c --- /dev/null +++ b/docs/controllers/actions.html @@ -0,0 +1,28 @@ + + + + + +Controllers & Actions | GraphQL ASP.NET + + + + +
+

Controllers & Actions

What is an Action?

info

An action is a method on a controller, marked as being a query or mutation field, that is part of your graph schema.

Controllers and actions are the bread and butter of GraphQL ASP.NET. Just like with Web API, they serve as an entry point into your business logic.

In this graphql query we have two top level query fields: hero and droid. We declare action methods for each to handle the data request.

Sample Query
query {
hero(episode: EMPIRE){
id
name
}
droid(id: 2001){
id
name
primaryFunction
}
}
Controllers.cs
// HeroController.cs
public class HeroController : GraphController
{
[QueryRoot]
public Human Hero(Episode episode)
{
return new Human(...);
}
}

// DroidController.cs
public class DroidController : GraphController
{
[QueryRoot]
public Droid Droid(int id)
{
return new Droid(...);
}
}

In the above example, it makes sense these these methods would exist on different controllers, HeroController and DroidController. However, unlike with a REST API request, which will usually invoke one action method and returns the data generated, GraphQL will invoke every action method requested and aggregates the results. If one action fails, the other may not and the results of both the errors and the data retrieved would be returned.

The data returned by your action methods then return their requested child fields, those data items to their children and so on. In many cases this is just a selection of the appropriate properties on a model object, but more complex scenarios involving child objects, type extensions or directly executing POCO methods can also occur.

note

Since mutations may have a shared state or could otherwise produce race conditions in a data store, top level fields in a mutation operation are executed sequentially, in the order they are declared. All child requests there after are dispatched and executed asynchronously [Spec § 6.2.2].


To declare an action you first declare a controller that inherits from GraphQL.AspNet.Controllers.GraphController, then create a method with the the appropriate attribute to indicate it as a query or mutation method.

An Action Method:

Must declare an operation type or a type extension attribute.
+✅ Must declare a return type.
+✅ May be synchronous or asynchronous.
+🧨 Must not return System.Object.
+🧨 Must not declare, as an input parameter, an object that implements any variation of IDictionary.

See below for more detail of the input and return parameter restrictions.

Declaring An Operation Type

Attributes [Query], [QueryRoot], [Mutation] and [MutationRoot] are used to declare your action methods. Usage of the mutation and query attributes are exactly the same, they differ only in which of the root graph types to place a field reference. Lets look at the most common ways to use them:


💻 [Query], [Mutation]

When used alone, without any parameters, the field name in the schema is the same as the method name.

Using [Query] & [Mutation]
public class BakeryController : GraphController
{
[Query]
public Donut FindDonut(int id)
{/* ... */ }

[Mutation]
public CakeModel UpdateCake(CakeModel cake)
{/* ... */ }
}
Sample Query
query {
bakery {
findDonut(id: 5){
name
flavor
}
}
}

mutation {
bakery {
updateCake(cake: {id: 5, name: "Birthday Cake"}){
id
name
}
}
}


💻 [Query("donut")],[Mutation("alterCake")]

You can supply the name of the field you want to use in the schema allowing for different naming schemes in your C# code vs. your object graph.

Using [Query] & [Mutation]
public class BakeryController : GraphController
{
[Query("donut")]
public Donut FindDonut(int id)
{/* ... */ }

[Mutation("alterCake")]
public CakeModel UpdateCake(CakeModel cake)
{/* ... */ }
}
Sample Query
query {
bakery {
donut(id: 5){
name
flavor
}
}
}

mutation {
bakery {
alterCake(cake: {id: 5, name: "Birthday Cake"}) {
id
name
}
}
}


💻 [Query("donut", typeof(Donut))], [Mutation("donut", typeof(CakeModel))]

Sometimes, especially when you return action results, you may need to explicitly declare what data type you are returning from the method. This is because returning IGraphActionResult obfuscates the results from the templating engine and it won't be able to infer the underlying type expression of the field.

Using [Query] & [Mutation]
public class BakeryController : GraphController
{
[Query("donut")]
public Donut FindDonut(int id)
{/* ... */ }

[Mutation("alterCake",typeof(CakeModel))]
public async Task<IGraphActionResult> UpdateCake(CakeModel cake)
{
await _service.UpdateCake(cake);
return this.Ok(cake);
}
}


💻 [QueryRoot], [MutationRoot]

These are special overloads to [Query] and [Mutation]. They are declared in the same way but instruct GraphQL to ignore any inherited field fragments from the controller.

Using [QueryRoot]
public class BakeryController : GraphController
{
[QueryRoot("donut")]
public Donut FindDonut(int id)
{/* ... */ }
}
Sample Query
# Notice the bakery field is gone!
query {
donut(id: 5){
name
flavor
}
}


💻 [GraphRoute]

If you'll recall, Web API uses [Route("somePathSegment")] declared on a controller to nest all the REST end points under that url piece. The same holds true here. Graph controllers can declare [GraphRoute("someFieldName")] under which all the controller actions will be nested as child fields. This is the default behavior even if you don't declare a custom name (your controller name is used). Using the QueryRoot and MutationRoot attributes negates this and appends the action directly to the root graph type.

Using [GraphRoute]
[GraphRoute("BakedGoods")]
public class BakeryController : GraphController
{
[Query("donut")]
public Donut FindDonut(int id)
{/* ... */ }
}
Sample Query
query {
bakedGoods {
donut(id: 5){
name
flavor
}
}
}

A complete explanation of the constructors for these attributes is available in the attributes reference and a detailed explanation of the nesting rules is available under the field paths section.

Returning Data

GraphQL creates your schema by first looking your controller action methods for the objects they return. It then inspects the properties/methods on those objects for other objects, then the properties of those child objects and so on. In GraphQL, there are no unknown or variable fields. For the library to determine your schema it MUST know what each action method returns.

Unlike rest, the data you return is restricted to formats acceptable by graphql.

Working with Dictionaries

Dictionary types, such as Dictionary<TKey,TValue>, are generally not useful in GraphQL. They are forbidden as input parameters, since its not possible to validate arbitrary key/value pairs, and GraphQL makes no use of their lookup abilities as output objects.

This isn't to say that dictionaries should be ignored. On the contrary, use them as needed to generate your data and perform your business logic. Just don't return a dictionary from your action method.

However, Batch operations, also called Data Loaders, are a special type of extension method that uses dictionaries to map child data to multiple parents. Batch operations are incredibly important to the performance of your queries when you start working with large quantities of deeply nested parent/child relationships.

Lists and Nulls

Rules concerning GraphQL's two meta graph types, LIST and NON_NULL apply to action methods as well. Like all fields, the runtime will attempt to determine the complete type expression for your action method and you have the ability to override it as needed.

Working with Interfaces

Returning an interface graph type is a great way to deliver heterogeneous data results, especially in search operations. One got'cha is that the runtime must know the possible concrete object types that implement that interface in case a query uses a fragment with a type specification. That is to say that if we return IPastry, we must let GraphQL know that Cake and Donut exist and should be a part of our schema. Just like with C#, interfaces in graphql contain no logic. If i return an IPastry, GraphQL still needs to know if the actual object is a Cake or a Donut and invoke the correct resolver for any child fields.

info

When your action method returns an interface you must declare OBJECT types that implement that interface in some other way.

e.g. If your schema contains IPastry, it must also contain Cake and Donut.

Take this example:

BakeryController.cs
public class BakeryController : GraphController
{
[QueryRoot]
public IPastry SearchPastries(string name)
{/* ... */}
}
Query
query {
searchPastries(name: "chocolate*") {
id
name

...on Donut {
isFilled
}

...on Cake {
icingFlavor
}
}
}

No where in our code have we told GraphQL about Cake or Donut. When it goes to parse the fragments declared in the query it will try to validate that graph types exist named Cake and Donut to ensure the fields in the fragments are valid, since we've never declared those graph types it won't be able to.

There are a number of ways to indicate these required relationships in your code in order to generate your schema correctly.


📃 Add OBJECT Types Directly to the Action Method

If you have just two or three possible types, add them directly to the query attribute. You can safely add your types across multiple methods as needed, it will only be included in the schema once.

public class BakeryController : GraphController
{
[QueryRoot(typeof(Cake), typeof(Donut))]
public IPastry SearchPastries(string name)
{/* ... */}
}

📜 Using the PossibleTypes attribute

The [Query] attribute can get a bit hard to read with a ton of data in it (especially with Unions). Use the [PossibleTypes] attribute to help with readability.

public class BakeryController : GraphController
{
[QueryRoot]
[PossibleTypes(typeof(Cake), typeof(Donut), typeof(Scone), typeof(Croissant))]
public IPastry SearchPastries(string name)
{/* ... */}
}

🧮 Declare Types at Startup

The schema configuration contains a host of options for auto-loading graph types. Here we've added our 100s and 1000s of types of pastries at our bakery to a shared assembly, obtained a reference to it through one of the types it contains, then added the whole assembly to our schema. GraphQL will automatically scan the assembly and ingest all the graph types mentioned in any controllers it finds as well as any objects marked with the [GraphType] attribute.

startup code
// we can define all our objects in a single assembly, then load it
Assembly pastryAssembly = Assembly.GetAssembly(typeof(Cake));

services.AddGraphQL(options =>
{
options.AddAssembly(pastryAssembly);
});

⚠️ A Note On Type Ingestion

You might be wondering, "if I just define Cake and Donut in my application, why can't GraphQL just include them like it does the controller?".

It certainly can, but there are risks to arbitrarily grabbing class references not exposed on a schema. With introspection queries, all of those classes and their method/property names could be exposed and pose a security risk. It might not be able to query the data, but imagine if a enum named EmployeeDiscountCodes was accidentally added to your graph. All the values of that enum would be publically exposed via introspection.

To combat this GraphQL will only ingest types that are:

  • Referenced in a GraphController action method OR
  • Attributed with at least once instance of a [GraphType] or [GraphField] attribute somewhere within the class OR
  • Added explicitly at startup during .AddGraphQL().

This behavior is controlled with your schema's declaration configuration to make it more or less restrictive based on your needs. Ultimately you are in control of how aggressive or restrictive GraphQL should be; even going so far as declaring that every type be declared with [GraphType] and every field with [GraphField] lest it be ignored completely. The amount of automatic vs. manual wire up will vary from use case to use case but you should be able to achieve the result you desire.

Graph Action Results

Action Results provide a clean way to standardize your responses to different conditions across your application. In a Web API controller, if you've ever used this.OK() or this.NotFound() you've used the concept of an action result before.

Using action results can make your code a lot more readable and provide helpful, customizable messaging to the requestor.

For Example, using this.Error() injects a custom error message into the response providing some additional information other than just a null result.

BakeryController.cs
// BakeryController.cs
public class BakeryController : GraphController
{
[QueryRoot(typeof(IPastry))]
public async Task<IGraphActionResult> SearchPastries(string name)
{
if(name == null || name.Length < 3)
{
return this.Error(GraphMessageSeverity.Warning, "At least 3 characters is required");
}
else
{
var results = await _service.SearchPastries(name);
return this.Ok(results);
}
}
}

The full list of graph action results can be found in the reference section .

Create a class that implements IGraphActionResult and create your own.

IGraphActionResult.cs
public interface IGraphActionResult
{
Task Complete(BaseResolutionContext context);
}

IGraphActionResult has one method. It accepts the raw resolution context (either a FieldResolutionContext or a DirectiveResolutionContext) that you can manipulate as needed. Combine this with any data you supply to your action result when you instantiate it and you have the ability to generate any response with any data value or any number and type of error messages etc. Take a look at the source code for built in graph action results for some more detailed examples.

Its Not REST

Action results for graph fields are not the same as REST action results. For Example, BadRequest() does not return an HTTP status 400 for the request. An errored field is usually just one of many in the query and graphql supports partial query resolution. We use the errors collection on a graphql response to provide details on what happened with any given field. The overall query will almost always return an HTTP status 200.

Method Parameters

Parameters on your action methods are interpreted as field arguments in your graph. GraphQL will inspect your method parameters and add the appropriate SCALAR, ENUM and INPUT_OBJECT graph types to your schema automatically.

Naming your Input Arguments

By default, GraphQL will name a field's arguments the same as the parameter names in your method. Sometimes you'll want to override this, like when needing to use a C# keyword as an argument name. Use the [FromGraphQL] attribute on the parameter to accomplish this.

Overriding a Default Argument Name
public class BakeryController : GraphController
{
[QueryRoot]
public IEnumerable<Donut> SearchDonuts([FromGraphQL("name")] string searchText)
{/* ... */}
}

We can then execute the query:

Sample Query
// GraphQL Query
query {
searchPastries(name: "chocolate*") {
id
name
flavor
}
}

Default Argument Values

In GraphQL, not all field arguments are required. Add a default value to your method parameters to mark them as optional:

Using an Optional Field Argument
public class BakeryController : GraphController
{
[QueryRoot]
public Donut SearchDonuts(string name = "*")
{/* ... */}
}
Sample Query
# Pass a value
query {
searchDonuts(name: "Chocolate*"){
id
flavor
}
}

# The default value for name will be used (e.g. "*")
query {
searchDonuts {
id
flavor
}
}

⚠️ Nullable vs. Not Required

Note that there is a difference between "nullable" and "not required" for field arguments. If we have a nullable int as an input parameter, without a default value we still have to pass it to the field, even if we pass it as null; just like if we were to invoke the method from our C# code.

NumberController.cs
public class NumberController : GraphController
{
// "seed" is still required, but you can supply null
[QueryRoot]
public int CreateRandomInt(int? seed)
{/* ... */}
}
Sample Queries
# The argument must be passed
# but it can passed as null
query {
createRandomInt(seed: null)
}

## ***
## ERROR, argument not supplied
## ***
query {
createRandomInt
}

By also defining a default value we can achieve the flexibility we are looking for.

NumberController.cs
public class NumberController : GraphController
{
[QueryRoot]
public int CreateRandomInt(int? seed = null)
{/* ... */}
}
Sample Queries
# Pass a value
query {
createRandomInt(seed: 5)
}

# Pass null
query {
createRandomInt(seed: null)
}

# Or omit the parameter (value received: null)
query {
createRandomInt
}

Working With Lists

When constructing a set of items as an argument to an action method, GraphQL will instantiate a List<T> internally and fill it with the appropriate data; be that another list, another input object, a scalar etc. While you can declare an array (e.g. Donut[], int[] etc.) as your list structure for an input argument, graphql has to rebuild its internal representation to meet the requirements of your method. In some cases, especially with nested lists, or combinations of lists and arrays, this results in an O(N) increase in processing time.

tip

Use IEnumerable<T> or IList<T> as your argument types to avoid a performance bottleneck when sending lots of items as input data.

This example shows various ways of accepting collections of data as inputs to controller actions.

BakeryController.cs
public class BakeryController : GraphController
{
// a list of donuts
// schema syntax: [Donut]
[Mutation("createDonuts")]
public bool CreateDonuts(IEnumerable<Donut> donuts)
{/*....*/}

// when used as a "list of list"
// schema syntax: [[Donut]]
[Mutation("createDonutsBySet")]
public bool CreateDonuts(List<List<Donut>> donuts)
{/*....*/}

// when supplied as a regular array
// schema syntax: [Donut]
[Mutation("donutsAsAnArray")]
public bool DonutsAsAnArray(Donut[] donuts)
{/*....*/}

// This is a valid nested list believe it or not
// schema syntax: [[[Donut]]]
[Mutation("mixedDonuts")]
public bool MixedDonuts(List<IEnumerable<Donut[]>> donuts)
{/*....*/}
}

Don't Use Dictionaries

You might be tempted to use a dictionary as a parameter to accept arbitrary key value pairs into your methods. GraphQL will reject it and throw a declaration exception when your schema is created:

BakeryController.cs
public class BakeryController : GraphController
{
// ERROR, a GraphTypeDeclarationException
// will be thrown.
[QueryRoot]
public IEnumerable<Donut>
SearchDonuts(IDictionary searchParams)
{/* ... */}
}
Invalid Arguments
# ERROR, Unknown arguments on searchDonuts
query {
searchDonuts(
name: "jelly*"
filled: true
dayOld: false){
id
name
}
}

At runtime, GraphQL will try to validate every argument on every field passed on a query. No where have we declared an argument filled to be a boolean or name to be a string.

Well, lets just pass it as an input object to a declared argument, right?

Invalid Input Object
# ERROR, Unknown fields on searchParams
query {
searchDonuts( searchParams : {name: "jelly*" filled: true dayOld: false }){
id
name
}
}

But this is also not allowed. All we've done is pushed the problem down one level. No where on our IDictionary type is there a Name property declared as a string or a Filled property declared as a boolean. Since GraphQL can't fully validate the query against the schema before executing it, it's rejected.

Instead declare a search object with the parameters you need and use it as the input:

BakeryController.cs
public class DonutSearchParams
{
public string Name { get; set; }
public bool? Filled { get; set; }
public bool? DayOld { get; set; }
}

public class BakeryController : GraphController
{
[QueryRoot]
public IEnumerable<Donut> SearchDonuts(DonutSearchParams searchParams)
{/* ... */}
}
Valid Query
query {
searchDonuts( searchParams : {filled: true}){
id
name
}
}

Cancellation Tokens

As with REST based action methods, your graph controller action methods can accept an optional CancellationToken. This is useful when doing some long running activities such as IO, database queries, API orchestration etc. To make use of a cancellation token simply add it as a parameter to your method. GraphQL will automatically capture the token, wire it up for you and hide it from your schema.

BakeryController.cs | Adding a CancellationToken
public class BakeryController : GraphController
{
// Add a CancellationToken to your controller method
[QueryRoot(typeof(IEnumerable<Donut>))]
public async Task<IGraphActionResult> SearchDonuts(string name, CancellationToken cancelToken)
{/* ... */}
}
caution

Depending on your usage of the cancellation token a TaskCanceledException may be thrown. GraphQL will not attempt to intercept this exception and will log it as an error-level, unhandled exception event if allowed to propegate. The query will still be cancelled as expected.

Defining a Query Timeout

By default, the library does not define a timeout for an executed query. The query will run as long as the underlying HTTP connection is open. In fact, the CancellationToken passed to your action methods is the same Cancellation Token offered on the HttpContext when it receives the initial request.

Optionally, you can define a query timeout for a given schema:

Startup Code
services.AddGraphQL(o =>
{
// define a 2 minute timeout for every query.
o.ExecutionOptions.QueryTimeout = TimeSpan.FromMinutes(2);
})

When a timeout is defined, the token passed to your action methods is a combined token representing the HttpContext as well as the timeout operation. That is to say the token will indicate a cancellation if the allotted query time expires or the http connection is closed, which ever comes first. When the timeout expires the caller will receive a response indicating the timeout. However, if the its the HTTP connection that is closed, the operation is simply halted and no result is produced.

Timeouts and Subscriptions

The same rules for cancellation tokens apply to subscriptions as well. Since the websocket connection is a long running operation it will never be closed until the connection is closed. To prevent some processes from spinning out of control its a good idea to define a query timeout when implementing a subscription server. This way, even though the connection remains open the query will terminate and release resources if something goes awry.

+ + + + \ No newline at end of file diff --git a/docs/controllers/actions.md b/docs/controllers/actions.md deleted file mode 100644 index 345400f..0000000 --- a/docs/controllers/actions.md +++ /dev/null @@ -1,696 +0,0 @@ ---- -id: actions -title: Controllers & Actions -sidebar_label: Actions -sidebar_position: 0 ---- - - -## What is an Action? - -:::info - An action is a method on a controller, marked as being a query or mutation field, that is part of your graph schema. -::: - -Controllers and actions are the bread and butter of GraphQL ASP.NET. Just like with Web API, they serve as an entry point into your business logic. - -In this graphql query we have two top level query fields: `hero` and `droid`. We declare action methods for each to handle the data request. - -```graphql title="Sample Query" -query { - hero(episode: EMPIRE){ - id - name - } - droid(id: 2001){ - id - name - primaryFunction - } -} -``` - -```csharp title="Controllers.cs" -// HeroController.cs -public class HeroController : GraphController -{ - [QueryRoot] - public Human Hero(Episode episode) - { - return new Human(...); - } -} - -// DroidController.cs -public class DroidController : GraphController -{ - [QueryRoot] - public Droid Droid(int id) - { - return new Droid(...); - } -} -``` - -In the above example, it makes sense these these methods would exist on different controllers, `HeroController` and `DroidController`. However, unlike with a REST API request, which will usually invoke one action method and returns the data generated, GraphQL will invoke every action method requested and aggregates the results. If one action fails, the other may not and the results of both the errors and the data retrieved would be returned. - -The data returned by your action methods then return their requested child fields, those data items to their children and so on. In many cases this is just a selection of the appropriate properties on a model object, but more complex scenarios involving child objects, [type extensions](./type-extensions) or directly executing POCO methods can also occur. - -:::note -Since mutations may have a shared state or could otherwise produce race conditions in a data store, top level fields in a mutation operation are executed sequentially, in the order they are declared. All child requests there after are dispatched and executed asynchronously [[Spec § 6.2.2](https://graphql.github.io/graphql-spec/October2021/#sec-Mutation)\]. -::: ---- - -To declare an action you first declare a controller that inherits from `GraphQL.AspNet.Controllers.GraphController`, then create a method with the the appropriate attribute to indicate it as a query or mutation method. - -**An Action Method:** - - ✅ **Must** declare an [operation type](./actions#declaring-an-operation-type) or a [type extension](../controllers/type-extensions) attribute.
- ✅ **Must** declare a return type.
- ✅ **May** be synchronous or asynchronous.
- 🧨 **Must not** return `System.Object`.
- 🧨 **Must not** declare, as an input parameter, an object that implements any variation of `IDictionary`.
- -See below for more detail of the [input](./actions#method-parameters) and [return](./actions#returning-data) parameter restrictions. - -## Declaring An Operation Type - -Attributes `[Query]`, `[QueryRoot]`, `[Mutation]` and `[MutationRoot]` are used to declare your action methods. Usage of the mutation and query attributes are exactly the same, they differ only in which of the root graph types to place a field reference. Lets look at the most common ways to use them: - -
- -💻 `[Query]`, `[Mutation]` - -When used alone, without any parameters, the field name in the schema is the same as the method name. - -```csharp title="Using [Query] & [Mutation]" -public class BakeryController : GraphController -{ - // highlight-next-line - [Query] - public Donut FindDonut(int id) - {/* ... */ } - - // highlight-next-line - [Mutation] - public CakeModel UpdateCake(CakeModel cake) - {/* ... */ } -} -``` - -```graphql title="Sample Query" -query { - bakery { - findDonut(id: 5){ - name - flavor - } - } -} - -mutation { - bakery { - updateCake(cake: {id: 5, name: "Birthday Cake"}){ - id - name - } - } -} -``` - -
-
- -💻 `[Query("donut")]`,`[Mutation("alterCake")]` - -You can supply the name of the field you want to use in the schema allowing for different naming schemes in your C# code vs. your object graph. - -```csharp title="Using [Query] & [Mutation]" -public class BakeryController : GraphController -{ - // highlight-next-line - [Query("donut")] - public Donut FindDonut(int id) - {/* ... */ } - - // highlight-next-line - [Mutation("alterCake")] - public CakeModel UpdateCake(CakeModel cake) - {/* ... */ } -} -``` - -```graphql title="Sample Query" -query { - bakery { - donut(id: 5){ - name - flavor - } - } -} - -mutation { - bakery { - alterCake(cake: {id: 5, name: "Birthday Cake"}) { - id - name - } - } -} -``` - -
-
- -💻 `[Query("donut", typeof(Donut))]`, `[Mutation("donut", typeof(CakeModel))]` - - Sometimes, especially when you return action results, you may need to explicitly declare what data type you are returning from the method. This is because returning `IGraphActionResult` obfuscates the results from the templating engine and it won't be able to infer the underlying type expression of the field. - - -```csharp title="Using [Query] & [Mutation]" -public class BakeryController : GraphController -{ - [Query("donut")] - public Donut FindDonut(int id) - {/* ... */ } - - // highlight-next-line - [Mutation("alterCake",typeof(CakeModel))] - public async Task UpdateCake(CakeModel cake) - { - await _service.UpdateCake(cake); - return this.Ok(cake); - } -} -``` - -
-
- -💻 `[QueryRoot]`, `[MutationRoot]` - -These are special overloads to `[Query]` and `[Mutation]`. They are declared in the same way but instruct GraphQL to ignore any inherited field fragments from the controller. - -```csharp title="Using [QueryRoot]" -public class BakeryController : GraphController -{ - // highlight-next-line - [QueryRoot("donut")] - public Donut FindDonut(int id) - {/* ... */ } -} -``` - -```graphql title="Sample Query" -# Notice the bakery field is gone! -query { - donut(id: 5){ - name - flavor - } -} -``` - -
-
- -💻 `[GraphRoute]` - -If you'll recall, Web API uses `[Route("somePathSegment")]` declared on a controller to nest all the REST end points under that url piece. The same holds true here. Graph controllers can declare `[GraphRoute("someFieldName")]` under which all the controller actions will be nested as child fields. This is the default behavior even if you don't declare a custom name (your controller name is used). Using the `QueryRoot` and `MutationRoot` attributes negates this and appends the action directly to the root graph type. - - -```csharp title="Using [GraphRoute]" -// highlight-next-line -[GraphRoute("BakedGoods")] -public class BakeryController : GraphController -{ - [Query("donut")] - public Donut FindDonut(int id) - {/* ... */ } -} -``` - -```graphql title="Sample Query" -query { - bakedGoods { - donut(id: 5){ - name - flavor - } - } -} -``` - -A complete explanation of the constructors for these attributes is available in the [attributes reference](../reference/attributes) and a detailed explanation of the nesting rules is available under the [field paths](./field-paths) section. - -## Returning Data - -GraphQL creates your schema by first looking your controller action methods for the objects they return. It then inspects the properties/methods on those objects for other objects, then the properties of those child objects and so on. In GraphQL, there are no unknown or variable fields. For the library to determine your schema it MUST know what each action method returns. - -Unlike rest, the data you return is restricted to formats acceptable by graphql. - -### Working with Dictionaries - -Dictionary types, such as `Dictionary`, are generally not useful in GraphQL. They are forbidden as input parameters, since its not possible to validate arbitrary key/value pairs, and GraphQL makes no use of their lookup abilities as output objects. - -This isn't to say that dictionaries should be ignored. On the contrary, use them as needed to generate your data and perform your business logic. Just don't return a dictionary from your action method. - -However, [Batch operations](../controllers/batch-operations), also called `Data Loaders`, are a special type of extension method that uses dictionaries to map child data to multiple parents. Batch operations are incredibly important to the performance of your queries when you start working with large quantities of deeply nested parent/child relationships. - -### Lists and Nulls - -Rules concerning GraphQL's two meta graph types, [LIST and NON_NULL](../types/list-non-null) apply to action methods as well. Like all fields, the runtime will attempt to determine the complete [type expression](../advanced/type-expressions) for your action method and you have the ability to override it as needed. - -### Working with Interfaces - -Returning an [interface graph type](../types/interfaces) is a great way to deliver heterogeneous data results, especially in search operations. One got'cha is that the runtime must know the possible concrete object types that implement that interface in case a query uses a fragment with a type specification. That is to say that if we return `IPastry`, we must let GraphQL know that `Cake` and `Donut` exist and should be a part of our schema. Just like with C#, interfaces in graphql contain no logic. If i return an `IPastry`, GraphQL still needs to know if the actual object is a `Cake` or a `Donut` and invoke the correct resolver for any child fields. - -:::info -When your action method returns an interface you must declare OBJECT types that implement that interface in some other way. - -e.g. If your schema contains `IPastry`, it must also contain `Cake` and `Donut`. -::: -Take this example: - -```csharp title="BakeryController.cs" -public class BakeryController : GraphController -{ - [QueryRoot] - // highlight-next-line - public IPastry SearchPastries(string name) - {/* ... */} -} -``` - -```graphql title="Query" -query { - searchPastries(name: "chocolate*") { - id - name - - ...on Donut { - isFilled - } - - ...on Cake { - icingFlavor - } - } -} -``` - -No where in our code have we told GraphQL about `Cake` or `Donut`. When it goes to parse the fragments declared in the query it will try to validate that graph types exist named `Cake` and `Donut` to ensure the fields in the fragments are valid, since we've never declared those graph types it won't be able to. - -There are a number of ways to indicate these required relationships in your code in order to generate your schema correctly. - -
- - 📃 **Add OBJECT Types Directly to the Action Method** - -If you have just two or three possible types, add them directly to the query attribute. You can safely add your types across multiple methods as needed, it will only be included in the schema once. - -```csharp -public class BakeryController : GraphController -{ - // highlight-next-line - [QueryRoot(typeof(Cake), typeof(Donut))] - public IPastry SearchPastries(string name) - {/* ... */} -} -``` - -
- -📜 **Using the PossibleTypes attribute** - -The `[Query]` attribute can get a bit hard to read with a ton of data in it (especially with [Unions](../types/unions)). Use the `[PossibleTypes]` attribute to help with readability. - -```csharp -public class BakeryController : GraphController -{ - [QueryRoot] - // highlight-next-line - [PossibleTypes(typeof(Cake), typeof(Donut), typeof(Scone), typeof(Croissant))] - public IPastry SearchPastries(string name) - {/* ... */} -} -``` - -
- -🧮 **Declare Types at Startup** - -The [schema configuration](../reference/schema-configuration) contains a host of options for auto-loading graph types. Here we've added our 100s and 1000s of types of pastries at our bakery to a shared assembly, obtained a reference to it through one of the types it contains, then added the whole assembly to our schema. GraphQL will automatically scan the assembly and ingest all the graph types mentioned in any controllers it finds as well as any objects marked with the `[GraphType]` attribute. - -```csharp title="startup code" -// we can define all our objects in a single assembly, then load it -Assembly pastryAssembly = Assembly.GetAssembly(typeof(Cake)); - -services.AddGraphQL(options => -{ - // highlight-next-line - options.AddAssembly(pastryAssembly); -}); -``` - -
- -⚠️ **A Note On Type Ingestion** - - -You might be wondering, "if I just define `Cake` and `Donut` in my application, why can't GraphQL just include them like it does the controller?". - -It certainly can, but there are risks to arbitrarily grabbing class references not exposed on a schema. With introspection queries, all of those classes and their method/property names could be exposed and pose a security risk. It might not be able to query the data, but imagine if a enum named `EmployeeDiscountCodes` was accidentally added to your graph. All the values of that enum would be publically exposed via introspection. - -To combat this GraphQL will only ingest types that are: - -- Referenced in a `GraphController` action method **OR** -- Attributed with at least once instance of a `[GraphType]` or `[GraphField]` attribute somewhere within the class **OR** -- Added explicitly at startup during `.AddGraphQL()`. - -This behavior is controlled with your schema's declaration configuration to make it more or less restrictive based on your needs. Ultimately you are in control of how aggressive or restrictive GraphQL should be; even going so far as declaring that every type be declared with `[GraphType]` and every field with `[GraphField]` lest it be ignored completely. The amount of automatic vs. manual wire up will vary from use case to use case but you should be able to achieve the result you desire. - -### Graph Action Results - -Action Results provide a clean way to standardize your responses to different conditions across your application. In a Web API controller, if you've ever used `this.OK()` or `this.NotFound()` you've used the concept of an action result before. - -Using action results can make your code a lot more readable and provide helpful, customizable messaging to the requestor. - -For Example, using `this.Error()` injects a custom error message into the response providing some additional information other than just a null result. - -```csharp title="BakeryController.cs" -// BakeryController.cs -public class BakeryController : GraphController -{ - [QueryRoot(typeof(IPastry))] - public async Task SearchPastries(string name) - { - if(name == null || name.Length < 3) - { - // highlight-next-line - return this.Error(GraphMessageSeverity.Warning, "At least 3 characters is required"); - } - else - { - var results = await _service.SearchPastries(name); - return this.Ok(results); - } - } -} -``` - -> The full list of graph action results can be found in the [reference section](../advanced/graph-action-results) . - - -Create a class that implements `IGraphActionResult` and create your own. - -```csharp title="IGraphActionResult.cs" -public interface IGraphActionResult -{ - Task Complete(BaseResolutionContext context); -} -``` - -`IGraphActionResult` has one method. It accepts the raw resolution context (either a `FieldResolutionContext` or a `DirectiveResolutionContext`) that you can manipulate as needed. Combine this with any data you supply to your action result when you instantiate it and you have the ability to generate any response with any data value or any number and type of error messages etc. Take a look at the source code for built in [graph action results](https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Controllers/ActionResults) for some more detailed examples. - -:::caution Its Not REST -Action results for graph fields are not the same as REST action results. For Example, `BadRequest()` does not return an HTTP status 400 for the request. An errored field is usually just one of many in the query and graphql supports partial query resolution. We use the `errors` collection on a graphql response to provide details on what happened with any given field. The overall query will almost always return an HTTP status 200. -::: - -## Method Parameters - -Parameters on your action methods are interpreted as field arguments in your graph. GraphQL will inspect your method parameters and add the appropriate [`SCALAR`](../types/scalars), [`ENUM`](../types/enums) and [`INPUT_OBJECT`](../types/input-objects) graph types to your schema automatically. - -### Naming your Input Arguments - -By default, GraphQL will name a field's arguments the same as the parameter names in your method. Sometimes you'll want to override this, like when needing to use a C# keyword as an argument name. Use the `[FromGraphQL]` attribute on the parameter to accomplish this. - -```csharp title="Overriding a Default Argument Name" -public class BakeryController : GraphController -{ - [QueryRoot] - // highlight-next-line - public IEnumerable SearchDonuts([FromGraphQL("name")] string searchText) - {/* ... */} -} -``` - -We can then execute the query: - -```graphql title="Sample Query" -// GraphQL Query -query { - searchPastries(name: "chocolate*") { - id - name - flavor - } -} -``` - -### Default Argument Values - -In GraphQL, not all field arguments are required. Add a default value to your method parameters to mark them as optional: - -```csharp title="Using an Optional Field Argument" -public class BakeryController : GraphController -{ - [QueryRoot] - // highlight-next-line - public Donut SearchDonuts(string name = "*") - {/* ... */} -} -``` - -```graphql title="Sample Query" -# Pass a value -query { - searchDonuts(name: "Chocolate*"){ - id - flavor - } -} - -# The default value for name will be used (e.g. "*") -query { - searchDonuts { - id - flavor - } -} -``` - -
- -⚠️ **Nullable vs. Not Required** - -Note that there is a difference between "nullable" and "not required" for field arguments. If we have a nullable int as an input parameter, without a default value we still have to pass it to the field, even if we pass it as `null`; just like if we were to invoke the method from our C# code. - -```csharp title="NumberController.cs" -public class NumberController : GraphController -{ - // "seed" is still required, but you can supply null - [QueryRoot] - // highlight-next-line - public int CreateRandomInt(int? seed) - {/* ... */} -} -``` - -```graphql title="Sample Queries" -# The argument must be passed -# but it can passed as null -query { - createRandomInt(seed: null) -} - -## *** -## ERROR, argument not supplied -## *** -query { - createRandomInt -} -``` - -By also defining a default value we can achieve the flexibility we are looking for. - -```csharp title="NumberController.cs" -public class NumberController : GraphController -{ - [QueryRoot] - // highlight-next-line - public int CreateRandomInt(int? seed = null) - {/* ... */} -} -``` - -```graphql title="Sample Queries" -# Pass a value -query { - createRandomInt(seed: 5) -} - -# Pass null -query { - createRandomInt(seed: null) -} - -# Or omit the parameter (value received: null) -query { - createRandomInt -} -``` - -### Working With Lists - -When constructing a set of items as an argument to an action method, GraphQL will instantiate a `List` internally and fill it with the appropriate data; be that another list, another input object, a scalar etc. While you can declare an array (e.g. `Donut[]`, `int[]` etc.) as your list structure for an input argument, graphql has to rebuild its internal representation to meet the requirements of your method. In some cases, especially with nested lists, or combinations of lists and arrays, this results in an `O(N)` increase in processing time. - -:::tip -Use `IEnumerable` or `IList` as your argument types to avoid a performance bottleneck when sending lots of items as input data. -::: - -This example shows various ways of accepting collections of data as inputs to controller actions. - -```csharp title="BakeryController.cs" -public class BakeryController : GraphController -{ - // a list of donuts - // schema syntax: [Donut] - [Mutation("createDonuts")] - public bool CreateDonuts(IEnumerable donuts) - {/*....*/} - - // when used as a "list of list" - // schema syntax: [[Donut]] - [Mutation("createDonutsBySet")] - public bool CreateDonuts(List> donuts) - {/*....*/} - - // when supplied as a regular array - // schema syntax: [Donut] - [Mutation("donutsAsAnArray")] - public bool DonutsAsAnArray(Donut[] donuts) - {/*....*/} - - // This is a valid nested list believe it or not - // schema syntax: [[[Donut]]] - [Mutation("mixedDonuts")] - public bool MixedDonuts(List> donuts) - {/*....*/} -} -``` - -### Don't Use Dictionaries - -You might be tempted to use a dictionary as a parameter to accept arbitrary key value pairs into your methods. GraphQL will reject it and throw a declaration exception when your schema is created: - - -```csharp title="BakeryController.cs" -public class BakeryController : GraphController -{ - // ERROR, a GraphTypeDeclarationException - // will be thrown. - [QueryRoot] - public IEnumerable - SearchDonuts(IDictionary searchParams) - {/* ... */} -} -``` - - -```graphql title="Invalid Arguments" -# ERROR, Unknown arguments on searchDonuts -query { - searchDonuts( - name: "jelly*" - filled: true - dayOld: false){ - id - name - } -} -``` - -At runtime, GraphQL will try to validate every argument on every field passed on a query. No where have we declared an argument `filled` to be a boolean or `name` to be a string. - -Well, lets just pass it as an input object to a declared argument, right? - -```graphql title="Invalid Input Object" -# ERROR, Unknown fields on searchParams -query { - searchDonuts( searchParams : {name: "jelly*" filled: true dayOld: false }){ - id - name - } -} -``` - -But this is also not allowed. All we've done is pushed the problem down one level. No where on our `IDictionary` type is there a `Name` property declared as a string or a `Filled` property declared as a boolean. Since GraphQL can't fully validate the query against the schema before executing it, it's rejected. - -Instead declare a search object with the parameters you need and use it as the input: - -```csharp title="BakeryController.cs" -public class DonutSearchParams -{ - public string Name { get; set; } - public bool? Filled { get; set; } - public bool? DayOld { get; set; } -} - -public class BakeryController : GraphController -{ - [QueryRoot] - public IEnumerable SearchDonuts(DonutSearchParams searchParams) - {/* ... */} -} -``` - -```graphql title="Valid Query" -query { - searchDonuts( searchParams : {filled: true}){ - id - name - } -} -``` - -## Cancellation Tokens - -As with REST based action methods, your graph controller action methods can accept an optional `CancellationToken`. This is useful when doing some long running activities such as IO, database queries, API orchestration etc. To make use of a cancellation token simply add it as a parameter to your method. GraphQL will automatically capture the token, wire it up for you and hide it from your schema. - -```csharp title="BakeryController.cs | Adding a CancellationToken" -public class BakeryController : GraphController -{ - // Add a CancellationToken to your controller method - [QueryRoot(typeof(IEnumerable))] - // highlight-next-line - public async Task SearchDonuts(string name, CancellationToken cancelToken) - {/* ... */} -} -``` - -:::caution - Depending on your usage of the cancellation token a `TaskCanceledException` may be thrown. GraphQL will not attempt to intercept this exception and will log it as an error-level, unhandled exception event if allowed to propegate. The query will still be cancelled as expected. -::: - -### Defining a Query Timeout - -By default, the library does not define a timeout for an executed query. The query will run as long as the underlying HTTP connection is open. In fact, the `CancellationToken` passed to your action methods is the same Cancellation Token offered on the HttpContext when it receives the initial request. - -Optionally, you can define a query timeout for a given schema: - -```csharp title="Startup Code" -services.AddGraphQL(o => -{ - // define a 2 minute timeout for every query. - // highlight-next-line - o.ExecutionOptions.QueryTimeout = TimeSpan.FromMinutes(2); -}) -``` - -When a timeout is defined, the token passed to your action methods is a combined token representing the HttpContext as well as the timeout operation. That is to say the token will indicate a cancellation if the allotted query time expires or the http connection is closed, which ever comes first. When the timeout expires the caller will receive a response indicating the timeout. However, if the its the HTTP connection that is closed, the operation is simply halted and no result is produced. - -:::danger Timeouts and Subscriptions -The same rules for cancellation tokens apply to subscriptions as well. Since the websocket connection is a long running operation it will never be closed until the connection is closed. To prevent some processes from spinning out of control its a good idea to define a query timeout when implementing a subscription server. This way, even though the connection remains open the query will terminate and release resources if something goes awry. -::: \ No newline at end of file diff --git a/docs/controllers/authorization.html b/docs/controllers/authorization.html new file mode 100644 index 0000000..b8bd3ac --- /dev/null +++ b/docs/controllers/authorization.html @@ -0,0 +1,24 @@ + + + + + +Authorization | GraphQL ASP.NET + + + + +
+

Authorization

Quick Examples

If you've wired up ASP.NET authorization before, you'll likely familiar with the [Authorize] attribute and how its used to enforce security.

GraphQL ASP.NET works the same way.

General Authorization Check
public class BakeryController : GraphController
{
[Authorize]
[MutationRoot("orderDonuts", typeof(CompletedDonutOrder))]
public async Task<IGraphActionResult> OrderDonuts(DonutOrderModel order)
{/*...*/}
}
Restrict by Policy
public class BakeryController : GraphController
{
[Authorize(Policy = "CustomerLoyaltyProgram")]
[MutationRoot("orderDonuts", typeof(CompletedDonutOrder))]
public async Task<IGraphActionResult> OrderDonuts(DonutOrderModel order)
{/*...*/}
}
Restrict by Role
public class BakeryController : GraphController
{
[Authorize(Roles = "Admin, Employee")]
[MutationRoot("purchaseDough")]
public async Task<bool> PurchaseDough(int kilosOfDough)
{/*...*/}
}
Multiple Authorization Requirements
// The library supports nested policy and role checks at Controller and Action levels.
[Authorize(Policy = "CurrentCustomer")]
public class BakeryController : GraphController
{
// The user would have to pass the CurrentCustomer policy
// and the LoyaltyProgram policy to access the `orderDonuts` field

[Authorize(Policy = "LoyaltyProgram")]
[MutationRoot("orderDonuts", typeof(CompletedDonutOrder))]
public async Task<IGraphActionResult> OrderDonuts(DonutOrderModel order)
{/*...*/}
}
Use of [AllowAnonymous]
[Authorize]
public class BakeryController : GraphController
{
[Authorize(Policy = "CustomerLoyaltyProgram")]
[MutationRoot("orderDonuts", typeof(CompletedDonutOrder))]
public async Task<IGraphActionResult> OrderDonuts(DonutOrderModel order)
{/*...*/}

// No Authorization checks on RetrieveDonutList
[AllowAnonymous]
[Mutation("donutList")]
public async Task<IEnumerable<Donut>> RetrieveDonutList()
{/*...*/}
}

Use of IAuthorizationService

Under the hood, GraphQL taps into your IServiceProvider to obtain a reference to the IAuthorizationService that gets created when you configure .AddAuthorization() at startup. Take a look at the Schema Item Security Pipeline Components for the full picture.

When does Authorization Occur?

GraphQL ASP.NET makes use of the result from ASP.NET's security pipeline. Whether you use Kerberos tokens, oauth2, username/password, API tokens or if you support 2-factor authentication or one-time-use passwords, GraphQL doesn't care. The entirety of your authentication and authorization scheme is executed by GraphQL, no special arrangements or configuration is needed.

GraphQL ASP.NET draws from your configured authentication/authorization solution.

Execution directives and field resolutions are passed through the libraries internal pipeline where securty is enforced as a series of middleware components before the respective resolvers are invoked. Should a requestor not be authorized for a given schema item they are informed via an error message and denied access to the item.

Field Authorizations

If a requestor is not authorized to a requested field a value of null is used as the resolved value and an error message is recorded to the query results.

Null propagation rules still apply to unauthorized fields meaning if the field cannot accept a null value, its propagated up the field chain potentially nulling out a parent or "parent of a parent" depending on your schema.

By default, a single unauthorized field result does not necessarily kill an entire query, it depends on the structure of your object graph and the query being executed. When a field request is terminated any down-stream child fields are discarded immediately but sibling fields or unrelated ancestors continue to execute as normal.

Since this authorization occurs "per field" and not "per controller action" its possible to define the same security chain for POCO properties. This allows you to effectively deny access, by policy, to a single property of an instantiated object. Performing security checks for every field of data (especially in parent/child scenarios) has a performance cost though, especially for larger data sets. For most scenarios enforcing security at the controller level is sufficient.

Field Authorization Failures are Obfuscated

When GraphQL denies a requestor access to a field a message naming the field path is added to the response. This message is generic on purpose. Suppose there was a query where the user requests the allDonuts field but is denied access:

    {
donut(id: 5) {
name
}
allDonuts {
name
}

}

The result might look like this:

Denied Field Access
{
"errors": [
{
"message": "Access Denied to field [query]/allDonuts",
"locations": [
{
"line": 7,
"column": 3
}
],
"path": [
"allDonuts"
],
"extensions": {
"code": "ACCESS_DENIED",
"timestamp": "2022-12-22T22:22:25.017-07:00",
"severity": "CRITICAL"
}
}
],
"data": {
"donut": {
"name": "Super Mega Donut",
},
"allDonuts": null
}
}
tip

To view more details authorization failure reasons, such as specific policy failures, you'll need to expose exceptions on the request or turn on logging.

GraphQL automatically raises the SchemaItemAuthorizationCompleted log event at a Warning level when a security check fails.

Authorization on Execution Directives

Execution directives are applied to the query document, before a query plan is created to fulfill the request. However, it is the query plan that determines which field resolvers should be called. As a result, execution directives have the potential to alter the document structure and change how a query plan might be structured. Because of this, not executing a query directive has the potential to cause a the expected query to be different than what the requestor intended.

Therefore, if an execution directive fails authorization the query is rejected and not executed. The caller will receive an error message as part of the response indicating the unauthorized directive. Like field authorization failures, the message is obfuscated and contains only a generic message. You'll need to expose exception on the request or turn on logging to see additional details.

Authorization Methods

GraphQL ASP.NET supports two methods of applying the authorization rules out of the box.

  • PerField: Each field is authorized individually. If a query references some fields the user can access and some they cannot, those fields the user can access are resolved as expected. A null value is assigned to the fields the user cannot access.

  • PerRequest: All fields that require authorization are authorized at once. If the user is unauthorized on 1 or more fields the entire request is denied and not executed.

Configure the authorization method at startup:

Startup
services.AddGraphQL(schemaOptions =>
{
schemaOptions.AuthorizationOptions.Method = AuthorizationMethod.PerRequest;
});

Performance Considerations

Authorization is not free. There is a minor, but real, performance cost to inspecting and evaluating policies on each field. This true regardless of yor choice of PerField or PerRequest authorization. Every secured field still needs to be evaluated, whether it is done up front or as the query progresses. In a REST query, you generally only secure your top-level controller methods, consider doing the same with your GraphQL queries.

tip

Centralize your authorization checks to your controller methods. There is usually no need to apply [Authorize] attributes to each and every method and property across your entire schema.

+ + + + \ No newline at end of file diff --git a/docs/controllers/authorization.md b/docs/controllers/authorization.md deleted file mode 100644 index fe6da09..0000000 --- a/docs/controllers/authorization.md +++ /dev/null @@ -1,191 +0,0 @@ ---- -id: authorization -title: Authorization -sidebar_label: Authorization -sidebar_position: 3 ---- - -## Quick Examples - -If you've wired up ASP.NET authorization before, you'll likely familiar with the `[Authorize]` attribute and how its used to enforce security. - -GraphQL ASP.NET works the same way. - -```csharp title="General Authorization Check" -public class BakeryController : GraphController -{ - // highlight-next-line - [Authorize] - [MutationRoot("orderDonuts", typeof(CompletedDonutOrder))] - public async Task OrderDonuts(DonutOrderModel order) - {/*...*/} -} -``` -```csharp title="Restrict by Policy" -public class BakeryController : GraphController -{ - // highlight-next-line - [Authorize(Policy = "CustomerLoyaltyProgram")] - [MutationRoot("orderDonuts", typeof(CompletedDonutOrder))] - public async Task OrderDonuts(DonutOrderModel order) - {/*...*/} -} -``` -```csharp title="Restrict by Role" -public class BakeryController : GraphController -{ - // highlight-next-line - [Authorize(Roles = "Admin, Employee")] - [MutationRoot("purchaseDough")] - public async Task PurchaseDough(int kilosOfDough) - {/*...*/} -} -``` - -```csharp title="Multiple Authorization Requirements" -// The library supports nested policy and role checks at Controller and Action levels. -// highlight-next-line -[Authorize(Policy = "CurrentCustomer")] -public class BakeryController : GraphController -{ - // The user would have to pass the CurrentCustomer policy - // and the LoyaltyProgram policy to access the `orderDonuts` field - - // highlight-next-line - [Authorize(Policy = "LoyaltyProgram")] - [MutationRoot("orderDonuts", typeof(CompletedDonutOrder))] - public async Task OrderDonuts(DonutOrderModel order) - {/*...*/} -} -``` - -```csharp title="Use of [AllowAnonymous]" -[Authorize] -public class BakeryController : GraphController -{ - [Authorize(Policy = "CustomerLoyaltyProgram")] - [MutationRoot("orderDonuts", typeof(CompletedDonutOrder))] - public async Task OrderDonuts(DonutOrderModel order) - {/*...*/} - - // No Authorization checks on RetrieveDonutList - // highlight-next-line - [AllowAnonymous] - [Mutation("donutList")] - public async Task> RetrieveDonutList() - {/*...*/} -} -``` - -## Use of IAuthorizationService - -Under the hood, GraphQL taps into your `IServiceProvider` to obtain a reference to the `IAuthorizationService` that gets created when you configure `.AddAuthorization()` at startup. Take a look at the [Schema Item Security Pipeline Components](https://github.com/graphql-aspnet/graphql-aspnet/tree/master/src/graphql-aspnet/Middleware/SchemaItemSecurity) for the full picture. - -## When does Authorization Occur? - - -GraphQL ASP.NET makes use of the result from [ASP.NET's security pipeline](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction). Whether you use Kerberos tokens, oauth2, username/password, API tokens or if you support 2-factor authentication or one-time-use passwords, GraphQL doesn't care. The entirety of your authentication and authorization scheme is executed by GraphQL, no special arrangements or configuration is needed. - -> GraphQL ASP.NET draws from your configured authentication/authorization solution. - -Execution directives and field resolutions are passed through the libraries internal [pipeline](../reference/how-it-works#middleware-pipelines) where securty is enforced as a series of middleware components before the respective resolvers are invoked. Should a requestor not be authorized for a given schema item they are informed via an error message and denied access to the item. - - -## Field Authorizations - -If a requestor is not authorized to a requested field a value of `null` is used as the resolved value and an error message is recorded to the query results. - -Null propagation rules still apply to unauthorized fields meaning if the field cannot accept a null value, its propagated up the field chain potentially nulling out a parent or "parent of a parent" depending on your schema. - -By default, a single unauthorized field result does not necessarily kill an entire query, it depends on the structure of your object graph and the query being executed. When a field request is terminated any down-stream child fields are discarded immediately but sibling fields or unrelated ancestors continue to execute as normal. - -Since this authorization occurs "per field" and not "per controller action" its possible to define the same security chain for POCO properties. This allows you to effectively deny access, by policy, to a single property of an instantiated object. Performing security checks for every field of data (especially in parent/child scenarios) has a performance cost though, especially for larger data sets. For most scenarios enforcing security at the controller level is sufficient. - -### Field Authorization Failures are Obfuscated - -When GraphQL denies a requestor access to a field a message naming the field path is added to the response. This message is generic on purpose. Suppose there was a query where the user requests the `allDonuts` field but is denied access: - -```graphql - { - donut(id: 5) { - name - } - allDonuts { - name - } - - } - -``` - -The result might look like this: - -```json title="Denied Field Access" -{ - "errors": [ - // highlight-start - { - "message": "Access Denied to field [query]/allDonuts", - "locations": [ - { - "line": 7, - "column": 3 - } - ], - "path": [ - "allDonuts" - ], - "extensions": { - "code": "ACCESS_DENIED", - "timestamp": "2022-12-22T22:22:25.017-07:00", - "severity": "CRITICAL" - } - } - // highlight-end - ], - "data": { - "donut": { - "name": "Super Mega Donut", - }, - // highlight-next-line - "allDonuts": null - } -} -``` - -:::tip - To view more details authorization failure reasons, such as specific policy failures, you'll need to expose exceptions on the request or turn on [logging](../logging/structured-logging). - - GraphQL automatically raises the `SchemaItemAuthorizationCompleted` log event at a `Warning` level when a security check fails. -::: - -## Authorization on Execution Directives - -Execution directives are applied to the _query document_, before a query plan is created to fulfill the request. However, it is the query plan that determines which field resolvers should be called. As a result, execution directives have the potential to alter the document structure and change how a query plan might be structured. Because of this, not executing a query directive has the potential to cause a the expected query to be different than what the requestor intended. - -Therefore, if an execution directive fails authorization the query is rejected and not executed. The caller will receive an error message as part of the response indicating the unauthorized directive. Like field authorization failures, the message is obfuscated and contains only a generic message. You'll need to expose exception on the request or turn on logging to see additional details. - -## Authorization Methods - -GraphQL ASP.NET supports two methods of applying the authorization rules out of the box. - -- `PerField`: Each field is authorized individually. If a query references some fields the user can access and some they cannot, those fields the user can access are resolved as expected. A `null` value is assigned to the fields the user cannot access. - -- `PerRequest`: All fields that require authorization are authorized at once. If the user is unauthorized on 1 or more fields the entire request is denied and not executed. - -Configure the authorization method at startup: - -```csharp title="Startup" -services.AddGraphQL(schemaOptions => -{ - schemaOptions.AuthorizationOptions.Method = AuthorizationMethod.PerRequest; -}); -``` - -## Performance Considerations - -Authorization is not free. There is a minor, but real, performance cost to inspecting and evaluating policies on each field. This true regardless of yor choice of `PerField` or `PerRequest` authorization. Every secured field still needs to be evaluated, whether it is done up front or as the query progresses. In a REST query, you generally only secure your top-level controller methods, consider doing the same with your GraphQL queries. - -:::tip -Centralize your authorization checks to your controller methods. There is usually no need to apply `[Authorize]` attributes to each and every method and property across your entire schema. -::: \ No newline at end of file diff --git a/docs/controllers/batch-operations.html b/docs/controllers/batch-operations.html new file mode 100644 index 0000000..db69c94 --- /dev/null +++ b/docs/controllers/batch-operations.html @@ -0,0 +1,24 @@ + + + + + +Batch Operations | GraphQL ASP.NET + + + + +
+

Batch Operations

caution

Read the section on type extensions before reading this document. Batch Operations expand on type extensions and understanding how they work is critical.

The N+1 Problem

There are plenty of articles on the web discussing the theory behind the N+1 problem (links below). Instead, we'll jump into an example to illustrate the issue when it comes to GraphQL.

Let's build on our example from the discussion on type extensions where we created an extension to retrieve Cake Orders for a single Bakery. What if we're a national chain and need to see the last 50 orders for each of our stores in a region? This seems like a reasonable thing an auditor would do so lets alter our controller to fetch all our bakeries and then let our type extension fetch the cake orders.

Retrieving Multiple Bakeries
public class Bakery
{
public int Id { get; set; }
public string Name { get; set; }
}

public class BakedGoodsCompanyController : GraphController
{
[QueryRoot("bakeries")]
public async Task<List<Bakery>> RetrieveBakeries(Regions region = Regions.All)
{/*...*/}

// retrieve the cake orders for a single bakery
[TypeExtension(typeof(Bakery), "orders")]
public async Task<List<CakeOrder>> RetrieveCakeOrders(Bakery bakery, int limitTo = 15){

return await _service.RetrieveCakeOrders(bakery.Id, limitTo);
}
}
Sample Query
query {
bakeries(region: SOUTH_WEST){
name
orders(limitTo: 50) {
id
writtenPhrase
}
}
}

Well that was easy, right? Not so fast!

The bakeries field returns a List<Bakery> but the RetrieveCakeOrders method takes in a single Bakery. GraphQL will, for each bakery retrieved, execute the orders field to retrieve its orders. If bakeries retrieved 50 stores in the south west region, graphql will execute RetrieveCakeOrders 50 times, which will execute 50 database queries.

This is the N+1 problem. 1 query for the bakeries + N queries for the cake orders, where N is the number of bakeries first retrieved.

If only we could batch the request and fetch all the cake orders for all the bakeries at once, then assign the Cake Orders back to their respective bakeries, we'd be a lot better off. No matter the number of bakeries retrieved, we'd execute 2 queries; 1 for bakeries and 1 for orders.This is where batch extensions come in to play.

[BatchTypeExtension] Attribute

A batch operation is implemented as a type extension but with the word Batch in it. Lets look at an example:

A Batch Type Extension
public class BakedGoodsCompanyController : GraphController
{
[QueryRoot("bakeries")]
public async Task<List<Bakery>> RetrieveBakeries(Region region){/*...*/}

// declare the batch operation as an extension
[BatchTypeExtension(typeof(Bakery), "orders", typeof(List<CakeOrder>))]
public async Task<IGraphActionResult> RetrieveCakeOrders(
IEnumerable<Bakery> bakeries,
int limitTo = 15)
{
//fetch all the orders at once
var allOrders = await _service.RetrieveCakeOrders(bakeries.Select(x => x.Id), limitTo);

// return the batch of orders
return this.StartBatch()
.FromSource(bakeries, bakery => bakery.Id)
.WithResults(allOrders, cakeOrder => cakeOrder.BakeryId)
.Complete();
}
}

Key things to notice:

  • We've used [BatchTypeExtension] instead of [TypeExtension]
  • The method takes in an IEnumerable<Bakery> instead of a single Bakery.
  • The method returns an action result created from this.StartBatch()

The contents of your extension method is going to vary widely from use case to use case. Here we've forwarded the ids of the bakeries to a service to fetch the orders. The important take away is that RetrieveCakeOrders is now called only once, regardless of how many items are in the IEnumerable<Bakery> parameter.

Data Loaders

You'll often hear the term Data Loaders when reading about GraphQL implementations. Methods that load the child data being requested as a single operation before assigning that data to each of the parents. There is no difference with GraphQL ASP.NET. You still have to write that method. But with the ability to capture action method parameters and clever use of an IGraphActionResult we can combine the data load phase with the assignment phase into a single batch operation, at least on the surface. The aim is to make it easy to read and easier to write.

Returning Data

this.StartBatch() returns a builder to define how you want GraphQL to construct your batch. We need to tell it how each of the child items we fetched maps to the parents that were supplied (if at all).

In the example, we matched on a bakery's primary key selecting Bakery.Id from each of the source items and pairing it against CakeOrder.BakeryId from each of the results. This is enough information for the builder to generate a valid result. Depending on the contents of your data and the type expression of your extension there are few scenarios that emerge:

1 Source -> 1 Result

If you've defined your extension field to be a single item (i.e. CakeOrder instead of IEnumerable<CakeOrder>) GraphQL will enforce the type check and reject/fail the resolution for any parent item mapped to more than one child. This is useful for sibling relationships where two objects might be related but aren't otherwise aggregated together. For example, a person and their spouse.

1 Source -> N Results

If you've defined your extension to return a collection of items, like in the example, then GraphQL will generate an array of 0 or more children for every parent included in the batch.

N Sources -> N Results

To GraphQL, many to many relationships are treated the same as one to many. Internally, it doesn't care how you map your data, only that the type expression of the results are enforced. Each child can map to multiple parents and in the cases of overlap, GraphQL will resolve the requested fields of that child once then render it to each parent in the outgoing response package.

1 Source -> No Results

For 1:1 results there are only two options; either the data exists and a value is returned or it doesn't and null is returned. But with 1:N relationships, sometimes you want to indicate that no results were included for a parent item and sometimes you want to indicate that no results exist. This could be represented as being an empty array vs. null. When working with children, for every parent supplied to this.StartBatch, GraphQL will generate a field result of at least an empty array. To indicate a parent item should receive null instead of [] exclude it from the batch.

Note that it is your method's responsibility to be compliant with the type expression of the field. If a field is marked as NON_NULL and you exclude the parent item from the batch (resulting in a null result for the field for that item) the field will be marked invalid and register an error.

caution

Excluding a source item from this.StartBatch() will result in it receiving null for its resolved field value. Be mindful of your extension's type expression. If you've made the field non-nullable an error will be generated.

Returning IDictionary<TSource, TValue>

Using this.StartBatch is the preferred way of returning data from a batch extension but there is a small amount of overhead to it. It has to join two separate lists of data on a common key, which could take a few extra cycles for large data sets.

Another option would be to generate the same result yourself while you're generating your data set. Once its all said and done, this.StartBatch() creates an IDictionary<TSource, TValue> where TSource is a parent object and TValue is either a single child or an IEnumerable<Child> depending on the type expression of your field. A batch extension is the only operation that will accept a return type of IDictionary.

When returning IDictionary<TSource, TValue>, the key MUST BE the original object reference supplied to the the extension method, not a copy.

This is the example above reconfigured to a custom dictionary. Note that when we use an IDictionary return type, GraphQL is able to infer our field data type and an explicit declaration is no longer needed on the attribute.

Using a Custom Dictionary
public class BakedGoodsCompanyController : GraphController
{
[QueryRoot("bakeries")]
public async Task<List<Bakery>> RetrieveBakeries(Region region){/*...*/}

// declare the batch operation as an extension
[BatchTypeExtension(typeof(Bakery), "orders")]
public async Task<IDictionary<Bakery, IEnumerable<CakeOrder>>> RetrieveCakeOrders(IEnumerable<Bakery> bakeries, int limitTo = 15)
{
//fetch all the orders at once
Dictionary<Bakery, IEnumerable<CakeOrder>> allOrders = await _service
.RetrieveCakeOrders(bakeries, limitTo);

return allOrders;
}
}

Other Resources

What is the N+1 Problem in GraphQL?

+ + + + \ No newline at end of file diff --git a/docs/controllers/batch-operations.md b/docs/controllers/batch-operations.md deleted file mode 100644 index a39a13d..0000000 --- a/docs/controllers/batch-operations.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -id: batch-operations -title: Batch Operations -sidebar_label: Batch Operations -sidebar_position: 5 ---- - -:::caution -Read the section on [type extensions](./type-extensions) before reading this document. Batch Operations expand on type extensions and understanding how they work is critical. -::: - -## The N+1 Problem - -There are plenty of articles on the web discussing the theory behind the N+1 problem ([links below](./batch-operations#other-resources)). Instead, we'll jump into an example to illustrate the issue when it comes to GraphQL. - -Let's build on our example from the discussion on type extensions where we created an extension to retrieve `Cake Orders` for a **single** `Bakery`. What if we're a national chain and need to see the last 50 orders for each of our stores in a region? This seems like a reasonable thing an auditor would do so lets alter our controller to fetch all our bakeries and then let our type extension fetch the cake orders. - -```csharp title="Retrieving Multiple Bakeries" -public class Bakery -{ - public int Id { get; set; } - public string Name { get; set; } -} - -public class BakedGoodsCompanyController : GraphController -{ - [QueryRoot("bakeries")] - // highlight-next-line - public async Task> RetrieveBakeries(Regions region = Regions.All) - {/*...*/} - - // retrieve the cake orders for a single bakery - [TypeExtension(typeof(Bakery), "orders")] - public async Task> RetrieveCakeOrders(Bakery bakery, int limitTo = 15){ - - return await _service.RetrieveCakeOrders(bakery.Id, limitTo); - } -} -``` - -```graphql title="Sample Query" -query { - bakeries(region: SOUTH_WEST){ - name - orders(limitTo: 50) { - id - writtenPhrase - } - } -} -``` - -Well that was easy, right? Not so fast! - - The `bakeries` field returns a `List` but the `RetrieveCakeOrders` method takes in a single `Bakery`. GraphQL will, **for each bakery retrieved**, execute the `orders` field to retrieve its orders. If `bakeries` retrieved 50 stores in the south west region, graphql will execute `RetrieveCakeOrders` 50 times, which will execute 50 database queries. - -This is the N+1 problem. `1 query` for the bakeries + `N queries` for the cake orders, where N is the number of bakeries first retrieved. - -If only we could batch the request and fetch all the cake orders for all the bakeries at once, then assign the `Cake Orders` back to their respective bakeries, we'd be a lot better off. No matter the number of bakeries retrieved, we'd execute 2 queries; 1 for `bakeries` and 1 for `orders`.This is where batch extensions come in to play. - -## \[BatchTypeExtension\] Attribute - -A batch operation is implemented as a type extension but with the word `Batch` in it. Lets look at an example: - -```csharp title="A Batch Type Extension" -public class BakedGoodsCompanyController : GraphController -{ - [QueryRoot("bakeries")] - public async Task> RetrieveBakeries(Region region){/*...*/} - - // declare the batch operation as an extension - // highlight-next-line - [BatchTypeExtension(typeof(Bakery), "orders", typeof(List))] - public async Task RetrieveCakeOrders( - IEnumerable bakeries, - int limitTo = 15) - { - //fetch all the orders at once - var allOrders = await _service.RetrieveCakeOrders(bakeries.Select(x => x.Id), limitTo); - - // return the batch of orders - // highlight-start - return this.StartBatch() - .FromSource(bakeries, bakery => bakery.Id) - .WithResults(allOrders, cakeOrder => cakeOrder.BakeryId) - .Complete(); - // highlight-end - } -} -``` - -Key things to notice: - -- We've used `[BatchTypeExtension]` instead of `[TypeExtension]` -- The method takes in an `IEnumerable` instead of a single `Bakery`. -- The method returns an action result created from `this.StartBatch()` - -The contents of your extension method is going to vary widely from use case to use case. Here we've forwarded the ids of the bakeries to a service to fetch the orders. The important take away is that `RetrieveCakeOrders` is now called only once, regardless of how many items are in the `IEnumerable` parameter. - - -## Data Loaders - -You'll often hear the term `Data Loaders` when reading about GraphQL implementations. Methods that load the child data being requested as a single operation before assigning that data to each of the parents. There is no difference with GraphQL ASP.NET. You still have to write that method. But with the ability to capture action method parameters and clever use of an `IGraphActionResult` we can combine the data load phase with the assignment phase into a single batch operation, at least on the surface. The aim is to make it easy to read and easier to write. - -## Returning Data - -`this.StartBatch()` returns a builder to define how you want GraphQL to construct your batch. We need to tell it how each of the child items we fetched maps to the parents that were supplied (if at all). - -In the example, we matched on a bakery's primary key selecting `Bakery.Id` from each of the source items and pairing it against `CakeOrder.BakeryId` from each of the results. This is enough information for the builder to generate a valid result. Depending on the contents of your data and the type expression of your extension there are few scenarios that emerge: - -**1 Source -> 1 Result** - -If you've defined your extension field to be a single item (i.e. `CakeOrder` instead of `IEnumerable`) GraphQL will enforce the type check and reject/fail the resolution for any parent item mapped to more than one child. This is useful for sibling relationships where two objects might be related but aren't otherwise aggregated together. For example, a person and their spouse. - -**1 Source -> N Results** - -If you've defined your extension to return a collection of items, like in the example, then GraphQL will generate an array of 0 or more children for every parent included in the batch. - -**N Sources -> N Results** - -To GraphQL, many to many relationships are treated the same as one to many. Internally, it doesn't care how you map your data, only that the type expression of the results are enforced. Each child can map to multiple parents and in the cases of overlap, GraphQL will resolve the requested fields of that child once then render it to each parent in the outgoing response package. - -**1 Source -> No Results** - -For 1:1 results there are only two options; either the data exists and a value is returned or it doesn't and `null` is returned. But with 1:N relationships, sometimes you want to indicate that no results were _included_ for a parent item and sometimes you want to indicate that no results _exist_. This could be represented as being an empty array vs. `null`. When working with children, for every parent supplied to `this.StartBatch`, GraphQL will generate a field result of **at least** an empty array. To indicate a parent item should receive `null` instead of `[]` exclude it from the batch. - -Note that it is your method's responsibility to be compliant with the type expression of the field. If a field is marked as `NON_NULL` and you exclude the parent item from the batch (resulting in a null result for the field for that item) the field will be marked invalid and register an error. - -:::caution -Excluding a source item from `this.StartBatch()` will result in it receiving `null` for its resolved field value. Be mindful of your extension's type expression. If you've made the field non-nullable an error will be generated. -::: - -#### Returning `IDictionary` - -Using `this.StartBatch` is the preferred way of returning data from a batch extension but there is a small amount of overhead to it. It has to join two separate lists of data on a common key, which could take a few extra cycles for large data sets. - -Another option would be to generate the same result yourself while you're generating your data set. Once its all said and done, `this.StartBatch()` creates an `IDictionary` where `TSource` is a parent object and `TValue` is either a single child or an `IEnumerable` depending on the type expression of your field. A batch extension is the only operation that will accept a return type of `IDictionary`. - -> When returning `IDictionary`, the key **MUST BE** the original object reference supplied to the the extension method, not a copy. - -This is the example above reconfigured to a custom dictionary. Note that when we use an `IDictionary` return type, GraphQL is able to infer our field data type and an explicit declaration is no longer needed on the attribute. - -```csharp title="Using a Custom Dictionary" -public class BakedGoodsCompanyController : GraphController -{ - [QueryRoot("bakeries")] - public async Task> RetrieveBakeries(Region region){/*...*/} - - // declare the batch operation as an extension - // highlight-start - [BatchTypeExtension(typeof(Bakery), "orders")] - public async Task>> RetrieveCakeOrders(IEnumerable bakeries, int limitTo = 15) - // highlight-end - { - //fetch all the orders at once - Dictionary> allOrders = await _service - .RetrieveCakeOrders(bakeries, limitTo); - - return allOrders; - } -} -``` - -## Other Resources - -[What is the N+1 Problem in GraphQL?](https://itnext.io/what-is-the-n-1-problem-in-graphql-dd4921cb3c1a) diff --git a/docs/controllers/field-paths.html b/docs/controllers/field-paths.html new file mode 100644 index 0000000..731a715 --- /dev/null +++ b/docs/controllers/field-paths.html @@ -0,0 +1,24 @@ + + + + + +Field Paths | GraphQL ASP.NET + + + + +
+

What is a Field Path?

GraphQL is statically typed. All possible fields on all possible objects must be pre-defined and well-known in advance. This is what defines the schema of your graph. Along with this, each field must be "resolvable" in a known and consistant manner. If a user requests the name field of a donut, graphql must know what steps to take in order to generate a data value for that field.

In .NET terms that means each field must be represented by a method or property on some class or struct. Traditionally speaking, this can introduce a lot of overhead in defining intermediate types that do nothing but organize our data.

Let's think about this query:

Sample Query
query {
groceryStore {
bakery {
pastries {
donut(id: 15){
name
flavor
}
}
}
deli {
meats {
beef (id: 23) {
name
cut
}
}
}
}
}

Knowing what we know, you may think we need to create classes for the grocery store, the bakery, pastries, a donut, the deli counter, meats, beef etc. in order to create properties and methods for all those fields. Its a lot of setup for what basically boils down to two methods to retrieve a donut and a cut of beef by their respective ids. Some other GraphQL libraries take this approach and it provides an extreme amount of customization at the cost of being rather verbose.

GraphQL ASP.NET takes a different appraoch and uses a templating pattern similar to what we do with REST controllers we can create rich graphs with very little boiler plate. Adding a new branch to your graph is as simple as defining a path to it in a controller.

Sample Controller
[GraphRoute("groceryStore")]
public class GroceryStoreController : GraphController
{
[Query("bakery/pastries/donut")]
public Donut RetrieveDonut(int id)
{/* ...*/}

[Query("deli/meats/beef")]
public Meat RetrieveCutOfBeef(int id)
{/* ...*/}
}

Internally, for each encountered path segment (e.g. bakery, meats), GraphQL generates a virutal, intermediate graph type to fulfill resolver requests for you and acts as a pass through to your real code. It does this in concert with your real code and performs a lot of checks at start up to ensure that the combination of your real types as well as virutal types can be put together to form a functional graph. If a collision occurs the server will fail to start.

Intermediate Type Names

You may notice some object types in your schema named as Query_Bakery, Mutation_Deli. These are the virtual types generated at runtime to create a valid schema from your path segments.

Declaring Field Paths

Declaring fields works just like it does with a REST query. You can nest fields as deep as you want and spread them across any number of controllers in order to create a rich organizational hierarchy to your data. This is best explained by code, take a look at these two controllers:

BakeryController.cs
[GraphRoute("groceryStore/bakery")]
public class BakeryController : GraphController
{
[Query("pastries/search")]
public IEnumerable<IPastry> SearchPastries(string nameLike)
{/* ... */}

[Query("pastries/recipe")]
public Task<Recipe> RetrieveRecipe(int id)
{/* ... */}

[Query("breadCounter/orders")]
public IEnumerable<BreadOrder> FindOrders(int customerId)
{/* ... */}
}
PharmacyController.cs
[GraphRoute("groceryStore/pharmacy")]
public class PharmacyController : GraphController
{
[Query("employees/search")]
public IPastry SearchEmployees(string nameLike)
{/* ... */}

[QueryRoot("pharmacyHours")]
public HoursOfOperation RetrievePharmacyHours(DayOfTheWeek day)
{/* ... */}

[Query("orders")]
public IEnumerable<Prescription> FindOrders(int customerId)
{/* ... */}
}

And this single query we can perform:

Sample Query
query SearchGroceryStore {
groceryStore {
bakery {
pastries {
search(nameLike: "chocolate"){
name
type
}
recipe(id: 15) {
name
ingredients {
name
}
}
}
}
pharmacy {
orders(customerId: 45123){
dayOrdered
type
doctorsName
}
}
}
pharmacyHours(day: MONDAY){
openAt
closeAt
}
}

With REST, this is probably 4 separate requests or one super contrived request but with GraphQL and a carefully thought out set of field paths we can model our data hierarchy quickly and without over complicating the code. There is no more code in this example than would be required by a REST API; we've just changed how its interpreted at runtime.

Actions Must Have a Unique Path

Each field of each type in your schema must uniquely map to one method or property getter; commonly referred to as its resolver. We can't declare a field twice.

Take this example:

Overloaded Methods
[GraphRoute("bakery")]
public class BakeryController : GraphController
{
// Both Methods represent the same 'orderDonuts' field on the graph

[Mutation]
public BoxOfDonuts OrderDonuts(int quantity){/*...*/}

[Mutation]
public BoxOfDonuts OrderDonuts(string type, int quantity){/*...*/}
}

From a GraphQL perspective this equivilant to trying to define a Bakery type with two fields named orderDonuts. Since both methods map to a field path this would cause a GraphTypeDeclarationException to be thrown when your application starts.

With Web API, the ASP.NET runtime could inspect any combinations of parameters passed on the query string or the POST body to work out which overload to call. You might be thinking, why can't GraphQL inspect the passed input arguments and make the same determination?

Putting aside that it violates the specification, in some cases it probably could. But looking at this example we run into an issue:

[GraphRoute("bakery")]
public class BakeryController : GraphController
{
// Both Methods represent the same 'orderDonuts' field on the object graph
[Mutation]
public Manager OrderDonuts(int quantity, string type){/*...*/}

[Mutation]
public Manager OrderDonuts(string type, int quantity){/*...*/}
}

GraphQL states that input arguments can be passed in any order [Spec § 2.6]. By definition, there is not enough information in the query syntax language to decide which overload to invoke. To combat the issue, the runtime will reject any field that it can't uniquely identify.

No problem through, there are a number of ways fix the conflict.

Declare Explicit Names

You can declare explicit names for each of your methods. Not only does this resolve the method overloading conflict but should an errant refactor of your code occur, your graph fields won't magically be renamed to their new method names and break your front-end.

Use Explicit Field Names
[GraphRoute("bakery")]
public class BakeryController : GraphController
{
// GraphQL treats these fields differently!

[Mutation("orderDonutsByQuantity")]
public Manager OrderDonuts(int quantity){/*...*/}

[Mutation("orderDonutsByType")]
public Manager OrderDonuts(string type, int quantity){/*...*/}
}

But this can feel a bit awkward in some situations so instead...

Change The Hierarchy

Another alternative is to change where in the object graph the field sits. Here we've moved one field to the root mutation type and left the other under the controller's own virtual Bakery type. This can be a good strategy if you have one primary way of interacting with your data and a few auxillary methods such as a quick dozen donuts at the drive thru window or going into the shop and selecting which ones you want.

Change the Field Path
[GraphRoute("bakery")]
public class BakeryController : GraphController
{
[MutationRoot("orderDonuts")]
public IEnumerable<Donut> OrderDonuts(int count)
{/*...*/}

[Mutation("orderDonuts")]
public IEnumerable<Donut> OrderDonuts(
string type,
int count)
{/*...*/}
}
Sample Queries
mutation {
orderDonuts(count: 12) {
name
flavor
}
}

mutation {
bakery {
orderDonuts(type: "Chocolate" count: 3) {
name
flavor
}
}
}

Combine the Fields

Lastly, we can make use of input objects with optional fields and combine parameters into a more robust method.

Use an Input Object
[GraphRoute("bakery")]
public class BakeryController : GraphController
{
[Mutation("orderDonuts")]
public IEnumerable<Donut> OrderDonuts(DonutOrderModel order)
{/*...*/}
}

public class DonutOrderModel
{
public int? Quantity { get; set; }
public string Type { get; set; }
}
Sample Queries
mutation byQuantity {
bakery{
orderDonuts (order: {quantity: 12}){
id
type
}
}
}

mutation byType {
bakery{
orderDonuts (order: {type: "Chocolate" quantity: 12}){
id
type
}
}
}

When you start thinking about large object graphs, 100s of controllers and 100s of types, you have to put some thought in to how you organize your data. Coming up with an intuitive structure to your hierarchy is going to be dependent on your audience and use cases. There is no one-size fits all approach, but with the ability to move graph fields by updating one string, its trivial to build as you iterate.

Field Path Names

info

Each segment of a virtual field path must individually conform to the required naming standards for fields and graph type names.

In reality this primarily means don't start your fields with a double underscore, __, as thats reserved by the introspection system. The complete regex is available in the source code at Constants.RegExPatterns.NameRegex.

These are some valid field paths:

Valid Field Fragments
[Mutation("store/bakery/deliCounter/sandwiches/order")]
[Query("path1/path2/path3/path4/")]
[Mutation("path1/path1/path1/path1/path1/path1/path1/path1/path1")]

But if even one segment of the path is invalid GraphQL will reject it completely.

Invalid Field Fragments
[Query("store/__bakery")]  // can't start with "__"
[Query("store/βakery")] // unicode characters are not allowed
[Query("path1/path2/path 33")] // spaces are not allowed

Field Naming Formats

At runtime, when your schema is generated, the naming requirements it defines for fields will be enforced for each path segment individually. By default, this means camelCasing:

If you declare:

[Mutation("Store/Bakery/DeliCounter")]

You would still query with :

mutation {
store {
bakery {
deliCounter {
...

}
}
}

You can alter the naming formats for fields, enum values and graph types using the declaration options on your schema configuration.

+ + + + \ No newline at end of file diff --git a/docs/controllers/field-paths.md b/docs/controllers/field-paths.md deleted file mode 100644 index 0349b1e..0000000 --- a/docs/controllers/field-paths.md +++ /dev/null @@ -1,348 +0,0 @@ ---- -id: field-paths -title: Field Paths -sidebar_label: Field Paths -sidebar_position: 2 -hide_title: true ---- - -## What is a Field Path? - GraphQL is statically typed. All possible fields on all possible objects must be pre-defined and well-known in advance. This is what defines the schema of your graph. Along with this, each field must be "resolvable" in a known and consistant manner. If a user requests the `name` field of a donut, graphql must know what steps to take in order to generate a data value for that field. - - In .NET terms that means each field must be represented by a method or property on some class or struct. Traditionally speaking, this can introduce a lot of overhead in defining intermediate types that do nothing but organize our data. - -Let's think about this query: - -```graphql title="Sample Query" -query { - groceryStore { - bakery { - pastries { - donut(id: 15){ - name - flavor - } - } - } - deli { - meats { - beef (id: 23) { - name - cut - } - } - } - } -} -``` - -Knowing what we know, you may think we need to create classes for the grocery store, the bakery, pastries, a donut, the deli counter, meats, beef etc. in order to create properties and methods for all those fields. Its a lot of setup for what basically boils down to two methods to retrieve a donut and a cut of beef by their respective ids. Some other GraphQL libraries take this approach and it provides an extreme amount of customization at the cost of being rather verbose. - -GraphQL ASP.NET takes a different appraoch and uses a templating pattern similar to what we do with REST controllers we can create rich graphs with very little boiler plate. Adding a new branch to your graph is as simple as defining a path to it in a controller. - -```csharp title="Sample Controller" -// highlight-next-line -[GraphRoute("groceryStore")] -public class GroceryStoreController : GraphController -{ - // highlight-next-line - [Query("bakery/pastries/donut")] - public Donut RetrieveDonut(int id) - {/* ...*/} - - // highlight-next-line - [Query("deli/meats/beef")] - public Meat RetrieveCutOfBeef(int id) - {/* ...*/} -} -``` - -Internally, for each encountered path segment (e.g. `bakery`, `meats`), GraphQL generates a virutal, intermediate graph type to fulfill resolver requests for you and acts as a pass through to your real code. It does this in concert with your real code and performs a lot of checks at start up to ensure that the combination of your real types as well as virutal types can be put together to form a functional graph. If a collision occurs the server will fail to start. - -:::info Intermediate Type Names -You may notice some object types in your schema named as `Query_Bakery`, `Mutation_Deli`. These are the virtual types generated at runtime to create a valid schema from your path segments. -::: - -## Declaring Field Paths - -Declaring fields works just like it does with a REST query. You can nest fields as deep as you want and spread them across any number of controllers in order to create a rich organizational hierarchy to your data. This is best explained by code, take a look at these two controllers: - -```csharp title="BakeryController.cs" -// highlight-next-line -[GraphRoute("groceryStore/bakery")] -public class BakeryController : GraphController -{ - // highlight-next-line - [Query("pastries/search")] - public IEnumerable SearchPastries(string nameLike) - {/* ... */} - - // highlight-next-line - [Query("pastries/recipe")] - public Task RetrieveRecipe(int id) - {/* ... */} - - // highlight-next-line - [Query("breadCounter/orders")] - public IEnumerable FindOrders(int customerId) - {/* ... */} -} -``` -```csharp title="PharmacyController.cs" -// highlight-next-line -[GraphRoute("groceryStore/pharmacy")] -public class PharmacyController : GraphController -{ - // highlight-next-line - [Query("employees/search")] - public IPastry SearchEmployees(string nameLike) - {/* ... */} - - // highlight-next-line - [QueryRoot("pharmacyHours")] - public HoursOfOperation RetrievePharmacyHours(DayOfTheWeek day) - {/* ... */} - - // highlight-next-line - [Query("orders")] - public IEnumerable FindOrders(int customerId) - {/* ... */} -} -``` - -And this single query we can perform: - -```graphql title="Sample Query" -query SearchGroceryStore { - groceryStore { - bakery { - pastries { - search(nameLike: "chocolate"){ - name - type - } - recipe(id: 15) { - name - ingredients { - name - } - } - } - } - pharmacy { - orders(customerId: 45123){ - dayOrdered - type - doctorsName - } - } - } - pharmacyHours(day: MONDAY){ - openAt - closeAt - } -} -``` - -With REST, this is probably 4 separate requests or one super contrived request but with GraphQL and a carefully thought out set of field paths we can model our data hierarchy quickly and without over complicating the code. There is no more code in this example than would be required by a REST API; we've just changed how its interpreted at runtime. - -## Actions Must Have a Unique Path - -Each field of each type in your schema must uniquely map to one method or property getter; commonly referred to as its resolver. We can't declare a field twice. - -Take this example: - -```csharp title="Overloaded Methods" -[GraphRoute("bakery")] -public class BakeryController : GraphController -{ - // Both Methods represent the same 'orderDonuts' field on the graph - - [Mutation] - // highlight-next-line - public BoxOfDonuts OrderDonuts(int quantity){/*...*/} - - [Mutation] - // highlight-next-line - public BoxOfDonuts OrderDonuts(string type, int quantity){/*...*/} -} -``` - -From a GraphQL perspective this equivilant to trying to define a `Bakery` type with two fields named `orderDonuts`. Since both methods map to a field path this would cause a `GraphTypeDeclarationException` to be thrown when your application starts. - -With Web API, the ASP.NET runtime could inspect any combinations of parameters passed on the query string or the POST body to work out which overload to call. You might be thinking, why can't GraphQL inspect the passed input arguments and make the same determination? - -Putting aside that it [violates the specification](http://spec.graphql.org/October2021/#sec-Objects), in some cases it probably could. But looking at this example we run into an issue: - -```csharp -[GraphRoute("bakery")] -public class BakeryController : GraphController -{ - // Both Methods represent the same 'orderDonuts' field on the object graph - [Mutation] - public Manager OrderDonuts(int quantity, string type){/*...*/} - - [Mutation] - public Manager OrderDonuts(string type, int quantity){/*...*/} -} -``` -GraphQL states that input arguments can be passed in any order [Spec § [2.6](https://graphql.github.io/graphql-spec/October2021/#sec-Language.Arguments)]. By definition, there is not enough information in the query syntax language to decide which overload to invoke. To combat the issue, the runtime will reject any field that it can't uniquely identify. - -No problem through, there are a number of ways fix the conflict. - -### Declare Explicit Names - -You can declare explicit names for each of your methods. Not only does this resolve the method overloading conflict but should an errant refactor of your code occur, your graph fields won't magically be renamed to their new method names and break your front-end. - -```csharp title="Use Explicit Field Names" -[GraphRoute("bakery")] -public class BakeryController : GraphController -{ - // GraphQL treats these fields differently! - - // highlight-next-line - [Mutation("orderDonutsByQuantity")] - public Manager OrderDonuts(int quantity){/*...*/} - - // highlight-next-line - [Mutation("orderDonutsByType")] - public Manager OrderDonuts(string type, int quantity){/*...*/} -} -``` - -But this can feel a bit awkward in some situations so instead... - -### Change The Hierarchy - -Another alternative is to change where in the object graph the field sits. Here we've moved one field to the root mutation type and left the other under the controller's own virtual `Bakery` type. This can be a good strategy if you have one primary way of interacting with your data and a few auxillary methods such as a quick dozen donuts at the drive thru window or going into the shop and selecting which ones you want. - - -```csharp title="Change the Field Path" -[GraphRoute("bakery")] -public class BakeryController : GraphController -{ - // highlight-next-line - [MutationRoot("orderDonuts")] - public IEnumerable OrderDonuts(int count) - {/*...*/} - - // highlight-next-line - [Mutation("orderDonuts")] - public IEnumerable OrderDonuts( - string type, - int count) - {/*...*/} -} -``` - -```graphql title="Sample Queries" -mutation { - orderDonuts(count: 12) { - name - flavor - } -} - -mutation { - bakery { - orderDonuts(type: "Chocolate" count: 3) { - name - flavor - } - } -} -``` - -### Combine the Fields - -Lastly, we can make use of input objects with optional fields and combine parameters into a more robust method. - -```csharp title="Use an Input Object" -[GraphRoute("bakery")] -public class BakeryController : GraphController -{ - [Mutation("orderDonuts")] - // highlight-next-line - public IEnumerable OrderDonuts(DonutOrderModel order) - {/*...*/} -} - -public class DonutOrderModel -{ - public int? Quantity { get; set; } - public string Type { get; set; } -} -``` - -```graphql title="Sample Queries" -mutation byQuantity { - bakery{ - orderDonuts (order: {quantity: 12}){ - id - type - } - } -} - -mutation byType { - bakery{ - orderDonuts (order: {type: "Chocolate" quantity: 12}){ - id - type - } - } -} -``` - -When you start thinking about large object graphs, 100s of controllers and 100s of types, you have to put some thought in to how you organize your data. Coming up with an intuitive structure to your hierarchy is going to be dependent on your audience and use cases. There is no one-size fits all approach, but with the ability to move graph fields by updating one string, its trivial to build as you iterate. - -## Field Path Names - -:::info - Each segment of a virtual field path must individually conform to the required naming standards for fields and graph type names. -::: - -In reality this primarily means don't start your fields with a double underscore, `__`, as thats reserved by the introspection system. The complete regex is available in the source code at `Constants.RegExPatterns.NameRegex`. - -These are some valid field paths: - -```csharp title="Valid Field Fragments" -[Mutation("store/bakery/deliCounter/sandwiches/order")] -[Query("path1/path2/path3/path4/")] -[Mutation("path1/path1/path1/path1/path1/path1/path1/path1/path1")] -``` - -But if even one segment of the path is invalid GraphQL will reject it completely. - -```csharp title="Invalid Field Fragments" -[Query("store/__bakery")] // can't start with "__" -[Query("store/βakery")] // unicode characters are not allowed -[Query("path1/path2/path 33")] // spaces are not allowed -``` - -### Field Naming Formats - -At runtime, when your schema is generated, the naming requirements it defines for fields will be enforced for each path segment individually. By default, this means `camelCasing`: - -If you declare: - -```csharp -[Mutation("Store/Bakery/DeliCounter")] -``` - -You would still query with : - -```javascript -mutation { - store { - bakery { - deliCounter { - ... - - } - } -} -``` - -You can alter the naming formats for fields, enum values and graph types using the declaration options on your [schema configuration](../reference/schema-configuration#graphnamingformatter). - diff --git a/docs/controllers/model-state.html b/docs/controllers/model-state.html new file mode 100644 index 0000000..8a6a0bd --- /dev/null +++ b/docs/controllers/model-state.html @@ -0,0 +1,28 @@ + + + + + +Model State | GraphQL ASP.NET + + + + +
+

Model State

What is Model State?

GraphQL, as a language, can easily enforce query level requirements like :

✅ The data must a collection.
+✅ The data value cannot be null.
+✅ The argument 'zipCode' must be supplied as a string.


But it fails to enforce the individual business level requirements:

🧨 The employee's last name must be less than 70 characters.
+🧨 A customer's phone number should be 7 or 10 digits.
+🧨 A customer must order at least 1 donut.

This is where model state can come in handy. Its completely optional, but if you choose to make use of it, it provides a handy way to inforce business level requirements in your action methods.

Using Model Validation

When your controller action is invoked the runtime will analyze the input parameters and execute the validation attributes attached to each object to determine its validation state. This works in the same way as it does with a Web API controller.

In this example we use the [Range] attribute to limit the quantity of donuts that can be ordered to three dozen.

DonutOrderModel.cs
public class DonutOrderModel
{
[Range(1, 36)]
public int Quantity { get; set; }
public string Type { get; set; }
}
BakeryController.cs
public class BakeryController : GraphController
{
//constructor with service injection omitted for brevity...

[MutationRoot("orderDonuts", typeof(CompletedDonutOrder))]
public async Task<IGraphActionResult> OrderDonuts(DonutOrderModel order)
{
if (!this.ModelState.IsValid)
return this.BadRequest(this.ModelState);

var result = await _service.PlaceDonutOrder(order);
return this.Ok(result);
}
}
Sample Query
# A valid query
# that breaks a required business rule
mutation {
orderDonuts(order: {quantity: 60, type: "glazed"}) {
id
name
}
}

Just like with ASP.NET, this.ModelState contains an entry for each "validatiable" object passed to the method and its current validation state (valid, invalid, skipped etc.) along with all the rules that did not pass. Also, just like with ASP.NET you can define custom attributes that inherit from ValidationAttribute and GraphQL will execute them as well.

In the example, we returned a IGraphActionResult to make use of this.BadRequest() which will add the friendly error messages to the outgoing response automatically. But we could have easily just returned null, thrown an exception or generated a generic custom error message. However you choose to deal with ModelState is up to you.

tip

GraphQL will validate the data but it doesn't take action when model validation fails. That's up to you. If a valid query is provided your action method will be invoked and executed.


⚠️ Implementation Note

The library makes use of the same System.ComponentModel.DataAnnotations.Validator that ASP.NET does to validate its input objects. All the applicable rules that apply to Web API model validation also apply to your controllers and models.

However, where Web API will validate model binding rules and represent binding errors it its ModelState object (such as invalid or missing property names) the GraphQL implementation will not. GraphQL binding issues such as invalid type expressions, nullability and missing arguments are taken care of at the query level, long before a query plan is finalized and the action method is invoked. In fact, your action methods won't even be invoked unless all the correct data was supplied and the query was properly structured.

The model state of GraphQL ASP.NET is a close approximation of Web API's model state object, but it is not an exact match.

+ + + + \ No newline at end of file diff --git a/docs/controllers/model-state.md b/docs/controllers/model-state.md deleted file mode 100644 index ff8d5a9..0000000 --- a/docs/controllers/model-state.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -id: model-state -title: Model State -sidebar_label: Model State -sidebar_position: 1 ---- - -## What is Model State? - -GraphQL, as a language, can easily enforce query level requirements like : - -✅ The data must a collection.
-✅ The data value cannot be null.
-✅ The argument 'zipCode' must be supplied as a string. - -
- -But it fails to enforce the individual business level requirements: - -🧨 The employee's last name must be less than 70 characters.
-🧨 A customer's phone number should be 7 or 10 digits.
-🧨 A customer must order at least 1 donut. - -This is where model state can come in handy. Its completely optional, but if you choose to make use of it, it provides a handy way to inforce business level requirements in your action methods. - -## Using Model Validation - -When your controller action is invoked the runtime will analyze the input parameters and execute the validation attributes attached to each object to determine its validation state. This works in the same way as it does with a Web API controller. - -In this example we use the `[Range]` attribute to limit the quantity of donuts that can be ordered to three dozen. - -```csharp title="DonutOrderModel.cs" -public class DonutOrderModel -{ - // highlight-next-line - [Range(1, 36)] - public int Quantity { get; set; } - public string Type { get; set; } -} -``` - -```csharp title="BakeryController.cs" -public class BakeryController : GraphController -{ - //constructor with service injection omitted for brevity... - - [MutationRoot("orderDonuts", typeof(CompletedDonutOrder))] - public async Task OrderDonuts(DonutOrderModel order) - { - // highlight-start - if (!this.ModelState.IsValid) - return this.BadRequest(this.ModelState); - // highlight-end - - var result = await _service.PlaceDonutOrder(order); - return this.Ok(result); - } -} -``` - - -```graphql title="Sample Query" -# A valid query -# that breaks a required business rule -mutation { - orderDonuts(order: {quantity: 60, type: "glazed"}) { - id - name - } -} -``` - -Just like with ASP.NET, `this.ModelState` contains an entry for each "validatiable" object passed to the method and its current validation state (valid, invalid, skipped etc.) along with all the rules that did not pass. Also, just like with ASP.NET you can define custom attributes that inherit from `ValidationAttribute` and GraphQL will execute them as well. - -In the example, we returned a IGraphActionResult to make use of `this.BadRequest()` which will add the friendly error messages to the outgoing response automatically. But we could have easily just returned null, thrown an exception or generated a generic custom error message. However you choose to deal with `ModelState` is up to you. - -:::tip -GraphQL will validate the data but it doesn't take action when model validation fails. That's up to you. If a valid query is provided your action method will be invoked and executed. -::: - -
- -⚠️ **Implementation Note** - -The library makes use of the same `System.ComponentModel.DataAnnotations.Validator` that ASP.NET does to validate its input objects. [All the applicable rules](https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-7.0) that apply to Web API model validation also apply to your controllers and models. - -However, where Web API will validate model binding rules and represent binding errors it its ModelState object (such as invalid or missing property names) the GraphQL implementation will not. GraphQL binding issues such as invalid type expressions, nullability and missing arguments are taken care of at the query level, long before a query plan is finalized and the action method is invoked. In fact, your action methods won't even be invoked unless all the correct data was supplied and the query was properly structured. - -The model state of GraphQL ASP.NET is a close approximation of Web API's model state object, but it is not an exact match. \ No newline at end of file diff --git a/docs/controllers/type-extensions.html b/docs/controllers/type-extensions.html new file mode 100644 index 0000000..542ddaf --- /dev/null +++ b/docs/controllers/type-extensions.html @@ -0,0 +1,28 @@ + + + + + +Type Extensions | GraphQL ASP.NET + + + + +
+

Type Extensions

Working with Child Data

The Motiviation for using Type Extensions

Before we dive into type extensions we have to talk about parent-child relationships. So far, the examples we've seen have used well defined fields in an object graph. Be that an action method on a controller or a property on an object. But when we think about real world data, there are scenarios where that poses a problem. Lets suppose for a moment we have a chain of bakery stores that let customers place orders for cakes at an individual store and customize the writing on the cake.

Sample Bakery Model
public class Bakery
{
public int Id { get; set; }
public List<CakeOrder> Orders { get; set; }
}

public class CakeOrder
{
public Customer Customer { get; set; }
public string WrittenPhrase { get; set; }
public Bakery Bakery { get; set; }
}

// ...Customer class excluded for brevity

But consider the following scenarios:

  • What happens when we retrieve a single CakeOrder via a controller?
  • Do we automatically have to populate the entire Bakery and Customer objects?
    • Even if a caller didn't request any of that data?
  • What happens when retrieving a bakery that may have 1000s of cake orders?

Our application is going to slow to a crawl very quickly doing all this extra data loading. In the case of a single Bakery, a timeout may occur trying to fetch many years of cake orders to populate the bakery instance from a database query only to discard them when a graphql query doesn't ask for it. If we're using something like Entity Framework how do we know when to use an Include statement to populate the child data? (Hint: you don't)

One solution could be to use lazy loading on our model.

Lazy Loading Child Data (Terrible!)
public class Bakery
{

private ICakeService _service;
private Lazy<List<CakeOrder>> _orders;

public Bakery(int id, ICakeService service)
{
this.Id = id;
_service = service;
_orders = new Lazy<List<CakeOrder>>(this.RetrieveCakeOrders);
}

private List<CakeOrder> RetrieveCakeOrders()
{
return _service.RetrieveCakeOrders(this.Id);
}

public int Id { get; }
public List<CakeOrder> Orders => _orders.Value;
}

Well that's just plain awful. We've over complicated our bakery model and made it dependent on a service instance to exist. If this was a real world example, you'd need some sort of error handling in there too.

The [TypeExtension] Attribute

We've talked before about GraphQL maintaining a 1:1 mapping between a field in the graph and a method to retrieve data for it (i.e. its assigned resolver). What prevents us from creating a method to fetch a list of Cake Orders and saying, "Hey, GraphQL! When someone asks for a set of bakery orders call a custom method instead of a property getter on the Bakery class." As it turns out, that is exactly what a Type Extension does.

Using a Type Extension
public class Bakery
{
public int Id { get; set; }
public string Name { get; set; }
}

public class BakedGoodsCompanyController : GraphController
{
[QueryRoot("bakery")]
public Bakery RetrieveBakery(int id){/*...*/}

// declare a extension to the Bakery object
[TypeExtension(typeof(Bakery), "orders")]
public async Task<List<CakeOrder>> RetrieveCakeOrders(Bakery bakery, int limitTo = 15)
{
return await _service.RetrieveCakeOrders(bakery.Id, limitTo);
}
}

Much Cleaner!!

There is a lot to unpack here, so lets step through it:

  • We've declared the RetrieveBakery method as a root field named bakery that allows us to fetch a single bakery.
  • We've added a method named RetrieveCakeOrders, declared it as an extension to the Bakery object and gave it a field name of orders.
  • The extension returns List<CakeOrder> as the type of data it generates.
  • The method takes in a Bakery instance (more on that in a second) as well as an integer, with a default value of 15, to limit the number of orders to retrieve.

Now we can query the orders field from anywhere a bakery is returned in the object graph and GraphQL will invoke our method:

Sample Query
query {
bakery(id: 5){
name
orders(limitTo: 50) {
id
writtenPhrase
}
}
}
tip

Type Extensions allow you to attach new fields to a graph type without altering the original System.Type.

❓ But what about the Bakery parameter?

When we return a value from a property, an instance of an object must exist in order to supply that value. That is to say if you want the Name property of a bakery, you need a bakery instance to retrieve it from. The same is true for a type extension except that instead of calling a property getter on the instance, graphql hands the entire object to your method and lets you figure out what needs to happen to resolve the field.

GraphQL inspects the type being extended and finds a parameter on the method to match it. It captures that parameter, hides it from the object graph, and fills it with the result of the parent field, in this case the resolution of field bakery(id: 5).

This is immensely scalable:

✅ There are no wasted cycles fetching CakeOrders unless the requestor specifically asks for them.
+✅ We have full access to type expression validation and model validation for our other method parameters.
+✅Since its a controller action we have full access to graph action results and can return this.Ok(), this.Error() etc. to give a rich experience.
+✅Field Security and use of the [Authorize] attribute is also wired up for us.
+✅The bakery model is greatly simplified.

Can Every Field be a Type Extension?

Theoretically, yes. But take a moment and think about performance. For basic objects with few dozen properties which is faster:

  • Option 1: One database query to retrieve 24 columns of a single record then only use six in a data result
  • Option 2: Six separate database queries, one for each column requested.

Type extensions shine in parent-child relationships when preloading lots of data is a concern. But be careful not to isolate every graph field just to avoid retrieving extra data at all. Fetching a few extra bytes from a database is negligible compared to querying a database 20 individual times. Your REST APIs were already querying extra data and they were likely transmitting that data to the client.

It comes down to your use case. There are times when it makes sense to seperate things using type extensions and times when preloading whole objects is better. For many applications, once you've deployed to production, the queries being executed are finite. Design your model objects and extensions to be performant in the ways your data is being requested, not in the ways it could be requested.

+ + + + \ No newline at end of file diff --git a/docs/controllers/type-extensions.md b/docs/controllers/type-extensions.md deleted file mode 100644 index 41a9ec5..0000000 --- a/docs/controllers/type-extensions.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -id: type-extensions -title: Type Extensions -sidebar_label: Type Extensions -sidebar_position: 4 ---- - -## Working with Child Data -_The Motiviation for using Type Extensions_ - -Before we dive into type extensions we have to talk about parent-child relationships. So far, the examples we've seen have used well defined fields in an object graph. Be that an action method on a controller or a property on an object. But when we think about real world data, there are scenarios where that poses a problem. Lets suppose for a moment we have a chain of bakery stores that let customers place orders for cakes at an individual store and customize the writing on the cake. - -```csharp title="Sample Bakery Model" -public class Bakery -{ - public int Id { get; set; } - // highlight-next-line - public List Orders { get; set; } -} - -public class CakeOrder -{ - public Customer Customer { get; set; } - public string WrittenPhrase { get; set; } - // highlight-next-line - public Bakery Bakery { get; set; } -} - -// ...Customer class excluded for brevity -``` - -But consider the following scenarios: - -- What happens when we retrieve a single `CakeOrder` via a controller? -- Do we automatically have to populate the entire `Bakery` and `Customer` objects? - - Even if a caller didn't request any of that data? -- What happens when retrieving a bakery that may have 1000s of cake orders? - -Our application is going to slow to a crawl very quickly doing all this extra data loading. In the case of a single Bakery, a timeout may occur trying to fetch many years of cake orders to populate the bakery instance from a database query only to discard them when a graphql query doesn't ask for it. If we're using something like Entity Framework how do we know when to use an Include statement to populate the child data? (Hint: you don't) - -One solution could be to use lazy loading on our model. - -```csharp title="Lazy Loading Child Data (Terrible!)" -public class Bakery -{ - - private ICakeService _service; - private Lazy> _orders; - - public Bakery(int id, ICakeService service) - { - this.Id = id; - _service = service; - _orders = new Lazy>(this.RetrieveCakeOrders); - } - - private List RetrieveCakeOrders() - { - return _service.RetrieveCakeOrders(this.Id); - } - - public int Id { get; } - public List Orders => _orders.Value; -} -``` - -Well that's just plain awful. We've over complicated our bakery model and made it dependent on a service instance to exist. If this was a real world example, you'd need some sort of error handling in there too. - -## The [TypeExtension] Attribute - -We've talked before about GraphQL maintaining a 1:1 mapping between a field in the graph and a method to retrieve data for it (i.e. its assigned resolver). What prevents us from creating a method to fetch a list of Cake Orders and saying, "Hey, GraphQL! When someone asks for a set of bakery orders call a custom method instead of a property getter on the `Bakery` class." As it turns out, that is exactly what a `Type Extension` does. - -```csharp title="Using a Type Extension" -public class Bakery -{ - public int Id { get; set; } - public string Name { get; set; } -} - -public class BakedGoodsCompanyController : GraphController -{ - [QueryRoot("bakery")] - public Bakery RetrieveBakery(int id){/*...*/} - - // declare a extension to the Bakery object - // highlight-next-line - [TypeExtension(typeof(Bakery), "orders")] - public async Task> RetrieveCakeOrders(Bakery bakery, int limitTo = 15) - { - return await _service.RetrieveCakeOrders(bakery.Id, limitTo); - } -} -``` - -Much Cleaner!! - -There is a lot to unpack here, so lets step through it: - -- We've declared the `RetrieveBakery` method as a root field named `bakery` that allows us to fetch a single bakery. -- We've added a method named `RetrieveCakeOrders`, declared it as an _extension_ to the `Bakery` object and gave it a field name of `orders`. -- The extension returns `List` as the type of data it generates. -- The method takes in a `Bakery` instance (more on that in a second) as well as an integer, with a default value of `15`, to limit the number of orders to retrieve. - -Now we can query the `orders` field from anywhere a bakery is returned in the object graph and GraphQL will invoke our method: - -```graphql title="Sample Query" -query { - bakery(id: 5){ - name - orders(limitTo: 50) { - id - writtenPhrase - } - } -} -``` - -:::tip -Type Extensions allow you to attach new fields to a graph type without altering the original `System.Type`. -::: - -#### ❓ But what about the Bakery parameter? - -When we return a value from a property, an instance of an object must exist in order to supply that value. That is to say if you want the `Name` property of a bakery, you need a bakery instance to retrieve it from. The same is true for a `type extension` except that instead of calling a property getter on the instance, graphql hands the entire object to your method and lets you figure out what needs to happen to resolve the field. - -GraphQL inspects the type being extended and finds a parameter on the method to match it. It captures that parameter, hides it from the object graph, and fills it with the result of the parent field, in this case the resolution of field `bakery(id: 5)`. - -This is immensely scalable: - -✅ There are no wasted cycles fetching `CakeOrders` unless the requestor specifically asks for them.
-✅ We have full access to [type expression validation](../advanced/type-expressions) and [model validation](./model-state) for our other method parameters.
-✅Since its a controller action we have full access to graph action results and can return `this.Ok()`, `this.Error()` etc. to give a rich experience.
-✅[Field Security](./authorization) and use of the `[Authorize]` attribute is also wired up for us.
-✅The bakery model is greatly simplified. - -## Can Every Field be a Type Extension? - -Theoretically, yes. But take a moment and think about performance. For basic objects with few dozen properties which is faster: - -- _Option 1:_ One database query to retrieve 24 columns of a single record then only use six in a data result -- _Option 2:_ Six separate database queries, one for each column requested. - -Type extensions shine in parent-child relationships when preloading lots of data is a concern. But be careful not to isolate every graph field just to avoid retrieving extra data at all. Fetching a few extra bytes from a database is negligible compared to querying a database 20 individual times. Your REST APIs were already querying extra data and they were likely transmitting that data to the client. - -It comes down to your use case. There are times when it makes sense to seperate things using type extensions and times when preloading whole objects is better. For many applications, once you've deployed to production, the queries being executed are finite. Design your model objects and extensions to be performant in the ways your data is being requested, not in the ways it _could be_ requested. \ No newline at end of file diff --git a/docs/development/_category_.json b/docs/development/_category_.json deleted file mode 100644 index 1e9c1e6..0000000 --- a/docs/development/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Development Concerns", - "position": 7, - "collapsed": false -} \ No newline at end of file diff --git a/docs/development/debugging.html b/docs/development/debugging.html new file mode 100644 index 0000000..1c5c252 --- /dev/null +++ b/docs/development/debugging.html @@ -0,0 +1,24 @@ + + + + + +Debugging Your Schema | GraphQL ASP.NET + + + + +
+

Debugging Your Schema

Disable Field Asynchronousity

GraphQL will execute sibling fields asynchronously during normal operation. This includes multiple top-level controller action calls. However, during a debugging session, having multiple fields trying to resolve themselves can play havoc with your debug cursor. If you've ever encountered a situation where the yellow line in Visual Studio seemly jumps around to random lines of code then you've experienced this issue.

At startup, it can help to disable asynchronous field resolution and instead force each field to execute in sequential order awaiting its completion before beginning the next one.

Configure Debug Mode
services.AddGraphQL(options =>
{
// Enable debug mode for the schema
options.ExecutionOptions.DebugMode = true;
});
Performance Killer

Don't forget to disable debug mode in production though. Awaiting fields individually will significantly impact performance.

+ + + + \ No newline at end of file diff --git a/docs/development/debugging.md b/docs/development/debugging.md deleted file mode 100644 index 372bcc8..0000000 --- a/docs/development/debugging.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -id: debugging -title: Debugging Your Schema -sidebar_label: Debugging -sidebar_position: 0 ---- - -## Disable Field Asynchronousity - -GraphQL will execute sibling fields asynchronously during normal operation. This includes multiple top-level controller action calls. However, during a debugging session, having multiple fields trying to resolve themselves can play havoc with your debug cursor. If you've ever encountered a situation where the yellow line in Visual Studio seemly jumps around to random lines of code then you've experienced this issue. - -At startup, it can help to disable asynchronous field resolution and instead force each field to execute in sequential order awaiting its completion before beginning the next one. - -```csharp title="Configure Debug Mode" -services.AddGraphQL(options => -{ - // Enable debug mode for the schema - options.ExecutionOptions.DebugMode = true; -}); -``` -:::danger Performance Killer -Don't forget to disable debug mode in production though. Awaiting fields individually will _**significantly**_ impact performance. -::: \ No newline at end of file diff --git a/docs/development/entity-framework.html b/docs/development/entity-framework.html new file mode 100644 index 0000000..dca9c4b --- /dev/null +++ b/docs/development/entity-framework.html @@ -0,0 +1,24 @@ + + + + + +Using Entity Framework | GraphQL ASP.NET + + + + +
+

Using Entity Framework

DbContext and Parallel Query Operations

In a standard REST application we would register our DbContext like so:

Adding Entity Framework at Startup
services.AddDbContext<AppDbContext>(o =>
{
o.UseSqlServer("<connectionString>");
});

This default registration adds the DbContext to the DI container is as a Scoped service. Meaning one instance is generated per Http request. However, consider the following graph controller and query:

FoodController.cs
public class FoodController : GraphController
{
private AppDbContext _context;
public FoodController(AppDbContext context){/**/}

[QueryRoot]
public IFood SearchMeat(string name){/**/}

[QueryRoot]
public IFood SearchVeggies(string name){/**/}
}
Sample Query
query {
searchMeat(name: "steak*") {
name
}
searchVeggies(name: "green*") {
name
}
}

The FoodController contains two action methods both of which are requested. Since this is a query and not a mutation, both top-level action methods are executed in parallel. This can result in an exception being thrown:

Ef Core Error

This is caused by graphql attempting to execute both controller actions simultaneously. EF Core will reject multiple active queries. There are a few ways to address this and each comes with its own trade offs:

Register DbContext as Transient

One way to correct this problem is to register your DbContext as a transient object.

Option 1: Register DbContext as Transient
services.AddDbContext<AppDbContext>(
options =>
{
options.UseSqlServer("<connectionString>");
},
ServiceLifetime.Transient);

Now each invocation will get its own DbContext and the queries can execute in parallel without issue.

The tradeoff here is that you lose the singular scoped unit-of-work for the whole request granted by a shared context.

If you have services registered to the DI container that make use of the DbContext you would want to register them as Transient as well lest one scoped service be created for the request trapping a single DbContext instance. Sometimes, however; this is unavoidable, especially with legacy code...

Execute Controller Actions in Isolation

Another option is to instruct graphql to execute its controller actions in sequence, rather than in parallel.

Option 2: Isolate GraphQL Controller Actions
services.AddGraphQL(o =>
{
o.ExecutionOptions.ResolverIsolation = ResolverIsolationOptions.ControllerActions;
});

This will instruct graphql to execute each encountered controller action one after the other. Your scoped DbContext would then be able to process the queries without issue.

The tradeoff with this method is a slight increase in processing time since the methods are called in sequence. All other field resolutions would be executed in parallel.

If your application has other resources or services that may have similar restrictions, it can be beneficial to isolate the other resolver types as well. You can add them to the ResolverIsolation configuration option as needed.

+ + + + \ No newline at end of file diff --git a/docs/development/entity-framework.md b/docs/development/entity-framework.md deleted file mode 100644 index d1bba4f..0000000 --- a/docs/development/entity-framework.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -id: entity-framework -title: Using Entity Framework -sidebar_label: Entity Framework -sidebar_position: 2 ---- - -## DbContext and Parallel Query Operations -In a standard REST application we would register our `DbContext` like so: - -```csharp title="Adding Entity Framework at Startup" -// highlight-next-line -services.AddDbContext(o => -{ - o.UseSqlServer(""); -}); -``` -This default registration adds the `DbContext` to the DI container is as a `Scoped` service. Meaning one instance is generated per Http request. However, consider the following graph controller and query: - - - -```csharp title="FoodController.cs" -public class FoodController : GraphController -{ - private AppDbContext _context; - public FoodController(AppDbContext context){/**/} - - [QueryRoot] - // highlight-next-line - public IFood SearchMeat(string name){/**/} - - [QueryRoot] - // highlight-next-line - public IFood SearchVeggies(string name){/**/} -} -``` - -```graphql title="Sample Query" -query { - # highlight-next-line - searchMeat(name: "steak*") { - name - } - # highlight-next-line - searchVeggies(name: "green*") { - name - } -} -``` - -The `FoodController` contains two action methods both of which are requested. Since this is a query and not a mutation, both top-level action methods are executed in parallel. This can result in an exception being thrown: - -![Ef Core Error](../assets/ef-core-error.png) - -This is caused by graphql attempting to execute both controller actions simultaneously. EF Core will reject multiple active queries. There are a few ways to address this and each comes with its own trade offs: - -### Register DbContext as Transient - -One way to correct this problem is to register your DbContext as a transient object. - -```csharp title="Option 1: Register DbContext as Transient" -services.AddDbContext( - options => - { - options.UseSqlServer(""); - }, - // highlight-next-line - ServiceLifetime.Transient); -``` -Now each invocation will get its own DbContext and the queries can execute in parallel without issue. - -The tradeoff here is that you lose the singular scoped unit-of-work for the whole request granted by a shared context. - -If you have services registered to the DI container that make use of the DbContext you would want to register them as `Transient` as well lest one scoped service be created for the request trapping a single DbContext instance. Sometimes, however; this is unavoidable, especially with legacy code... - -### Execute Controller Actions in Isolation -Another option is to instruct graphql to execute its controller actions in sequence, rather than in parallel. - -```csharp title="Option 2: Isolate GraphQL Controller Actions" -services.AddGraphQL(o => -{ - // highlight-next-line - o.ExecutionOptions.ResolverIsolation = ResolverIsolationOptions.ControllerActions; -}); -``` - -This will instruct graphql to execute each encountered controller action one after the other. Your scoped `DbContext` would then be able to process the queries without issue. - -The tradeoff with this method is a slight increase in processing time since the methods are called in sequence. All other field resolutions would be executed in parallel. - -If your application has other resources or services that may have similar restrictions, it can be beneficial to isolate the other resolver types as well. You can add them to the ResolverIsolation configuration option as needed. diff --git a/docs/development/unit-testing.html b/docs/development/unit-testing.html new file mode 100644 index 0000000..95be544 --- /dev/null +++ b/docs/development/unit-testing.html @@ -0,0 +1,24 @@ + + + + + +Unit Testing | GraphQL ASP.NET + + + + +
+

Unit Testing

.NET 8+

info

Your test projects must target .NET 8 or greater to use the test framework

GraphQL ASP.NET has more than 3500 unit tests and 91% code coverage. All the internal integration tests are powered by a framework designed to quickly build a configurable, fully mocked server instance to perform a query against the runtime. It may be helpful to use and extend the framework to test your own controllers.

This document explains how to perform some common test functions for your own controller methods.

Install the Framework

Add a reference to the Nuget Package GraphQL.AspNet.TestFramework to your unit test project. The framework is just a class library and not dependent on any individual testing framework like NUnit or XUnit. However, it does mock some runtime only objects and, as a result, is dependent on Moq.

Install the Test Framework
# Using the dotnet CLI
> dotnet add package GraphQL.AspNet.TestFramework

# Using Package Manager Console
> Install-Package GraphQL.AspNet.TestFramework

Create a Test Server

  1. Create a new instance of the TestServerBuilder. The builder takes in an optional set of flags to perform some auto configurations for common scenarios such as exposing exceptions or altering the casing of graph type names.

  2. Configure your test scenario

    • Use .User to add any permissions to the mocked user account
    • Use .Authorization to add any security policy definitions if you wish to test security
    • Use .AddGraphQL() to mimic the functionality of schema configuration used when your application starts.
    • TestServerBuilder implements IServiceCollection. Add any additional mocked services as needed to ensure your controllers are wired up correctly by the runtime.
  3. Build the server instance using .Build()

Configuring a Test Server Instance
[Test]
public async Task MyController_InvocationTest()
{
// Arrange
var builder = new TestServerBuilder();
builder.AddGraphQL(o => {
o.AddController<MyController>();
});

// Act
var server = builder.Build();

// continued...
}

Custom Schemas

Use TestServerBuild<TSchema> to test against a custom defined schema instance.

Execute a Query

Follow these steps to execute a query against the runtime. If your controller is registered to the test server and the appropriate field is requested in the query, it will be invoked.

  1. Create a builder to generate a mocked execution context (the object that the runtime acts on) using .CreateQueryContextBuilder()
  2. Configure the query text, variables etc. on the builder.
  3. Build the context and submit it for processing:
    • Use server.ExecuteQuery() to process the context. context.Result will be filled with the final IQueryExecutionResult which can be inspected for resultant data fields and error messages.
    • Use server.RenderResult() to generate the json string a client would recieve if they performed the query.
Executing a Test Query
[Test]
public async Task MyController_InvocationTest()
{
// Arrange
var builder = new TestServerBuilder();
builder.AddGraphQL(o => {
o.AddController<MyController>();
});

var server = builder.Build();
var queryBuilder = server.CreateQueryContextBuilder();
queryBuilder.AddQueryText("query { controller { actionMethod { property1 } } }");

var queryContext = queryBuilder.Build();

// Act
var result = await server.RenderResult(queryContext);

/* result contains the string for:
{
"data" : {
"controller": {
"actionMethod" : {
"property1" : "value1"
}
}
}
}
*/
}

Other Test Scenarios

Throwing Exceptions

If you need to test that your controller throws an appropriate exception you can inspect the response object (instead of rendering a result). The exception will be attached to an error message generated during the query execution.

Testing for Thrown Exceptions
[Test]
public async Task MyController_InvocationTest()
{
// Arrange
var builder = new TestServerBuilder();
builder.AddGraphQL(o => {
o.AddController<MyController>();
});

var server = builder.Build();
var queryBuilder = server.CreateQueryContextBuilder();
queryBuilder.AddQueryText("query { controller { actionMethod { property1 } } }");

var queryContext = queryBuilder.Build();

// Act
// Use ExecuteQuery instead of RenderResult to obtain the response object
var result = await server.ExecuteQuery(queryContext);

// Assert
// ensure a message was captured
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Messages.Count);
Assert.IsInstanceOf(typeof(MyException), result.Messages[0].Exception);
}
tip

Use server.ExecuteQuery to obtain a reference to the response object. This allows you to interrogate the message data before its rendered as a json document.

Authn & Authz

Test authentication and authorization scenarios by configuring both the policies the server should support and the claims or roles the user will present during the test.

[Test]
public async Task WhenUserHasPolicy_ThenAllowExecution()
{
// Arrange
var builder = new TestServerBuilder();
builder.AddGraphQL(o => {
o.AddController<MyController>();
});

// configure the policies the server will recognize
// and the claims the user context will have.
// This specific test assumes that the controller method
// defines an authorization requirement of "policy1".
builder.Authorization.AddClaimPolicy("policy1", "myClaimType", "myClaimValue");
builder.UserContext.AddUserClaim("myClaimType", "myClaimValue")

var server = builder.Build();
var queryBuilder = server.CreateQueryContextBuilder();
queryBuilder.AddQueryText("query { controller { actionMethod { property1 } } }");

var queryContext = queryBuilder.Build();

// Act
var result = await server.RenderResult(queryContext);

// Assert
// ....
}

The user context is always injected when you run a query on the test server. By default it is an anonymous user and credentials are applied when you add a claim or policy to the context during setup.

Demo project

See the demos page for a working demo using XUnit.

+ + + + \ No newline at end of file diff --git a/docs/development/unit-testing.md b/docs/development/unit-testing.md deleted file mode 100644 index de1d1ab..0000000 --- a/docs/development/unit-testing.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -id: unit-testing -title: Unit Testing -sidebar_label: Unit Testing -sidebar_position: 1 ---- - -.NET 8+ -
-
- -:::info -Your test projects must target .NET 8 or greater to use the test framework -::: - -GraphQL ASP.NET has more than `3500 unit tests and 91% code coverage`. All the internal integration tests are powered by a framework designed to quickly build a configurable, fully mocked server instance to perform a query against the runtime. It may be helpful to use and extend the framework to test your own controllers. - -This document explains how to perform some common test functions for your own controller methods. - - -## Install the Framework - -Add a reference to the [Nuget Package](https://www.nuget.org/packages/GraphQL.AspNet.TestFramework) `GraphQL.AspNet.TestFramework` to your unit test project. The framework is just a class library and not dependent on any individual testing framework like NUnit or XUnit. However, it does mock some runtime only objects and, as a result, is dependent on [Moq](https://www.nuget.org/packages/Moq). - -```powershell title="Install the Test Framework" -# Using the dotnet CLI -> dotnet add package GraphQL.AspNet.TestFramework - -# Using Package Manager Console -> Install-Package GraphQL.AspNet.TestFramework -``` - - - -## Create a Test Server - -1. Create a new instance of the `TestServerBuilder`. The builder takes in an optional set of flags to perform some auto configurations for common scenarios such as exposing exceptions or altering the casing of graph type names. -2. Configure your test scenario - - - Use `.User` to add any permissions to the mocked user account - - Use `.Authorization` to add any security policy definitions if you wish to test security - - Use `.AddGraphQL()` to mimic the functionality of schema configuration used when your application starts. - - `TestServerBuilder` implements `IServiceCollection`. Add any additional mocked services as needed to ensure your controllers are wired up correctly by the runtime. - -3. Build the server instance using `.Build()` - -```csharp title="Configuring a Test Server Instance" -[Test] -public async Task MyController_InvocationTest() -{ - // Arrange - var builder = new TestServerBuilder(); - builder.AddGraphQL(o => { - o.AddController(); - }); - - // Act - var server = builder.Build(); - - // continued... -} - -``` - -:::tip Custom Schemas -Use `TestServerBuild` to test against a custom defined schema instance. -::: - -## Execute a Query - -Follow these steps to execute a query against the runtime. If your controller is registered to the test server and the appropriate field is requested in the query, it will be invoked. - -1. Create a builder to generate a mocked execution context (the object that the runtime acts on) using `.CreateQueryContextBuilder()` -2. Configure the query text, variables etc. on the builder. -3. Build the context and submit it for processing: - - Use `server.ExecuteQuery()` to process the context. `context.Result` will be filled with the final `IQueryExecutionResult` which can be inspected for resultant data fields and error messages. - - Use `server.RenderResult()` to generate the json string a client would recieve if they performed the query. - - -```csharp title="Executing a Test Query" -[Test] -public async Task MyController_InvocationTest() -{ - // Arrange - var builder = new TestServerBuilder(); - builder.AddGraphQL(o => { - o.AddController(); - }); - - var server = builder.Build(); - var queryBuilder = server.CreateQueryContextBuilder(); - queryBuilder.AddQueryText("query { controller { actionMethod { property1 } } }"); - - var queryContext = queryBuilder.Build(); - - // Act - var result = await server.RenderResult(queryContext); - - /* result contains the string for: - { - "data" : { - "controller": { - "actionMethod" : { - "property1" : "value1" - } - } - } - } - */ -} -``` - -## Other Test Scenarios - -### Throwing Exceptions -If you need to test that your controller throws an appropriate exception you can inspect the response object (instead of rendering a result). The exception will be attached to an error message generated during the query execution. - -```csharp title="Testing for Thrown Exceptions" -[Test] -public async Task MyController_InvocationTest() -{ - // Arrange - var builder = new TestServerBuilder(); - builder.AddGraphQL(o => { - o.AddController(); - }); - - var server = builder.Build(); - var queryBuilder = server.CreateQueryContextBuilder(); - queryBuilder.AddQueryText("query { controller { actionMethod { property1 } } }"); - - var queryContext = queryBuilder.Build(); - - // Act - // Use ExecuteQuery instead of RenderResult to obtain the response object - // highlight-next-line - var result = await server.ExecuteQuery(queryContext); - - // Assert - // ensure a message was captured - Assert.IsNotNull(result); - Assert.AreEqual(1, result.Messages.Count); - Assert.IsInstanceOf(typeof(MyException), result.Messages[0].Exception); -} -``` - -:::tip -Use `server.ExecuteQuery` to obtain a reference to the response object. This allows you to interrogate the message data before its rendered as a json document. -::: - - -### Authn & Authz - -Test authentication and authorization scenarios by configuring both the policies the server should support and the claims or roles the user will present during the test. - -```csharp -[Test] -public async Task WhenUserHasPolicy_ThenAllowExecution() -{ - // Arrange - var builder = new TestServerBuilder(); - builder.AddGraphQL(o => { - o.AddController(); - }); - - // configure the policies the server will recognize - // and the claims the user context will have. - // This specific test assumes that the controller method - // defines an authorization requirement of "policy1". - // highlight-start - builder.Authorization.AddClaimPolicy("policy1", "myClaimType", "myClaimValue"); - builder.UserContext.AddUserClaim("myClaimType", "myClaimValue") - // highlight-end - - var server = builder.Build(); - var queryBuilder = server.CreateQueryContextBuilder(); - queryBuilder.AddQueryText("query { controller { actionMethod { property1 } } }"); - - var queryContext = queryBuilder.Build(); - - // Act - var result = await server.RenderResult(queryContext); - - // Assert - // .... -} -``` - -The user context is always injected when you run a query on the test server. By default it is an anonymous user and credentials are applied when you add a claim or policy to the context during setup. - -## Demo project -See the [demos page](../reference/demo-projects.md) for a working demo using XUnit. \ No newline at end of file diff --git a/docs/execution/_category_.json b/docs/execution/_category_.json deleted file mode 100644 index f8dbcbb..0000000 --- a/docs/execution/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Query Execution", - "position": 6, - "collapsed": false -} \ No newline at end of file diff --git a/docs/execution/malicious-queries.html b/docs/execution/malicious-queries.html new file mode 100644 index 0000000..c886fad --- /dev/null +++ b/docs/execution/malicious-queries.html @@ -0,0 +1,24 @@ + + + + + +Dealing with Malicious Queries | GraphQL ASP.NET + + + + +
+

Dealing with Malicious Queries

When GraphQL ASP.NET parses a query it creates two values that attempt to describe the query in terms of impact and server load; Max Depth and Estimated Complexity. There also exists limiters to these values that can be set in the schema configuration such that should any query plan exceed the limits you set, the plan will be rejected and the query not fulfilled.

Maximum Allowed Field Depth

Field depth refers to how deeply nested a field is within a query.

In this example, for instance, the "search" field has a depth of 4 and the maximum depth reached is 6: groceryStore > bakery > pastries > recipe > ingredients > name

Sample Query
query SearchGroceryStore {
groceryStore {
bakery {
pastries {
search(nameLike: "chocolate"){
name
type
}
recipe(id: 15) {
name
ingredients {
name
}
}
}
}
}
}

This becomes important on large object graphs where its possible for a requestor to submit a query that is 10s or 100s of nodes deep. Running such a large query can have performance implications if ran en masse. Think of large, deeply nested queries run as part of a DDos attack.

To combat this you can set a maximum allowed depth for any query targeting your schema. During the parsing phase, once GraphQL has gathered enough information about the query document and target operation, it will inspect the maximum depth and if it violates your constraint, immediately reject the query without executing it.

To set a maximum allowed depth, set the appropriate property in your schema configuration at startup:

Configure Max Query Depth
services.AddGraphQL(options =>
{
options.ExecutionOptions.MaxQueryDepth = 15;
});
Default Max Query Depth

The default value for MaxQueryDepth is null (i.e. no limit).

Query Complexity

The field depth is only part of the picture though. The way in which your fields interact with each other also plays a role.

Take for instance this query:

Sample Query
query PhoneManufacturer {
allParts {
id
name
suppliers {
name
address
}
}
}

It would not be far fetched to assume that this phone manufacturer has at least 500 parts in their inventory and that those parts might be sourced from 2-3 individual suppliers. If that's the case our result is going to contain 3000 field resolutions (500 parts * 3 suppliers * 2 fields per supplier) just to show the name and address of each supplier. Thats a lot of data!!!! What if we added order history per supplier? Now we'd looking at 100,000+ results. The take away here is that your field resolutions can balloon quickly, even on small queries, if you're not careful.

While this query only has a field depth of 3, allParts > suppliers > name, the performance implications are much more impactful than the bakery in the first example because of the type of data involved. (Side note: this is a perfect example where a batch operation would improve performance exponentially.)

GraphQL will assign an estimated complexity score to each query plan to help gauge the load its likely to incur on the server when trying to execute. As you might expect you can set a maximum allowed complexity value and reject any queries that exceed your limit:

Configure Max Allowed Query Complexity
services.AddGraphQL(options =>
{
options.ExecutionOptions.MaxQueryComplexity = 50.00;
});
Default Max Complexity

The default value for MaxQueryComplexity is null (i.e. no maximum).

Calculating Query Complexity

After a query plan is generated, the chosen operation is inspected and weights are applied to each of the fields then summed together to generate a final score.

A complexity score is derived from these attributes:

AttributeDescription
Operation TypeThis refers to the operation being a mutation or a query. Mutations are weighted more than queries.
Execution ModeWhether or not a given field is being executed as a batch operation or "per source item".
Resolver TypeThe type of resolver being invoked. For example, controller actions are weighted more heavily than simple property resolvers.
Type ExpressionDoes the field produce 1 single item or a collection of items?
Complexity FactorA user controlled value to influence the calculation for queries or mutations that are particularly long running

The code for calculating the value can be seen in DefaultOperationComplexityCalculator<TSchema>

Setting a Complexity Weight

You can influence the complexity value of any given field by applying a weight to the field as part of its declaration.

The attributes [GraphField], [Query], [Mutation], [QueryRoot], [MutationRoot] expose access to this value.

BakeryController.cs
public class BakeryController : GraphController
{
// Complexity is a float value
[QueryRoot(Complexity = 1.3)]
public Donut RetrieveDonutType(int id){/*...*/}
}
  • A factor greater than 1 will increase the weight applied to this field
  • A factor less than 1 will decrease the weight
  • The minimum value is 0 and the default value is 1

Complexity scores that do not exceed the limit are written to QueryPlanGenerated (EventId: 86400), a debug level event, after the query plan is successfully generated. Complexity scores that do exceed the limit are written directly to the errors collection on the query response.

Profile Your Queries

There is no magic bullet for choosing complexity values or setting a maximum allowed value as its going to be largely dependent on your data and how customers query it. Spend time profiling your queries, investigate their calculated complexities and act accordingly.

Implement Your Own Complexity Calculation

You can override how the library calculates the complexity of any given query operation. Implement IQueryOperationComplexityCalculator<TSchema> and inject it into your DI container before calling .AddGraphQL().

This interface has one method where IGraphFieldExecutableOperation represents the collection of requested fields contexts along with the input arguments, child fields and directives that are about to be executed:

IQueryOperationComplexityCalculator<TSchema>.cs
public interface IQueryOperationComplexityCalculator<TSchema>
{
float Calculate(IGraphFieldExecutableOperation operation);
}
+ + + + \ No newline at end of file diff --git a/docs/execution/malicious-queries.md b/docs/execution/malicious-queries.md deleted file mode 100644 index 763f20a..0000000 --- a/docs/execution/malicious-queries.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -id: malicious-queries -title: Dealing with Malicious Queries -sidebar_label: Malicious Queries -sidebar_position: 1 ---- - -When GraphQL ASP.NET parses a query it creates two values that attempt to describe the query in terms of impact and server load; Max Depth and Estimated Complexity. There also exists limiters to these values that can be set in the schema configuration such that should any query plan exceed the limits you set, the plan will be rejected and the query not fulfilled. - -## Maximum Allowed Field Depth - -Field depth refers to how deeply nested a field is within a query. - -In this example, for instance, the "search" field has a depth of 4 and the maximum depth reached is 6: `groceryStore > bakery > pastries > recipe > ingredients > name` - -```graphql title="Sample Query" -query SearchGroceryStore { - groceryStore { - bakery { - pastries { - search(nameLike: "chocolate"){ - name - type - } - recipe(id: 15) { - name - ingredients { - name - } - } - } - } - } -} -``` - -This becomes important on large object graphs where its possible for a requestor to submit a query that is 10s or 100s of nodes deep. Running such a large query can have performance implications if ran en masse. Think of large, deeply nested queries run as part of a DDos attack. - -To combat this you can set a maximum allowed depth for any query targeting your schema. During the parsing phase, once GraphQL has gathered enough information about the query document and target operation, it will inspect the maximum depth and if it violates your constraint, immediately reject the query without executing it. - -To set a maximum allowed depth, set the appropriate property in your schema configuration at startup: - -```csharp title="Configure Max Query Depth" -services.AddGraphQL(options => -{ - options.ExecutionOptions.MaxQueryDepth = 15; -}); -``` - -:::info Default Max Query Depth -The default value for `MaxQueryDepth` is `null` (i.e. no limit). -::: - -## Query Complexity - -The field depth is only part of the picture though. The way in which your fields interact with each other also plays a role. - -Take for instance this query: - -```graphql title="Sample Query" -query PhoneManufacturer { - allParts { - id - name - suppliers { - name - address - } - } -} -``` - -It would not be far fetched to assume that this phone manufacturer has at least 500 parts in their inventory and that those parts might be sourced from 2-3 individual suppliers. If that's the case our result is going to contain 3000 field resolutions (500 parts \* 3 suppliers \* 2 fields per supplier) just to show the name and address of each supplier. Thats a lot of data!!!! What if we added order history per supplier? Now we'd looking at 100,000+ results. The take away here is that your field resolutions can balloon quickly, even on small queries, if you're not careful. - -While this query only has a field depth of 3, `allParts > suppliers > name`, the performance implications are much more impactful than the bakery in the first example because of the type of data involved. (Side note: this is a perfect example where a [batch operation](../controllers/batch-operations) would improve performance exponentially.) - -GraphQL will assign an `estimated complexity` score to each query plan to help gauge the load its likely to incur on the server when trying to execute. As you might expect you can set a maximum allowed complexity value and reject any queries that exceed your limit: - -```csharp title="Configure Max Allowed Query Complexity" -services.AddGraphQL(options => -{ - options.ExecutionOptions.MaxQueryComplexity = 50.00; -}); -``` - -:::info Default Max Complexity -The default value for `MaxQueryComplexity` is `null` (i.e. no maximum). -::: - -### Calculating Query Complexity - -After a query plan is generated, the chosen operation is inspected and weights are applied to each of the fields then summed together to generate a final score. - -A complexity score is derived from these attributes: - -| Attribute | Description | -| ----------------- | ---------------------------------------------------------------------------------------------------------------- | -| Operation Type | This refers to the operation being a `mutation` or a `query`. Mutations are weighted more than queries.| -| Execution Mode | Whether or not a given field is being executed as a batch operation or "per source item". | -| Resolver Type | The type of resolver being invoked. For example, controller actions are weighted more heavily than simple property resolvers. | -| Type Expression | Does the field produce 1 single item or a collection of items? | -| Complexity Factor | A user controlled value to influence the calculation for queries or mutations that are particularly long running | - -The code for calculating the value can be seen in [`DefaultOperationComplexityCalculator`](https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet/Engine/DefaultOperationComplexityCalculator%7BTSchema%7D.cs) - -### Setting a Complexity Weight - -You can influence the complexity value of any given field by applying a weight to the field as part of its declaration. - -The attributes `[GraphField]`, `[Query]`, `[Mutation]`, `[QueryRoot]`, `[MutationRoot]` expose access to this value. - -```csharp title="BakeryController.cs" -public class BakeryController : GraphController -{ - // Complexity is a float value - // highlight-next-line - [QueryRoot(Complexity = 1.3)] - public Donut RetrieveDonutType(int id){/*...*/} -} -``` - -- A factor greater than 1 will increase the weight applied to this field -- A factor less than 1 will decrease the weight -- The minimum value is `0` and the default value is `1` - -Complexity scores that do not exceed the limit are written to `QueryPlanGenerated (EventId: 86400)`, a debug level event, after the query plan is successfully generated. Complexity scores that do exceed the limit are written directly to the errors collection on the query response. - - -:::tip Profile Your Queries -There is no magic bullet for choosing complexity values or setting a maximum allowed value as its going to be largely dependent on your data and how customers query it. Spend time profiling your queries, investigate their calculated complexities and act accordingly. -::: - -## Implement Your Own Complexity Calculation - -You can override how the library calculates the complexity of any given query operation. Implement `IQueryOperationComplexityCalculator` and inject it into your DI container before calling `.AddGraphQL()`. - -This interface has one method where `IGraphFieldExecutableOperation` represents the collection of requested fields contexts along with the input arguments, child fields and directives that are about to be executed: - -```csharp title="IQueryOperationComplexityCalculator.cs" -public interface IQueryOperationComplexityCalculator -{ - float Calculate(IGraphFieldExecutableOperation operation); -} -``` diff --git a/docs/execution/metrics.html b/docs/execution/metrics.html new file mode 100644 index 0000000..c608679 --- /dev/null +++ b/docs/execution/metrics.html @@ -0,0 +1,24 @@ + + + + + +Profiling Your Queries | GraphQL ASP.NET + + + + +
+

Profiling Your Queries

GraphQL ASP.NET tracks query metrics through the IQueryExecutionMetrics interface attached to each query execution context as its processed by the runtime and allows for tracing and timing of individual fields as they are started and completed.

The metrics themselves enable 3 levels of tracing:

  • The start time and duration of a query as a whole.
  • The start time and duration of an arbitrary "phase" of the query.
  • The start and duration of an in individual field resolution.

Out of the box, the GraphQL ASP.NET implements the Apollo Tracing specification for tracking query performance and uses three phases: Parsing, Validation and Execution.

GraphQL ASP.NET implements the Apollo Tracing format for capturing query profile information.

A sample query profile, serialized to json

Query metrics are appended to the extensions node of the standard query response. This query of 3 fields will generate a tracing object similar to this:

Sample Query
{
hero(episode: EMPIRE) {
id
name
__typename
}
}
Profile Results
{
// data and error nodes omitted
"extensions": {
"tracing": {
"version": 1,
"startTime": "2019-09-29T18:21:35.903+00:00",
"endTime": "2019-09-29T18:21:35.904+00:00",
"duration": 4862,
"execution": {
"resolvers": [
{
"path": [
"hero"
],
"parentType": "Query",
"fieldName": "hero",
"returnType": "Character!",
"startOffset": 78200,
"duration": 140500
},
{
"path": [
"hero",
"id"
],
"parentType": "Human",
"fieldName": "id",
"returnType": "ID!",
"startOffset": 297900,
"duration": 24100
},
{
"path": [
"hero",
"name"
],
"parentType": "Human",
"fieldName": "name",
"returnType": "String",
"startOffset": 352100,
"duration": 18200
},
{
"path": [
"hero",
"__typename"
],
"parentType": "Human",
"fieldName": "__typename",
"returnType": "String!",
"startOffset": 404300,
"duration": 18400
}
]
}
}
}

Enable Query Profiling

Metrics can be turned on for all requests during configuration in Startup.cs:

Enable Metrics at Startup
services.AddGraphQL(options =>
{
options.ExecutionOptions.EnableMetrics = true;
});

If you choose to override the default processor that accepts HTTP requests you can also enable metrics on a "per request" basis by overriding the EnableMetrics property and/or the CreateRequest() method to handle any conditional logic. See the section on the GraphQL Http Processor for more details.

Delivering Profiling Results

Options to profile a query vs. sending the results to a requestor are separate flags. Since the metrics package is attached to the query's primary context the results can be easily captured either by overriding the default http processor or in the event logger (during the Request Completed event) and perform an operation without sending them to the requestor. This allows you to perform silent profiling when necessary and can be a useful tool for random sampling and quality control in many scenarios.

To enable delivery of the metrics results to the requestor, set the appropriate schema configuration property at startup:

Expose Metrics at Startup
services.AddGraphQL(options =>
{
options.ResponseOptions.ExposeMetrics = true;
});

As with enabling metrics, additional control can be gained by overriding HandleQueryMetrics() on the http processor.

Performance Costs

Just as with logging, profiling your queries to this level of detail is not free. There is a performance cost that increases as your queries get larger. Care should be taken on deciding when to enable query profiling. It is recommended to keep profiling turned off in production during normal use.

Implementing a Custom Profiling Scheme

Customizing the way metrics are captured is not a trivial task but can be done:

  1. Implement IQueryExecutionMetricsFactory<TSchema> and register it to your DI container before calling .AddGraphQL(). This will override the internal factory and use your implementation to generate metrics packages for any received requests.
  2. Implement IQueryExecutionMetrics and have your factory return transient instances of this class when requested.

The runtime will now send metrics events to your objects and you can proceed with handling the data. However, the default pipeline structure is still only going to deliver 3 named phases to your metrics package (Parsing, Validation, Execution). If you want to alter the phase sequence or add new ones, you'll need to implement your own core pipeline components, which is beyond the scope of this documentation.

+ + + + \ No newline at end of file diff --git a/docs/execution/metrics.md b/docs/execution/metrics.md deleted file mode 100644 index 41f2424..0000000 --- a/docs/execution/metrics.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -id: metrics -title: Profiling Your Queries -sidebar_label: Query Profiling -sidebar_position: 0 ---- - -GraphQL ASP.NET tracks query metrics through the `IQueryExecutionMetrics` interface attached to each query execution context as its processed by the runtime and allows for tracing and timing of individual fields as they are started and completed. - -The metrics themselves enable 3 levels of tracing: - -- The start time and duration of a query as a whole. -- The start time and duration of an arbitrary "phase" of the query. -- The start and duration of an in individual field resolution. - -Out of the box, the GraphQL ASP.NET implements the [Apollo Tracing](https://github.com/apollographql/apollo-tracing) specification for tracking query performance and uses three phases: `Parsing`, `Validation` and `Execution`. - -> GraphQL ASP.NET implements the [Apollo Tracing](https://github.com/apollographql/apollo-tracing) format for capturing query profile information. - -#### A sample query profile, serialized to json - -Query metrics are appended to the `extensions` node of the standard query response. This query of 3 fields will generate a `tracing` object similar to this: - -```graphql title="Sample Query" -{ - hero(episode: EMPIRE) { - id - name - __typename - } -} -``` - -```json title="Profile Results" -{ - // data and error nodes omitted - "extensions": { - "tracing": { - "version": 1, - "startTime": "2019-09-29T18:21:35.903+00:00", - "endTime": "2019-09-29T18:21:35.904+00:00", - "duration": 4862, - "execution": { - "resolvers": [ - { - "path": [ - "hero" - ], - "parentType": "Query", - "fieldName": "hero", - "returnType": "Character!", - "startOffset": 78200, - "duration": 140500 - }, - { - "path": [ - "hero", - "id" - ], - "parentType": "Human", - "fieldName": "id", - "returnType": "ID!", - "startOffset": 297900, - "duration": 24100 - }, - { - "path": [ - "hero", - "name" - ], - "parentType": "Human", - "fieldName": "name", - "returnType": "String", - "startOffset": 352100, - "duration": 18200 - }, - { - "path": [ - "hero", - "__typename" - ], - "parentType": "Human", - "fieldName": "__typename", - "returnType": "String!", - "startOffset": 404300, - "duration": 18400 - } - ] - } - } -} -``` - -## Enable Query Profiling - -Metrics can be turned on for all requests during configuration in `Startup.cs`: - -```csharp title="Enable Metrics at Startup" -services.AddGraphQL(options => -{ - options.ExecutionOptions.EnableMetrics = true; -}); -``` - -If you choose to override the default processor that accepts HTTP requests you can also enable metrics on a "per request" basis by overriding the `EnableMetrics` property and/or the `CreateRequest()` method to handle any conditional logic. See the section on the [`GraphQL Http Processor`](../reference/http-processor) for more details. - -## Delivering Profiling Results - -Options to profile a query vs. sending the results to a requestor are separate flags. Since the metrics package is attached to the query's primary context the results can be easily captured either by overriding the default http processor or in the event logger (during the `Request Completed` event) and perform an operation without sending them to the requestor. This allows you to perform silent profiling when necessary and can be a useful tool for random sampling and quality control in many scenarios. - -To enable delivery of the metrics results to the requestor, set the appropriate schema configuration property at startup: - -```csharp title="Expose Metrics at Startup" -services.AddGraphQL(options => -{ - options.ResponseOptions.ExposeMetrics = true; -}); -``` - -As with enabling metrics, additional control can be gained by overriding `HandleQueryMetrics()` on the http processor. - -## Performance Costs - -Just as with [logging](../logging/structured-logging), profiling your queries to this level of detail is not free. There is a performance cost that increases as your queries get larger. Care should be taken on deciding when to enable query profiling. It is recommended to keep profiling turned off in production during normal use. - -## Implementing a Custom Profiling Scheme - -Customizing the way metrics are captured is not a trivial task but can be done: - -1. Implement `IQueryExecutionMetricsFactory` and register it to your DI container before calling `.AddGraphQL()`. This will override the internal factory and use your implementation to generate metrics packages for any received requests. -2. Implement `IQueryExecutionMetrics` and have your factory return transient instances of this class when requested. - -The runtime will now send metrics events to your objects and you can proceed with handling the data. However, the default pipeline structure is still only going to deliver 3 named phases to your metrics package (Parsing, Validation, Execution). If you want to alter the phase sequence or add new ones, you'll need to implement your own core pipeline components, which is beyond the scope of this documentation. \ No newline at end of file diff --git a/docs/introduction/_category_.json b/docs/introduction/_category_.json deleted file mode 100644 index 0ceee4c..0000000 --- a/docs/introduction/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Introduction", - "position": 1, - "collapsed": false -} \ No newline at end of file diff --git a/docs/introduction/made-for-aspnet-developers.html b/docs/introduction/made-for-aspnet-developers.html new file mode 100644 index 0000000..ca36ba7 --- /dev/null +++ b/docs/introduction/made-for-aspnet-developers.html @@ -0,0 +1,24 @@ + + + + + +Made for ASP.NET Developers | GraphQL ASP.NET + + + + +
+

Made for ASP.NET Developers

This library is designed by people who use ASP.NET in their day to day activities and is built for similar minded developers. When you first started digging in to GraphQL you most likely came across the plethora of articles, documents, tutorials and groups centered around JavaScript. JavaScript certainly has the highest adoption rate and with the tools provided by Apollo its no surprise. Its amazing how well those tools fit in with the existing knowledge and coding paradigms of JavaScript developers on both sides of the fence (be that front end or back end).

We believe that tooling and workflow is everything when it comes to picking up a technology. Its much more difficult for you (or your team) to adopt something new if there is no connection to what you already know. Migrating your personal development efforts or an entire team from .NET to NodeJS to leverage Apollo Server, for instance, is hard. The learning curve and even the monetary cost of bringing a team up to speed is high. But if you can leverage existing skills you reduce that cost significantly.

GraphQL ASP.NET aims to reuse your existing knowledge of ASP.NET

This is a core, guiding principle for the development of this library. We aim to reuse what you know. Or if you are still learning, make what you learn transferable to other .NET technologies. When coming from a .NET background, being able to reason about your graph queries in terms of Controllers and Actions eases the cognitive load as you transition to thinking in terms of Fields and object graphs.

Using familiar concepts like Binding Models and View Models; commonly used attributes like [Authorize], [Required], [StringLength]; modern ASP.NET's abstraction concepts like IServiceCollection and ILogger all play a part in hopes of giving you a familiar programming model that you can start using immediately without reinventing too many wheels.

Take, for instance, this controller and a sample query that would call it. Can you tell what it does? If you are familiar with Web API then the answer is probably yes!

PersonController.cs
public class PersonController: GraphController
{
private IPersonService _service;
public PersonController(IPersonService personService)
{
_service = personService;
}

[QueryRoot("person")]
public async Task<Person> RetrievePerson(int id)
{
return await _service.FetchPerson(id);
}
}
Sample Query
query {
person(id: 5){
firstName
lastName
title
}
}

Another consideration when trying to implement GraphQL in .NET is the amount of boiler plate code required. Since C# is a strongly typed language the volume of additional coding required to generate an object graph tends to be high, especially in larger graphs. Many libraries take the approach of ultimate flexibility, requiring you to completely code your object graph, and all the fields that can be queried, and individually map all model properties and resolver methods manually.

To address this, GraphQL ASP.NET has adopted an opinionated approach to its implementation. It makes some minor assumptions about how you will deliver your data in exchange for some much needed code generation and specification support. If the code in the controller above makes sense and feels natural to you; then this library might be worth a look. In terms of GraphQL, this single controller will:

  • Generate a fully qualified schema definition
  • Provide introspection support
  • Generate all required graph types (there are 17 in this example)

The library will automatically wire up your graph controllers and scan your model objects. There is no additional, required configuration. When you add a new controller, new actions or new model properties they are automatically injected everywhere that object is used.

Are you working on a large project that has shared assemblies between services? No problem, you can direct GraphQL on where to look for controllers and model objects or even be explicit in what you want it to consume...down to the individual property level.

You're in control

Out of the box the library tries to pick the route of least resistance, but there are many ways to control what classes, enums etc. are included (or excluded) in your object graph.

Plays Nice with Web API Controllers, Razor Views and Razor Pages

This library is an extension to the standard ASP.NET pipeline, not a replacement. At its core, a graphql query is just another GET or POST route on your application. At startup the library registers a middleware component to handle requests using appBuilder.Map().

Also, if you are integrating into an existing project, you'll find a lot of your utility code should work out of the box which should ease your migration. Any existing services, custom authorization and model validation attributes etc. can be directly attached to graph action methods and input models. You might even find that most of your model objects work with little to no changes as well.

Scoped Dependency Injection

Services are injected into graph controllers in the same manner as ASP.NET controllers and with the same scope resolutions.

User Authorization

The user model is exactly the same. In fact, the ClaimsPrincipal passed to this.User on a Web API controller is the same instance used to validate any [Authorize] attributes on your graph controller actions. Internally, it uses the same IAuthorizationService that gets added when you call .AddAuthorization() during startup.

Custom Action Results

Many teams define custom action results beyond this.Ok() and this.BadRequest() for their REST queries to standardize how they will respond to requests on their Web API controllers to provide consistent messaging, perform some sort of logging or create a common return payload. GraphQL ASP.NET supports this model as well. Out of the box you get support for many of the relevant action results around returning data, indicating an error or denying access, but you can implement your own IGraphActionResult to standardize how a given result is converted into a response object and used by the runtime. This includes control to invalidate the field, inject customized error messages or cancel the field request altogether.

Side Note: Not all action results make sense in GraphQL. For instance, you won't find a way to download a file or indicate a 204 (no content) result. A GraphQL field must always return a piece of data (even if its null). Since the IGraphActionResult object is only a small piece in an entire query of many fields, its scope of abilities is paired to match.

+ + + + \ No newline at end of file diff --git a/docs/introduction/made-for-aspnet-developers.md b/docs/introduction/made-for-aspnet-developers.md deleted file mode 100644 index c298214..0000000 --- a/docs/introduction/made-for-aspnet-developers.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -id: made-for-aspnet-developers -title: Made for ASP.NET Developers -sidebar_label: Made for ASP.NET Developers -sidebar_position: 1 ---- - -This library is designed by people who use [ASP.NET](https://dotnet.microsoft.com/en-us/apps/aspnet) in their day to day activities and is built for similar minded developers. When you first started digging in to GraphQL you most likely came across the plethora of [articles](https://www.graphqlweekly.com/), [documents](https://en.wikipedia.org/wiki/GraphQL), [tutorials](https://www.howtographql.com/) and [groups](https://www.apollographql.com/) centered around JavaScript. JavaScript certainly has the highest adoption rate and with the tools provided by [Apollo](https://www.apollographql.com/) its no surprise. Its amazing how well those tools fit in with the existing knowledge and coding paradigms of JavaScript developers on both sides of the fence (be that front end or back end). - -We believe that tooling and workflow is everything when it comes to picking up a technology. Its much more difficult for you (or your team) to adopt something new if there is no connection to what you already know. Migrating your personal development efforts or an entire team from .NET to NodeJS to leverage Apollo Server, for instance, is hard. The learning curve and even the monetary cost of bringing a team up to speed is high. But if you can leverage existing skills you reduce that cost significantly. - -> GraphQL ASP.NET aims to reuse your existing knowledge of ASP.NET - -This is a core, guiding principle for the development of this library. We aim to reuse what you know. Or if you are still learning, make what you learn transferable to other .NET technologies. When coming from a .NET background, being able to reason about your graph queries in terms of `Controllers` and `Actions` eases the cognitive load as you transition to thinking in terms of Fields and object graphs. - -Using familiar concepts like _Binding Models_ and _View Models_; commonly used attributes like `[Authorize]`, `[Required]`, `[StringLength]`; modern ASP.NET's abstraction concepts like `IServiceCollection` and `ILogger` all play a part in hopes of giving you a familiar programming model that you can start using immediately without reinventing too many wheels. - -Take, for instance, this controller and a sample query that would call it. Can you tell what it does? If you are familiar with Web API then the answer is probably yes! - -```cs title="PersonController.cs" -public class PersonController: GraphController -{ - private IPersonService _service; - public PersonController(IPersonService personService) - { - _service = personService; - } - - [QueryRoot("person")] - public async Task RetrievePerson(int id) - { - return await _service.FetchPerson(id); - } -} -``` - -```graphql title="Sample Query" -query { - person(id: 5){ - firstName - lastName - title - } -} -``` - -Another consideration when trying to implement GraphQL in .NET is the amount of boiler plate code required. Since C# is a strongly typed language the volume of additional coding required to generate an object graph tends to be high, especially in larger graphs. Many libraries take the approach of ultimate flexibility, requiring you to completely code your object graph, and all the fields that can be queried, and individually map all model properties and resolver methods manually. - -To address this, GraphQL ASP.NET has adopted an opinionated approach to its implementation. It makes some minor assumptions about how you will deliver your data in exchange for some much needed code generation and specification support. If the code in the controller above makes sense and feels natural to you; then this library might be worth a look. In terms of GraphQL, this single controller will: - -- Generate a fully qualified schema definition -- Provide introspection support -- Generate all required graph types (there are 17 in this example) - -The library will automatically wire up your graph controllers and scan your model objects. There is no additional, required configuration. When you add a new controller, new actions or new model properties they are automatically injected everywhere that object is used. - -Are you working on a large project that has shared assemblies between services? No problem, you can direct GraphQL on where to look for controllers and model objects or even be explicit in what you want it to consume...down to the individual property level. - -:::note You're in control - Out of the box the library tries to pick the route of least resistance, but there are many ways to control what classes, enums etc. are included (or excluded) in your object graph. -::: - -## Plays Nice with Web API Controllers, Razor Views and Razor Pages - -This library is an extension to the standard ASP.NET pipeline, not a replacement. At its core, a graphql query is just another GET or POST route on your application. At startup the library registers a middleware component to handle requests using `appBuilder.Map()`. - -Also, if you are integrating into an existing project, you'll find a lot of your utility code should work out of the box which should ease your migration. Any existing services, custom authorization and model validation attributes etc. can be directly attached to graph action methods and input models. You might even find that most of your model objects work with little to no changes as well. - -## Scoped Dependency Injection - -Services are injected into graph controllers in the same manner as ASP.NET controllers and with the same scope resolutions. - -## User Authorization - -The user model is exactly the same. In fact, the `ClaimsPrincipal` passed to `this.User` on a Web API controller is the same instance used to validate any `[Authorize]` attributes on your graph controller actions. Internally, it uses the same `IAuthorizationService` that gets added when you call `.AddAuthorization()` during startup. - -## Custom Action Results - -Many teams define custom action results beyond `this.Ok()` and `this.BadRequest()` for their REST queries to standardize how they will respond to requests on their Web API controllers to provide consistent messaging, perform some sort of logging or create a common return payload. GraphQL ASP.NET supports this model as well. Out of the box you get support for many of the relevant action results around returning data, indicating an error or denying access, but you can implement your own `IGraphActionResult` to standardize how a given result is converted into a response object and used by the runtime. This includes control to invalidate the field, inject customized error messages or cancel the field request altogether. - -_Side Note:_ Not all action results make sense in GraphQL. For instance, you won't find a way to download a file or indicate a 204 (no content) result. A GraphQL field must always return a piece of data (even if its null). Since the `IGraphActionResult` object is only a small piece in an entire query of many fields, its scope of abilities is paired to match. diff --git a/docs/introduction/what-is-graphql.html b/docs/introduction/what-is-graphql.html new file mode 100644 index 0000000..80861d1 --- /dev/null +++ b/docs/introduction/what-is-graphql.html @@ -0,0 +1,24 @@ + + + + + +What is GraphQL? | GraphQL ASP.NET + + + + +
+

What is GraphQL?

GraphQL is a query language specification originally created by Meta for their own internal use. It was eventually open-sourced and moved to its own foundation, the GraphQL Foundation, and hosted by the Linux Foundation. The specification provides an alternative to traditional REST queries that we all know and love in giving the requestor more control over what data to return.

With REST someone may requst data by querying against a URL with the GET HTTP verb. It's up to the server to decide what data to return.

Sample REST Query
//REST Request:
GET https://www.the-rebel-alliance.site/directory/persons/1

// Response
{
"id": 1,
"name": "Luke Skywalker",
"department": "Tatooine",
"favoriteSong": "Papa Was a Rollin' Stone",
}

But with GraphQL a user describes the data they want in their request to the GraphQL server.

Sample GraphQL Query
# GraphQL Request
query {
person(id: 1){
name
department
favoriteSong
}
}

# Response
{
"data": {
"person": {
"name": "Luke Skywalker",
"department": "Tatooine",
"favoriteSong": "Papa Was a Rollin' Stone",
}
}
}

Is it Better than REST?

Nope! As with any choice in technology there are pros and cons. It comes down to what trade offs you're willing to accept. Using GraphQL or REST or gRPC or Carrier Pigeon for communicating between systens is never a cut and dry decision. Your team's needs are going to be different than others. To claim that GraphQL should replace REST would be short sighted.

One of the primary benefits of GraphQL is the requestor only gets the data they need. In the REST example above no choice was given for the JSON object that was generated All 4 fields were (and always will be) returned. In the second example notice that we specified only to return name, department and favoriteSong. We already know the id in this instance and returning isn't necessary. For two characters of data that's not a big deal, but if the the REST response included a detailed biography of the person, returning that amount of data when its not needed (such as on a search page) could hamper performance. The issue compounds itself when we start dealing with parent-child relationships and hundreds of rows of data. Being able to ignore a field could mean the difference between a 50KB result and a 50MB result.

GraphQL is not a .NET Technology

GraphQL is a query language specification; a contract describing a syntax and rule set for how to process requests for data. There are many implementations of GraphQL for various tech stacks from JavaScript, to Java, to Ruby and even other .NET implementations to choose from.

Library NameLanguage
Apollo ServerJavaScript
GraphQL RubyRuby
GraphQL JavaJava
GraphQL .NET.NET
Hot Chocolate.NET

Why Choose GraphQL?

"Can we add one more thing?" - Product Managers

Have you ever heard that in a stand-up meeting? Lets say your team is building a website to show a list of all the members of the rebel alliance. The original specs of a REST end point calls for the return of an object in the above example. Then, for the 2nd time this week, the product owner decided to make a change. "Hey, lets add everybody's direct manager's name to their employee card!", Karen said. The project manager sighs audibly, Dave rolls his eyes and now you have to make a code change to return the new field from the endpoint.

With GraphQL the requestor, that is the client application, adds the new field to their query and the data is automatically returned. Since they have to update the UI they are going to be impacted anyways so no additional harm done. From the back-end though, using GraphQL means there is no need for a server update; no change request, additional QA, no PR approvals and most importantly no new deployment. Server-side code tends to be scrutinized a lot more than client-side code and for good reason. The fewer changes we have to make to a production server the better! This is, of course, dependent on how you design your object graph but with a little forward thinking you can greatly minimize the required server changes once you setup your schema.

+ + + + \ No newline at end of file diff --git a/docs/introduction/what-is-graphql.md b/docs/introduction/what-is-graphql.md deleted file mode 100644 index 37d27b8..0000000 --- a/docs/introduction/what-is-graphql.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -id: what-is-graphql -title: What is GraphQL? -sidebar_label: What is GraphQL? -sidebar_position: 0 ---- - -[GraphQL](https://graphql.org) is a query language specification originally created by [Meta](https://facebook.com) for their own internal use. It was eventually open-sourced and moved to its own foundation, the [GraphQL Foundation](https://foundation.graphql.org/), and hosted by the [Linux Foundation](https://www.linuxfoundation.org/). The specification provides an alternative to traditional REST queries that we all know and love in giving the requestor more control over what data to return. - -With REST someone may requst data by querying against a URL with the `GET` HTTP verb. It's up to the server to decide what data to return. - -```javascript title="Sample REST Query" -//REST Request: -GET https://www.the-rebel-alliance.site/directory/persons/1 - -// Response -{ - "id": 1, - "name": "Luke Skywalker", - "department": "Tatooine", - "favoriteSong": "Papa Was a Rollin' Stone", -} -``` - -But with GraphQL a user _describes_ the data they want in their request to the GraphQL server. - -```graphql title="Sample GraphQL Query" -# GraphQL Request -query { - person(id: 1){ - name - department - favoriteSong - } -} - -# Response -{ - "data": { - "person": { - "name": "Luke Skywalker", - "department": "Tatooine", - "favoriteSong": "Papa Was a Rollin' Stone", - } - } -} -``` - -## Is it Better than REST? - -Nope! As with any choice in technology there are pros and cons. It comes down to what trade offs you're willing to accept. Using `GraphQL` or `REST` or `gRPC` or `Carrier Pigeon` for communicating between systens is never a cut and dry decision. Your team's needs are going to be different than others. To claim that GraphQL should replace REST would be short sighted. - -One of the primary benefits of GraphQL is the requestor only gets the data they need. In the REST example above no choice was given for the JSON object that was generated All 4 fields were (and always will be) returned. In the second example notice that we specified only to return `name`, `department` and `favoriteSong`. We already know the `id` in this instance and returning isn't necessary. For two characters of data that's not a big deal, but if the the REST response included a detailed biography of the person, returning that amount of data when its not needed (such as on a search page) could hamper performance. The issue compounds itself when we start dealing with parent-child relationships and hundreds of rows of data. Being able to ignore a field could mean the difference between a 50KB result and a 50MB result. - -## GraphQL is not a .NET Technology - -GraphQL is a query [language specification](https://spec.graphql.org/); a contract describing a syntax and rule set for how to process requests for data. There are many implementations of GraphQL for various tech stacks from JavaScript, to Java, to Ruby and even other .NET implementations to choose from. - -#### Other Popular GraphQL Implementations - -| Library Name | Language | -| --------------------------------------------------------------- | ---------- | -| [Apollo Server](https://github.com/apollographql/apollo-server) | JavaScript | -| [GraphQL Ruby](https://github.com/rmosolgo/graphql-ruby) | Ruby | -| [GraphQL Java](https://github.com/graphql-java/graphql-java) | Java | -| [GraphQL .NET](https://github.com/graphql-dotnet/graphql-dotnet)| .NET | -| [Hot Chocolate](https://github.com/ChilliCream/hotchocolate) | .NET | - -## Why Choose GraphQL? - -> "Can we add one more thing?" - Product Managers - -Have you ever heard that in a stand-up meeting? Lets say your team is building a website to show a list of all the members of the rebel alliance. The original specs of a REST end point calls for the return of an object in the above example. Then, for the 2nd time this week, the product owner decided to make a change. "Hey, lets add everybody's direct manager's name to their employee card!", Karen said. The project manager sighs audibly, Dave rolls his eyes and now you have to make a code change to return the new field from the endpoint. - -With GraphQL the **requestor**, that is the client application, adds the new field to their query and the data is automatically returned. Since they have to update the UI they are going to be impacted anyways so no additional harm done. From the back-end though, using GraphQL means there is no need for a server update; no change request, additional QA, no PR approvals and most importantly no new deployment. Server-side code tends to be scrutinized a lot more than client-side code and for good reason. The fewer changes we have to make to a production server the better! This is, of course, dependent on how you design your object graph but with a little forward thinking you can greatly minimize the required server changes once you setup your schema. diff --git a/docs/logging/_category_.json b/docs/logging/_category_.json deleted file mode 100644 index 5d46192..0000000 --- a/docs/logging/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Logging", - "position": 5, - "collapsed": false -} \ No newline at end of file diff --git a/docs/logging/standard-events.html b/docs/logging/standard-events.html new file mode 100644 index 0000000..b636588 --- /dev/null +++ b/docs/logging/standard-events.html @@ -0,0 +1,26 @@ + + + + + +Standard Logging Events | GraphQL ASP.NET + + + + +
+

Standard Logging Events

GraphQL ASP.NET tracks many standard events. Most of these are recorded during the execution of a query. Some, such as those around field resolution, can be recorded many times in the course of a single request.

Common Event Properties

All logging events share a common set of properties.

PropertyDescription
EventIdThe numeric constant assigned to the event.
EventNameThe human-friendly name of the event.
LogEntryIdA guid unique to the given log entry.
MessageA simple text message.
DateTimeUTCA date and time, in UTC-0, when the event was created.

Constants for EventIds can be found at GraphQL.AspNet.Logging.LogEventIds

Constants for all built in log entry properties can be found at GraphQL.AspNet.Logging.LogPropertyNames

Scope Id

Another common, but not universal, property is ScopeId. Any log entries related to the execution of a single query will have a common scope id. In general, this id is unique per HTTP request.

Schema Level Events

Schema Route Registered

This event is recorded when GraphQL successfully registers an entry in the ASP.NET route table to accept requests for a target schema. This event is recorded once per application instance.

Important Properties

PropertyDescription
SchemaTypeNameThe full name of your your schema type. For most single schema applications this will be GraphQL.AspNet.Schemas.GraphSchema.
RoutePathThe relative URL that was registered for the schema type, e.g. '/graphql

Schema Instance Created

This event is recorded each time an instance of your schema is created. By default, schema's are stored as singleton instances so this event should be recorded once per application instance.

Important Properties

PropertyDescription
SchemaTypeNameThe full name of your your schema type. For most single schema applications this will be GraphQL.AspNet.Schemas.GraphSchema.
SupportedOperationTypesA comma separated list of the operations the schema is tracking (e.g. query, mutation and/or subscription)
GraphTypesA collection of objects containing each registered graph type's name, type kind, .NET type association and number of fields (if any)

Schema Pipeline Registered

This event is recorded each time an instance of a schema pipeline is created by your DI container. Like schemas, field middleware pipelines are stored as singleton instances so this event should be recorded once per pipleline per application instance.

Important Properties

PropertyDescription
PipelineNameThe human friendly name of the pipeline that was created
MiddlewareComponentsAn array of the registered names of the components in your schema's field pipeline. The names are ordered according to their position in the pipeline.

Request Level Events

Request Received

This is event is recorded when the query execution pipeline first receives a new query to process.

Important Properties

PropertyDescription
Usernamethe value of this.User.Identity.Name or null
QueryRequestIdA unique id identifying the overall request that was received.
QueryTextThe query provided by the user.

Query Plan Generated

This is event is recorded when the runtime generates a new query plan. This event may or may not be recorded on each request if you are making use of the query cache.

Important Properties

PropertyDescription
QueryPlanIdA unique id assigned to the created query plan.
SchemaTypeNamethe full .NET type name of the schema type this plan targets
IsValidA boolean value indicating if the query document resulted in a valid plan
MaxDepth*The maximum nested depth of any given field in the plan
EstimatedComplexity*The complexity score assigned to the query plan by the runtime

* See the section on dealing with malicious queries for more details on MaxDepth and EstimatedComplexity.

Query Cache Hit

This event is recorded when the runtime is able to pull an existing instance of a Query Plan from the query cache for the received query document. This event is only recorded if the query cache is enabled.

Important Properties

PropertyDescription
QueryPlanHashCodeThe unique hash key generated from the query document and used to search for an existing plan in the query cache.
SchemaTypeNamethe full .NET type name of the schema type this plan targets

Query Cache Miss

This event is recorded when the runtime is unable to pull an existing instance of a Query Plan from the query cache for the received query document. This event is only recorded if the query cache is enabled.

Important Properties

PropertyDescription
QueryPlanHashCodeThe unique hash key generated from the query document and used to search for an existing plan in the query cache.
SchemaTypeNamethe full .NET type name of the schema type this plan targets

Query Cache Add

This is event is recorded when the runtime successfully stores an instance of a Query Plan into the query cache. This event is only recorded if the query cache is enabled.

Important Properties

PropertyDescription
QueryPlanHashCodeThe unique hash key generated from the query document and used to search for an existing plan in the query cache.
SchemaTypeNamethe full .NET type name of the schema type this plan targets
QueryPlanIdThe unique query plan Id created when it was generated. Can be used to cross link this event to the Query Plan Generated event for further details.

Request Completed

This is event is recorded when the final result for the request is generated and is returned from the runtime to be serialized. No actual data values are recorded to the logs to prevent leaks of potentially sensitive information.

Important Properties

PropertyDescription
QueryRequestIdA unique id identifying the overall request.
HasDatatrue or false indicating if at least one data value was included in the result
HasErrorstrue or false indicating if at least one error message was included in the result
TotalExecutionMsA numerical value indicating the total runtime of the request, in milliseconds.

Request Cancelled

This is event is recorded when the a request is explicitly cancelled, usually by the underlying HTTP connection.

Important Properties

PropertyDescription
QueryRequestIdA unique id identifying the overall request.
TotalExecutionMsA numerical value indicating the total runtime of the request, in milliseconds.

Request Timeout

This is event is recorded when the a request is is cancelled due to reaching a maximum timeout limit defined by the schema.

Important Properties

PropertyDescription
QueryRequestIdA unique id identifying the overall request.
TotalExecutionMsA numerical value indicating the total runtime of the request, in milliseconds.

Directive Level Events

Execution Directive Applied

This event is recorded when an execution directive is successfully executed against an IDocumentPart on an incoming query.

This is event is recorded when the final result for the request is generated and is returned from the runtime to be serialized. No actual data values are recorded to the logs to prevent leaks of potentially sensitive information.

Important Properties

PropertyDescription
SchemaTypeNameThe full .NET type name of the schema type this plan targets
DirectiveNameThe name of the directive as it exists in the target schema
DirectiveInternalNameThe .NET class name of the directive
DirectiveLocationThe target location in the query document (e.g. FIELD, FRAGMENT_SPREAD etc.)

Type System Directive Applied

This event is recorded when a schema is first generated and all known type system directives are applied to the schema items +to which they are attached. An entry is recorded for each directive applied.

Important Properties

PropertyDescription
SchemaTypeNameThe full .NET type name of the schema type this plan targets
SchemaItemPathThe path of the item being resolved, e.g. [type]/Donut/id
DirectiveNameThe name of the directive as it exists in the target schema
DirectiveInternalNameThe .NET class name of the directive
DirectiveLocationThe target location in the query document (e.g. FIELD, FRAGMENT_SPREAD etc.)

Auth Events

Item Authentication Started

This is event is record when a security context on a query is authenticated to determine an +appropriate ClaimsPrincipal to use for authorization.

Important Properties

PropertyDescription
PipelineRequestIdA unique id identifying the individual field request.
SchemaItemPathThe path of the item being resolved, e.g. [type]/Donut/id

Item Authentication Completed

This is event is recorded after a security context is authenticated and a ClaimsPrincipal was generated (if required).

Important Properties

PropertyDescription
PipelineRequestIdA unique id identifying the individual field request.
SchemaItemPathThe path of the item being resolved, e.g. [type]/Donut/id
UsernameThe value of the Name field on the active Identity or null
AuthenticationSchemeThe key representing the chosen authentication schema (e.g. Bearer, Kerberos etc.)
AuthenticationSchemaSuccesstrue if authentication against the scheme was successful
SchemaItemPathThe path of the item being resolved, e.g. [type]/Donut/id

Item Authorization Started

This is event is recorded when an authenticated user is authorized against schema item (typically a Field or Directive).

Important Properties

PropertyDescription
PipelineRequestIdA unique id identifying the individual field request.
SchemaItemPathThe path of the item being resolved, e.g. [type]/Donut/id
UsernameThe value of the Name field on the active Identity or null

Item Authorization Completed

This is event is recorded after a schema item authorization has completed.

Important Properties

PropertyDescription
PipelineRequestIdA unique id identifying the individual field request.
SchemaItemPathThe path of the item being resolved, e.g. [type]/Donut/id
UsernameThe value of the Name field on the active Identity or null
AuthorizationStatusSkipped, Authorized or Unauthorized
LogMessageAn internal message containing an explanation of why authorization failed.

Field Level Events

After a query plan has been created GraphQL ASP.NET begins resolving each field needed to fulfill the request. This group of events is recorded for each item of each field that is processed. Since all fields are executed asynchronously (even if the resolvers themselves are synchronous) the order in which the events are recorded can be unpredictable and overlap between fields can occur. Using the recorded date along with the PipelineRequestId can help to filter the noise.

Field Resolution Started

This event is recorded when a new field is queued for resolution.

Important Properties

PropertyDescription
PipelineRequestIdA unique id identifying the individual field request.
FieldExecutionModeIndicates if this pipeline is being executed for a single source item or as a batch
FieldPathThe path of the field being resolved, e.g. [type]/Donut/id

Field Resolution Completed

This is event is recorded when a field completes its execution pipeline and a result is generated. No actual data values are recorded to the logs to prevent leaks of potentially sensitive information.

Important Properties

PropertyDescription
PipelineRequestIdA unique id identifying the individual field request.
FieldPathThe path of the field being resolved, e.g. [type]/Donut/id
HasDatatrue or false indicating if at least one data value was included in the result

Controller Level Events

After the security challenge has completed, but before field resolution is completed, if the pipeline executes a controller method to resolve the field these events will be recorded. If the target resolver of the field is a property or POCO method, these events are skipped.

Action Invocation Started

This event is recorded when a controller begins processing a request to execute an action method.

Important Properties

PropertyDescription
PipelineRequestIdA unique id identifying the individual field request.
ControllerTypeNameThe full .NET type of the controller being invoked
ActionNameThe name of the method (not the field) that was being invoked when the exception occurred
FieldPathThe schema field path that represents the action method
SourceObjectTypeThe .NET type name of the input source to the action.
IsAsynctrue or false indicating if the controller is going to invoke the action asynchronously or not

Action Model State Validated

This event occurs after the controller has processed the input objects and validated the state of the model being passed to the action method.

Important Properties

PropertyDescription
PipelineRequestIdA unique id identifying the individual field request.
ControllerTypeNameThe full .NET type of the controller being invoked
ActionNameThe name of the method (not the field) that was being invoked when the exception occurred
FieldPathThe schema field path that represents the action method
ModelIsValidtrue or false indicating if the all model items completed validation successfully
ModelItemsA list of items, one for each item that was invalid, indicating the parameter name and the string messages generated indicating the errors.

Action Invocation Completed

This event is recorded when a controller completes the invocation of an action method and a result is created.

Important Properties

PropertyDescription
PipelineRequestIdA unique id identifying the individual field request.
ControllerTypeNameThe full .NET type of the controller being invoked
ActionNameThe name of the method (not the field) that was being invoked when the exception occurred
FieldPathThe schema field path that represents the action method
ResultTypeNameThe .NET type name of the data returned from the action. The may be an actual field value or an IGraphActionResult type.

Action Invocation Exception

This event is recorded by the controller if it is unable to invoke the target action method. This usually indicates some sort of data corruption or failed conversion of source data to the requested parameter types of the target action method. This can happen if the query plan or variables collection is altered by a 3rd party outside of the normal pipeline. Should this event occur the field will be abandoned and a null value returned as the field result. Child fields to this instance will not be processed but the operation will continue to attempt to resolve other sibling fields and their children.

Important Properties

PropertyDescription
PipelineRequestIdA unique id identifying the individual field request.
ControllerTypeNameThe full .NET type of the controller being invoked
ActionNameThe name of the method (not the field) that was being invoked when the exception occurred
ExceptionMessageThe message on the exception that was thrown
ExceptionTypeNameThe .NET type name of the exception that was thrown
StackTraceThe complete stack trace text of the exception

Action Unhandled Exception

This event is recorded if an unhandled exception occurs within the controller action method body. Should this event occur the field will be abandoned and a null value returned as the result of the field. Child fields will not be processed but the operation will continue to attempt to resolve other sibling fields and their children.

Important Properties

PropertyDescription
PipelineRequestIdA unique id identifying the individual field request.
ControllerTypeNameThe full .NET type of the controller being invoked
ActionNameThe name of the method (not the field) that was being invoked when the exception occurred
ExceptionMessageThe message on the exception that was thrown
ExceptionTypeNameThe .NET type name of the exception that was thrown
StackTraceThe complete stack trace text of the exception

Other Events

Unhandled Exception

This event is recorded if any pipeline invocation is unable to recover from an error. If this is event is recorded the request is abandoned and an error status is returned to the requestor. This event is always recorded at a Critical log level. This event will be immediately followed by a Request Completed event.

Important Properties

PropertyDescription
ExceptionMessageThe message on the exception that was thrown
ExceptionTypeNameThe .NET type name of the exception that was thrown
StackTraceThe complete stack trace text of the exception
+ + + + \ No newline at end of file diff --git a/docs/logging/standard-events.md b/docs/logging/standard-events.md deleted file mode 100644 index 0deb8fe..0000000 --- a/docs/logging/standard-events.md +++ /dev/null @@ -1,375 +0,0 @@ ---- -id: standard-events -title: Standard Logging Events -sidebar_label: Standard Events -sidebar_position: 1 ---- - -GraphQL ASP.NET tracks many standard events. Most of these are recorded during the execution of a query. Some, such as those around field resolution, can be recorded many times in the course of a single request. - -## Common Event Properties - -All logging events share a common set of properties. - -| Property | Description | -| ------------- | -------------------------------------------------------- | -| _EventId_ | The numeric constant assigned to the event. | -| _EventName_ | The human-friendly name of the event. | -| _LogEntryId_ | A guid unique to the given log entry. | -| _Message_ | A simple text message. | -| _DateTimeUTC_ | A date and time, in UTC-0, when the event was _created_. | - -> Constants for `EventIds` can be found at `GraphQL.AspNet.Logging.LogEventIds` - -> Constants for all built in log entry properties can be found at `GraphQL.AspNet.Logging.LogPropertyNames` - -:::tip Scope Id -Another common, but not universal, property is `ScopeId`. Any log entries related to the execution of a single query will have a common scope id. In general, this id is unique per HTTP request. -::: - -## Schema Level Events - -### Schema Route Registered - -This event is recorded when GraphQL successfully registers an entry in the ASP.NET route table to accept requests for a target schema. This event is recorded once per application instance. - -**Important Properties** - -| Property | Description | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| _SchemaTypeName_ | The full name of your your schema type. For most single schema applications this will be `GraphQL.AspNet.Schemas.GraphSchema`. | -| _RoutePath_ | The relative URL that was registered for the schema type, e.g. `'/graphql` | - -### Schema Instance Created - -This event is recorded each time an instance of your schema is created. By default, schema's are stored as singleton instances so this event should be recorded once per application instance. - -**Important Properties** - -| Property | Description | -| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| _SchemaTypeName_ | The full name of your your schema type. For most single schema applications this will be `GraphQL.AspNet.Schemas.GraphSchema`. | -| _SupportedOperationTypes_ | A comma separated list of the operations the schema is tracking (e.g. query, mutation and/or subscription) | -| _GraphTypes_ | A collection of objects containing each registered graph type's name, type kind, .NET type association and number of fields (if any) | - -### Schema Pipeline Registered - -This event is recorded each time an instance of a schema pipeline is created by your DI container. Like schemas, field middleware pipelines are stored as singleton instances so this event should be recorded once per pipleline per application instance. - -**Important Properties** - -| Property | Description | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| _PipelineName_ | The human friendly name of the pipeline that was created | -| _MiddlewareComponents_ | An array of the registered names of the components in your schema's field pipeline. The names are ordered according to their position in the pipeline. | - -## Request Level Events - -### Request Received - -This is event is recorded when the query execution pipeline first receives a new query to process. - -**Important Properties** - -| Property | Description | -| -------------------- | -------------------------------------------------------------- | -| _Username_ | the value of `this.User.Identity.Name` or null | -| _QueryRequestId_ | A unique id identifying the overall request that was received. | -| _QueryText_ | The query provided by the user. | - -### Query Plan Generated - -This is event is recorded when the runtime generates a new query plan. This event may or may not be recorded on each request if you are making use of the query cache. - -**Important Properties** - -| Property | Description | -| ----------------------- | ------------------------------------------------------------------------- | -| _QueryPlanId_ | A unique id assigned to the created query plan. | -| _SchemaTypeName_ | the full .NET type name of the schema type this plan targets | -| _IsValid_ | A boolean value indicating if the query document resulted in a valid plan | -| _MaxDepth\*_ | The maximum nested depth of any given field in the plan | -| _EstimatedComplexity\*_ | The complexity score assigned to the query plan by the runtime | - -\* See the section on dealing with [malicious queries](../execution/malicious-queries) for more details on `MaxDepth` and `EstimatedComplexity`. - -### Query Cache Hit - -This event is recorded when the runtime is able to pull an existing instance of a Query Plan from the query cache for the received query document. This event is only recorded if the query cache is enabled. - -**Important Properties** - -| Property | Description | -| ------------------- | ----------------------------------------------------------------------------------------------------------------- | -| _QueryPlanHashCode_ | The unique hash key generated from the query document and used to search for an existing plan in the query cache. | -| _SchemaTypeName_ | the full .NET type name of the schema type this plan targets | - -### Query Cache Miss - -This event is recorded when the runtime is unable to pull an existing instance of a Query Plan from the query cache for the received query document. This event is only recorded if the query cache is enabled. - -**Important Properties** - -| Property | Description | -| ------------------- | ----------------------------------------------------------------------------------------------------------------- | -| _QueryPlanHashCode_ | The unique hash key generated from the query document and used to search for an existing plan in the query cache. | -| _SchemaTypeName_ | the full .NET type name of the schema type this plan targets | - -### Query Cache Add - -This is event is recorded when the runtime successfully stores an instance of a Query Plan into the query cache. This event is only recorded if the query cache is enabled. - -**Important Properties** - -| Property | Description | -| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -| _QueryPlanHashCode_ | The unique hash key generated from the query document and used to search for an existing plan in the query cache. | -| _SchemaTypeName_ | the full .NET type name of the schema type this plan targets | -| _QueryPlanId_ | The unique query plan Id created when it was generated. Can be used to cross link this event to the `Query Plan Generated` event for further details. | - -### Request Completed - -This is event is recorded when the final result for the request is generated and is returned from the runtime to be serialized. No actual data values are recorded to the logs to prevent leaks of potentially sensitive information. - -**Important Properties** - -| Property | Description | -| -------------------- | ------------------------------------------------------------------------------------- | -| _QueryRequestId_ | A unique id identifying the overall request. | -| _HasData_ | `true` or `false` indicating if at least one data value was included in the result | -| _HasErrors_ | `true` or `false` indicating if at least one error message was included in the result | -| _TotalExecutionMs_ | A numerical value indicating the total runtime of the request, in milliseconds. | - -### Request Cancelled - -This is event is recorded when the a request is explicitly cancelled, usually by the underlying HTTP connection. - -**Important Properties** - -| Property | Description | -| -------------------- | ------------------------------------------------------------------------------------- | -| _QueryRequestId_ | A unique id identifying the overall request. | -| _TotalExecutionMs_ | A numerical value indicating the total runtime of the request, in milliseconds. | - - -### Request Timeout - -This is event is recorded when the a request is is cancelled due to reaching a maximum timeout limit defined by the schema. - -**Important Properties** - -| Property | Description | -| -------------------- | ------------------------------------------------------------------------------------- | -| _QueryRequestId_ | A unique id identifying the overall request. | -| _TotalExecutionMs_ | A numerical value indicating the total runtime of the request, in milliseconds. | - - -## Directive Level Events - -### Execution Directive Applied - -This event is recorded when an execution directive is successfully executed against an `IDocumentPart` on an incoming query. - -This is event is recorded when the final result for the request is generated and is returned from the runtime to be serialized. No actual data values are recorded to the logs to prevent leaks of potentially sensitive information. - -**Important Properties** - -| Property | Description | -| -------------------- | ------------------------------------------------------------------------------------- | -| _SchemaTypeName_ | The full .NET type name of the schema type this plan targets | -| _DirectiveName_ | The name of the directive as it exists in the target schema | -| _DirectiveInternalName_ | The .NET class name of the directive | -| _DirectiveLocation_ | The target location in the query document (e.g. FIELD, FRAGMENT_SPREAD etc.) | - -### Type System Directive Applied - -This event is recorded when a schema is first generated and all known type system directives are applied to the schema items -to which they are attached. An entry is recorded for each directive applied. - -**Important Properties** - -| Property | Description | -| -------------------- | ------------------------------------------------------------------------------------- | -| _SchemaTypeName_ | The full .NET type name of the schema type this plan targets | -| _SchemaItemPath_ | The path of the item being resolved, e.g. `[type]/Donut/id` | -| _DirectiveName_ | The name of the directive as it exists in the target schema | -| _DirectiveInternalName_ | The .NET class name of the directive | -| _DirectiveLocation_ | The target location in the query document (e.g. FIELD, FRAGMENT_SPREAD etc.) | - - -## Auth Events - -### Item Authentication Started - -This is event is record when a security context on a query is authenticated to determine an -appropriate ClaimsPrincipal to use for authorization. - -**Important Properties** - -| Property | Description | -| ------------------- | ------------------------------------------------------------ | -| _PipelineRequestId_ | A unique id identifying the individual field request. | -| _SchemaItemPath_ | The path of the item being resolved, e.g. `[type]/Donut/id` | - - -### Item Authentication Completed - -This is event is recorded after a security context is authenticated and a ClaimsPrincipal was generated (if required). - -**Important Properties** - -| Property | Description | -| ----------------------------- | ------------------------------------------------------------ | -| _PipelineRequestId_ | A unique id identifying the individual field request. | -| _SchemaItemPath_ | The path of the item being resolved, e.g. `[type]/Donut/id` | -| _Username_ | The value of the `Name` field on the active Identity or null| -| _AuthenticationScheme_ | The key representing the chosen authentication schema (e.g. `Bearer`, `Kerberos` etc.) | -| _AuthenticationSchemaSuccess_ | `true` if authentication against the scheme was successful | -| _SchemaItemPath_ | The path of the item being resolved, e.g. `[type]/Donut/id` | - -### Item Authorization Started - -This is event is recorded when an authenticated user is authorized against schema item (typically a Field or Directive). - -**Important Properties** - -| Property | Description | -| ------------------- | ------------------------------------------------------------ | -| _PipelineRequestId_ | A unique id identifying the individual field request. | -| _SchemaItemPath_ | The path of the item being resolved, e.g. `[type]/Donut/id` | -| _Username_ | The value of the `Name` field on the active Identity or null| - -### Item Authorization Completed - -This is event is recorded after a schema item authorization has completed. - -**Important Properties** - -| Property | Description | -| --------------------- | -------------------------------------------------------------------------- | -| _PipelineRequestId_ | A unique id identifying the individual field request. | -| _SchemaItemPath_ | The path of the item being resolved, e.g. `[type]/Donut/id` | -| _Username_ | The value of the `Name` field on the active Identity or null | -| _AuthorizationStatus_ | `Skipped`, `Authorized` or `Unauthorized` | -| _LogMessage_ | An internal message containing an explanation of why authorization failed. | - - -## Field Level Events - -After a query plan has been created GraphQL ASP.NET begins resolving each field needed to fulfill the request. This group of events is recorded for each item of each field that is processed. Since all fields are executed asynchronously (even if the resolvers themselves are synchronous) the order in which the events are recorded can be unpredictable and overlap between fields can occur. Using the recorded date along with the `PipelineRequestId` can help to filter the noise. - -### Field Resolution Started - -This event is recorded when a new field is queued for resolution. - -**Important Properties** - -| Property | Description | -| -------------------- | --------------------------------------------------------------------------------------- | -| _PipelineRequestId_ | A unique id identifying the individual field request. | -| _FieldExecutionMode_ | Indicates if this pipeline is being executed for a `single source item` or as a `batch` | -| _FieldPath_ | The path of the field being resolved, e.g. `[type]/Donut/id` | - -### Field Resolution Completed - -This is event is recorded when a field completes its execution pipeline and a result is generated. No actual data values are recorded to the logs to prevent leaks of potentially sensitive information. - -**Important Properties** - -| Property | Description | -| ------------------- | ---------------------------------------------------------------------------------- | -| _PipelineRequestId_ | A unique id identifying the individual field request. | -| _FieldPath_ | The path of the field being resolved, e.g. `[type]/Donut/id` | -| _HasData_ | `true` or `false` indicating if at least one data value was included in the result | - -## Controller Level Events - -After the security challenge has completed, but before field resolution is completed, if the pipeline executes a controller method to resolve the field these events will be recorded. If the target resolver of the field is a property or POCO method, these events are skipped. - -### Action Invocation Started - -This event is recorded when a controller begins processing a request to execute an action method. - -**Important Properties** - -| Property | Description | -| -------------------- | -------------------------------------------------------------------------------------------------- | -| _PipelineRequestId_ | A unique id identifying the individual field request. | -| _ControllerTypeName_ | The full .NET type of the controller being invoked | -| _ActionName_ | The name of the method (not the field) that was being invoked when the exception occurred | -| _FieldPath_ | The schema field path that represents the action method | -| _SourceObjectType_ | The .NET type name of the input source to the action. | -| _IsAsync_ | `true` or `false` indicating if the controller is going to invoke the action asynchronously or not | - -### Action Model State Validated - -This event occurs after the controller has processed the input objects and validated the state of the model being passed to the action method. - -**Important Properties** - -| Property | Description | -| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| _PipelineRequestId_ | A unique id identifying the individual field request. | -| _ControllerTypeName_ | The full .NET type of the controller being invoked | -| _ActionName_ | The name of the method (not the field) that was being invoked when the exception occurred | -| _FieldPath_ | The schema field path that represents the action method | -| _ModelIsValid_ | `true` or `false` indicating if the all model items completed validation successfully | -| _ModelItems_ | A list of items, one for each item that was invalid, indicating the parameter name and the string messages generated indicating the errors. | - -### Action Invocation Completed - -This event is recorded when a controller completes the invocation of an action method and a result is created. - -**Important Properties** - -| Property | Description | -| -------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| _PipelineRequestId_ | A unique id identifying the individual field request. | -| _ControllerTypeName_ | The full .NET type of the controller being invoked | -| _ActionName_ | The name of the method (not the field) that was being invoked when the exception occurred | -| _FieldPath_ | The schema field path that represents the action method | -| _ResultTypeName_ | The .NET type name of the data returned from the action. The may be an actual field value or an `IGraphActionResult` type. | - -### Action Invocation Exception - -This event is recorded by the controller if it is unable to invoke the target action method. This usually indicates some sort of data corruption or failed conversion of source data to the requested parameter types of the target action method. This can happen if the query plan or variables collection is altered by a 3rd party outside of the normal pipeline. Should this event occur the field will be abandoned and a null value returned as the field result. Child fields to this instance will not be processed but the operation will continue to attempt to resolve other sibling fields and their children. - -**Important Properties** - -| Property | Description | -| -------------------- | ----------------------------------------------------------------------------------------- | -| _PipelineRequestId_ | A unique id identifying the individual field request. | -| _ControllerTypeName_ | The full .NET type of the controller being invoked | -| _ActionName_ | The name of the method (not the field) that was being invoked when the exception occurred | -| _ExceptionMessage_ | The message on the exception that was thrown | -| _ExceptionTypeName_ | The .NET type name of the exception that was thrown | -| _StackTrace_ | The complete stack trace text of the exception | - -### Action Unhandled Exception - -This event is recorded if an unhandled exception occurs `within the controller action method body`. Should this event occur the field will be abandoned and a null value returned as the result of the field. Child fields will not be processed but the operation will continue to attempt to resolve other sibling fields and their children. - -**Important Properties** - -| Property | Description | -| -------------------- | ----------------------------------------------------------------------------------------- | -| _PipelineRequestId_ | A unique id identifying the individual field request. | -| _ControllerTypeName_ | The full .NET type of the controller being invoked | -| _ActionName_ | The name of the method (not the field) that was being invoked when the exception occurred | -| _ExceptionMessage_ | The message on the exception that was thrown | -| _ExceptionTypeName_ | The .NET type name of the exception that was thrown | -| _StackTrace_ | The complete stack trace text of the exception | - -## Other Events - -### Unhandled Exception - -This event is recorded if any pipeline invocation is unable to recover from an error. If this is event is recorded the request is abandoned and an error status is returned to the requestor. This event is always recorded at a `Critical` log level. This event will be immediately followed by a `Request Completed` event. - -**Important Properties** - -| Property | Description | -| ------------------- | --------------------------------------------------- | -| _ExceptionMessage_ | The message on the exception that was thrown | -| _ExceptionTypeName_ | The .NET type name of the exception that was thrown | -| _StackTrace_ | The complete stack trace text of the exception | diff --git a/docs/logging/structured-logging.html b/docs/logging/structured-logging.html new file mode 100644 index 0000000..6afc4b0 --- /dev/null +++ b/docs/logging/structured-logging.html @@ -0,0 +1,24 @@ + + + + + +Structured Logging | GraphQL ASP.NET + + + + +
+

Structured Logging

GraphQL ASP.NET utilizes structured logging for reporting runtime events. The log messages generated aren't just strings but actual objects. All internal log events are raised as objects that inherit from IGraphLogEntry.

IServiceCollection.AddLogging()

GraphQL's logging extends off of .NET's built in logging framework. To enable it, you need to register logging to your application at startup. By doing so, GraphQL will automatically wire up its own logging as well.

Register Standard Logging
// Adding Logging before calling AddGraphQL
services.AddLogging();

service.AddGraphQL(/*...*/);

Using IGraphLogger

Its common practice to inject an instance of ILogger or ILoggerFactory into a controller in order to log various events of your controller methods.

This is fully supported but the library can also generate an instance of IGraphLogger with a few helpful methods for raising "on the fly" log entries if you wish to make use of it. IGraphLogger implements ILogger, the two can be used interchangeably as needed.

Using IGraphLogger
public class BakeryController : GraphController
{
private IGraphLogger _graphLogger;
private IDonutService _service;
public BakeryController(IDonutService service, IGraphLogger graphLogger)
{
_service = service;
_graphLogger = graphLogger;
}

[MutationRoot]
public Donut CreateDonut(string name)
{
Donut donut = _service.CreateDonut(name);

var donutEvent = new GraphLogEntry("New Donut Created!");
donutEvent["Name"] = name;
donutEvent["Id"] = donut.Id;

_graphLogger.Log(LogLevel.Information, donutEvent);

return donut;
}
}
tip

GraphLogEntry is an untyped implementation of IGraphLogEntry and can be used on the fly for quick operations or as a basis for custom log entries.

Custom ILoggers

By default, the log events will return a contextual message on .ToString() with the important data related to the event. This is very handy when logging to the console during development.

console logger

But given the extra data the log entries contain, it makes more sense to create a custom ILogger to take advantage of the full object.

Custom ILogger
public class MyCustomLogger : ILogger
{
// other code ommited for brevity

public void Log<TState>(LogLevel logLevel, EventId eventId,
TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (state is IGraphLogEntry logEntry)
{
// handle the log entry here
}
}
}
info

The state parameter of ILogger.Log() will be an instance of IGraphLogEntry whenever a GraphQL ASP.NET log event is recorded.

Log Entries are KeyValuePair Collections

While the various standard log events declare explicit properties for the data they return, every log entry is just a collection of key/value pairs that can be iterated through for quick serialization.

public interface IGraphLogEntry : IEnumerable<KeyValuePair<string, object>>
{ /*...*/ }

Logging Category

All log events are registered under the the GraphQL.AspNet category. With the exception of field authorization fails, all log events are recorded at Debug or Trace.

Here we've enabled the log events through appsettings.json

appsettings.json
{
"Logging" : {
"IncludeScopes" : false,
"LogLevel": {
"Default" : "Information",
"System": "Debug",
"Microsoft": "Information",
"GraphQL.AspNet" : "Debug"
}
}
}

Log Entries are not allocated unless their respective log levels are enabled. It is not uncommon for real world queries to generate 100s of log entries per request. Take care to ensure you have setup your logging appropriately in a given environment as it can greatly impact performance if left on by accident.

caution

It is never a good idea to enable trace level logging outside of development.

Scoped Log Entries

IGraphLogger is generated from your service provider on a "per scope" basis. By default, this is at the HTTP Request level. Any log entries created through it using .Log(LogLevel, IGraphLogEntry) will be injected with a scopeId and along with the date can produce a complete record of a request from when it was received through query plan generation, authorization and field resolution.

Here we've used a custom ILogProvider and written out a small sample of events to a json array. Note the shared scopeId on each entry. See the example projects to download the code.

[{
"eventId": 86000,
"eventName": "GraphQL Request Received",
"dateTimeUTC": "2022-09-23T22:05:39.6023597+00:00",
"logEntryId": "6afdf3d1f9464679becfbd2b96aa594f",
"operationRequestId": "a13f5f7232dc475783c4e4798cfb50d2",
"userName": "john-doe",
"queryOperationName": null,
"queryText": "query {\n hero(episode: EMPIRE){\n id\n name \n }\n}",
"scopeId": "3c3c858b1b344d0b9c0f8ac6b95469d1"
},
{
"eventId": 86400,
"eventName": "GraphQL Query Plan Generated",
"dateTimeUTC": "2022-09-23T22:05:39.7214431+00:00",
"logEntryId": "d133e032e2fc42a98a639ee8c72d2497",
"schemaType": "GraphQL.AspNet.Schemas.GraphSchema",
"isValid": true,
"operationCount": 1,
"estimatedComplexity": 9.5076,
"maxDepth": 2,
"queryPlanId": "57caffc5d5124f2fb08419ae99724974",
"scopeId": "3c3c858b1b344d0b9c0f8ac6b95469d1"
},
{
"eventId": 86599,
"eventName": "GraphQL Field Resolution Completed",
"path": "[type]/Human/Id",
"dateTimeUTC": "2022-09-23T22:05:39.8528847+00:00",
"logEntryId": "750ee155bf7e4eea895e5eec01d3fb6d",
"pipelineRequestId": "aa670d3e2d6a41e1b314711acf6bc51c",
"typeExpression": "ID!",
"hasData": true,
"resultIsValid": true,
"scopeId": "3c3c858b1b344d0b9c0f8ac6b95469d1"
}]
+ + + + \ No newline at end of file diff --git a/docs/logging/structured-logging.md b/docs/logging/structured-logging.md deleted file mode 100644 index ce6f5cf..0000000 --- a/docs/logging/structured-logging.md +++ /dev/null @@ -1,169 +0,0 @@ ---- -id: structured-logging -title: Structured Logging -sidebar_label: Structured Logging -sidebar_position: 0 ---- - -GraphQL ASP.NET utilizes structured logging for reporting runtime events. The log messages generated aren't just strings but actual objects. All internal log events are raised as objects that inherit from `IGraphLogEntry`. - -## IServiceCollection.AddLogging() - -GraphQL's logging extends off of .NET's built in logging framework. To enable it, you need to register logging to your application at startup. By doing so, GraphQL will automatically wire up its own logging as well. - -```csharp title="Register Standard Logging" -// Adding Logging before calling AddGraphQL -services.AddLogging(); - -service.AddGraphQL(/*...*/); -``` - -## Using IGraphLogger - -Its common practice to inject an instance of `ILogger` or `ILoggerFactory` into a controller in order to log various events of your controller methods. - -This is fully supported but the library can also generate an instance of `IGraphLogger` with a few helpful methods for raising "on the fly" log entries if you wish to make use of it. `IGraphLogger` implements `ILogger`, the two can be used interchangeably as needed. - -```csharp title="Using IGraphLogger" -public class BakeryController : GraphController -{ - private IGraphLogger _graphLogger; - private IDonutService _service; - // highlight-next-line - public BakeryController(IDonutService service, IGraphLogger graphLogger) - { - _service = service; - _graphLogger = graphLogger; - } - - [MutationRoot] - public Donut CreateDonut(string name) - { - Donut donut = _service.CreateDonut(name); - - // highlight-start - var donutEvent = new GraphLogEntry("New Donut Created!"); - donutEvent["Name"] = name; - donutEvent["Id"] = donut.Id; - - _graphLogger.Log(LogLevel.Information, donutEvent); - // highlight-end - - return donut; - } -} -``` - -:::tip - `GraphLogEntry` is an untyped implementation of `IGraphLogEntry` and can be used on the fly for quick operations or as a basis for custom log entries. -::: - -## Custom ILoggers - -By default, the log events will return a contextual message on `.ToString()` with the important data related to the event. This is very handy when logging to the console during development. - -![console logger](../assets/console-logger.png) - -But given the extra data the log entries contain, it makes more sense to create a custom `ILogger` to take advantage of the full object. - -```csharp title="Custom ILogger" -public class MyCustomLogger : ILogger -{ - // other code ommited for brevity - - public void Log(LogLevel logLevel, EventId eventId, - TState state, Exception exception, Func formatter) - { - // highlight-next-line - if (state is IGraphLogEntry logEntry) - { - // handle the log entry here - } - } -} -``` - -:::info - The state parameter of `ILogger.Log()` will be an instance of `IGraphLogEntry` whenever a GraphQL ASP.NET log event is recorded. -::: - -## Log Entries are KeyValuePair Collections - -While the various [standard log events](./standard-events) declare explicit properties for the data they return, every log entry is just a collection of key/value pairs that can be iterated through for quick serialization. - -```csharp -public interface IGraphLogEntry : IEnumerable> -{ /*...*/ } -``` - -## Logging Category - -All log events are registered under the the `GraphQL.AspNet` category. With the exception of field authorization fails, all log events are recorded at `Debug` or `Trace`. - -Here we've enabled the log events through `appsettings.json` - -```json title="appsettings.json" -{ - "Logging" : { - "IncludeScopes" : false, - "LogLevel": { - "Default" : "Information", - "System": "Debug", - "Microsoft": "Information", - // highlight-next-line - "GraphQL.AspNet" : "Debug" - } - } -} -``` - -Log Entries are not allocated unless their respective log levels are enabled. It is not uncommon for real world queries to generate 100s of log entries per request. Take care to ensure you have setup your logging appropriately in a given environment as it can greatly impact performance if left on by accident. - -:::caution -It is never a good idea to enable trace level logging outside of development. -::: - -### Scoped Log Entries - -`IGraphLogger` is generated from your service provider on a "per scope" basis. By default, this is at the HTTP Request level. Any log entries created through it using `.Log(LogLevel, IGraphLogEntry)` will be injected with a `scopeId` and along with the date can produce a complete record of a request from when it was received through query plan generation, authorization and field resolution. - -Here we've used a custom `ILogProvider` and written out a small sample of events to a json array. Note the shared `scopeId` on each entry. See the [example projects](./../reference/demo-projects.md) to download the code. - -```json -[{ - "eventId": 86000, - "eventName": "GraphQL Request Received", - "dateTimeUTC": "2022-09-23T22:05:39.6023597+00:00", - "logEntryId": "6afdf3d1f9464679becfbd2b96aa594f", - "operationRequestId": "a13f5f7232dc475783c4e4798cfb50d2", - "userName": "john-doe", - "queryOperationName": null, - "queryText": "query {\n hero(episode: EMPIRE){\n id\n name \n }\n}", - "scopeId": "3c3c858b1b344d0b9c0f8ac6b95469d1" -}, -{ - "eventId": 86400, - "eventName": "GraphQL Query Plan Generated", - "dateTimeUTC": "2022-09-23T22:05:39.7214431+00:00", - "logEntryId": "d133e032e2fc42a98a639ee8c72d2497", - "schemaType": "GraphQL.AspNet.Schemas.GraphSchema", - "isValid": true, - "operationCount": 1, - "estimatedComplexity": 9.5076, - "maxDepth": 2, - "queryPlanId": "57caffc5d5124f2fb08419ae99724974", - "scopeId": "3c3c858b1b344d0b9c0f8ac6b95469d1" -}, -{ - "eventId": 86599, - "eventName": "GraphQL Field Resolution Completed", - "path": "[type]/Human/Id", - "dateTimeUTC": "2022-09-23T22:05:39.8528847+00:00", - "logEntryId": "750ee155bf7e4eea895e5eec01d3fb6d", - "pipelineRequestId": "aa670d3e2d6a41e1b314711acf6bc51c", - "typeExpression": "ID!", - "hasData": true, - "resultIsValid": true, - "scopeId": "3c3c858b1b344d0b9c0f8ac6b95469d1" -}] -``` diff --git a/docs/logging/subscription-events.html b/docs/logging/subscription-events.html new file mode 100644 index 0000000..de0199b --- /dev/null +++ b/docs/logging/subscription-events.html @@ -0,0 +1,25 @@ + + + + + +Subscription Logging Events | GraphQL ASP.NET + + + + +
+

Subscription Logging Events

GraphQL ASP.NET tracks some special events related to the management of subscriptions. They are outlined below.

Common Event Properties

The common event properties outlined on the standard events page apply to all subscription events as well.

Server Level Events

Subscription Event Dispatch Queue Alert

This event is recorded when the server's schema-agnostic, internal dispatch queue reaches a given threshold. The internal dispatch queue is where all subscription events destined for connected clients are staged before being processed. The thresholds at which this alert is recorded can be customized.

PropertyDescription
ThresholdLevelReachedThe declared threshold level that was reached causing this entry to be recorded. (Expressed in # of queued events)
EventQueueCountThe actual number of events currently queued.
CustomMessageAn optional, staticly declared message registered with the threshold level.

Schema Level Events

Subscription Route Registered

This event is recorded when GraphQL successfully registers an entry in the ASP.NET route table to accept requests for a target schema as well as +register the middleware component necessary to receive websocket requests.

Important Properties

PropertyDescription
SchemaTypeNameThe full name of your your schema type. For most single schema applications this will be GraphQL.AspNet.Schemas.GraphSchema.
SchemaSubscriptionRoutePathThe relative URL that was registered for the schema type, e.g. '/graphql

Client Connection Events

Client Registered

This event is recorded when GraphQL successfully accepts a client and has assigned a client proxy to manage the connection. This event is recorded just prior to the connection is "started" and messaging begins.

Important Properties

PropertyDescription
ClientIdA unique id asigned to the client when it first connected.
SchemaTypeNameThe full name of your your schema type. For most single schema applications this will be GraphQL.AspNet.Schemas.GraphSchema.
ClientTypeNameThe full name of the assigned client proxy type. In general, a different type is used per messaging protocol.
ClientProtocolThe protocol negotiated by the client and server that will be used for the duration of the connection.

Client Dropped

This event is recorded when GraphQL is releasing a client. The connection has been "stopped" and no additional messagings are being broadcast. This event occurs just before the HTTP connection is closed.

Important Properties

PropertyDescription
ClientIdA unique id asigned to the client when it first connected.
ClientTypeNameThe full name of the assigned client proxy type. In general, a different type is used per messaging protocol.
ClientProtocolThe protocol negotiated by the client and server that will be used for the duration of the connection.

Unsupported Client Protocol

This event is recorded when GraphQL attempts to create an appropriate proxy class for the connection but no such proxy could be deteremined from the details providied in the initial request. In general, this means the provided websocket sub protocols did not match a supported protocol for this server and schema combination.

PropertyDescription
SchemaTypeNameThe full name of your your schema type. For most single schema applications this will be GraphQL.AspNet.Schemas.GraphSchema.
ClientProtocolThe protocol(s) requested by the client connection that were not accepted

Important Properties

PropertyDescription
ClientIdA unique id asigned to the client when it first connected.
ClientTypeNameThe full name of the assigned client proxy type. In general, a different type is used per messaging protocol.

Client Messaging Events

Subscription Event Received

This event is recorded by a client proxy when it received an event from the router and has determined that it should be handled.

Important Properties

PropertyDescription
ClientIdA unique id asigned to the client when it first connected.
SchemaTypeNameThe full name of your your schema type. For most single schema applications this will be GraphQL.AspNet.Schemas.GraphSchema.
SubscriptionPathThe path to the target top-level subscription field in the schema
SubscriptionCountThe number of registered subscriptions, for this client, that will receive this event.
SubscriptionIdsA comma seperated list of id values representing the subscriptions that will receive this event.
MachineNameThe Environment.MachineName of the current server.

Subscription Registered

This event is recorded by a client proxy when it starts a new subscription on behalf of its connected client.

Important Properties

PropertyDescription
ClientIdA unique id asigned to the client when it first connected.
SubscriptionPathThe path to the target top-level subscription field in the schema
SubscriptionIdThe subscription id requested by the client.

Subscription Registered

This event is recorded by a client proxy when it unregistered and abandons an existing subscription. This may be due to the server ending the subscription or the client requesting it be stopped.

Important Properties

PropertyDescription
ClientIdA unique id asigned to the client when it first connected.
SubscriptionPathThe path to the target top-level subscription field in the schema
SubscriptionIdThe subscription id requested by the client.

Client Message Received

This event is recorded by a client proxy when it successfully receives and deserializes a message from its connected client. Not all client proxies may record this event. The messages a client proxy defines must implement ILoggableClientProxyMessage in order to use this event.

Important Properties

PropertyDescription
ClientIdA unique id asigned to the client when it first connected.
MessageTypeA string value representing the type of the message that was received
MessageIdThe globally unique message id that was assigned to the incoming message.

Client Message Sent

This event is recorded by a client proxy when it successfully serializes and transmits a message to its connected client. Not all client proxies may record this event. The messages a client proxy defines must implement ILoggableClientProxyMessage in order to use this event.

Important Properties

PropertyDescription
ClientIdA unique id asigned to the client when it first connected.
MessageTypeA string value representing the type of the message that was received
MessageIdThe globally unique message id that was assigned to the incoming message.

Subscription Events

Subscription events refer to the events that are raised from mutations and processed by client proxies with registered subscriptions against those events.

Subscription Event Published

This event is recorded just after an event is handed off to a ISubscriptionEventPublisher for publishing to a storage medium. Custom publishers do not need to record this event manually.

Important Properties

PropertyDescription
SchemaTypeThe schema type name as it was published. This will likely include additional information not recorded in standard schema level events.
DataTypeThe data type name of the data object that was published.
SubscriptionEventIdThe globally unique id of the subscription event.
SubscriptionEventNameThe name of the event as its defined in the schema.
MachineNameThe Environment.MachineName of the current server.

Subscription Event Received

This event is recorded by the event router just after it receives an event. The router will then proceed to forward the event to the correct client instances for processing.

Important Properties

PropertyDescription
SchemaTypeThe schema type name as it was recevied. This will likely include additional information not recorded in standard schema level events.
DataTypeThe data type name of the data object that was received.
SubscriptionEventIdThe globally unique id of the subscription event.
SubscriptionEventNameThe name of the event as its defined in the schema.
MachineNameThe Environment.MachineName of the current server.
+ + + + \ No newline at end of file diff --git a/docs/logging/subscription-events.md b/docs/logging/subscription-events.md deleted file mode 100644 index 4aa906a..0000000 --- a/docs/logging/subscription-events.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -id: subscription-events -title: Subscription Logging Events -sidebar_label: Subscription Events -sidebar_position: 2 ---- - -GraphQL ASP.NET tracks some special events related to the management of [subscriptions](../advanced/subscriptions.md). They are outlined below. - -**Common Event Properties** - -The common event properties outlined on the [standard events](./standard-events.md) page apply to all subscription events as well. - -## Server Level Events - -### Subscription Event Dispatch Queue Alert -This event is recorded when the server's schema-agnostic, internal dispatch queue reaches a given threshold. The internal dispatch queue is where all subscription events destined for connected clients are staged before being processed. The thresholds at which this alert is recorded can be [customized](../advanced/subscriptions.md#dispatch-queue-monitoring). - -| Property | Description | -| ---------------- | ---------------------------------------------------------------------| -| _ThresholdLevelReached_ | The declared threshold level that was reached causing this entry to be recorded. (Expressed in # of queued events) | -| _EventQueueCount_ | The actual number of events currently queued. | -| _CustomMessage_ | An optional, staticly declared message registered with the threshold level. | - -## Schema Level Events - -### Subscription Route Registered - -This event is recorded when GraphQL successfully registers an entry in the ASP.NET route table to accept requests for a target schema as well as -register the middleware component necessary to receive websocket requests. - -**Important Properties** - -| Property | Description | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| _SchemaTypeName_ | The full name of your your schema type. For most single schema applications this will be `GraphQL.AspNet.Schemas.GraphSchema`. | -| _SchemaSubscriptionRoutePath_ | The relative URL that was registered for the schema type, e.g. `'/graphql` | - - -## Client Connection Events - - ### Client Registered - -This event is recorded when GraphQL successfully accepts a client and has assigned a client proxy to manage the connection. This event is recorded just prior to the connection is "started" and messaging begins. - -**Important Properties** - -| Property | Description | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| _ClientId_ | A unique id asigned to the client when it first connected. | -| _SchemaTypeName_ | The full name of your your schema type. For most single schema applications this will be `GraphQL.AspNet.Schemas.GraphSchema`. | -| _ClientTypeName_ | The full name of the assigned client proxy type. In general, a different type is used per messaging protocol. | -| _ClientProtocol_ | The protocol negotiated by the client and server that will be used for the duration of the connection. | - - ### Client Dropped - -This event is recorded when GraphQL is releasing a client. The connection has been "stopped" and no additional messagings are being broadcast. This event occurs just before the HTTP connection is closed. - -**Important Properties** - -| Property | Description | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| _ClientId_ | A unique id asigned to the client when it first connected. | -| _ClientTypeName_ | The full name of the assigned client proxy type. In general, a different type is used per messaging protocol. | -| _ClientProtocol_ | The protocol negotiated by the client and server that will be used for the duration of the connection. | - - - ### Unsupported Client Protocol - -This event is recorded when GraphQL attempts to create an appropriate proxy class for the connection but no such proxy could be deteremined from the details providied in the initial request. In general, this means the provided websocket sub protocols did not match a supported protocol for this server and schema combination. - -| Property | Description | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| _SchemaTypeName_ | The full name of your your schema type. For most single schema applications this will be `GraphQL.AspNet.Schemas.GraphSchema`. | -| _ClientProtocol_ | The protocol(s) requested by the client connection that were not accepted | - -**Important Properties** - -| Property | Description | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| _ClientId_ | A unique id asigned to the client when it first connected. | -| _ClientTypeName_ | The full name of the assigned client proxy type. In general, a different type is used per messaging protocol. | - - -## Client Messaging Events - -### Subscription Event Received - -This event is recorded by a client proxy when it received an event from the router and has determined that it should be handled. - -**Important Properties** - -| Property | Description | -| ---------------- | --------------------------------------------------------------------- | -| _ClientId_ | A unique id asigned to the client when it first connected. | -| _SchemaTypeName_ | The full name of your your schema type. For most single schema applications this will be `GraphQL.AspNet.Schemas.GraphSchema`. | | -| _SubscriptionPath_ | The path to the target top-level subscription field in the schema | -| _SubscriptionCount_ | The number of registered subscriptions, for this client, that will receive this event. | -| _SubscriptionIds_ | A comma seperated list of id values representing the subscriptions that will receive this event. | -| _MachineName_ | The `Environment.MachineName` of the current server. | - -### Subscription Registered - -This event is recorded by a client proxy when it starts a new subscription on behalf of its connected client. - -**Important Properties** - -| Property | Description | -| ---------------- | --------------------------------------------------------------------- | -| _ClientId_ | A unique id asigned to the client when it first connected. | -| _SubscriptionPath_ | The path to the target top-level subscription field in the schema | -| _SubscriptionId_ | The subscription id requested by the client. | - - -### Subscription Registered - -This event is recorded by a client proxy when it unregistered and abandons an existing subscription. This may be due to the server ending the subscription or the client requesting it be stopped. - -**Important Properties** - -| Property | Description | -| ---------------- | --------------------------------------------------------------------- | -| _ClientId_ | A unique id asigned to the client when it first connected. | -| _SubscriptionPath_ | The path to the target top-level subscription field in the schema | -| _SubscriptionId_ | The subscription id requested by the client. | - - -### Client Message Received - -This event is recorded by a client proxy when it successfully receives and deserializes a message from its connected client. Not all client proxies may record this event. The messages a client proxy defines must implement `ILoggableClientProxyMessage` in order to use this event. - -**Important Properties** - -| Property | Description | -| ---------------- | --------------------------------------------------------------------- | -| _ClientId_ | A unique id asigned to the client when it first connected. | -| _MessageType_ | A string value representing the type of the message that was received | -| _MessageId_ | The globally unique message id that was assigned to the incoming message. | - -### Client Message Sent - -This event is recorded by a client proxy when it successfully serializes and transmits a message to its connected client. Not all client proxies may record this event. The messages a client proxy defines must implement `ILoggableClientProxyMessage` in order to use this event. - -**Important Properties** - -| Property | Description | -| ---------------- | --------------------------------------------------------------------- | -| _ClientId_ | A unique id asigned to the client when it first connected. | -| _MessageType_ | A string value representing the type of the message that was received | -| _MessageId_ | The globally unique message id that was assigned to the incoming message. | - -## Subscription Events - -Subscription events refer to the events that are raised from mutations and processed by client proxies with registered subscriptions against those events. - - ### Subscription Event Published - - This event is recorded just after an event is handed off to a `ISubscriptionEventPublisher` for publishing to a storage medium. Custom publishers do not need to record this event manually. - - -**Important Properties** - -| Property | Description | -| ---------------- | ---------------------------------------------------------------------| -| _SchemaType_ | The schema type name as it was published. This will likely include additional information not recorded in standard schema level events. | -| _DataType_ | The data type name of the data object that was published. | -| _SubscriptionEventId_ | The globally unique id of the subscription event. | -| _SubscriptionEventName_ | The name of the event as its defined in the schema. | -| _MachineName_ | The `Environment.MachineName` of the current server. | - - ### Subscription Event Received - - This event is recorded by the event router just after it receives an event. The router will then proceed to forward the event to the correct client instances for processing. - -**Important Properties** - -| Property | Description | -| ---------------- | ---------------------------------------------------------------------| -| _SchemaType_ | The schema type name as it was recevied. This will likely include additional information not recorded in standard schema level events. | -| _DataType_ | The data type name of the data object that was received. | -| _SubscriptionEventId_ | The globally unique id of the subscription event. | -| _SubscriptionEventName_ | The name of the event as its defined in the schema. | -| _MachineName_ | The `Environment.MachineName` of the current server. | diff --git a/docs/quick/_category_.json b/docs/quick/_category_.json deleted file mode 100644 index 779779e..0000000 --- a/docs/quick/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Getting Started", - "position": 0, - "collapsed": false -} \ No newline at end of file diff --git a/docs/quick/code-examples.html b/docs/quick/code-examples.html new file mode 100644 index 0000000..f823411 --- /dev/null +++ b/docs/quick/code-examples.html @@ -0,0 +1,24 @@ + + + + + +Code Examples | GraphQL ASP.NET + + + + +
+

Code Examples

Below is a quick introduction to some common scenarios and the C# code to support them.

Configuring Services

The library uses a standard "Add & Use" pattern for configuring services with your application. A route is added to the ASP.NET request pipeline to handle GET and POST requests when you call .UseGraphQL(). Place it as appropriate amongst any other configurations, routes, authorization etc. when you build your pipeline.

Program.cs
var builder = WebApplication.CreateBuilder(args);

// Add graphql services to the DI container
builder.Services.AddGraphQL();

var app = builder.Build();

// Configure the HTTP request pipeline
app.UseGraphQL();
app.Run();

The configuration steps may vary slightly when using a Startup.cs file; typical for .NET 5 or earlier

A Basic Controller

A simple controller to return data based on a string value.

Notice we inherit from GraphController not the standard web api Controller.

HeroController.cs
public class HeroController : GraphController
{
[QueryRoot]
public Human Hero(string episode)
{
if(episode == "Empire")
{
return new Human()
{
Id = 1000,
Name = "Han Solo",
HomePlanet = "Corellia",
}
}
else
{
return new Human()
{
Id = 1001,
Name = "Luke SkyWalker",
HomePlanet = "Tatooine",
}
}
}
}
Sample Query
query {
hero(episode: "Empire") {
name
homePlanet
}
}
JSON Result
{
"data" : {
"hero": {
"name" : "Han Solo",
"homePlanet" : "Corellia"
}
}
}
Did you notice?

In the query the hero field is camelCased but in C# the method is ProperCased? Field names are automatically translated to standard GraphQL conventions. The same goes for your graph type names, enum values etc.

You can also implement your own GraphNameFormatter and alter the name formats for each of your registered schemas.

Using an Interface

If your models share a common interface just return it from a controller action and the library will create the appropriate graph types for you.

Don't forget to declare the object types that implement your interface (e.g. Droid and Human) or the library won't know what resolvers to invoke at runtime. In this example, we've declared them inline but you can easily add them at startup to reduce the noise.

HeroController.cs
public class HeroController : GraphController
{
[QueryRoot(typeof(Droid), typeof(Human))]
public ICharacter Hero(Episode episode)
{
if(episode == Episode.Empire)
{
return new Human()
{
Id = 1000,
Name = "Han Solo",
HomePlanet = "Corellia",
}
}
else
{
return new Droid()
{
Id = 2000,
Name = "R2R2",
Type = DroidType.AstroMech
}
}
}
}
GraphQL Query
query {
hero(episode: JEDI) {
id
name

... on Human {
homePlanet
}

... on Droid {
type
}
}
}

Field Paths

We've used [QueryRoot] so far to force a controller action to be a root field on the query type. But we can use an approximation of Web API's url templates to create any combination of nested fields needed. When you have 50 controllers with 20-40 actions each, organizing your object hierarchy becomes trivial.

RebelAllianceController.cs
[GraphRoute("rebels")]
public class RebelAllianceController : GraphController
{
[Query("directory/hero")]
public Human RetrieveHero(Episode episode)
{
// Wedge is the true hero
return new Human()
{
Id = 1003,
Name = "Wedge Antilles",
HomePlanet = "Corellia",
};
}
}
Sample Query
query {
rebels {
directory {
hero(episode: EMPIRE) {
name
homePlanet
}
}
}
}

Dependency Injection

At runtime, GraphQL invokes your graph controllers and injected services with the same dependency scope as the original HTTP Request. Add a known service to a controller's constructor and it will be automatically resolved with its configured scope.

PersonsController.cs
public class PersonsController : GraphController
{
private IPersonService _personService;
public PersonsController(IPersonService service)
{
_personService = service;
}

[QueryRoot("person")]
public async Task<Human> RetrievePerson(int id)
{
return await _personService.RetrievePerson(id);
}
}
Query
query {
person(id: 1000) {
id
name
homePlanet
}
}
Did You Notice?

We switched to an asynchronous method with Task<Human>. GraphQL ASP.NET follows your lead and will execute your actions asynchronously or synchronously as needed.

Authorization

Add the [Authorize] attribute and you're done. GraphQL ASP.NET uses the same authorization pipeline as your application.

PersonsController.cs
public class PersonsController : GraphController
{
private IPersonService _personService;
public PersonsController(IPersonService service)
{
_personService = service;
}

[Authorize]
[QueryRoot("self")]
public async Task<Employee> RetrievePerson()
{
return await _personService.RetrievePerson(this.User.Name);
}
}
Sample Query
query {
self {
id
name
title
}
}

✅ Notes on Authorization

  • Your controller actions have full access to the same ClaimsPrincipal that you get with this.User on an web api controller. In fact, its the same object reference.
  • Out of the box, the library performs authorization on a "per field" basis. This includes POCO object properties! If you have a piece of sensitive data attached to a property, say Birthday, on your Person model, you can apply a policy or role to it. Unauthorized user's won't be able to query for that field, even if they can access the controller that produced the object its attached or every other field on the object.
    • Note: You'll have to implement .NET's IAuthorizeData interface on your own custom attribute, the [Authorize] attribute provided by .NET does not allow targeting of properties.
  • GraphQL obeys layered authorization requirements as well. Place an authorization attribute at the controller level and it'll be checked before any method level requirements.

Mutations & Model State

GraphQL ASP.NET will automatically enforce the query specification rules for you, but that doesn't help for business-level requirements like string length or integer ranges. For that, it uses the familiar goodness of Validation Attributes (e.g. [StringLength], [Range] etc.).

PersonsController.cs
public class PersonsController : GraphController
{
/* constructor hidden for brevity */

[MutationRoot("joinTheResistance")]
public async Task<Human> CreatePerson(Human model)
{
// ***************************
// Check if the model passed validation
// requirements before using it
// ***************************
if(!this.ModelState.IsValid)
return null;

return await _service.CreatePerson(model);
}
}
Human.cs
public class Human
{
public int? Id{ get; set; }

[StringLength(35)]
public string Name { get; set; }

public string HomePlanet { get; set; }
}
Sample Query
mutation {
joinTheResistance(
newPerson: {
name: "Lando Calrissian"
homePlanet: "Bespin" }) {
id
name
homePlanet
}
}
Did You Notice?

We used Human as an input argument and as the returned data object. The library will automatically generate the appropriate graph types for INPUT_OBJECT and OBJECT, respectively, add them to your schema when needed.

Action Results

Just as Web API makes use of IActionResult to perform post processing on the result of a controller method, GraphQL ASP.NET makes use of IGraphActionResult.

Reusing the previous example, here we make use of this.BadRequest() to automatically generate an appropriate error message in the response when model validation fails. Field origin information including the path array and line/column number of the original query are wired up automatically.

// C# Controller
public class PersonsController : GraphController
{
[MutationRoot("joinTheResistance", typeof(Human))]
public async IGraphActionResult CreatePerson(Human model)
{
// ***************************
// Check if the model passes validation
// requirements before using it
// ***************************
if(!this.ModelState.IsValid)
return this.BadRequest(this.ModelState);

var result = await _service.CreatePerson(model);
return result != null
? this.Ok(result)
: this.Error("Woops Something broke");
}
}

public class Human
{
public int? Id{ get; set; }

[StringLength(35)]
public string Name { get; set; }

public string HomePlanet { get; set; }
}
GraphQL is not Rest

Unlike WebAPI, BadRequest() doesn't generate a HTTP Status 400 error for the request. If there are multiple controller methods being resolved GraphQL can still generate a partial response and render data for other parts of the query. Most "error" related action results add a standard error message to the result with different reason codes.

+ + + + \ No newline at end of file diff --git a/docs/quick/code-examples.md b/docs/quick/code-examples.md deleted file mode 100644 index bd102cf..0000000 --- a/docs/quick/code-examples.md +++ /dev/null @@ -1,363 +0,0 @@ ---- -id: code-examples -title: Code Examples -sidebar_label: Code Examples -sidebar_position: 2 ---- - -Below is a quick introduction to some common scenarios and the C# code to support them. - -## Configuring Services - -The library uses a standard "Add & Use" pattern for configuring services with your application. A route is added to the ASP.NET request pipeline to handle GET and POST requests when you call `.UseGraphQL()`. Place it as appropriate amongst any other configurations, routes, authorization etc. when you build your pipeline. - -```csharp title="Program.cs" -var builder = WebApplication.CreateBuilder(args); - -// Add graphql services to the DI container -// highlight-next-line -builder.Services.AddGraphQL(); - -var app = builder.Build(); - -// Configure the HTTP request pipeline -// highlight-next-line -app.UseGraphQL(); -app.Run(); -``` -> _The configuration steps may vary slightly when using a Startup.cs file; typical for .NET 5 or earlier _ - - -## A Basic Controller - -A simple controller to return data based on a string value. - ->Notice we inherit from `GraphController` not the standard web api Controller. - -```csharp title="HeroController.cs" -// highlight-next-line -public class HeroController : GraphController -{ - [QueryRoot] - public Human Hero(string episode) - { - if(episode == "Empire") - { - return new Human() - { - Id = 1000, - Name = "Han Solo", - HomePlanet = "Corellia", - } - } - else - { - return new Human() - { - Id = 1001, - Name = "Luke SkyWalker", - HomePlanet = "Tatooine", - } - } - } -} -``` - -```graphql title="Sample Query" -query { - hero(episode: "Empire") { - name - homePlanet - } -} -``` - -```js title="JSON Result" -{ - "data" : { - "hero": { - "name" : "Han Solo", - "homePlanet" : "Corellia" - } - } -} -``` - -:::info Did you notice? -In the query the hero field is `camelCased` but in C# the method is `ProperCased`? Field names are automatically translated to standard GraphQL conventions. The same goes for your graph type names, enum values etc. - -You can also implement your own `GraphNameFormatter` and alter the name formats for each of your registered schemas. -::: - -## Using an Interface - -If your models share a common interface just return it from a controller action and the library will create the appropriate graph types for you. - -> Don't forget to declare the object types that implement your interface (e.g. Droid and Human) or the library won't know what resolvers to invoke at runtime. In this example, we've declared them inline but you can easily add them at startup to reduce the noise. - -```csharp title="HeroController.cs" -public class HeroController : GraphController -{ - // highlight-next-line - [QueryRoot(typeof(Droid), typeof(Human))] - // highlight-next-line - public ICharacter Hero(Episode episode) - { - if(episode == Episode.Empire) - { - return new Human() - { - Id = 1000, - Name = "Han Solo", - HomePlanet = "Corellia", - } - } - else - { - return new Droid() - { - Id = 2000, - Name = "R2R2", - Type = DroidType.AstroMech - } - } - } -} -``` - -```graphql title="GraphQL Query" -query { - hero(episode: JEDI) { - id - name - - ... on Human { - homePlanet - } - - ... on Droid { - type - } - } -} -``` - -## Field Paths - -We've used `[QueryRoot]` so far to force a controller action to be a root field on the `query` type. But we can use an approximation of Web API's url templates to create any combination of nested fields needed. When you have 50 controllers with 20-40 actions each, organizing your object hierarchy becomes trivial. - - -```csharp title="RebelAllianceController.cs" -// highlight-next-line -[GraphRoute("rebels")] -public class RebelAllianceController : GraphController -{ - // highlight-next-line - [Query("directory/hero")] - public Human RetrieveHero(Episode episode) - { - // Wedge is the true hero - return new Human() - { - Id = 1003, - Name = "Wedge Antilles", - HomePlanet = "Corellia", - }; - } -} -``` - -```graphql title="Sample Query" -query { - rebels { - directory { - hero(episode: EMPIRE) { - name - homePlanet - } - } - } -} -``` - - -## Dependency Injection - -At runtime, GraphQL invokes your graph controllers and injected services with the same dependency scope as the original HTTP Request. Add a known service to a controller's constructor and it will be automatically resolved with its configured scope. - -```cs title="PersonsController.cs" -public class PersonsController : GraphController -{ - private IPersonService _personService; - // highlight-next-line - public PersonsController(IPersonService service) - { - _personService = service; - } - - [QueryRoot("person")] - public async Task RetrievePerson(int id) - { - return await _personService.RetrievePerson(id); - } -} -``` - -```graphql title="Query" -query { - person(id: 1000) { - id - name - homePlanet - } -} -``` - - -:::info Did You Notice? -We switched to an asynchronous method with `Task`. GraphQL ASP.NET follows your lead and will execute your actions asynchronously or synchronously as needed. -::: - -## Authorization - -Add the `[Authorize]` attribute and you're done. GraphQL ASP.NET uses the same authorization pipeline as your application. - - -```csharp title="PersonsController.cs" -public class PersonsController : GraphController -{ - private IPersonService _personService; - public PersonsController(IPersonService service) - { - _personService = service; - } - - // highlight-next-line - [Authorize] - [QueryRoot("self")] - public async Task RetrievePerson() - { - return await _personService.RetrievePerson(this.User.Name); - } -} -``` - -```graphql title="Sample Query" -query { - self { - id - name - title - } -} -``` - -#### ✅ Notes on Authorization - -- Your controller actions have full access to the same `ClaimsPrincipal` that you get with `this.User` on an web api controller. In fact, its the same object reference. -- Out of the box, the library performs authorization on a "per field" basis. This includes POCO object properties! If you have a piece of sensitive data attached to a property, say Birthday, on your Person model, you can apply a policy or role to it. Unauthorized user's won't be able to query for that field, even if they can access the controller that produced the object its attached or every other field on the object. - - _Note: You'll have to implement .NET's_ `IAuthorizeData` _interface on your own custom attribute, the_ `[Authorize]` _attribute provided by .NET does not allow targeting of properties._ -- GraphQL obeys layered authorization requirements as well. Place an authorization attribute at the controller level and it'll be checked before any method level requirements. - -## Mutations & Model State - -GraphQL ASP.NET will automatically enforce the query specification rules for you, but that doesn't help for business-level requirements like string length or integer ranges. For that, it uses the familiar goodness of Validation Attributes (e.g. `[StringLength]`, `[Range]` etc.). - - -```csharp title="PersonsController.cs" -public class PersonsController : GraphController -{ - /* constructor hidden for brevity */ - - [MutationRoot("joinTheResistance")] - public async Task CreatePerson(Human model) - { - // *************************** - // Check if the model passed validation - // requirements before using it - // *************************** - // highlight-start - if(!this.ModelState.IsValid) - return null; - // highlight-end - - return await _service.CreatePerson(model); - } -} -``` - -```csharp title="Human.cs" -public class Human -{ - public int? Id{ get; set; } - - // highlight-next-line - [StringLength(35)] - public string Name { get; set; } - - public string HomePlanet { get; set; } -} -``` - -```graphql title="Sample Query" -mutation { - joinTheResistance( - newPerson: { - name: "Lando Calrissian" - homePlanet: "Bespin" }) { - id - name - homePlanet - } -} -``` - -:::info Did You Notice? -We used `Human` as an input argument **and** as the returned data object. The library will automatically generate the appropriate graph types for `INPUT_OBJECT` and `OBJECT`, respectively, add them to your schema when needed. -::: - - -## Action Results - -Just as Web API makes use of `IActionResult` to perform post processing on the result of a controller method, GraphQL ASP.NET makes use of `IGraphActionResult`. - -Reusing the previous example, here we make use of `this.BadRequest()` to automatically generate an appropriate error message in the response when model validation fails. Field origin information including the path array and line/column number of the original query are wired up automatically. - -```csharp -// C# Controller -public class PersonsController : GraphController -{ - [MutationRoot("joinTheResistance", typeof(Human))] - public async IGraphActionResult CreatePerson(Human model) - { - // *************************** - // Check if the model passes validation - // requirements before using it - // *************************** - if(!this.ModelState.IsValid) - // highlight-next-line - return this.BadRequest(this.ModelState); - - var result = await _service.CreatePerson(model); - return result != null - // highlight-start - ? this.Ok(result) - : this.Error("Woops Something broke"); - // highlight-end - } -} - -public class Human -{ - public int? Id{ get; set; } - - [StringLength(35)] - public string Name { get; set; } - - public string HomePlanet { get; set; } -} -``` - -:::note GraphQL is not Rest - Unlike WebAPI, `BadRequest()` doesn't generate a HTTP Status 400 error for the request. If there are multiple controller methods being resolved GraphQL can still generate a partial response and render data for other parts of the query. Most "error" related action results add a standard error message to the result with different reason codes. -::: \ No newline at end of file diff --git a/docs/quick/create-app.html b/docs/quick/create-app.html new file mode 100644 index 0000000..9149197 --- /dev/null +++ b/docs/quick/create-app.html @@ -0,0 +1,24 @@ + + + + + +Building Your First App | GraphQL ASP.NET + + + + +
+

Building Your First App

This document will help walk you through creating a new Web API project, installing the GraphQL ASP.NET library and writing your first controller.

Create a New Web API Project

💻 Setup a new ASP.NET Core Web API project:

web api project

Add the Package From Nuget

💻 Add the GraphQL.AspNet nuget package:

# Using the dotnet CLI
> dotnet add package GraphQL.AspNet

Create a Controller

💻 Create your first Graph Controller:

BakeryController.cs
using GraphQL.AspNet.Attributes;
using GraphQL.AspNet.Controllers;

public class BakeryController : GraphController
{
[QueryRoot("donut")]
public Donut RetrieveDonut()
{
return new Donut()
{
Id = 3,
Name = "Snowy Dream",
Flavor = "Vanilla"
};
}
}

public class Donut
{
public int Id { get; set; }
public string Name { get; set; }
public string Flavor { get; set; }
}

Configure Startup

💻 Register GraphQL with your services collection and your application pipeline:

Program.cs
using GraphQL.AspNet.Configuration;

var builder = WebApplication.CreateBuilder(args);

// Add graphql services to the DI container.
builder.Services.AddGraphQL();

var app = builder.Build();

// Configure the HTTP request pipeline.
app.UseGraphQL();
app.Run();

The configuration steps may vary slightly when using a Startup.cs file; typical for .NET 5 or earlier

Execute a Query

💻 Start the application and using your favorite tool, execute a query:

Sample Query
query {
donut {
id
name
flavor
}
}

Results:

query results

The port number on your app may be different than that shown in the image

+ + + + \ No newline at end of file diff --git a/docs/quick/create-app.md b/docs/quick/create-app.md deleted file mode 100644 index a391baf..0000000 --- a/docs/quick/create-app.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -id: create-app -title: Building Your First App -sidebar_label: Your First App -sidebar_position: 1 -description: Step by Step instructions for creating a sample application ---- - -This document will help walk you through creating a new Web API project, installing the GraphQL ASP.NET library and writing your first controller. - -## Create a New Web API Project -💻 Setup a new `ASP.NET Core Web API` project: - -![web api project](../assets/create-new-web-api-project.png "Select ASP.NET Core Web API") - -## Add the Package From Nuget -💻 Add the `GraphQL.AspNet` nuget package: - -```powershell -# Using the dotnet CLI -> dotnet add package GraphQL.AspNet -``` - -## Create a Controller - -💻 Create your first Graph Controller: - -```csharp title="BakeryController.cs" -using GraphQL.AspNet.Attributes; -using GraphQL.AspNet.Controllers; - -public class BakeryController : GraphController -{ - [QueryRoot("donut")] - public Donut RetrieveDonut() - { - return new Donut() - { - Id = 3, - Name = "Snowy Dream", - Flavor = "Vanilla" - }; - } -} - -public class Donut -{ - public int Id { get; set; } - public string Name { get; set; } - public string Flavor { get; set; } -} -``` - - -## Configure Startup - -💻 Register GraphQL with your services collection and your application pipeline: - -```csharp title="Program.cs" -using GraphQL.AspNet.Configuration; - -var builder = WebApplication.CreateBuilder(args); - -// Add graphql services to the DI container. -// highlight-next-line -builder.Services.AddGraphQL(); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -// highlight-next-line -app.UseGraphQL(); -app.Run(); -``` -> _The configuration steps may vary slightly when using a Startup.cs file; typical for .NET 5 or earlier _ - -## Execute a Query - -💻 Start the application and using your favorite tool, execute a query: - -```graphql title="Sample Query" -query { - donut { - id - name - flavor - } -} -``` - -### Results: - -![query results](../assets/overview-sample-query-results.png "Results using GraphQL Playground") -> _The port number on your app may be different than that shown in the image_ \ No newline at end of file diff --git a/docs/quick/overview.html b/docs/quick/overview.html new file mode 100644 index 0000000..fb3ad33 --- /dev/null +++ b/docs/quick/overview.html @@ -0,0 +1,24 @@ + + + + + +Overview | GraphQL ASP.NET + + + + +
+

Overview

Use the menus on the left to navigate through the documentation. You do not need to read the various sections in order, feel free to use this as a reference guide as you dig deeper.

Nuget & Installation

.NET Standard 2.0 .NET 8 .NET 9

The library is available on nuget and can be added to your project via the conventional means.

How to Install The Library
# Using the dotnet CLI
> dotnet add package GraphQL.AspNet

# Using Package Manager Console
> Install-Package GraphQL.AspNet

👉 Your First App: Step by step instructions for configuring app services and writing your first controller.

👉 Code Examples: A few code snippets if you just want the gist of things.

Other Helpful Pages

These pages may be helpful in getting started with the library:

💡 Controllers & Actions : Everything you need to know about creating a GraphController and defining action methods.

📜 Attributes : A reference list of the various [Attributes] used to configure your controllers and models.

📐 Schema Configuration : A reference list of the various configuration options.

📌 Demo Projects : A number of downloadable sample projects covering a wide range of topics

+ + + + \ No newline at end of file diff --git a/docs/quick/overview.md b/docs/quick/overview.md deleted file mode 100644 index b3bb6af..0000000 --- a/docs/quick/overview.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -id: overview -title: Overview -sidebar_label: Overview -sidebar_position: 0 -description: A quick overview of how to use the library ---- - -Use the menus on the left to navigate through the documentation. You do not need to read the various sections in order, feel free to use this as a reference guide as you dig deeper. - -## Nuget & Installation - -.NET Standard 2.0 .NET 8 .NET 9

- -The library is available on [nuget](https://www.nuget.org/packages/GraphQL.AspNet/) and can be added to your project via the conventional means. - -```powershell title="How to Install The Library" -# Using the dotnet CLI -> dotnet add package GraphQL.AspNet - -# Using Package Manager Console -> Install-Package GraphQL.AspNet -``` - - -👉 [Your First App](./create-app.md): Step by step instructions for configuring app services and writing your first controller. - -👉 [Code Examples](./code-examples.md): A few code snippets if you just want the gist of things. - - - - -## Other Helpful Pages - -These pages may be helpful in getting started with the library: - - - -💡 [Controllers & Actions](../controllers/actions.md) : Everything you need to know about creating a `GraphController` and defining action methods. - -📜 [Attributes](../reference/attributes.md) : A reference list of the various `[Attributes]` used to configure your controllers and models. - -📐 [Schema Configuration](../reference/schema-configuration.md) : A reference list of the various configuration options. - -📌 [Demo Projects](../reference/demo-projects.md) : A number of downloadable sample projects covering a wide range of topics - - \ No newline at end of file diff --git a/docs/reference/_category_.json b/docs/reference/_category_.json deleted file mode 100644 index 5d9d820..0000000 --- a/docs/reference/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "References", - "position": 9, - "collapsed": false -} \ No newline at end of file diff --git a/docs/reference/attributes.html b/docs/reference/attributes.html new file mode 100644 index 0000000..cccb151 --- /dev/null +++ b/docs/reference/attributes.html @@ -0,0 +1,28 @@ + + + + + +Attributes | GraphQL ASP.NET + + + + +
+

Attributes

This document contains an alphabetical reference of each of the class, property and method attributes used by GraphQL ASP.NET.

ApplyDirective

Declares that a given type system directive should be applied to the target schema item (an object, a field, an enum etc.). See the page on type system directives for complete details on how to build your own. Directives can be applied by type, by name and with or without parameters.

public class Person 
{
// apply by registered system type
[ApplyDirective(typeof(DeprecatedDirective))]
public string FirstName{ get; set; }

// apply by name, also with a reason parameter
[ApplyDirective("deprecated", "Last Name is deprecated")]
public string LastName{ get; set; }
}

BatchTypeExtension

Declares a controller action method as a field on another graph type rather than a query or mutation action. All source items needing this field resolved will be resolved in a single field request. +The batch method must declare a parameter of IEnumerable<TypeToExtend>.

[BatchTypeExtension(typeToExtend, fieldName)]

  • typeToExtend - The graph type to which this field will be added
  • fieldName - The name to give to this field.

Declares a batch type extension with the given field name. The return type of this field will be taken from the return type of the method. The return type of the method must be IDictionary<TypeToExtend, ReturnFieldType>

public class HeroController : GraphController
{
[BatchTypeExtension(typeof(Human), "droids")]
public IDictionary<Human, IEnumerable<Droid>> Hero(IEnumerable<Human> humans)
{
//....
}
}

[BatchTypeExtension(typeToExtend, fieldName, returnType)]

  • typeToExtend - The graph type to which this field will be added
  • fieldName - The name to give to this field.
  • returnType - The type of data returned from this field

Declares a batch type extension return type explicitly allowing use of IGraphActionResult and more importantly, this.StartBatch() for generating +a batch collection result.

public class HeroController : GraphController
{
[BatchTypeExtension(typeof(Human), "droids", typeof(IEnumerable<Droid>))]
public IGraphActionResult Hero(IEnumerable<Human> humans)
{
//....
}
}

[BatchTypeExtension(typeToExtend, fieldName, unionName, unionTypeA, unionTypeB, additionalUnionTypes)]

  • typeToExtend - The graph type to which this field will be added
  • fieldName - The name to give to this field.
  • unionName - The name to give to the union in the object graph
  • unionTypeA - The first member type of the union (must be an object, not an interface)
  • unionTypeB - The second member type of the union (must be an object, not an interface)
  • additionalUnionTypes - N additional union types to declare as part of this union

Declares the batch type extension as returning a union rather than a single specific data type.

public class HeroController : GraphController
{
[BatchTypeExtension(typeof(Human), "bestFriend", "DroidOrHuman", typeof(Droid), typeof(Human))]
public IGraphActionResult Hero(IEnumerable<Human> humans)
{
//....
}
}

Deprecated

Indicates to any introspection queries that the field or action method is deprecated and due to be removed.

[Deprecated]

[Deprecated(reasonText)]

  • reasonText - The reason for the deprecation that is displayed in an introspection query.
public class CharacterController : GraphController
{
[Query]
[Deprecated("Use the field SuperHero, this field will be removed soon")]
public IGraphActionResult Hero(Episode episode = Episode.EMPIRE)
{
//....
}
}

Description

Adds a human-readable description to any type, interface, field, parameter, enum value etc.

[Description(text)]

  • text - The text to display in an introspection query.
[Description("A field containing information related to the characters of Star Wars")]
public class CharacterController : GraphController
{
[Query]
[Description("The hero of a given Star Wars Episode (Default: EMPIRE)")]
public IGraphActionResult Hero(Episode episode = Episode.EMPIRE)
{
//....
}
}

DirectiveLocations

A set of flags indicating where in a query document the given directive can be declared. Also serves to indicate which directive action +method should be invoked for a particular location.

[DirectiveLocations(directiveLocation)]

public sealed class AllowFragment : GraphDirective
{
[DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)]
public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument)
{
return ifArgument ? this.Ok() : this.Cancel();
}
}

FromGraphQL

Indicates additional or non-standard settings related to the method parameter its attached to. Can be used for controller action methods +and directive action methods.

[FromGraphQL(argumentName)]

  • argumentName: The name of this parameter in the object graph
public class CharacterController : GraphController
{
[Query]
public IGraphActionResult Hero([FromGraphQL("id")] int heroId)
{
//....
}
}

[FromGraphQL(TypeExpression = "Type!")]

  • TypeExpression: A custom type expression, in query syntax language, to declare explicit nullability and list rules for this parameter.
public class CharacterController : GraphController
{
[Query]
public IGraphActionResult Hero([FromGraphQL(TypeExpression = "Type!")] string heroId)
{
//....
}
}

GraphEnumValue

Acts to explicitly declare an enumeration value as being exposed on an enumeration graph type.

[GraphEnumValue]

[GraphEnumValue(name)]

  • name: the name to use in the object graph for this enum value.
public enum Episode
{
NewHope,
Empire,

[GraphEnumValue("Jedi")]
ReturnOfTheJedi,
}

GraphField

Acts to explicitly declare a method or property as being part of a graph type.

[GraphField]

[GraphField(name)]

  • name - The name of this field as it should appear in the object graph to be queried
public class Human
{
public int Id{get; set; }

[GraphField("name")]
public string FullName { get; set; }
}

[GraphField(TypeExpression = "Type!")]

  • TypeExpression - Define a custom type expression; useful in setting a normally optional input field (such as a string or other object) to being required. Supply the type expression as a valid graphql syntax type expression.
public class Human
{
public int Id{get; set; }

[GraphField(TypeExpression = "Type!")]
public Employer Boss { get; set; }
}

GraphRoot

Indicates that the controller should not attempt to register a virtual field for itself and that all methods should be extended off the their respective root types.

[GraphRoot]

[GraphRoot]
public class HeroController : GraphController
{
[Query]
public Human Hero(Episode episode)
{
//..
}
}
# GraphQL Query
query {
hero(episode: EMPIRE) {
name
homePlanet
}
}

GraphRoute

Indicates a field path in each root graph type where this controller should append its action methods.

[GraphRoute(template)]

  • template - A set of / separated path segments representing a nested set of fields where the controller should reside.
    • The "[controller]" meta tag can be used and will be replaced by the controller name at runtime.
[GraphRoute("starWars/characters")]
public class HeroController : GraphController
{
[Query]
public Human Hero(Episode episode)
{
//..
}
}
Sample Query
query {
starWars {
characters {
hero(episode: EMPIRE) {
name
homePlanet
}
}
}
}

GraphSkip

Indicates that the entity to which its attached should be skipped and not included in a schema. GraphSkip can be defined on any controller, method, property, interface, enum or enum value.

[GraphSkip]

C# Class with GraphSkip
public class Donut
{
public int Id{get; set;}
public string Name{get;set;}

[GraphSkip]
public string Recipe {get; set;}
}
GraphQL Type Definition
# Recipe is not included in the schema
type Donut {
Id: String
Name: String
}

GraphType

Indicates additional or non-standard settings for the the class, interface or enum to which its attached. Also indicates the item is explicitly declared as a graph type and should be included in a schema.

[GraphType(name)]

  • name : The name of graph type as it should appear in the object graph

[GraphType(name, inputName)]

  • name : The name of graph type as it should appear in the schema when used as an OBJECT
  • inputName: The name of the graph type in the schema when used as an INPUT_OBJECT
[GraphType("person", "personModel")]
public class Human
{
public int Id{get; set; }
public string FullName { get; set; }
}

Mutation, MutationRoot, Query, QueryRoot

Controller action method attributes that indicate the method belongs to the specified operation type (query or mutation). When declared as "Root" (i.e. QueryRoot), it indicates that the action method should be declared directly on its operation graph type and not nested underneath a controller's virtual field.

tip

[Query], [QueryRoot], [Mutation] and [MutationRoot] all have identical constructor options.

[Query(template)]

  • template - The field path template to use for this method.
public class CharacterController : GraphController
{
[Query("hero")]
public Human RetrieveTheHero(Episode episode)
{
// ....
}
}
Sample Query
query {
character {
# field is named "hero" not "RetrieveTheHero"
hero(episode: EMPIRE) {
name
homePlanet
}
}
}

[Query(returnType, params otherTypes)]

  • returnType: the expected return type of this field.
    • Must be used when this field returns an IGraphActionResult
  • otherTypes: additional possible types this field could return.
    • Can be used to declare possible concrete types when this field returns an interface.
public class CharacterController : GraphController
{
[Query(typeof(Droid), typeof(Human))]
public ICharacter Hero(Episode episode)
{
// ....
}
}

[Query(template, returnType)]

  • template - The field path template to use for this method.
  • returnType: the expected return type of this field.
    • Must be used when this field returns an IGraphActionResult
public class CharacterController : GraphController
{
[Query("hero", typeof(Human))]
public IGraphActionResult RetrieveTheHero(Episode episode)
{
// ....
}
}

[Query(template, unionName, unionTypeA, unionTypeB, additionalUnionTypes)]

  • template - The field path template to use for this method.
  • unionName - The name to give to the union in the object graph
  • unionTypeA - The first member type of the union (must be an object, not an interface)
  • unionTypeB - The second member type of the union (must be an object, not an interface)
  • additionalUnionTypes - N additional union types to declare as part of this union
public class CharacterController : GraphController
{
[Query("hero", "DroidOrHuman", typeof(Droid), typeof(Human))]
public IGraphActionResult RetrieveCharacter(int id)
{
// ....
}
}

Additional Properties

  • TypeExpression: Define a custom type expression; useful in setting a normally optional field (such as a string or other object) to being required. Supply the type expression as a valid graphql syntax type expression.
public class CharacterController : GraphController
{
// declare that this field must return a value (a null human is not allowed)
[Query("hero", typeof(Human), TypeExpression = "Type!")]
public IGraphActionResult RetrieveTheHero(Episode episode)
{
// ....
}
}

PossibleTypes

(optional) When returning an interface from an action method, this attribute allows for the declaration of additional object types to help reduce clutter in the primary query or mutation declaration.

[PossibleTypes(typeof(TypeA), typeof(TypeB) ...)]

These two controller examples are identical:

Example A
public class CharacterController : GraphController
{
[Query("hero", typeof(Human), typeof(Droid), typeof(Gungan)]
public ICharacter RetrieveTheHero(Episode episode)
{
// ....
}
}
Example B
public class CharacterController : GraphController
{
[Query("hero")]
[PossibleTypes(typeof(Human), typeof(Droid), typeof(Gungan))]
public ICharacter RetrieveTheHero(Episode episode)
{
// ....
}
}

SpecifiedBy

(optional) Provides a convienent way to apply the @specifiedBy directive to a custom scalar. When not used, no url is provided to introspection requests for the scalar information.

[SpecifiedBy(url)]

[SpecifiedBy("https://documentation.example.com/api/money-scalar")]
public class MoneyScalar : IScalarGraphType
{
// details ommited...
}

TypeExtension

Declares a controller action method as a field on another graph type rather than a query or mutation action.

[TypeExtension(typeToExtend, fieldName)]

  • typeToExtend - The graph type to which this field will be added
  • fieldName - The name to give to this field.

Declares a type extension with the given field name. The return type of this field will be taken from the return type of the method.

public class DroidController : GraphController
{
[TypeExtension(typeof(Droid), "ownedBy")]
public Human RetrieveDroidOwner(Droid droid)
{
//....
}
}

[TypeExtension(typeToExtend, fieldName, returnType)]

  • typeToExtend - The graph type to which this field will be added
  • fieldName - The name to give to this field.
  • returnType - The type of data returned from this field

Declares a type extension with an explicit return type. useful when returning IGraphActionResult.

public class HeroController : GraphController
{
[TypeExtension(typeof(Human), "ownedBy", typeof(Droid))]
public IGraphActionResult RetrieveDroidOwner(Droid droid)
{
//....
}
}

[TypeExtension(typeToExtend, fieldName, unionName, unionTypeA, unionTypeB, additionalUnionTypes)]

  • typeToExtend - The graph type to which this field will be added
  • fieldName - The name to give to this field.
  • unionName - The name to give to the union in the object graph
  • unionTypeA - The first member type of the union (must be an object, not an interface)
  • unionTypeB - The second member type of the union (must be an object, not an interface)
  • additionalUnionTypes - N additional union types to declare as part of this union

Declares the type extension as returning a union rather than a specific data type.

public class HeroController : GraphController
{
[TypeExtension(typeof(Droid), "bestFriend", "DroidOrHuman", typeof(Droid), typeof(Human))]
public IGraphActionResult RetrieveDroidsBestFriend(Droid droid)
{
//....
}
}
+ + + + \ No newline at end of file diff --git a/docs/reference/attributes.md b/docs/reference/attributes.md deleted file mode 100644 index d7838f5..0000000 --- a/docs/reference/attributes.md +++ /dev/null @@ -1,599 +0,0 @@ ---- -id: attributes -title: Attributes -sidebar_label: Attributes -sidebar_position: 3 ---- - -This document contains an alphabetical reference of each of the class, property and method attributes used by GraphQL ASP.NET. - -## ApplyDirective - -Declares that a given type system directive should be applied to the target schema item (an object, a field, an enum etc.). See the page on [type system directives](../advanced/directives.md#type-system-directives) for complete details on how to build your own. Directives can be applied by type, by name and with or without parameters. - - -```csharp -public class Person -{ - // apply by registered system type - // highlight-next-line - [ApplyDirective(typeof(DeprecatedDirective))] - public string FirstName{ get; set; } - - // apply by name, also with a reason parameter - // highlight-next-line - [ApplyDirective("deprecated", "Last Name is deprecated")] - public string LastName{ get; set; } -} -``` - -## BatchTypeExtension - -Declares a controller action method as a field on another graph type rather than a query or mutation action. All source items needing this field resolved will be resolved in a single field request. -The batch method must declare a parameter of `IEnumerable`. - -#### `[BatchTypeExtension(typeToExtend, fieldName)]` - -- `typeToExtend` - The graph type to which this field will be added -- `fieldName` - The name to give to this field. - -Declares a batch type extension with the given field name. The return type of this field will be taken from the return type of the method. The return type of the method must be `IDictionary` - -```csharp -public class HeroController : GraphController -{ - // highlight-next-line - [BatchTypeExtension(typeof(Human), "droids")] - public IDictionary> Hero(IEnumerable humans) - { - //.... - } -} -``` - -#### `[BatchTypeExtension(typeToExtend, fieldName, returnType)]` - -- `typeToExtend` - The graph type to which this field will be added -- `fieldName` - The name to give to this field. -- `returnType` - The type of data returned from this field - -Declares a batch type extension return type explicitly allowing use of `IGraphActionResult` and more importantly, `this.StartBatch()` for generating -a batch collection result. - -```csharp -public class HeroController : GraphController -{ - // highlight-next-line - [BatchTypeExtension(typeof(Human), "droids", typeof(IEnumerable))] - public IGraphActionResult Hero(IEnumerable humans) - { - //.... - } -} -``` - -#### `[BatchTypeExtension(typeToExtend, fieldName, unionName, unionTypeA, unionTypeB, additionalUnionTypes)]` - -- `typeToExtend` - The graph type to which this field will be added -- `fieldName` - The name to give to this field. -- `unionName` - The name to give to the union in the object graph -- `unionTypeA` - The first member type of the union (must be an object, not an interface) -- `unionTypeB` - The second member type of the union (must be an object, not an interface) -- `additionalUnionTypes` - N additional union types to declare as part of this union - -Declares the batch type extension as returning a union rather than a single specific data type. - -```csharp -public class HeroController : GraphController -{ - // highlight-next-line - [BatchTypeExtension(typeof(Human), "bestFriend", "DroidOrHuman", typeof(Droid), typeof(Human))] - public IGraphActionResult Hero(IEnumerable humans) - { - //.... - } -} -``` - -## Deprecated - -Indicates to any introspection queries that the field or action method is deprecated and due to be removed. - -#### `[Deprecated]` - -#### `[Deprecated(reasonText)]` - -- `reasonText` - The reason for the deprecation that is displayed in an introspection query. - -```csharp -public class CharacterController : GraphController -{ - [Query] - // highlight-next-line - [Deprecated("Use the field SuperHero, this field will be removed soon")] - public IGraphActionResult Hero(Episode episode = Episode.EMPIRE) - { - //.... - } -} -``` - -## Description - -Adds a human-readable description to any type, interface, field, parameter, enum value etc. - -#### `[Description(text)]` - -- `text` - The text to display in an introspection query. - -```csharp -[Description("A field containing information related to the characters of Star Wars")] -public class CharacterController : GraphController -{ - [Query] - // highlight-next-line - [Description("The hero of a given Star Wars Episode (Default: EMPIRE)")] - public IGraphActionResult Hero(Episode episode = Episode.EMPIRE) - { - //.... - } -} -``` - -## DirectiveLocations - -A set of flags indicating where in a query document the given directive can be declared. Also serves to indicate which directive action -method should be invoked for a particular location. - -#### `[DirectiveLocations(directiveLocation)]` - -```csharp -public sealed class AllowFragment : GraphDirective -{ - // highlight-next-line - [DirectiveLocations(DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)] - public IGraphActionResult Execute([FromGraphQL("if")] bool ifArgument) - { - return ifArgument ? this.Ok() : this.Cancel(); - } -} -``` - -## FromGraphQL - -Indicates additional or non-standard settings related to the method parameter its attached to. Can be used for controller action methods -and directive action methods. - -#### `[FromGraphQL(argumentName)]` - -- `argumentName`: The name of this parameter in the object graph - -```csharp -public class CharacterController : GraphController -{ - [Query] - // highlight-next-line - public IGraphActionResult Hero([FromGraphQL("id")] int heroId) - { - //.... - } -} -``` - -#### `[FromGraphQL(TypeExpression = "Type!")]` - -- `TypeExpression`: A custom type expression, in query syntax language, to declare explicit nullability and list rules for this parameter. - -```csharp -public class CharacterController : GraphController -{ - [Query] - // highlight-next-line - public IGraphActionResult Hero([FromGraphQL(TypeExpression = "Type!")] string heroId) - { - //.... - } -} -``` - -## GraphEnumValue - -Acts to explicitly declare an enumeration value as being exposed on an enumeration graph type. - -#### `[GraphEnumValue]` - -#### `[GraphEnumValue(name)]` - -- `name`: the name to use in the object graph for this enum value. - -```csharp -public enum Episode -{ - NewHope, - Empire, - - // highlight-next-line - [GraphEnumValue("Jedi")] - ReturnOfTheJedi, -} -``` - -## GraphField - -Acts to explicitly declare a method or property as being part of a graph type. - -#### `[GraphField]` - -#### `[GraphField(name)]` - -- `name` - The name of this field as it should appear in the object graph to be queried -```csharp -public class Human -{ - public int Id{get; set; } - - // highlight-next-line - [GraphField("name")] - public string FullName { get; set; } -} -``` - -#### `[GraphField(TypeExpression = "Type!")]` -- `TypeExpression` - Define a custom type expression; useful in setting a normally optional input field (such as a string or other object) to being required. Supply the type expression as a valid graphql syntax type expression. - -```csharp -public class Human -{ - public int Id{get; set; } - - // highlight-next-line - [GraphField(TypeExpression = "Type!")] - public Employer Boss { get; set; } -} -``` - -## GraphRoot - -Indicates that the controller should not attempt to register a virtual field for itself and that all methods should be extended off the their respective root types. - -#### `[GraphRoot]` - -
-
- -```csharp -// highlight-next-line -[GraphRoot] -public class HeroController : GraphController -{ - [Query] - public Human Hero(Episode episode) - { - //.. - } -} -``` - -
-
- -```graphql -# GraphQL Query -query { - hero(episode: EMPIRE) { - name - homePlanet - } -} -``` - -
-
- -## GraphRoute - -Indicates a field path in each root graph type where this controller should append its action methods. - -#### [GraphRoute(template)] - -- `template` - A set of `/` separated path segments representing a nested set of fields where the controller should reside. - - The `"[controller]"` meta tag can be used and will be replaced by the controller name at runtime. - -```csharp -// highlight-next-line -[GraphRoute("starWars/characters")] -public class HeroController : GraphController -{ - [Query] - public Human Hero(Episode episode) - { - //.. - } -} -``` - -```graphql title="Sample Query" -query { - starWars { - characters { - hero(episode: EMPIRE) { - name - homePlanet - } - } - } -} -``` -## GraphSkip - -Indicates that the entity to which its attached should be skipped and not included in a schema. GraphSkip can be defined on any controller, method, property, interface, enum or enum value. - -#### `[GraphSkip]` - -```csharp title="C# Class with GraphSkip" -public class Donut -{ - public int Id{get; set;} - public string Name{get;set;} - - // highlight-next-line - [GraphSkip] - public string Recipe {get; set;} -} -``` - -```graphql title="GraphQL Type Definition" -# Recipe is not included in the schema -type Donut { - Id: String - Name: String -} -``` - -## GraphType - -Indicates additional or non-standard settings for the the class, interface or enum to which its attached. Also indicates the item is explicitly declared as a graph type and should be included in a schema. - -#### `[GraphType(name)]` - -- `name` : The name of graph type as it should appear in the object graph - -#### `[GraphType(name, inputName)]` - -- `name` : The name of graph type as it should appear in the schema when used as an `OBJECT` -- `inputName`: The name of the graph type in the schema when used as an `INPUT_OBJECT` - -```csharp -// highlight-next-line -[GraphType("person", "personModel")] -public class Human -{ - public int Id{get; set; } - public string FullName { get; set; } -} -``` - -## Mutation, MutationRoot, Query, QueryRoot - -Controller action method attributes that indicate the method belongs to the specified operation type (query or mutation). When declared as "Root" (i.e. `QueryRoot`), it indicates that the action method should be declared directly on its operation graph type and not nested underneath a controller's virtual field. - -:::tip -`[Query]`, `[QueryRoot]`, `[Mutation]` and `[MutationRoot]` all have identical constructor options. -::: - -#### `[Query(template)]` - -- `template` - The field path template to use for this method. - -```csharp -public class CharacterController : GraphController -{ - // highlight-next-line - [Query("hero")] - public Human RetrieveTheHero(Episode episode) - { - // .... - } -} -``` - -```graphql title="Sample Query" -query { - character { - # field is named "hero" not "RetrieveTheHero" - hero(episode: EMPIRE) { - name - homePlanet - } - } -} -``` - -#### `[Query(returnType, params otherTypes)]` - -- `returnType`: the expected return type of this field. - - **Must** be used when this field returns an `IGraphActionResult` -- `otherTypes`: additional possible types this field could return. - - Can be used to declare possible concrete types when this field returns an interface. - -```csharp -public class CharacterController : GraphController -{ - // highlight-next-line - [Query(typeof(Droid), typeof(Human))] - public ICharacter Hero(Episode episode) - { - // .... - } -} -``` - -#### `[Query(template, returnType)]` - -- `template` - The field path template to use for this method. -- `returnType`: the expected return type of this field. - - **Must** be used when this field returns an `IGraphActionResult` - -```csharp -public class CharacterController : GraphController -{ - // highlight-next-line - [Query("hero", typeof(Human))] - public IGraphActionResult RetrieveTheHero(Episode episode) - { - // .... - } -} -``` - -#### `[Query(template, unionName, unionTypeA, unionTypeB, additionalUnionTypes)]` - -- `template` - The field path template to use for this method. -- `unionName` - The name to give to the union in the object graph -- `unionTypeA` - The first member type of the union (must be an object, not an interface) -- `unionTypeB` - The second member type of the union (must be an object, not an interface) -- `additionalUnionTypes` - N additional union types to declare as part of this union - -```csharp -public class CharacterController : GraphController -{ - // highlight-next-line - [Query("hero", "DroidOrHuman", typeof(Droid), typeof(Human))] - public IGraphActionResult RetrieveCharacter(int id) - { - // .... - } -} -``` - -** Additional Properties ** - -- `TypeExpression`: Define a custom type expression; useful in setting a normally optional field (such as a string or other object) to being required. Supply the type expression as a valid graphql syntax type expression. - -```csharp -public class CharacterController : GraphController -{ - // declare that this field must return a value (a null human is not allowed) - // highlight-next-line - [Query("hero", typeof(Human), TypeExpression = "Type!")] - public IGraphActionResult RetrieveTheHero(Episode episode) - { - // .... - } -} -``` - -## PossibleTypes - -_(optional)_ When returning an interface from an action method, this attribute allows for the declaration of additional object types to help reduce clutter in the primary query or mutation declaration. - -#### `[PossibleTypes(typeof(TypeA), typeof(TypeB) ...)]` - -These two controller examples are identical: - -```csharp title="Example A" -public class CharacterController : GraphController -{ - // highlight-next-line - [Query("hero", typeof(Human), typeof(Droid), typeof(Gungan)] - public ICharacter RetrieveTheHero(Episode episode) - { - // .... - } -} -``` - -```csharp title="Example B" -public class CharacterController : GraphController -{ - [Query("hero")] - // highlight-next-line - [PossibleTypes(typeof(Human), typeof(Droid), typeof(Gungan))] - public ICharacter RetrieveTheHero(Episode episode) - { - // .... - } -} -``` -## SpecifiedBy - -_(optional)_ Provides a convienent way to apply the `@specifiedBy` directive to a custom scalar. When not used, no url is provided to introspection requests for the scalar information. - -#### `[SpecifiedBy(url)]` - -```csharp -// highlight-next-line -[SpecifiedBy("https://documentation.example.com/api/money-scalar")] -public class MoneyScalar : IScalarGraphType -{ - // details ommited... -} -``` - - -## TypeExtension - -Declares a controller action method as a field on another graph type rather than a query or mutation action. - - -#### `[TypeExtension(typeToExtend, fieldName)]` - -- `typeToExtend` - The graph type to which this field will be added -- `fieldName` - The name to give to this field. - -Declares a type extension with the given field name. The return type of this field will be taken from the return type of the method. - -```csharp -public class DroidController : GraphController -{ - // highlight-next-line - [TypeExtension(typeof(Droid), "ownedBy")] - public Human RetrieveDroidOwner(Droid droid) - { - //.... - } -} -``` - -#### `[TypeExtension(typeToExtend, fieldName, returnType)]` - -- `typeToExtend` - The graph type to which this field will be added -- `fieldName` - The name to give to this field. -- `returnType` - The type of data returned from this field - -Declares a type extension with an explicit return type. useful when returning `IGraphActionResult`. - -```csharp -public class HeroController : GraphController -{ - // highlight-next-line - [TypeExtension(typeof(Human), "ownedBy", typeof(Droid))] - public IGraphActionResult RetrieveDroidOwner(Droid droid) - { - //.... - } -} -``` - -#### `[TypeExtension(typeToExtend, fieldName, unionName, unionTypeA, unionTypeB, additionalUnionTypes)]` - -- `typeToExtend` - The graph type to which this field will be added -- `fieldName` - The name to give to this field. -- `unionName` - The name to give to the union in the object graph -- `unionTypeA` - The first member type of the union (must be an object, not an interface) -- `unionTypeB` - The second member type of the union (must be an object, not an interface) -- `additionalUnionTypes` - N additional union types to declare as part of this union - -Declares the type extension as returning a union rather than a specific data type. - -```csharp -public class HeroController : GraphController -{ - // highlight-next-line - [TypeExtension(typeof(Droid), "bestFriend", "DroidOrHuman", typeof(Droid), typeof(Human))] - public IGraphActionResult RetrieveDroidsBestFriend(Droid droid) - { - //.... - } -} -``` diff --git a/docs/reference/benchmarks.md b/docs/reference/benchmarks.md deleted file mode 100644 index ea59128..0000000 --- a/docs/reference/benchmarks.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -id: performance -title: Benchmarks & Performance -sidebar_label: Benchmarks -sidebar_position: 10 ---- - -## Query Benchmarking -GraphQL ASP.NET is designed to be fast. For our benchmarks, we are tracking a number of query types which measure performance via the various paths through the library; multi controller queries, queries with variables etc. These are executed against an in-memory data store without an attached database. - -The goal of our benchmarks is to measure the library's abiliy to process a query in isolation, to perform the query and execute user code; not how long an action method takes to query a database for data and send a request over the wire. Obviously, real world workloads are going to be slower than these theoretical values, but the faster we can make the benchmarks the faster all other scenarios will be. - -As you can see all query types execute in sub-millisecond timeframes. If there is a specific query type or scenario that you are seeing a significant performance degregation with please open an issue on github and let us know! - -![benchmarks](../assets/benchmarks.png) -**Last Updated 2022-12-01; v0.14.0-beta** - -:::note Your Milage May Vary - Performance will vary depending on your hardware and environment conditions. You can execute your own test run via the bench marking solution located at `./src/graphql-aspnet-benchmarks.sln` -::: - -## Performance Testing - -:::caution Run Your Own Performance Tests -These performance tests are not intended to be used as data points when determining scaling requirements for your own production workloads. Your use cases will be different and effected by factors not present in our lab environment (e.g. database connections, service orchrestration, business logic etc.). Be sure to execute your own load tests using queries indicative of your expected user base and act accordingly. -::: - -We periodically execute tests against the library to measure throughput and stability, for a single server instance, under load. Our goal is to measure the theoretical limits in a multi-user, "production like" scenario. - -These tests are executed in a controlled setting with the following conditions: - -### Test Configuration and Specs -#### GraphQL ASP.NET Server: - -* source code: `./src/ancillary-projects/benchmarking/graphql-aspnet-load-server/` -* .NET 7 Runtime -* Garbage collection executing in [server mode](https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector#workstation-vs-server) -* Local area, wired, gigabit network -* Simple queries to fetch or mutate a single, in-memory object - -#### Memory Profiling Load: - -* JMeter script: `graphql-memory-profiling.jmx` -* 15 concurrent users executing a graphql query to fetch a single object -* Each user executes 10,000 requests at most 30ms apart - -#### GraphQL Load: - -* JMeter script: `graphql-load-generator.jmx` -* 300 concurrent users executing a graphql query to fetch a single object -* 300 concurrent users executing a graphql mutation (and raising a subscription event) -* Each user executes 10,000 requests at most 30ms apart - -#### Subscription Client Load: - -* Source Code: `~coming soon~` -* A custom console app that registers subscribers to receive subscription events generated via the Load Generating Workstation mutations -* 500 connected subscription clients -* 2 registered subscription per client (1000 total client subscriptions) - -#### REST Load: - -* JMeter script: `rest-load-generator.jmx` -* 300 concurrent users executing a REST Query that fetches a single object -* 300 concurrent users executing a REST Query that mutates and returns a single object -* Each user executes 10,000 requests at most 15ms apart -* This workload acts as a control to compare performance of the baseline web api against the overhead of the graphql library - -#### GraphQL Max Throughput Load: - -* JMeter script: `graphql-max-load-generator.jmx` -* Starts 20 new users each second until failure -* Each user executes a query every 0-15ms until failure - - -### Memory Profile Test - -A qualitative test executed with the server instance running in release mode, harnessed via `dotMemory`. Given the artificial environment restrictions this imposes its difficult to pin down exact KPIs but in general this test is used to monitor: - -|Metric |Expectations| -|-----------------|-------------| -|Memory Allocation| Expect to see steady Gen0 allocation over time, with no extreme spikes. | -|Object Survival | Expect to see little to no objects surviving to Gen1 and Gen2 heap collections per GC cycle. When the test completes, the server returns to a steady state of memory usage prior to the test beginning.| - -:::info Goal - The aim of this test is to ensure acceptable memory pressure and GC cycles on the server instance in a controlled usage scenario and ensure no memory leaks occur. -::: - -**Results** - -| Date | Version | | -|-------------|-----------------|---------| -|2022-11-30 |v0.13.1-beta | Execution is consistant. While no objects make it to Gen1 or Gen2 a GC cycle occurs about every 20 seconds.| -|2022-11-30 |v0.14.0-beta | Similar results to the v0.13.1-beta test in terms of generational memory allocations. There is a notable decrease in memory pressure. Time between GC cycles has improved to once every 33 seconds; a 65% increase in duration. | - - - -### General Usage Load Test - -A test with the server executing in release mode, WITHOUT the subscription server attached and with monitoring via passive `dotnet-counters`. This test measures the throughput of queries and mutations through the runtime as well as the load those queries place on the server CPU. Using GraphQL (as opposed to REST) will generate some additional overhead to parse and execute the query on top of the REST request which invokes it. As a result, the metrics of this test are expressed in terms of % increases over a comperable REST workload via a baseline ASP.NET web api controller. - - - - -|Metric |Expectations| -|--------------------------|-------------| -|CPU Utilization |Using [Process Explorer](https://learn.microsoft.com/en-us/sysinternals/downloads/process-explorer) to measure utilization, no more than a 5% increase when compared to the REST control load. | -|GC % Time |Using the metrics obtained via [dotnet-counters](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters) expect that the GC % time is within 1% of the REST control load | -|Throughput (req/sec) |Throughput, measured in requests per second, is within 10% of the peak load generated via the REST control load | - -:::info Goal - The aim of this test is to ensure adequate single instance throughput and that the overhead for using graphql on top of web api is kept to a minimum. -::: - -**Results** - -| Date | Version | Metric | REST Workload | GraphQL Workload | Variance | -|-----------|-------------|------------------|-----------------|--------------------|----------| -|2022-11-30 |v0.13.1-beta | CPU Utilization | 0.01-2% | 2-9% | +7% | -| | | GC % Time | < 1% | 2-4% | +3% | -| | | Throughput | 37,490 req/sec | 29,830 req/sec | -8k (-20%) | -|2022-12-1 |v0.14.0-beta | CPU Utilization |0.01-2% | 1-3% | +1% | -| | | GC % Time | < 1% | < 1% | +0% | -| | | Throughput | 37,360 req/sec | 36,509 req/sec | -2k (-2.3%) | - - - -### Max Throughput Test - -A simple test flooding the server with an ever-increasing amount of traffic until it begins to deny requests or fails completely. The throughput of each loading client is summed just prior to failure and recorded as the max throughput. - -**Results** - -| Date | Version | Metric |GraphQL Query | -|-----------|-------------|------------------|-----------------| -|2022-12-1 |v0.14.0-beta | Max Throughput | 57,919 req/sec * | - -\* We ran out of client machines and could not generate any more load against the test server. At the time, the server process indicated 5% CPU utilization and less than 750mb of committed memory. - - - -### Subscription Event Load Test - -_Coming Soon_ \ No newline at end of file diff --git a/docs/reference/demo-projects.html b/docs/reference/demo-projects.html new file mode 100644 index 0000000..225e8fa --- /dev/null +++ b/docs/reference/demo-projects.html @@ -0,0 +1,33 @@ + + + + + +Demo Projects | GraphQL ASP.NET + + + + +
+

Demo Projects

General

📌 Custom Directives
+Demostrates creating and applying a type system directive and a custom execution directive.

📌 Logging Provider
+Demonstrates the creation of a custom ILogProvider to intercept logging events and writing them to a json file.

📌 Custom Http Processor
+Demonstrates overriding the default HTTP Processor to conditionally process entire queries at the ASP.NET level.

📌 Unit Test Framework
+An example project that utilizes the test framework nuget package to execute some unit tests against a graph controller using xUnit.


Authentication & Authorization

📌 Field Authorization
+Demonstrates fields with authorization requirements and how access denied messages are returned to the client in the various authorization modes.

📌 Firebase Authentication
+Demonstrates how to setup a Firebase project and link a GraphQL ASP.NET project to it.


Subscriptions

📌 Subscriptions w/ Azure Service Bus
+Demonstrates the use of an external subscription event publisher and a consumer to deserialize and route events.

Use of this demo project requires your own Azure Service Bus namespace.

📌 Subscriptions w/ React & Apollo Client
+A sample react application that makes use of the apollo client to connect to a GraphQL ASP.NET server.


Extensions

📌 File Uploads
+Demonstrates the use the graphql-multipart-form-spec compliant extension to perform file uploads as part of a graphql query.

+ + + + \ No newline at end of file diff --git a/docs/reference/demo-projects.md b/docs/reference/demo-projects.md deleted file mode 100644 index effb951..0000000 --- a/docs/reference/demo-projects.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -id: demo-projects -title: Demo Projects -sidebar_label: Demo Projects -sidebar_position: 9 ---- - -### General - -📌 [Custom Directives](https://github.com/graphql-aspnet/demo-projects/tree/master/Custom-Directives)
-Demostrates creating and applying a type system directive and a custom execution directive. - -📌 [Logging Provider](https://github.com/graphql-aspnet/demo-projects/tree/master/LoggingProvider)
-Demonstrates the creation of a custom `ILogProvider` to intercept logging events and writing them to a json file. - -📌 [Custom Http Processor](https://github.com/graphql-aspnet/demo-projects/tree/master/Custom-HttpProcessor)
-Demonstrates overriding the default HTTP Processor to conditionally process entire queries at the ASP.NET level. - -📌 [Unit Test Framework](https://github.com/graphql-aspnet/demo-projects/tree/master/Unit-Testing)
-An example project that utilizes the test framework nuget package to execute some unit tests against a graph controller using xUnit. - -
- -### Authentication & Authorization - -📌 [Field Authorization](https://github.com/graphql-aspnet/demo-projects/tree/master/Authorization)
-Demonstrates fields with authorization requirements and how access denied messages are returned to the client in the various authorization modes. - -📌 [Firebase Authentication](https://github.com/graphql-aspnet/demo-projects/tree/master/Firebase-Authentication)
-Demonstrates how to setup a [Firebase](https://firebase.google.com/) project and link a GraphQL ASP.NET project to it. - -
- -### Subscriptions - -📌 [Subscriptions w/ Azure Service Bus](https://github.com/graphql-aspnet/demo-projects/tree/master/Subscriptions-AzureServiceBus)
-Demonstrates the use of an external subscription event publisher and a consumer to deserialize and route events. ->Use of this demo project requires your own [Azure Service Bus](https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview) namespace. - -📌 [Subscriptions w/ React & Apollo Client](https://github.com/graphql-aspnet/demo-projects/tree/master/Subscriptions-ReactApolloClient)
-A sample react application that makes use of the [apollo client](https://www.apollographql.com/docs/react/) to connect to a GraphQL ASP.NET server. - -
- -### Extensions - -📌 [File Uploads](https://github.com/graphql-aspnet/demo-projects/tree/master/File-Uploads)
-Demonstrates the use the [graphql-multipart-form-spec](https://github.com/jaydenseric/graphql-multipart-request-spec) compliant extension to perform file uploads as part of a graphql query. - diff --git a/docs/reference/global-configuration.html b/docs/reference/global-configuration.html new file mode 100644 index 0000000..410a083 --- /dev/null +++ b/docs/reference/global-configuration.html @@ -0,0 +1,25 @@ + + + + + +Global Configuration | GraphQL ASP.NET + + + + +
+

Global Configuration

Global configuration settings affect the entire server instance, they are not restricted to a single schema registration. All global settings are optional and define resonable default values. Use these to fine tune your server environment. You should change any global settings BEFORE calling .AddGraphQL().

Adding Schema Configuration Options
// -------------------
// Configure Global Options before calling AddGraphQL
// -------------------

services.AddGraphQL();

General

ControllerServiceLifetime

The configured service lifetime that all discovered controllers and directives will be registered as within the DI container during any schema's setup +process.

GraphQLServerSettings.ControllerServiceLifetime = ServiceLifetime.Transient;
Default ValueAcceptable Values
Transient Transient, Scoped, Singleton
danger

Registering GraphControllers as anything other than transient can cause unexpected behavior and result in unexplained crashes, data loss, data exposure and security issues in some scenarios. Consider restructuring your application before changing this setting. Adjusting this value should be a last resort, not a first option.

Subscriptions

MaxConcurrentReceiverCount

Indicates the maximum number of entities (i.e. client connections) that will receive a raised subscription event on this server instance. If there are more receivers than this configured limit the others are queued and will recieve the event in turn once as others finish processing it.

GraphQLSubscriptionServerSettings.MaxConcurrentReceiverCount = 500;
Default ValueAcceptable Values
500> 0

MaxConnectedClientCount

Indicates the maximum number of client connections this server instance will accept, combined, across all schemas. If this limit is reached a new connection will be automatically rejected even if ASP.NET was willing to accept it.

GraphQLSubscriptionServerSettings.MaxConnectedClientCount = null;
Default ValueAcceptable Values
-not set-null OR > 0

Note: By default this value is not set, indicating there is no limit. GraphQL will accept any connection passed by the ASP.NET runtime.

+ + + + \ No newline at end of file diff --git a/docs/reference/global-configuration.md b/docs/reference/global-configuration.md deleted file mode 100644 index 93e9b1f..0000000 --- a/docs/reference/global-configuration.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -id: global-configuration -title: Global Configuration -sidebar_label: Global Configuration -sidebar_position: 2 ---- - -Global configuration settings affect the entire server instance, they are not restricted to a single schema registration. All global settings are optional and define resonable default values. Use these to fine tune your server environment. You should change any global settings BEFORE calling `.AddGraphQL()`. - -```csharp title="Adding Schema Configuration Options" -// ------------------- -// Configure Global Options before calling AddGraphQL -// ------------------- - -services.AddGraphQL(); -``` - -## General -### ControllerServiceLifetime - -The configured service lifetime that all discovered controllers and directives will be registered as within the DI container during any schema's setup -process. - -```csharp -GraphQLServerSettings.ControllerServiceLifetime = ServiceLifetime.Transient; -``` -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `Transient ` | `Transient`, `Scoped`, `Singleton` | - -:::danger - Registering GraphControllers as anything other than transient can cause unexpected behavior and result in unexplained crashes, data loss, data exposure and security issues in some scenarios. Consider restructuring your application before changing this setting. Adjusting this value should be a last resort, not a first option. -::: - -## Subscriptions - -### MaxConcurrentReceiverCount - -Indicates the maximum number of entities (i.e. client connections) that will receive a raised subscription event on this server instance. If there are more receivers than this configured limit the others are queued and will recieve the event in turn once as others finish processing it. - -```csharp -GraphQLSubscriptionServerSettings.MaxConcurrentReceiverCount = 500; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `500` | > 0 | - - -### MaxConnectedClientCount - -Indicates the maximum number of client connections this server instance will accept, combined, across all schemas. If this limit is reached a new connection will be automatically rejected even if ASP.NET was willing to accept it. - - -```csharp -GraphQLSubscriptionServerSettings.MaxConnectedClientCount = null; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| _-not set-_ | null OR > 0 | - -> Note: By default this value is not set, indicating there is no limit. GraphQL will accept any connection passed by the ASP.NET runtime. \ No newline at end of file diff --git a/docs/reference/graph-controller.html b/docs/reference/graph-controller.html new file mode 100644 index 0000000..fea48e7 --- /dev/null +++ b/docs/reference/graph-controller.html @@ -0,0 +1,24 @@ + + + + + +Graph Controller | GraphQL ASP.NET + + + + +
+

Graph Controller

✅ See the section on Controllers & Actions for a detailed explination on how action methods work and how to declare them.

The GraphController, from which all of your controllers inherit, is a core object used throughout graphql. This page details some lesser known and lesser used object referenced made available to each controller.

ModelState

The completed model state dictionary contains an entry for each validated parameter.

public class CharacterController : GraphController
{
[Query]
public IGraphActionResult CreateCharacter(Character characterModel)
{
if(!this.ModelState.IsValid)
return this.BadRequest(this.ModelState);

//...
}
}

Request

The field request generated via the execution pipeline. It contains all the necessary information used by graphql to resolve the current field.

public class CharacterController : GraphController
{
[Query]
public IGraphActionResult Hero(Episode episode = Episode.EMPIRE)
{
if(this.Request.Field.IsLeaf)
{
// ...
}
}
}

Notable Items on the Request

  • Request.Field: A reference to the graph field definition, on the target schema, for the field currently being resolved.

    • .TypeExpression: The type expression describing the return value of this field
    • .IsLeaf: Indicates whether this field returns a leaf value (enum or scalar) or an object.
    • .Mode: Indicates the processing mode of this field (Batch or "per item")
    • .FieldSource: Indicates what member type generated the field; property, method, action etc.
    • .DataSource: The source data item being supplied to the field to be resolved.
  • Request.Items: A collection of key/value pairs accessible to all fields and directives in this individual request pipeline.

User

The User property contains the ClaimsPrincipal created by ASP.NET when this request was authorized.

public class CharacterController : GraphController
{
[Query]
public IGraphActionResult Hero(Episode episode = Episode.EMPIRE)
{
if(this.User.Identity.Name == "DebbieEast")
{
// ...
}
}
}

See the section on authorization for more details on how users are authenticated and authorized to action methods.

Schema

The Schema property contains a reference to the singleton instance of the schema the current controller is resolving a field for. This object is considered read-only and should not be modified.

public class CharacterController : GraphController
{
[Query]
public IGraphActionResult Hero(Episode episode = Episode.EMPIRE)
{
IObjectGraphType droidType = this.Schema.KnownTypes.FindGraphType(typeof(Droid), TypeKind.OBJECT);
// ...
}
}
+ + + + \ No newline at end of file diff --git a/docs/reference/graph-controller.md b/docs/reference/graph-controller.md deleted file mode 100644 index 89287dd..0000000 --- a/docs/reference/graph-controller.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -id: graph-controller -title: Graph Controller -sidebar_label: GraphController -sidebar_position: 4 ---- - -✅ See the section on [Controllers & Actions](../controllers/actions.md) for a detailed explination on how action methods work and how to declare them. - -The `GraphController`, from which all of your controllers inherit, is a core object used throughout graphql. This page details some lesser known and lesser used object referenced made available to each controller. - -## ModelState - -The completed model state dictionary contains an entry for each validated parameter. - -```csharp -public class CharacterController : GraphController -{ - [Query] - public IGraphActionResult CreateCharacter(Character characterModel) - { - // highlight-next-line - if(!this.ModelState.IsValid) - return this.BadRequest(this.ModelState); - - //... - } -} -``` - -## Request -The field request generated via the execution pipeline. It contains all the necessary information used by graphql to resolve the current field. - -```csharp -public class CharacterController : GraphController -{ - [Query] - public IGraphActionResult Hero(Episode episode = Episode.EMPIRE) - { - // highlight-next-line - if(this.Request.Field.IsLeaf) - { - // ... - } - } -} -``` -### Notable Items on the Request -- `Request.Field`: A reference to the graph field definition, on the target schema, for the field currently being resolved. - - - `.TypeExpression`: The type expression describing the return value of this field - - `.IsLeaf`: Indicates whether this field returns a leaf value (enum or scalar) or an object. - - `.Mode`: Indicates the processing mode of this field (Batch or "per item") - - `.FieldSource`: Indicates what member type generated the field; property, method, action etc. - - `.DataSource`: The source data item being supplied to the field to be resolved. - -- `Request.Items`: A collection of key/value pairs accessible to all fields and directives in this individual request pipeline. - -## User - -The User property contains the `ClaimsPrincipal` created by ASP.NET when this request was authorized. - -```csharp -public class CharacterController : GraphController -{ - [Query] - public IGraphActionResult Hero(Episode episode = Episode.EMPIRE) - { - // highlight-next-line - if(this.User.Identity.Name == "DebbieEast") - { - // ... - } - } -} -``` - -> See the section on [authorization](../controllers/authorization.md) for more details on how users are authenticated and authorized to action methods. - -## Schema - -The `Schema` property contains a reference to the singleton instance of the schema the current controller is resolving a field for. This object is considered read-only and should not be modified. - -```csharp -public class CharacterController : GraphController -{ - [Query] - public IGraphActionResult Hero(Episode episode = Episode.EMPIRE) - { - // highlight-next-line - IObjectGraphType droidType = this.Schema.KnownTypes.FindGraphType(typeof(Droid), TypeKind.OBJECT); - // ... - } -} -``` diff --git a/docs/reference/graph-directive.html b/docs/reference/graph-directive.html new file mode 100644 index 0000000..23f0705 --- /dev/null +++ b/docs/reference/graph-directive.html @@ -0,0 +1,24 @@ + + + + + +Graph Directive | GraphQL ASP.NET + + + + +
+

Graph Directive

✅ See the section on Directives for a detailed explination on how directive action methods work and how to declare them.

The GraphDirective, from which all of your directives inherit, is a core object used throughout graphql. This page details some lesser known and lesser used object referenced made available to each directive.

ModelState

The completed model state dictionary with an entry for each validated parameter of the directive. The model state for the field being resolved is not accessible by the directive.

public class AllowDirective : GraphDirective
{
public IGraphActionResult BeforeResolution(FilterModel model)
{
if(!this.ModelState.IsValid)
return this.BadRequest(this.ModelState);

//...
}
}

Request

The individual directive request spawned from the field pipeline.

public class AllowDirective : GraphDirective
{
public IGraphActionResult BeforeResolution(FilterModel model)
{
if(this.Request.SourceData is Human human)
{
// ...
}
}
}

Notable Items on the Request

  • Request.Directive: Useful metadata related to the directive type being resolved.
  • Request.LifeCycle: The enumeration value indicating which life cycle point is being executed.
  • Request.DirectiveLocation: Indicates location in the query text this directive instance is currently being executed.
  • Request.DataSource: The source data item being supplied to the field to be resolved.
  • Request.Items: A collection of key/value pairs accessible to all fields and directives in this individual request pipeline.

User

The ClaimsPrincipal created by ASP.NET when this request was authorized.

public class MyCustomDirective : GraphDirective
{
public IGraphActionResult BeforeResolution(FilterModel model)
{
if(this.User.Identity.Name == "DebbieEast")
{
// ...
}
}
}

Schema

The Schema property contains a reference to the singleton instance of the schema the current controller is resolving a field for. This object is considered read-only and should not be modified.

public class AllowDirective : GraphDirective
{
public IGraphActionResult BeforeResolution(FilterModel model)
{
IObjectGraphType droidType = this.Schema.KnownTypes.FindGraphType(typeof(Droid), TypeKind.OBJECT);
// ...
}
}
caution

For type system directives, executed as part of schema construction, the schema object available may be incomplete or null. Avoid using and do not rely on the data in this.Schema for type system directives.

+ + + + \ No newline at end of file diff --git a/docs/reference/graph-directive.md b/docs/reference/graph-directive.md deleted file mode 100644 index 5dba280..0000000 --- a/docs/reference/graph-directive.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -id: graph-directive -title: Graph Directive -sidebar_label: GraphDirective -sidebar_position: 5 ---- -✅ See the section on [Directives](../advanced/directives.md) for a detailed explination on how directive action methods work and how to declare them. - -The `GraphDirective`, from which all of your directives inherit, is a core object used throughout graphql. This page details some lesser known and lesser used object referenced made available to each directive. - - -## ModelState - -The completed model state dictionary with an entry for each validated parameter **of the directive**. The model state for the field being resolved is not accessible by the directive. - -```csharp -public class AllowDirective : GraphDirective -{ - public IGraphActionResult BeforeResolution(FilterModel model) - { - if(!this.ModelState.IsValid) - return this.BadRequest(this.ModelState); - - //... - } -} -``` - -## Request - -The individual directive request spawned from the field pipeline. - -```csharp -public class AllowDirective : GraphDirective -{ - public IGraphActionResult BeforeResolution(FilterModel model) - { - if(this.Request.SourceData is Human human) - { - // ... - } - } -} -``` - -### Notable Items on the Request - -- `Request.Directive`: Useful metadata related to the directive type being resolved. -- `Request.LifeCycle`: The enumeration value indicating which life cycle point is being executed. -- `Request.DirectiveLocation`: Indicates location in the query text this directive instance is currently being executed. -- `Request.DataSource`: The source data item being supplied to the field to be resolved. -- `Request.Items`: A collection of key/value pairs accessible to all fields and directives in this individual request pipeline. - -## User - -The `ClaimsPrincipal` created by ASP.NET when this request was authorized. - -```csharp -public class MyCustomDirective : GraphDirective -{ - public IGraphActionResult BeforeResolution(FilterModel model) - { - if(this.User.Identity.Name == "DebbieEast") - { - // ... - } - } -} -``` - - -## Schema - -The `Schema` property contains a reference to the singleton instance of the schema the current controller is resolving a field for. This object is considered read-only and should not be modified. - -```csharp -public class AllowDirective : GraphDirective -{ - public IGraphActionResult BeforeResolution(FilterModel model) - { - // highlight-next-line - IObjectGraphType droidType = this.Schema.KnownTypes.FindGraphType(typeof(Droid), TypeKind.OBJECT); - // ... - } -} -``` -:::caution - For type system directives, executed as part of schema construction, the schema object available may be incomplete or null. Avoid using and do not rely on the data in `this.Schema` for type system directives. -::: \ No newline at end of file diff --git a/docs/reference/how-it-works.html b/docs/reference/how-it-works.html new file mode 100644 index 0000000..50e2f92 --- /dev/null +++ b/docs/reference/how-it-works.html @@ -0,0 +1,24 @@ + + + + + +How it Works | GraphQL ASP.NET + + + + +
+

How it Works

This document is a high level overview how GraphQL ASP.NET ultimately generates a response to a query with some insight into core details. Its assumes a working knowledge of both ASP.NET and the GraphQL specification. If you are only interested in the "how" and not the "why", feel free to skip this.

Schema Generation

How it Works

Object Templating

When your application starts the runtime begins by inspecting the registered schemas declared in your Startup.cs for the different options you've declared and sets off gathering a collection of the possible graph types that may be required.

For each type it discovers, it generates a template that describes how you've asked GraphQL to use your classes. By inspecting declared attributes and the System.Type metadata it generates the appropriate information to create everything GraphQL ASP.NET will need to fulfill a query. Information such as input and output parameters for methods, property types, custom type naming, implemented interfaces, union declarations, field path definitions, validation requirements and enforced authorization policies are all gathered and stored at the application level under the globally configured IGraphTypeTemplateProvider.

From this collection of metadata, GraphQL then generates the appropriate IGraphType objects for each of your schemas based on their individual configurations. By default, this completed a ISchema is stored as a singleton in your DI container.

How does it know what objects to include?

GraphQL ASP.NET has a few methods of determining what objects to include in your schema. By default, it will inspect your application (the entry assembly) for any public classes that inherit from GraphController or GraphDirective and work from there. It checks every tagged query and mutation method, looks at every return value and every method parameter to find relevant scalars, enums and object types then inspects each one in turn, deeper and deeper down your object chain, to create a full map. It will even inspect the arbitrary C# interfaces implemented on each of your consumed objects. If that interface is ever used as a return type on an action method or a property, its automatically promoted to a graph type and included in the schema.

You have complete control of what to include. Be that including additional assemblies, preventing the inclusion of the startup assembly, manually specifying each model class and controller etc. Attributes exist such as [GraphSkip] to exclude certain properties, methods or entire classes and limit the scope of the inclusion. On the other side of the fence, you can configure it to only accept classes with an explicitly declared [GraphType] attribute, ignoring everything else. And for the most control, disable everything and manually call .AddType<T>() at startup for each class you want to have in your schema (controllers included). GraphQL will then happily generate declaration errors when it can't find a reference declared in your controllers. This can be an effective technique in spotting data leaks or rogue methods that slipped through a code review. Configure a unit test to generate a schema with different inclusion rules per environment and you now have an automatic CI/CD check in place to give your developers more freedom to write code during a sprint and only have to worry about configurations when submitting a PR.

You can even go so far as to add a class to the schema but prevent its publication in introspection queries which can provide some helpful obfuscation. Alternatively, just disable introspection queries altogether. While this does cause client tooling to complain endlessly and makes front-end development much harder; if you and your consumers (like your UI) can agree ahead of time on the query syntax then there is no issue.

Middleware Pipelines

Similar to how ASP.NET utilizes a middleware pipeline to fulfill an HTTP request, GraphQL ASP.NET follows suit to fulfill a graphQL request. Major tasks like validation, parsing, field resolution and result packaging are just middleware components added to a chain of tasks and executed to complete the operation.

At the same time as its constructing your schema, GraphQL sets up the 4 primary pipelines and stores them in the DI container as an ISchemaPipeline<TSchema, TContext>. Each pipeline can be extended, reworked or completely replaced as needed for your use case.

Query Execution

Query execution can be broken down into a series of phaes. For the sake of brevity we've left out the HTTP request steps required to invoke the GraphQL runtime but you can inspect the DefaultGraphQLHttpProcessor and read through the code.

Note: The concept of a phase here is just for organizing the information, there is no concrete "phase" value managed by the pipelines.

Phase 1: Parsing & Validation

HeroController.cs
public class HeroController : GraphController
{
[QueryRoot("hero")]
public Human RetrieveHero(Episode episode)
{
if(episode == Episode.Empire)
{
return new Human()
{
Id = 1000,
Name = "Han Solo",
HomePlanet = "Corellia",
}
}
else
{
return new Human()
{
Id = 1001,
Name = "Luke SkyWalker",
HomePlanet = "Tatooine",
}
}
}
}
Sample Query
query {
hero(episode: EMPIRE) {
name
homePlanet
}
}
Response JSON
{
"data" : {
"hero": {
"name" : "Han Solo",
"homePlanet" : "Corellia"
}
}
}

Sample query used as a reference example in this section

The supplied query document (top right in the example) is ran through a compilation cycle to ultimately generate an IQueryExecutionPlan. It is first lexed into a series of tokens representing the various parts; things like curly braces, colons, strings etc. Then it parses those tokens into a collection of SyntaxNodes (creating an Abstract Syntax Tree) representing concepts like FieldNode, InputValueNode, and OperationTypeNode following the graphql specification rules for source text documents.

Once parsed, the runtime will execute its internal rules engine against the generated ISyntaxTree, using the targeted ISchema, to create a query plan where it marries the nodes AST with concrete structures such as controllers, action methods and POCOs. It is at this stage where the hero field in the example is matched to the HeroController with its appropriate IGraphFieldResolver to invoke the RetrieveHero action method.

While generating a query plan the rules engine will do its best to complete an analysis of the entire document and return to the requestor every error it finds. Depending on the errors though, it may or may not be able to catch them all. For instance, a syntax error, like a missing }, will preclude generating a query plan so errors centered around invalid field names or a type expression mismatch won't be caught until the syntax error is fixed (just like any other compiler).

Phase 2: Execution

The engine now has a completed query plan that describes:

  • The named operation in the document to be executed (or the single anonymous operation in the example above)
  • The top level fields and every child field on the chosen operation.

Its successfully validated that:

  • All the referenced fields for the graph types exist and are valid where requested
  • Required input arguments have been supplied and their data is "resolvable"
    • This just means that we've validated that a number is a number, named fields on input objects exist etc.

For each field, the runtime will:

  • Substitute any deferred input arguments with referenced variable data
  • Generate a field execution context containing the necessary data about the source data, arguments and resolver.
  • Authenticate the user to the field.
  • Execute the resolver to fetch a data value.
  • Invoke any child fields requested.

Resolving a Field

GraphQL uses the phrase "resolver" to describe a method that, for a given field, takes in parameters and generates a result.

At startup, GraphQL ASP.NET automatically creates resolver references for your controller methods, POCO properties and tagged POCO methods. These are nothing more than delegates for method invocations; for properties it uses the getter registered with PropertyInfo.

Performance Note: The library makes heavy use of compiled Expression Trees to call its resolvers and for instantiating input objects. As a result, its many orders of magnitude faster than baseline reflected calls and nearly as performant as precompiled code.

Concerning Proxy Libraries (e.g. EF Core Proxies)

GraphQL ASP.NET natively supports Liskov substitutions for all graph types opening up the possibility for using libraries such as EF Proxies that can provide a tremendously powerful and easy to setup graph structure. By lazy loading any child collections you can expose access to your entire domain model with very little work.

Be careful though, EF Core Proxies and like libraries suffer from the same N + 1 problem as GraphQL libraries do. By choosing to use such lazy loading techniques you are circumventing GraphQL ASP.NET's provided mechanisms for handling such situations and need to be exceptionally careful to manage the issue yourself.

Phase 3: Response Generation

Once all fields have been processed the runtime makes a final pass to propagate any nullability errors up the field chain resulting in a final data set. This data set is then passed to the IGraphResponseWriter registered for the schema and the result is serialized to the HttpResponse.

Writing a response includes:

  • Serializing the errors generated during execution

    • All error messages have a severity level. Your schema configuration controls which are sent to the client.
    • You can add your own error messages through your controller actions by returning this.Error().
    • Error messages may or may not have associated exception data which may or may not be exposed to the client depending on who they are or the schema configuration settings.
  • Serializing the data field to the response stream

  • Serializing the extensions field (such as metrics data) to the response stream

Other Points of Interest

Hopefully we've given you a bit of insight into how the library works under the hood. The other documents on this site go into exhaustive detail of the different features and how to use them but since you're here:

  • The library targets netstandard2.0 and net6.0.
    • Out of the box there are no external dependencies beyond official Microsoft packages.
  • Every core component and all middleware components required to complete the tasks outlined in this document are referenced through dependency injection. Any one of them (or all of them) can be overridden and extended to do whatever you want as long as you register them prior to calling .AddGraphQL() at startup.

    • Inject your own IGraphResponseWriter to serialize your results to XML or CSV.
    • Build your own IOperationComplexityCalculator to intercept and alter how a query plan generates its complexity values to be more suitable to your needs.
    • On and on and on...

Architectural Diagrams

📌 Structural Diagrams

A set of diagrams outlining the major interfaces and classes that make up GraphQL Asp.Net.

📌 Execution Diagrams

A set of flowcharts and relational diagrams showing how various aspects of the library fit together at run time, including the query execution and field execution pipelines.

+ + + + \ No newline at end of file diff --git a/docs/reference/how-it-works.md b/docs/reference/how-it-works.md deleted file mode 100644 index 8abf63b..0000000 --- a/docs/reference/how-it-works.md +++ /dev/null @@ -1,172 +0,0 @@ ---- -id: how-it-works -title: How it Works -sidebar_label: How it Works -sidebar_position: 0 ---- - -> This document is a high level overview how GraphQL ASP.NET ultimately generates a response to a query with some insight into core details. Its assumes a working knowledge of both ASP.NET and the GraphQL specification. If you are only interested in the "how" and not the "why", feel free to skip this. - -## Schema Generation - -![How it Works](../assets/how-it-works-1.png) - -#### Object Templating - -When your application starts the runtime begins by inspecting the registered schemas declared in your `Startup.cs` for the different options you've declared and sets off gathering a collection of the possible graph types that may be required. - -For each type it discovers, it generates a template that describes _how_ you've asked GraphQL to use your classes. By inspecting declared attributes and the `System.Type` metadata it generates the appropriate information to create everything GraphQL ASP.NET will need to fulfill a query. Information such as input and output parameters for methods, property types, custom type naming, implemented interfaces, union declarations, field path definitions, validation requirements and enforced authorization policies are all gathered and stored at the application level under the globally configured `IGraphTypeTemplateProvider`. - -From this collection of metadata, GraphQL then generates the appropriate `IGraphType` objects for each of your schemas based on their individual configurations. By default, this completed a `ISchema` is stored as a singleton in your DI container. - -**How does it know what objects to include?** - -GraphQL ASP.NET has a few methods of determining what objects to include in your schema. By default, it will inspect your application (the entry assembly) for any public classes that inherit from `GraphController` or `GraphDirective` and work from there. It checks every tagged query and mutation method, looks at every return value and every method parameter to find relevant scalars, enums and object types then inspects each one in turn, deeper and deeper down your object chain, to create a full map. It will even inspect the arbitrary C# interfaces implemented on each of your consumed objects. If that interface is ever used as a return type on an action method or a property, its automatically promoted to a graph type and included in the schema. - -You have complete control of what to include. Be that including additional assemblies, preventing the inclusion of the startup assembly, manually specifying each model class and controller etc. Attributes exist such as `[GraphSkip]` to exclude certain properties, methods or entire classes and limit the scope of the inclusion. On the other side of the fence, you can configure it to only accept classes with an explicitly declared `[GraphType]` attribute, ignoring everything else. And for the most control, disable everything and manually call `.AddType()` at startup for each class you want to have in your schema (controllers included). GraphQL will then happily generate declaration errors when it can't find a reference declared in your controllers. This can be an effective technique in spotting data leaks or rogue methods that slipped through a code review. Configure a unit test to generate a schema with different inclusion rules per environment and you now have an automatic CI/CD check in place to give your developers more freedom to write code during a sprint and only have to worry about configurations when submitting a PR. - -You can even go so far as to add a class to the schema but prevent its publication in introspection queries which can provide some helpful obfuscation. Alternatively, just disable introspection queries altogether. While this does cause client tooling to complain endlessly and makes front-end development much harder; if you and your consumers (like your UI) can agree ahead of time on the query syntax then there is no issue. - -#### Middleware Pipelines - -Similar to how ASP.NET utilizes a middleware pipeline to fulfill an HTTP request, GraphQL ASP.NET follows suit to fulfill a graphQL request. Major tasks like validation, parsing, field resolution and result packaging are just [middleware components](../reference/middleware) added to a chain of tasks and executed to complete the operation. - -At the same time as its constructing your schema, GraphQL sets up the 4 primary pipelines and stores them in the DI container as an `ISchemaPipeline`. Each pipeline can be extended, reworked or completely replaced as needed for your use case. - -## Query Execution - -Query execution can be broken down into a series of phaes. For the sake of brevity we've left out the HTTP request steps required to invoke the GraphQL runtime but you can inspect the [DefaultGraphQLHttpProcessor](https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet/Defaults/DefaultGraphQLHttpProcessor%7BTSchema%7D.cs) and read through the code. - -Note: The concept of a phase here is just for organizing the information, there is no concrete "phase" value managed by the pipelines. - -### Phase 1: Parsing & Validation - -```csharp title="HeroController.cs" -public class HeroController : GraphController -{ - [QueryRoot("hero")] - public Human RetrieveHero(Episode episode) - { - if(episode == Episode.Empire) - { - return new Human() - { - Id = 1000, - Name = "Han Solo", - HomePlanet = "Corellia", - } - } - else - { - return new Human() - { - Id = 1001, - Name = "Luke SkyWalker", - HomePlanet = "Tatooine", - } - } - } -} -``` - -```graphql title="Sample Query" -query { - hero(episode: EMPIRE) { - name - homePlanet - } -} -``` - -```json title="Response JSON" -{ - "data" : { - "hero": { - "name" : "Han Solo", - "homePlanet" : "Corellia" - } - } -} -``` - -_Sample query used as a reference example in this section_ - -The supplied query document (top right in the example) is ran through a compilation cycle to ultimately generate an `IQueryExecutionPlan`. It is first lexed into a series of tokens representing the various parts; things like curly braces, colons, strings etc. Then it parses those tokens into a collection of `SyntaxNodes` (creating an Abstract Syntax Tree) representing concepts like `FieldNode`, `InputValueNode`, and `OperationTypeNode` following the [graphql specification rules for source text documents](https://spec.graphql.org/October2021/#sec-Source-Text). - -Once parsed, the runtime will execute its internal rules engine against the generated `ISyntaxTree`, using the targeted `ISchema`, to create a query plan where it marries the nodes AST with concrete structures such as controllers, action methods and POCOs. It is at this stage where the `hero` field in the example is matched to the `HeroController` with its appropriate `IGraphFieldResolver` to invoke the `RetrieveHero` action method. - -While generating a query plan the rules engine will do its best to complete an analysis of the entire document and return to the requestor every error it finds. Depending on the errors though, it may or may not be able to catch them all. For instance, a syntax error, like a missing `}`, will preclude generating a query plan so errors centered around invalid field names or a type expression mismatch won't be caught until the syntax error is fixed (just like any other compiler). - -### Phase 2: Execution - -The engine now has a completed query plan that describes: - -- The named operation in the document to be executed (or the single anonymous operation in the example above) -- The top level fields and every child field on the chosen operation. - -**Its successfully validated that:** - -- All the referenced fields for the graph types exist and are valid where requested -- Required input arguments have been supplied and their data is "resolvable" - - This just means that we've validated that a number is a number, named fields on input objects exist etc. - -**For each field, the runtime will:** - -- Substitute any deferred input arguments with referenced variable data -- Generate a field execution context containing the necessary data about the source data, arguments and resolver. -- Authenticate the user to the field. -- Execute the resolver to fetch a data value. -- Invoke any child fields requested. - -#### Resolving a Field - -GraphQL uses the phrase "resolver" to describe a method that, for a given field, takes in parameters and generates a result. - -At startup, GraphQL ASP.NET automatically creates resolver references for your controller methods, POCO properties and tagged POCO methods. These are nothing more than delegates for method invocations; for properties it uses the getter registered with `PropertyInfo`. - -> **_Performance Note_**: The library makes heavy use of compiled Expression Trees to call its resolvers and for instantiating input objects. As a result, its many orders of magnitude faster than baseline reflected calls and nearly as performant as precompiled code. - -**Concerning Proxy Libraries (e.g. EF Core Proxies)** - -GraphQL ASP.NET natively supports Liskov substitutions for all graph types opening up the possibility for using libraries such as EF Proxies that can provide a tremendously powerful and easy to setup graph structure. By lazy loading any child collections you can expose access to your entire domain model with very little work. - -Be careful though, [EF Core Proxies](https://docs.microsoft.com/en-us/ef/core/querying/related-data/lazy) and like libraries suffer from the same N + 1 problem as GraphQL libraries do. By choosing to use such lazy loading techniques you are circumventing GraphQL ASP.NET's provided mechanisms for handling such situations and need to be exceptionally careful to manage the issue yourself. - -### Phase 3: Response Generation - -Once all fields have been processed the runtime makes a final pass to propagate any nullability errors up the field chain resulting in a final data set. This data set is then passed to the `IGraphResponseWriter` registered for the schema and the result is serialized to the HttpResponse. - -**Writing a response includes:** - -- Serializing the `errors` generated during execution - - - All error messages have a severity level. Your schema configuration controls which are sent to the client. - - You can add your own error messages through your controller actions by returning `this.Error()`. - - Error messages may or may not have associated exception data which may or may not be exposed to the client depending on who they are or the schema configuration settings. - -- Serializing the `data` field to the response stream - -- Serializing the `extensions` field (such as metrics data) to the response stream - -## Other Points of Interest - -Hopefully we've given you a bit of insight into how the library works under the hood. The other documents on this site go into exhaustive detail of the different features and how to use them but since you're here: - -- The library targets [`netstandard2.0`](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) and `net6.0`. - - Out of the box there are no external dependencies beyond official Microsoft packages. - -* Every core component and all [middleware components](../reference/middleware.md) required to complete the tasks outlined in this document are referenced through dependency injection. Any one of them (or all of them) can be overridden and extended to do whatever you want as long as you register them prior to calling `.AddGraphQL()` at startup. - - - Inject your own `IGraphResponseWriter` to serialize your results to XML or CSV. - - Build your own `IOperationComplexityCalculator` to intercept and alter how a query plan generates its [complexity values](../execution/malicious-queries) to be more suitable to your needs. - - On and on and on... - -## Architectural Diagrams - -📌 [Structural Diagrams](../assets/2022-10-graphql-aspnet-structural-diagrams.pdf) - -A set of diagrams outlining the major interfaces and classes that make up GraphQL Asp.Net. - -📌 [Execution Diagrams](../assets/2022-10-graphql-aspnet-execution-diagrams.pdf) - -A set of flowcharts and relational diagrams showing how various aspects of the library fit together at run time, including the query execution and field execution pipelines. diff --git a/docs/reference/http-processor.html b/docs/reference/http-processor.html new file mode 100644 index 0000000..409b320 --- /dev/null +++ b/docs/reference/http-processor.html @@ -0,0 +1,24 @@ + + + + + +HTTP Processor | GraphQL ASP.NET + + + + +
+

HTTP Processor

The DefaultGraphQLHttpProcessor<TSchema> is mapped to a route for the target schema and accepts an HttpContext from the ASP.NET runtime. It inspects the received payload (the query text and variables) then packages an IQueryExecutionRequest and sends it to the GraphQL runtime. Once a result is generated the controller forwards that response to the response writer for serialization.

Extending the Http Processor

Extending the http processor allows you to add custom code and interject into a few places in the request processing flow. It most cases its easier to extend the default implementation than rolling your own or replacing the handler altogether.

First, extend from DefaultGraphQLHttpProcessor<TSchema> for your target schema. The processor is instantiated from your DI container on a "per request" basis. Any services referenced in your constructor will need to be servable from IServiceProvider. Those required by the default processor are automatically injected during the call to AddGraphQL().

Create a Custom HTTP Processor
public class MyHttpProcessor : DefaultGraphQLHttpProcessor<MySchema>
{
public MyHttpProcessor(
MySchema schema,
IGraphQLRuntime<MySchema> queryPipeline,
IQueryResponseWriter<MySchema> writer,
IGraphEventLogger logger = null)
: base(schema, queryPipeline, writer, metricsFactory, logger)
{
}
}

Second, override the http processor reference in your Startup.cs:

Register Your Custom Processor
services.AddGraphQL<MySchema>(options =>
{
options.QueryHandler.HttpProcessorType = typeof(MyHttpProcessor);
});

That's all there is. Your processor will now serve requests for your schema.

Helpful Methods

These methods can be overridden to provide custom logic at various points in the query operation.

CreateRequest(queryData)

Override this method to supply your own IQueryExecutionRequest to the runtime.

  • queryData: The raw data package read from the HttpContext

HandleQueryException(exception)

Override this method to provide some custom processing to an unhandled exception. If this method returns an IQueryExecutionResult it will be sent to the requestor, otherwise return null to allow a status 500 result to be generated.

It is exceedingly rare that this method will ever be called. The runtime will normally attach exceptions as messages to the graphql response. This method exists as a catch all just in case something occurs beyond all expected constraints.

  • exception: The exception that was thrown and unhandled by the runtime.

HandleQueryMetrics(metrics)

Override this method to perform some custom processing on a set of query metrics that were gathered for the executed query. This method will only be called if metrics were actually gathered.

  • metrics: the IQueryExecutionMetrics package that was populated during the request.

ExposeExceptions

Override this property to conditionally expose exceptions on the outgoing response. This can be useful to return true for users authorized as administrators yet hide the data from others.

ExposeMetrics

Override this property to conditionally expose gathered metrics on the outgoing response.

+ + + + \ No newline at end of file diff --git a/docs/reference/http-processor.md b/docs/reference/http-processor.md deleted file mode 100644 index d4c0180..0000000 --- a/docs/reference/http-processor.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -id: http-processor -title: HTTP Processor -sidebar_label: HTTP Processor -sidebar_position: 6 ---- - -The `DefaultGraphQLHttpProcessor` is mapped to a route for the target schema and accepts an `HttpContext` from the ASP.NET runtime. It inspects the received payload (the query text and variables) then packages an `IQueryExecutionRequest` and sends it to the GraphQL runtime. Once a result is generated the controller forwards that response to the response writer for serialization. - -## Extending the Http Processor - -Extending the http processor allows you to add custom code and interject into a few places in the request processing flow. It most cases its easier to extend the default implementation than rolling your own or replacing the handler altogether. - -First, extend from `DefaultGraphQLHttpProcessor` for your target schema. The processor is instantiated from your DI container on a "per request" basis. Any services referenced in your constructor will need to be servable from `IServiceProvider`. Those required by the default processor are automatically injected during the call to `AddGraphQL()`. - -```csharp title="Create a Custom HTTP Processor" -public class MyHttpProcessor : DefaultGraphQLHttpProcessor -{ - public MyHttpProcessor( - MySchema schema, - IGraphQLRuntime queryPipeline, - IQueryResponseWriter writer, - IGraphEventLogger logger = null) - : base(schema, queryPipeline, writer, metricsFactory, logger) - { - } -} -``` - -Second, override the http processor reference in your `Startup.cs`: - -```csharp title="Register Your Custom Processor" -services.AddGraphQL(options => -{ - options.QueryHandler.HttpProcessorType = typeof(MyHttpProcessor); -}); -``` - -That's all there is. Your processor will now serve requests for your schema. - -## Helpful Methods - -These methods can be overridden to provide custom logic at various points in the query operation. - -### CreateRequest(queryData) - -Override this method to supply your own `IQueryExecutionRequest` to the runtime. - -- `queryData`: The raw data package read from the HttpContext - -### HandleQueryException(exception) - -Override this method to provide some custom processing to an unhandled exception. If this method returns an `IQueryExecutionResult` it will be sent to the requestor, otherwise return null to allow a status 500 result to be generated. - -It is exceedingly rare that this method will ever be called. The runtime will normally attach exceptions as messages to the graphql response. This method exists as a catch all _just in case_ something occurs beyond all expected constraints. - -- `exception`: The exception that was thrown and unhandled by the runtime. - -### HandleQueryMetrics(metrics) - -Override this method to perform some custom processing on a set of query metrics that were gathered for the executed query. This method will only be called if metrics were actually gathered. - -- `metrics`: the `IQueryExecutionMetrics` package that was populated during the request. - -### ExposeExceptions - -Override this property to conditionally expose exceptions on the outgoing response. This can be useful to return true for users authorized as administrators yet hide the data from others. - -### ExposeMetrics - -Override this property to conditionally expose gathered metrics on the outgoing response. diff --git a/docs/reference/middleware.html b/docs/reference/middleware.html new file mode 100644 index 0000000..1461293 --- /dev/null +++ b/docs/reference/middleware.html @@ -0,0 +1,25 @@ + + + + + +Pipelines and Custom Middleware | GraphQL ASP.NET + + + + +
+

Pipelines and Custom Middleware

At the heart of GraphQL ASP.NET are 4 middleware pipelines; chains of components executed in a specific order to produce a result.

  • Query Execution Pipeline : Invoked once per request this pipeline is responsible for validating the incoming package on the POST or GET request, parsing the data and generating a query plan.
  • Field Execution Pipeline : Invoked once per requested field, this pipeline processes a single field resolver.
  • Schema Item Security Pipeline: Ensures the user on the request can access a given schema item (field, directive etc.).
  • Directive Execution Pipeline: Executes directives for various phases of schema and query document lifetimes.

All four pipelines can be extended or reworked to include custom components and perform additional work. A call to .AddGraphQL() returns a builder that can be used to restructure the pipelines when necessary.

Creating New Middleware

Each new middleware component must implement one of the four middleware interfaces depending on the type of component you are creating; much in the way you'd define a middleware component for ASP.NET. The four middleware interfaces are:

  • IQueryExecutionMiddleware
  • IFieldExecutionMiddleware
  • ISchemaItemSecurityMiddleware
  • IDirectiveExecutionMiddleware

The interfaces define one method, InvokeAsync, with identical signatures save for the type of data context accepted by each.

Query Execution Middleware Component definition.
public interface IQueryExecutionMiddleware
{
Task InvokeAsync(
QueryExecutionContext context,
GraphMiddlewareInvocationDelegate next,
CancellationToken cancelToken);
}

The library will invoke your component at the appropriate time and pass to it the active data context. Once you have performed any necessary work involving the context invoke the next delegate (the second parameter) to pass the context to the next component in the chain.

Example Custom Middleware Component
public class MyQueryMiddleware : IQueryExecutionMiddleware
{
public async Task InvokeAsync(
QueryExecutionContext context,
GraphMiddlewareInvocationDelegate next,
CancellationToken cancelToken)
{
// Do any necessary work with the context
if(context.Request.QueryText == null)
{
context.Messages.Critical("No Query Text Provided");
context.Cancel();
}

// invoke the next component in the chain
await next(context, cancelToken);
}
}

Registering New Middleware

Each pipeline can be extended using the SchemaBuilder returned from calling .AddGraphQL() at startup. Each schema that is added to GraphQL will generate its own builder with its own set of pipelines and components. They can be configured independently as needed.

Register Your Middleware During Startup
// obtain a reference to the builder after adding
// graphql for your schema
var schemaBuilder = services.AddGraphQL(options =>
{
options.ExecutionOptions.MaxQueryDepth = 15;
});

// add new middleware components to any pipeline
schemaBuilder.QueryExecutionPipeline.AddMiddleware<MyQueryMiddleware>();

Instead of adding to the end of the existing pipeline you can also call .Clear() to remove the default components and rebuild the pipeline from scratch. See below for the list of default middleware components and their order of execution. This can be handy when needing to inject a new component into the middle of the execution chain.

Component order Matters

Modifying the component order of a pipeline can cause unwanted side effects, including breaking the library such that it no longer functions. Take care when adding or removing middleware components.

The Context Object

Each context object has specific data fields required for it to perform its work (detailed below). However, all contexts share a common set of items to govern the flow of work.

  • QueryRequest: The original request being executed. Contains the query text, variables etc.
  • Messages: A collection of messages that will be added to the query result.
  • Cancel(): Marks the context as cancelled and sets the IsCancelled property to true. It is up to each middleware component to interpret the meaning of cancelled for its own purposes. A canceled field execution context, for instance, will be discarded and not rendered to the output whereas a canceled query context may or may not generate a result depending on when its cancelled.
  • IsValid: Determines if the context is in a valid and runnable state. Most middleware components will not attempt to process the context if its not in a valid state and will simply forward the request on. By default, a context is automatically invalidated if an error message is added with the Critical severity.
  • SecurityContext: The information received from ASP.NET containing the credentials of the active user. May be null if user authentication is not setup for your application.
  • Metrics: The metrics package performing any profiling of the query. Various middleware components will stop/start phases of execution using this object. If metrics are not enabled this object will be null.
  • Items: A key/value collection of items available to every context on every pipeline related to a single request. This field is developer driven and not used by the runtime.
  • Logger: An IGraphEventLogger instance scoped to the the current query.

Middleware is served from the DI Container

Each pipeline is registered as a singleton instance in your service provider but the components within the pipeline are invoked according to the service lifetime you supply when you register them, allowing you to manage dependencies as you see fit.

Register Middleware as Singletons

Register your middleware components with the Singleton lifetime scope whenever possible to boost performance.

It is recommended that your middleware components be singleton in nature if possible. The field execution and item authorization pipelines can be invoked many dozens of times per request and fetching new middleware instances for each invocation can impact performance. Most default components are registered as a singletons.

Query Execution Pipeline

The query execution pipeline is invoked once per request. It is supplied with the raw query text from the user and orchestrates the necessary calls to generate a a valid GraphQL result than can be returned to the client. It contains 9 components, in order of execution they are:

  1. ValidateQueryRequestMiddleware : Ensures that the data request recieved is valid and runnable (i.e. was a request provided, is query text defined etc.).
  2. RecordQueryMetricsMiddleware: Governs the query profiling for the context. It will start the recording and terminate it after all other components have completed their operations.
  3. QueryExecutionPlanCacheMiddleware : When the query cache is enabled for the schema, this component will analyze the incoming query text and attempt to fetch a pre-cached query plan from storage.
  4. ParseQueryPlanMiddleware: When required, this component will lex/parse the query text into a usable document from which a query plan can be created.
  5. ValidateQueryDocumentMiddleware: Performs a first pass validation of the query document, before any directives are applied.
  6. AssignQueryOperationMiddleware : Marries the operation name requested with the matching operation in the query document.
  7. ValidateOperationVariableDataMiddleware: Validates the supplied variables values against those required by the chosen operation.
  8. AuthorizeQueryOperationMiddleware: If the schema is configured for PerRequest authorization this component will invoke the authorization pipeline for each field of the selected operation that has security requirements and assign authorization results as appropriate.
  9. ApplyExecutionDirectivesMiddleware: Applies all execution directives, if any, to the chosen operation.
  10. GenerateQueryPlanMiddleware: When required, this component will attempt to generate a fully qualified query plan for its target schema using the chosen operation.
  11. ExecuteQueryOperationMiddleware : Uses the query plan to dispatches field execution contexts to resolve needed each field.
  12. PackageQueryResultMiddleware: Performs a final set of checks on the resolved field data and generates an IQueryExecutionResult for the query. +document on the context.

QueryExecutionContext

In addition to the common properties defined above, the query execution context defines a number of useful fields:

QueryExecutionContext.cs
public class QueryExecutionContext
{
public IQueryExecutionResult Result { get; set; }
public IQueryExecutionPlan QueryPlan { get; set; }
public IList<FieldDataItem> FieldResults { get; }

// other properties omitted for brevity
}
  • Result: The created IQueryExecutionResult. This property will be null until the result is created.
  • QueryPlan: the created (or retrieved from cache) query plan for the current query.
  • FieldResults: The individual, top-level data fields resolved for the selected operation. These fields are eventually packaged into the result object.

Field Execution Pipeline

The field execution pipeline is executed once for each field of data that needs to be resolved. Its primary job is to turn a request for a field into a data value that can be returned to the client. It contains 5 components, in order of execution they are:

  1. ValidateFieldExecutionMiddleware : Validates that the context and required invocation data has been correctly supplied.
  2. AuthorizeFieldMiddleware : If the schema is configured for PerField authorization this component will invoke the item authorization pipeline for the current field and assign authorization results as appropriate.
  3. InvokeFieldResolverMiddleware : The field resolver is called and a data value is created for the active context. This middleware component is ultimately responsible for invoking your controller actions.
  4. ProcessChildFieldsMiddleware : If any child fields are registered for this field they are executing using the context's field result as the new source object.

GraphFieldExecutionContext

In addition to the common properties defined above the field execution context defines a number of useful properties:

GraphFieldExecutionContext.cs
public class GraphFieldExecutionContext
{
public IGraphFieldRequest Request { get; }
public object Result { get; set; }

// other properties omitted for brevity
}
  • Request: The field request containing any source data, a reference to the metadata for the field as defined by the schema and a reference to the invocation requirements determined by the query plan.
  • Result: The raw data object produced from the field resolver. This value is passed as the source value to any child fields.

Schema Item Authorization Pipeline

The field authorization pipeline can be invoked as part of query execution or field execution depending on your schema's configuration. It contains 1 component:

  1. SchemItemSecurityRequirementsMiddleware : Gathers the authentication and authorization requirements for the given schema item and ensures that the item can be authorized.
  2. SchemaItemAuthenticationMiddleware : Authenticates the request to the field. This generates a ClaimsPrincipal to be authorized against if one is not already assigned.
  3. SchemaItemAuthorizationMiddleware: Inspects the active ClaimsPrincipal against the security requirements of the schema item and generates a SchemaItemSecurityChallengeResult indicating if the user is authorized or not. This component makes no decisions against the authorization state. It is up to the other pipelines to act on the authorization results in an appropriate manner.

GraphSchemaItemSecurityChallengeContext

In addition to the common properties defined above the field security context defines a number of useful properties:

GraphSchemaItemSecurityChallengeContext.cs
 public class GraphSchemaItemSecurityChallengeContext
{
public SchemaItemSecurityRequirements SecurityRequirements {get; set;}
public IGraphSchemaItemSecurityRequest Request { get; }
public SchemaItemSecurityChallengeResult Result { get; set; }

// common properties omitted for brevity
}
  • SecurityRequirements: The security rules that need to be checked to authorize a user.
  • Request: Contains details about the item currently being authorized.
  • Result: The generated challenge result indicating if the user is authorized to the item. This result will contain additional detailed information as to why a request was not authorized. This information is automatically added to any generated log events.

Directive Execution Pipeline

The directive execution pipeline will be invoked for each directive applied to each schema item during schema generation and each time the query engine encounters a directive on a query document. The directive pipeline contains four components by default:

  1. ValidateDirectiveExecutionMiddleware: Inspects the context against the validation requirements for directives and applies appropriate error messages as necessary.
  2. AuthorizeDirectiveMiddleware: If the schema is configured for PerField authorization this component will invoke the item authorization pipeline for the current directive and assign authorization results as appropriate.
  3. InvokeDirectiveResolverMiddleware: Generates a DirectiveResolutionContext and invokes the directive's resolver, calling the correct action methods.
  4. LogDirectiveExecutionMiddleware: Generates appropriate log messages depending on the directive invoked.

GraphDirectiveExecutionContext

GraphDirectiveExecutionContext
public class GraphDirectiveExecutionContext
{
public IGraphDirectiveRequest Request { get; }
public IDirective Directive {get;}
public ISchema Schema {get; }

// common properties omitted for brevity
}
  • Request: Contains the directive metadata for this context, including the DirectiveTarget, execution phase and executing location.
  • Directive: The specific IDirective, registered to the schema, that is being processed.
  • Schema: the schema instance where the directive is declared.

A Note on Schema Instances

Since the directive execution pipeline is used to construct a schema and apply type system directives, middleware components within it cannot inject a schema instance from the DI container. To do so would cause a circular reference in the DI container.

Instead use the schema instance attached to the GraphDirectiveExecutionContext.

note

You can inject a schema instance into components of every pipeline EXCEPT the Directive Execution Pipeline.

+ + + + \ No newline at end of file diff --git a/docs/reference/middleware.md b/docs/reference/middleware.md deleted file mode 100644 index 64dc879..0000000 --- a/docs/reference/middleware.md +++ /dev/null @@ -1,229 +0,0 @@ ---- -id: middleware -title: Pipelines and Custom Middleware -sidebar_label: Pipelines & Middleware -sidebar_position: 7 ---- - -At the heart of GraphQL ASP.NET are 4 middleware pipelines; chains of components executed in a specific order to produce a result. - -- `Query Execution Pipeline` : Invoked once per request this pipeline is responsible for validating the incoming package on the POST or GET request, parsing the data and generating a query plan. -- `Field Execution Pipeline` : Invoked once per requested field, this pipeline processes a single field resolver. -- `Schema Item Security Pipeline`: Ensures the user on the request can access a given schema item (field, directive etc.). -- `Directive Execution Pipeline`: Executes directives for various phases of schema and query document lifetimes. - -All four pipelines can be extended or reworked to include custom components and perform additional work. A call to `.AddGraphQL()` returns a builder that can be used to restructure the pipelines when necessary. - -## Creating New Middleware - -Each new middleware component must implement one of the four middleware interfaces depending on the type of component you are creating; much in the way you'd define a middleware component for ASP.NET. The four middleware interfaces are: - -- `IQueryExecutionMiddleware` -- `IFieldExecutionMiddleware` -- `ISchemaItemSecurityMiddleware` -- `IDirectiveExecutionMiddleware` - -The interfaces define one method, `InvokeAsync`, with identical signatures save for the type of data context accepted by each. - -```csharp title="Query Execution Middleware Component definition." -public interface IQueryExecutionMiddleware -{ - Task InvokeAsync( - QueryExecutionContext context, - GraphMiddlewareInvocationDelegate next, - CancellationToken cancelToken); -} -``` - -The library will invoke your component at the appropriate time and pass to it the active data context. Once you have performed any necessary work involving the context invoke the `next` delegate (the second parameter) to pass the context to the next component in the chain. - -```csharp title="Example Custom Middleware Component" -public class MyQueryMiddleware : IQueryExecutionMiddleware -{ - public async Task InvokeAsync( - QueryExecutionContext context, - GraphMiddlewareInvocationDelegate next, - CancellationToken cancelToken) - { - // Do any necessary work with the context - if(context.Request.QueryText == null) - { - context.Messages.Critical("No Query Text Provided"); - context.Cancel(); - } - - // invoke the next component in the chain - await next(context, cancelToken); - } -} -``` - -## Registering New Middleware - -Each pipeline can be extended using the `SchemaBuilder` returned from calling `.AddGraphQL()` at startup. Each schema that is added to GraphQL will generate its own builder with its own set of pipelines and components. They can be configured independently as needed. - -```csharp title="Register Your Middleware During Startup" -// obtain a reference to the builder after adding -// graphql for your schema -var schemaBuilder = services.AddGraphQL(options => -{ - options.ExecutionOptions.MaxQueryDepth = 15; -}); - -// add new middleware components to any pipeline -schemaBuilder.QueryExecutionPipeline.AddMiddleware(); - -``` - -Instead of adding to the end of the existing pipeline you can also call `.Clear()` to remove the default components and rebuild the pipeline from scratch. See below for the list of default middleware components and their order of execution. This can be handy when needing to inject a new component into the middle of the execution chain. - -:::caution Component order Matters - Modifying the component order of a pipeline can cause unwanted side effects, including breaking the library such that it no longer functions. Take care when adding or removing middleware components. -::: - -## The Context Object - -Each context object has specific data fields required for it to perform its work (detailed below). However, all contexts share a common set of items to govern the flow of work. - -- `QueryRequest`: The original request being executed. Contains the query text, variables etc. -- `Messages`: A collection of messages that will be added to the query result. -- `Cancel()`: Marks the context as cancelled and sets the `IsCancelled` property to true. It is up to each middleware component to interpret the meaning of cancelled for its own purposes. A canceled field execution context, for instance, will be discarded and not rendered to the output whereas a canceled query context may or may not generate a result depending on when its cancelled. -- `IsValid`: Determines if the context is in a valid and runnable state. Most middleware components will not attempt to process the context if its not in a valid state and will simply forward the request on. By default, a context is automatically invalidated if an error message is added with the `Critical` severity. -- `SecurityContext`: The information received from ASP.NET containing the credentials of the active user. May be null if user authentication is not setup for your application. -- `Metrics`: The metrics package performing any profiling of the query. Various middleware components will stop/start phases of execution using this object. If metrics are not enabled this object will be null. -- `Items`: A key/value collection of items available to every context on every pipeline related to a single request. This field is developer driven and not used by the runtime. -- `Logger`: An `IGraphEventLogger` instance scoped to the the current query. - -## Middleware is served from the DI Container - -Each pipeline is registered as a singleton instance in your service provider but the components within the pipeline are invoked according to the service lifetime you supply when you register them, allowing you to manage dependencies as you see fit. - -:::tip Register Middleware as Singletons - Register your middleware components with the `Singleton` lifetime scope whenever possible to boost performance. -::: - -It is recommended that your middleware components be singleton in nature if possible. The field execution and item authorization pipelines can be invoked many dozens of times per request and fetching new middleware instances for each invocation can impact performance. Most default components are registered as a singletons. - -## Query Execution Pipeline - -The query execution pipeline is invoked once per request. It is supplied with the raw query text from the user and orchestrates the necessary calls to generate a a valid GraphQL result than can be returned to the client. It contains 9 components, in order of execution they are: - -1. `ValidateQueryRequestMiddleware` : Ensures that the data request recieved is valid and runnable (i.e. was a request provided, is query text defined etc.). -2. `RecordQueryMetricsMiddleware`: Governs the query profiling for the context. It will start the recording and terminate it after all other components have completed their operations. -3. `QueryExecutionPlanCacheMiddleware` : When the query cache is enabled for the schema, this component will analyze the incoming query text and attempt to fetch a pre-cached query plan from storage. -4. `ParseQueryPlanMiddleware`: When required, this component will lex/parse the query text into a usable document from which a query plan can be created. -5. `ValidateQueryDocumentMiddleware`: Performs a first pass validation of the query document, before any directives are applied. -6. `AssignQueryOperationMiddleware` : Marries the operation name requested with the matching operation in the query document. -7. `ValidateOperationVariableDataMiddleware`: Validates the supplied variables values against those required by the chosen operation. -8. `AuthorizeQueryOperationMiddleware`: If the schema is configured for `PerRequest` authorization this component will invoke the authorization pipeline for each field of the selected operation that has security requirements and assign authorization results as appropriate. -9. `ApplyExecutionDirectivesMiddleware`: Applies all execution directives, if any, to the chosen operation. -10. `GenerateQueryPlanMiddleware`: When required, this component will attempt to generate a fully qualified query plan for its target schema using the chosen operation. -11. `ExecuteQueryOperationMiddleware` : Uses the query plan to dispatches field execution contexts to resolve needed each field. -12. `PackageQueryResultMiddleware`: Performs a final set of checks on the resolved field data and generates an `IQueryExecutionResult` for the query. -document on the context. - -#### QueryExecutionContext - -In addition to the common properties defined above, the query execution context defines a number of useful fields: - -```csharp title="QueryExecutionContext.cs" -public class QueryExecutionContext -{ - public IQueryExecutionResult Result { get; set; } - public IQueryExecutionPlan QueryPlan { get; set; } - public IList FieldResults { get; } - - // other properties omitted for brevity -} -``` -- `Result`: The created `IQueryExecutionResult`. This property will be null until the result is created. -- `QueryPlan`: the created (or retrieved from cache) query plan for the current query. -- `FieldResults`: The individual, top-level data fields resolved for the selected operation. These fields are eventually packaged into the result object. - -## Field Execution Pipeline - -The field execution pipeline is executed once for each field of data that needs to be resolved. Its primary job is to turn a request for a field into a data value that can be returned to the client. It contains 5 components, in order of execution they are: - -1. `ValidateFieldExecutionMiddleware` : Validates that the context and required invocation data has been correctly supplied. -2. `AuthorizeFieldMiddleware` : If the schema is configured for `PerField` authorization this component will invoke the item authorization pipeline for the current field and assign authorization results as appropriate. -3. `InvokeFieldResolverMiddleware` : The field resolver is called and a data value is created for the active context. This middleware component is ultimately responsible for invoking your controller actions. -4. `ProcessChildFieldsMiddleware` : If any child fields are registered for this field they are executing using the context's field result as the new source object. - -#### GraphFieldExecutionContext - -In addition to the common properties defined above the field execution context defines a number of useful properties: - -```csharp title="GraphFieldExecutionContext.cs" -public class GraphFieldExecutionContext -{ - public IGraphFieldRequest Request { get; } - public object Result { get; set; } - - // other properties omitted for brevity -} -``` - -- `Request`: The field request containing any source data, a reference to the metadata for the field as defined by the schema and a reference to the invocation requirements determined by the query plan. -- `Result`: The raw data object produced from the field resolver. This value is passed as the source value to any child fields. - -## Schema Item Authorization Pipeline - -The field authorization pipeline can be invoked as part of query execution or field execution depending on your schema's configuration. It contains 1 component: - -1. `SchemItemSecurityRequirementsMiddleware` : Gathers the authentication and authorization requirements for the given schema item and ensures that the item _can_ be authorized. -2. `SchemaItemAuthenticationMiddleware` : Authenticates the request to the field. This generates a ClaimsPrincipal to be authorized against if one is not already assigned. -3. `SchemaItemAuthorizationMiddleware`: Inspects the active `ClaimsPrincipal` against the security requirements of the schema item and generates a `SchemaItemSecurityChallengeResult` indicating if the user is authorized or not. This component makes no decisions against the authorization state. It is up to the other pipelines to act on the authorization results in an appropriate manner. - -#### GraphSchemaItemSecurityChallengeContext - -In addition to the common properties defined above the field security context defines a number of useful properties: - -```csharp title="GraphSchemaItemSecurityChallengeContext.cs" - public class GraphSchemaItemSecurityChallengeContext -{ - public SchemaItemSecurityRequirements SecurityRequirements {get; set;} - public IGraphSchemaItemSecurityRequest Request { get; } - public SchemaItemSecurityChallengeResult Result { get; set; } - - // common properties omitted for brevity -} -``` - -- `SecurityRequirements`: The security rules that need to be checked to authorize a user. -- `Request`: Contains details about the item currently being authorized. -- `Result`: The generated challenge result indicating if the user is authorized to the item. This result will contain additional detailed information as to why a request was not authorized. This information is automatically added to any generated log events. - -## Directive Execution Pipeline - -The directive execution pipeline will be invoked for each directive applied to each schema item during schema generation and each time the query engine encounters a directive on a query document. The directive pipeline contains four components by default: - -1. `ValidateDirectiveExecutionMiddleware`: Inspects the context against the validation requirements for directives and applies appropriate error messages as necessary. -2. `AuthorizeDirectiveMiddleware`: If the schema is configured for `PerField` authorization this component will invoke the item authorization pipeline for the current directive and assign authorization results as appropriate. -3. `InvokeDirectiveResolverMiddleware`: Generates a `DirectiveResolutionContext` and invokes the directive's resolver, calling the correct action methods. -4. `LogDirectiveExecutionMiddleware`: Generates appropriate log messages depending on the directive invoked. - -#### GraphDirectiveExecutionContext - -```csharp title="GraphDirectiveExecutionContext" -public class GraphDirectiveExecutionContext -{ - public IGraphDirectiveRequest Request { get; } - public IDirective Directive {get;} - public ISchema Schema {get; } - - // common properties omitted for brevity -} -``` - -- `Request`: Contains the directive metadata for this context, including the DirectiveTarget, execution phase and executing location. -- `Directive`: The specific `IDirective`, registered to the schema, that is being processed. -- `Schema`: the schema instance where the directive is declared. - -### A Note on Schema Instances - Since the directive execution pipeline is used to construct a schema and apply type system directives, middleware components within it cannot inject a schema instance from the DI container. To do so would cause a circular reference in the DI container. - -Instead use the schema instance attached to the `GraphDirectiveExecutionContext`. - -:::note - You can inject a schema instance into components of every pipeline EXCEPT the Directive Execution Pipeline. -::: \ No newline at end of file diff --git a/docs/reference/performance.html b/docs/reference/performance.html new file mode 100644 index 0000000..2c21947 --- /dev/null +++ b/docs/reference/performance.html @@ -0,0 +1,25 @@ + + + + + +Benchmarks & Performance | GraphQL ASP.NET + + + + +
+

Benchmarks & Performance

Query Benchmarking

GraphQL ASP.NET is designed to be fast. For our benchmarks, we are tracking a number of query types which measure performance via the various paths through the library; multi controller queries, queries with variables etc. These are executed against an in-memory data store without an attached database.

The goal of our benchmarks is to measure the library's abiliy to process a query in isolation, to perform the query and execute user code; not how long an action method takes to query a database for data and send a request over the wire. Obviously, real world workloads are going to be slower than these theoretical values, but the faster we can make the benchmarks the faster all other scenarios will be.

As you can see all query types execute in sub-millisecond timeframes. If there is a specific query type or scenario that you are seeing a significant performance degregation with please open an issue on github and let us know!

benchmarks +Last Updated 2022-12-01; v0.14.0-beta

Your Milage May Vary

Performance will vary depending on your hardware and environment conditions. You can execute your own test run via the bench marking solution located at ./src/graphql-aspnet-benchmarks.sln

Performance Testing

Run Your Own Performance Tests

These performance tests are not intended to be used as data points when determining scaling requirements for your own production workloads. Your use cases will be different and effected by factors not present in our lab environment (e.g. database connections, service orchrestration, business logic etc.). Be sure to execute your own load tests using queries indicative of your expected user base and act accordingly.

We periodically execute tests against the library to measure throughput and stability, for a single server instance, under load. Our goal is to measure the theoretical limits in a multi-user, "production like" scenario.

These tests are executed in a controlled setting with the following conditions:

Test Configuration and Specs

GraphQL ASP.NET Server:

  • source code: ./src/ancillary-projects/benchmarking/graphql-aspnet-load-server/
  • .NET 7 Runtime
  • Garbage collection executing in server mode
  • Local area, wired, gigabit network
  • Simple queries to fetch or mutate a single, in-memory object

Memory Profiling Load:

  • JMeter script: graphql-memory-profiling.jmx
  • 15 concurrent users executing a graphql query to fetch a single object
  • Each user executes 10,000 requests at most 30ms apart

GraphQL Load:

  • JMeter script: graphql-load-generator.jmx
  • 300 concurrent users executing a graphql query to fetch a single object
  • 300 concurrent users executing a graphql mutation (and raising a subscription event)
  • Each user executes 10,000 requests at most 30ms apart

Subscription Client Load:

  • Source Code: ~coming soon~
  • A custom console app that registers subscribers to receive subscription events generated via the Load Generating Workstation mutations
  • 500 connected subscription clients
  • 2 registered subscription per client (1000 total client subscriptions)

REST Load:

  • JMeter script: rest-load-generator.jmx
  • 300 concurrent users executing a REST Query that fetches a single object
  • 300 concurrent users executing a REST Query that mutates and returns a single object
  • Each user executes 10,000 requests at most 15ms apart
  • This workload acts as a control to compare performance of the baseline web api against the overhead of the graphql library

GraphQL Max Throughput Load:

  • JMeter script: graphql-max-load-generator.jmx
  • Starts 20 new users each second until failure
  • Each user executes a query every 0-15ms until failure

Memory Profile Test

A qualitative test executed with the server instance running in release mode, harnessed via dotMemory. Given the artificial environment restrictions this imposes its difficult to pin down exact KPIs but in general this test is used to monitor:

MetricExpectations
Memory AllocationExpect to see steady Gen0 allocation over time, with no extreme spikes.
Object SurvivalExpect to see little to no objects surviving to Gen1 and Gen2 heap collections per GC cycle. When the test completes, the server returns to a steady state of memory usage prior to the test beginning.
Goal

The aim of this test is to ensure acceptable memory pressure and GC cycles on the server instance in a controlled usage scenario and ensure no memory leaks occur.

Results

DateVersion
2022-11-30v0.13.1-betaExecution is consistant. While no objects make it to Gen1 or Gen2 a GC cycle occurs about every 20 seconds.
2022-11-30v0.14.0-betaSimilar results to the v0.13.1-beta test in terms of generational memory allocations. There is a notable decrease in memory pressure. Time between GC cycles has improved to once every 33 seconds; a 65% increase in duration.

General Usage Load Test

A test with the server executing in release mode, WITHOUT the subscription server attached and with monitoring via passive dotnet-counters. This test measures the throughput of queries and mutations through the runtime as well as the load those queries place on the server CPU. Using GraphQL (as opposed to REST) will generate some additional overhead to parse and execute the query on top of the REST request which invokes it. As a result, the metrics of this test are expressed in terms of % increases over a comperable REST workload via a baseline ASP.NET web api controller.

MetricExpectations
CPU UtilizationUsing Process Explorer to measure utilization, no more than a 5% increase when compared to the REST control load.
GC % TimeUsing the metrics obtained via dotnet-counters expect that the GC % time is within 1% of the REST control load
Throughput (req/sec)Throughput, measured in requests per second, is within 10% of the peak load generated via the REST control load
Goal

The aim of this test is to ensure adequate single instance throughput and that the overhead for using graphql on top of web api is kept to a minimum.

Results

DateVersionMetricREST WorkloadGraphQL WorkloadVariance
2022-11-30v0.13.1-betaCPU Utilization0.01-2%2-9% +7%
GC % Time< 1%2-4%+3%
Throughput37,490 req/sec29,830 req/sec-8k (-20%)
2022-12-1v0.14.0-betaCPU Utilization0.01-2%1-3% +1%
GC % Time< 1%< 1%+0%
Throughput37,360 req/sec36,509 req/sec-2k (-2.3%)

Max Throughput Test

A simple test flooding the server with an ever-increasing amount of traffic until it begins to deny requests or fails completely. The throughput of each loading client is summed just prior to failure and recorded as the max throughput.

Results

DateVersionMetricGraphQL Query
2022-12-1v0.14.0-betaMax Throughput 57,919 req/sec *

* We ran out of client machines and could not generate any more load against the test server. At the time, the server process indicated 5% CPU utilization and less than 750mb of committed memory.

Subscription Event Load Test

Coming Soon

+ + + + \ No newline at end of file diff --git a/docs/reference/query-cache.html b/docs/reference/query-cache.html new file mode 100644 index 0000000..828d815 --- /dev/null +++ b/docs/reference/query-cache.html @@ -0,0 +1,24 @@ + + + + + +Query Caching | GraphQL ASP.NET + + + + +
+

Query Caching

When GraphQL ASP.NET parses a query, it generates a query plan that contains all the required data needed to execute the requested operation. For most queries this process is near instantaneous but in some particularly large queries it may take an extra moment to generate a full query plan. The query cache will help alleviate this bottleneck by caching a plan for a set period of time to skip the parsing and generation phases when completing a request.

The query cache makes a concerted effort to only cache plans that are truly unique and thus it will take a moment to analyze the incoming query to see if it identical to one that is already cached. For small queries the amount of time it takes to scrub the query text and look up a plan in the cache could be as long as reparsing the query (200-300μs).

Consider using the Query Cache only if:

  • Your application's individual query size is regularly more than 1000 characters in length
  • You make use of a lot of interface graph types and a lot of object graph types for each of those interfaces.
  • When Profiling reveals a bottleneck in the parsing phase of any given query.

Enabling the Query Cache

At startup, inject the query cache into the service collection. The cache itself is schema agnostic.

Startup Code
// Register the query cache BEFORE calling .AddGraphQL
services.AddGraphQLLocalQueryCache();

services.AddGraphQL();
note

Because a query plan contains function pointers and references to local graph types, the default query cache is restricted to being "in process" for a single server instance and does not scale out to redis or other similar technologies.

+ + + + \ No newline at end of file diff --git a/docs/reference/query-caching.md b/docs/reference/query-caching.md deleted file mode 100644 index c59830a..0000000 --- a/docs/reference/query-caching.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -id: query-cache -title: Query Caching -sidebar_label: Query Caching -sidebar_position: 8 ---- - -When GraphQL ASP.NET parses a query, it generates a query plan that contains all the required data needed to execute the requested operation. For most queries this process is near instantaneous but in some particularly large queries it may take an extra moment to generate a full query plan. The query cache will help alleviate this bottleneck by caching a plan for a set period of time to skip the parsing and generation phases when completing a request. - -The query cache makes a concerted effort to only cache plans that are truly unique and thus it will take a moment to analyze the incoming query to see if it identical to one that is already cached. For small queries the amount of time it takes to scrub the query text and look up a plan in the cache could be _as long_ as reparsing the query (200-300μs). - -Consider using the Query Cache only if: - -- Your application's individual query size is regularly more than 1000 characters in length -- You make use of a lot of interface graph types and a lot of object graph types for each of those interfaces. -- When [Profiling](../execution/metrics) reveals a bottleneck in the `parsing` phase of any given query. - -## Enabling the Query Cache - -At startup, inject the query cache into the service collection. The cache itself is schema agnostic. - -```csharp title="Startup Code" -// Register the query cache BEFORE calling .AddGraphQL -// highlight-next-line -services.AddGraphQLLocalQueryCache(); - -services.AddGraphQL(); -``` - -:::note -Because a query plan contains function pointers and references to local graph types, the default query cache is restricted to being "in process" for a single server instance and does not scale out to redis or other similar technologies. -::: diff --git a/docs/reference/schema-configuration.html b/docs/reference/schema-configuration.html new file mode 100644 index 0000000..f4c64ae --- /dev/null +++ b/docs/reference/schema-configuration.html @@ -0,0 +1,25 @@ + + + + + +Schema Configuration | GraphQL ASP.NET + + + + +
+

Schema Configuration

This document contains a list of various configuration settings available during schema configuration. All options are added as part of the .AddGraphQL() method used at startup.

Adding Schema Configuration Options
services.AddGraphQL(schemaOptions =>
{
// *************************
// CONFIGURE YOUR SCHEMA HERE
// *************************
});


// Be sure to add graphql to the ASP.NET pipeline builder
appBuilder.UseGraphQL();

Builder Options

AddAssembly

// usage examples
schemaOptions.AddAssembly(assembly);

The runtime will scan the referenced assembly and auto-add any found required entities (controllers, types, enums, directives etc.) to the schema.

AddSchemaAssembly

// usage examples
schemaOptions.AddSchemaAssembly();

When declaring a new schema with .AddGraphQL<TSchema>(), the runtime will scan the assembly where TSchema is declared and auto-add any found required entities (controllers, types, enums, directives etc.) to the schema.

This method has no effect when using AddGraphQL().

AddType*

Multiple Options: AddGraphType, AddController, AddDirective, AddType

// usage examples
schemaOptions.AddGraphType<Donut>();
schemaOptions.AddController<BakeryController>();

Adds a single entity of a given type the schema. Use these methods to add individual graph types, directives or controllers. AddType acts a catch all and will try to infer the expected action to take against the supplied type. The other entity-specific methods will throw an exception should an unqualified type be supplied. For example, trying to supply a controller to .AddGraphType() will result in an exception.

ApplyDirective

schemaOptions.ApplyDirective("@deprecated")
.WithArguments("The name field is deprecated.")
.ToItems(schemaItem => schemaItem.IsGraphField<Person>("name"));

Allows for the runtime registration of a type system directive to a given schema item.

See the section on directives for complete details on how to use this method.

AutoRegisterLocalEntities

// usage examples
schemaOptions.AutoRegisterLocalEntities = true;
Default ValueAcceptable Values
truetrue, false

When true, the graph entities (controllers, types, enums etc.) that are declared in the startup assembly for the application are automatically registered to the schema. Typically this is your API project where Startup.cs or Program.cs is declared.

Authorization Options

Method

// usage examples
schemaOptions.AuthorizationOptions.Method = AuthorizationMethod.PerField;
Default ValueAcceptable Values
nullPerField, PerRequest

Controls how the graphql execution pipeline will authorize a request.

  • PerField: Each field of a query is evaluated individually allowing a data response to be generated that includes data the user can access and null values for those fields the user cannot access. Any unauthorized fields will also register an error in the response.

  • PerRequest: All fields of a query are validated BEFORE execution. Each field is validated individually, using its own authorization and authentication requirements. If the current user does not have access to 1 or more requested fields the entire request is denied and an error message generated.

See Subscription Security for additional considerations regarding authorization and subscriptions.

Declaration Options

AllowedOperations

// usage examples
schemaOptions.DeclarationOptions.AllowedOperations.Remove(GraphOperationType.Mutation);
Default ValueAcceptable Values
Query, MutationQuery, Mutatation, Subscription

Controls which top level operations are available on your schema. In general, this property is managed internally and you do not need to alter it. An operation not in the list will not be configured at start up.

Subscriptions are automatically added when the subscription library is added via .AddSubscriptions().

DisableIntrospection

// usage examples
schemaOptions.DeclarationOptions.DisableIntrospection = false;
Default ValueAcceptable Values
falsetrue, false

When true, any attempts to perform an introspection query will fail, preventing exposure of type meta data.

Note: Many tools, IDEs and client libraries not work if you disable introspection data.

FieldDeclarationRequirements

// usage examples
schemaOptions.DeclarationOptions.FieldDeclarationRequirements = TemplateDeclarationRequirements.Default;
Default ValueAcceptable Values
TemplateDeclarationRequirements.Defaultall enum values

Indicates to the runtime which fields and values of POCO classes must be explicitly declared for them to be added to a schema.

By default:

  • All values declared on an enum will be included.
  • All properties of POCOs and interfaces will be included.
  • All methods of POCOs and interfaces will NOT be included.

NOTE: Controller and Directive action methods are not effected by this setting.

GraphNamingFormatter

// usage examples
schemaOptions.DeclarationOptions.GraphNamingFormatter = new GraphNameFormatter(...);

An object that will format any string to an acceptable name for use in the graph.

Entity TypeDefault FormatExamples
Graph Type NamesPascal CasingDonut, BigHorse, SpeakerSystem
Field NamesCamel Casingflavor, minWidth, firstName
Enum ValuesAll CapsCHOCOLATE, FEET, PHONE_NUMBER

Default formats for the three different entity types

tip

To make radical changes to your name formats, beyond the available options, inherit from GraphNameFormatter and override the different formatting methods.

Execution Options

DebugMode

// usage examples
schemaOptions.ExecutionOptions.DebugMode = false;
Default ValueAcceptable Values
falsetrue, false

When true, each field and each list member of each field will be executed sequentially with no parallelization. All asynchronous methods will be individually awaited and allowed to throw immediately. A single encountered exception will halt the entire query process. This can be very helpful in preventing a jumping debug cursor. This option will greatly impact performance and can cause inconsistent query results if used in production. It should only be enabled for debugging.

EnableMetrics

// usage examples
schemaOptions.ExecutionOptions.EnableMetrics = false;
Default ValueAcceptable Values
falsetrue, false

When true, metrics and query profiling will be enabled for all queries processed for a given schema.

Note: This option DOES NOT control if those metrics are sent to the query requestor, just that they are generated. See ExposeMetrics in the response options for that switch.

MaxQueryComplexity

// usage examples
schemaOptions.ExecutionOptions.MaxQueryComplexity = 50.0f;
Default ValueAcceptable Values
-not set-Float Greater Than 0

The maximum allowed complexity value of a query. If a query is scored higher than this value it will be rejected.

MaxQueryDepth

// usage examples
schemaOptions.ExecutionOptions.MaxQueryDepth = 15;
Default ValueAcceptable Values
-not set-Integer Greater than 0

The maximum allowed field depth of any child field within a given query. If a query contains a child that is nested deeper than this value the query will be rejected.

QueryTimeout

// usage examples
schemaOptions.ExecutionOptions.QueryTimeout = TimeSpan.FromMinutes(2);
Default ValueAcceptable Values
-not set-> 10 milliseconds

The amount of time an individual query will be given to run before being abandoned and canceled by the runtime. By default, the timeout is disabled and a query will continue to execute as long as the underlying HTTP request is also executing. The minimum allowed amount of time for a query to run is 10ms.

ResolverIsolation

// usage examples
schemaOptions.ExecutionOptions.ResolverIsolation = ResolverIsolationOptions.ControllerActions | ResolverIsolation.Properties;
Default Value
ResolverIsolationOptions.None

Resolver types identified in ResolverIsolation are guaranteed to be executed independently. This is different than DebugMode. In debug mode a single encountered error will end the request whereas errors encountered in isolated resolvers will still be aggregated. This allows the returning partial results which can be useful in some use cases.

Response Options

AppendServerHeader

// usage examples
schemaOptions.ResponseOptions.AppendServerHeader = true;
Default ValueAcceptable Values
truetrue, false

When true, an X-GraphQL-AspNet-Server header with the current library version (e.g. v1.0.1) is added to the outgoing response. This option has no effect when a custom HttpProcessorType is declared.

ExposeExceptions

// usage examples
schemaOptions.ResponseOptions.ExposeExceptions = false;
Default ValueAcceptable Values
falsetrue, false

When true, exception details including message, type and stack trace will be sent to the requestor as part of any error messages.

WARNING

Setting this value to true can expose sensitive server details and may be considered a security risk.

ExposeMetrics

// usage examples
schemaOptions.ResponseOptions.ExposeMetrics = false;
Default ValueAcceptable Values
falsetrue, false

When true, the full set of metrics gathered when a query is executed is sent to the requestor. This value is disregarded unless ExecutionOptions.EnableMetrics is set to true.

Note: Metrics data for large queries can be quite expansive; double or tripling the size of the json data returned.

IndentDocument

// usage examples
schemaOptions.ResponseOptions.IndentDocument = true;
Default ValueAcceptable Values
truetrue, false

When true, the default json response writer will indent and "pretty up" the output response to make it more human-readable. Turning off this setting can result in a smaller output response.

MessageSeverityLevel

// usage examples
schemaOptions.ResponseOptions.MessageSeverityLevel = GraphMessageSeverity.Information;
Default ValueAcceptable Values
Information-any GraphMessageSeverity value-

Indicates which messages generated during a query should be sent to the requestor. Any message with a severity level equal to or greater than the provided level will be delivered.

Message Severity Levels

ValueRank

|

TimeStampLocalizer

// usage examples
schemaOptions.ResponseOptions.TimeStampLocalizer = (dtos) => dtos.DateTime;
Default ValueAcceptable Value
nullFunc<DateTimeOffset, DateTime>

A function to convert any system-provided timestamp values present in the output into a value of a given timezone. By default, no localization occurs and all times are delivered in their native UTC-0 format. This localizer does not effect any query field date values. Only those related to internal messaging (e.g. message creation dates, start and stop times for query metrics etc.) are effected.

QueryHandler Options

AuthenticatedRequestsOnly

// usage examples
schemaOptions.QueryHandler.AuthenticatedRequestsOnly = false;
Default ValueAcceptable Values
falsetrue, false

When true, only those requests that are successfully authenticated by the ASP.NET runtime will be passed to GraphQL. Should an unauthenticated request make it to the graphql query processor it will be immediately rejected.

note

This setting acts as a short cut to assigning custom HttpProcessorType. If you provide your own custom HttpProcessorType this setting has no effect.

DisableDefaultRoute

// usage examples
schemaOptions.QueryHandler.DisableDefaultRoute = false;
Default ValueAcceptable Values
falsetrue, false

When set to true the default route and http query processor will NOT be registered with the ASP.NET runtime when the application starts. GraphQL queries will not be processed unless manually invoked.

HttpProcessorType

// usage examples
schemaOptions.QueryHandler.HttpProcessorType = typeof(MyProcessorType);
Default Value
null

When set to a System.Type, GraphQL will attempt to load the provided type from the configured DI container in order to handle graphql requests. Any class wishing to act as an Http Processor must implement IGraphQLHttpProcessor<TSchema>.

tip

It can be easier to extend DefaultGraphQLHttpProcessor<TSchema> instead of implementing the interface from scratch if you only need to make minor changes.

Route

// usage examples
schemaOptions.QueryHandler.Route = "/graphql";
Default Value
/graphql

Represents the REST end point where GraphQL will listen for new POST and GET requests. In multi-schema configurations this value will need to be unique per schema type.

Subscription Server Options

These options are available to configure a subscription server for a given schema via .AddSubscriptions(subscriptionOptions)

Adding Subscription Configuration Options
services.AddGraphQL()
.AddSubscriptions(subscriptionOptions =>
{
// *************************
// CONFIGURE YOUR SUBSCRIPTION
// OPTIONS HERE
// *************************
});


// Be sure to add graphql to the ASP.NET pipeline builder
appBuilder.UseGraphQL();

AuthenticatedRequestsOnly

// usage examples
subscriptionOptions.AuthenticatedRequestsOnly = false;
Default ValueAcceptable Values
falsetrue, false

When true, only requests that are successfully authenticated by the ASP.NET runtime will be passed to GraphQL and registered as a subscription client. Connections with unauthenticated sources are immediately closed.

ConnectionKeepAliveInterval

The interval at which the subscription server will send a protocol-specific message to a connected graphql client informing it the connection is still open.

// usage examples
subscriptionOptions.ConnectionKeepAliveInterval = TimeSpan.FromMinutes(2);
Default Value
2 minutes
tip

This is an application level keep-alive supported by most graphql messaging protocols. This is a different keep-alive than the web socket specific keep alive provided by ASP.NET

ConnectionInitializationTimeout

When supported by a messaging protocol, represents a timeframe after the connection is initiated in which a successful initialization handshake must occur.

// usage examples
subscriptionOptions.ConnectionInitializationTimeout = TimeSpan.FromSeconds(30);
Default Value
30 seconds

Note: Not all messaging protocols require an explicit timeframe or support an inititalization handshake.

DefaultMessageProtocol

When set, represents a valid and supported messaging protocol that a client should use if it does not specify which protocols it can communicate in.

// usage examples
subscriptionOptions.DefaultMessageProtocol = "my-custom-protocol";
Default Value
null

Note: By default, this value is not set and connected clients MUST supply a prioritized protocol list.

DisableDefaultRoute

// usage examples
subscriptionOptions.DisableDefaultRoute = false;
Default ValueAcceptable Values
false true, false

When true, GraphQL will not register a component to listen for web socket requests. You must handle the acceptance of web sockets yourself and provision client proxies that can interact with the runtime. If you wish to implement your own web socket middleware handler, viewing DefaultGraphQLHttpSubscriptionMiddleware<TSchema> may help.

HttpMiddlewareComponentType

When set, represents the custom middleware component GraphQL will inject into the ASP.NET pipeline to intercept new web socket connection requests.

// usage examples
subscriptionOptions.HttpMiddlewareComponentType = typeof(MyMiddleware);
Default Value
null

When null, DefaultGraphQLHttpSubscriptionMiddleware<TSchema> is used.

RequireAuthenticatedConnection

Deteremines if a web socket request will be accepted in an unauthenticated state or not.

// usage examples
subscriptionOptions.RequiredAuthenticatedConnection = false;
Default ValueAcceptable Values
false true, false

When set to true, the subscription middleware will immediately reject any websocket requests from un-authenticated sources. This option does not include query authorization (i.e. can the user access the fields they are requesting). That occurs after the websocket is established.

When set to false, the subscription middleware will initially accept all web socket requests.

Route

Similar to the query/mutation query handler route this represents the path the default subscription middleware will look for when accepting new web sockets.

// usage examples
subscriptionOptions.Route = "/graphql";
Default Value
/graphql

Represents the http end point where GraphQL will listen for new web socket requests. In multi-schema configurations this value will need to be unique per schema type.

info

Your subscriptions can share the same route as your general queries for a schema or be different, its up to you.

SupportedMessageProtocols

When populated, represents a list of messaging protocol keys supported by this schema. A connected client MUST be able to communicate in one of the approved +values or it will be dropped.

// usage examples
var myProtocols = new Hashset<string>();
myProtocols.Add("protocol1");
myProtocols.Add("protocol2");
serverOptions.SupportedMessageProtocols = myProtocols;
Default Value
null

By default, SupportedMessageProtocols is null; meaning any server supported protocol will be usable by the target schema. If set to an empty set, then the schema is effectively disabled as no supported protocols will be matched.

+ + + + \ No newline at end of file diff --git a/docs/reference/schema-configuration.md b/docs/reference/schema-configuration.md deleted file mode 100644 index 8e24b61..0000000 --- a/docs/reference/schema-configuration.md +++ /dev/null @@ -1,576 +0,0 @@ ---- -id: schema-configuration -title: Schema Configuration -sidebar_label: Schema Configuration -sidebar_position: 1 ---- - -This document contains a list of various configuration settings available during schema configuration. All options are added as part of the `.AddGraphQL()` method used at startup. - -```csharp title="Adding Schema Configuration Options" -services.AddGraphQL(schemaOptions => -{ - // ************************* - // CONFIGURE YOUR SCHEMA HERE - // ************************* -}); - - -// Be sure to add graphql to the ASP.NET pipeline builder -appBuilder.UseGraphQL(); -``` - -## Builder Options - -### AddAssembly - -```csharp -// usage examples -schemaOptions.AddAssembly(assembly); -``` - -The runtime will scan the referenced assembly and auto-add any found required entities (controllers, types, enums, directives etc.) to the schema. - - -### AddSchemaAssembly - -```csharp -// usage examples -schemaOptions.AddSchemaAssembly(); -``` - -When declaring a new schema with .`AddGraphQL()`, the runtime will scan the assembly where `TSchema` is declared and auto-add any found required entities (controllers, types, enums, directives etc.) to the schema. - -This method has no effect when using `AddGraphQL()`. - -### AddType* - -Multiple Options: `AddGraphType`, `AddController`, `AddDirective`, `AddType` - -```csharp -// usage examples -schemaOptions.AddGraphType(); -schemaOptions.AddController(); -``` -Adds a single entity of a given type the schema. Use these methods to add individual graph types, directives or controllers. `AddType` acts a catch all and will try to infer the expected action to take against the supplied type. The other entity-specific methods will throw an exception should an unqualified type be supplied. For example, trying to supply a controller to `.AddGraphType()` will result in an exception. - -### ApplyDirective - -```csharp -schemaOptions.ApplyDirective("@deprecated") - .WithArguments("The name field is deprecated.") - .ToItems(schemaItem => schemaItem.IsGraphField("name")); -``` - -Allows for the runtime registration of a type system directive to a given schema item. - ->See the section on [directives](../advanced/directives.md#using-schema-options) for complete details on how to use this method. - -### AutoRegisterLocalEntities -```csharp -// usage examples -schemaOptions.AutoRegisterLocalEntities = true; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `true` | `true`, `false` | - -When true, the graph entities (controllers, types, enums etc.) that are declared in the startup assembly for the application are automatically registered to the schema. Typically this is your API project where `Startup.cs` or `Program.cs` is declared. - -## Authorization Options - -### Method -```csharp -// usage examples -schemaOptions.AuthorizationOptions.Method = AuthorizationMethod.PerField; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `null` | `PerField`, `PerRequest` | - -Controls how the graphql execution pipeline will authorize a request. - -* `PerField`: Each field of a query is evaluated individually allowing a data response to be generated that includes data the user can access and `null` values for those fields the user cannot access. Any unauthorized fields will also register an error in the response. - -* `PerRequest`: All fields of a query are validated BEFORE execution. Each field is validated individually, using its own authorization and authentication requirements. If the current user does not have access to 1 or more requested fields the entire request is denied and an error message generated. - -> See [Subscription Security](../advanced/subscriptions#security--query-authorization) for additional considerations regarding authorization and subscriptions. - -## Declaration Options - -### AllowedOperations - -```csharp -// usage examples -schemaOptions.DeclarationOptions.AllowedOperations.Remove(GraphOperationType.Mutation); -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `Query, Mutation` | `Query`, `Mutatation`, `Subscription` | - -Controls which top level operations are available on your schema. In general, this property is managed internally and you do not need to alter it. An operation not in the list will not be configured at start up. - -> Subscriptions are automatically added when the subscription library is added via `.AddSubscriptions()`. - - -### DisableIntrospection -```csharp -// usage examples -schemaOptions.DeclarationOptions.DisableIntrospection = false; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `false` | `true`, `false` | - -When `true`, any attempts to perform an introspection query will fail, preventing exposure of type meta data. - -> Note: Many tools, IDEs and client libraries not work if you disable introspection data. - -### FieldDeclarationRequirements -```csharp -// usage examples -schemaOptions.DeclarationOptions.FieldDeclarationRequirements = TemplateDeclarationRequirements.Default; -``` - -| Default Value | Acceptable Values | -| ----------------------------------------- | ----------------- | -| `TemplateDeclarationRequirements.Default` | _all enum values_ | - -Indicates to the runtime which fields and values of POCO classes must be explicitly declared for them to be added to a schema. - -By default: - -- All values declared on an `enum` **will be** included. -- All properties of POCOs and interfaces **will be** included. -- All methods of POCOs and interfaces **will NOT be** included. - -> NOTE: Controller and Directive action methods are not effected by this setting. - -### GraphNamingFormatter - -```csharp -// usage examples -schemaOptions.DeclarationOptions.GraphNamingFormatter = new GraphNameFormatter(...); -``` - -An object that will format any string to an acceptable name for use in the graph. - -| Entity Type | Default Format | Examples | -| ---------------- | -------------- | ------------------------------------ | -| Graph Type Names | Pascal Casing | `Donut`, `BigHorse`, `SpeakerSystem` | -| Field Names | Camel Casing | `flavor`, `minWidth`, `firstName` | -| Enum Values | All Caps | `CHOCOLATE`, `FEET`, `PHONE_NUMBER` | -_Default formats for the three different entity types_ - -:::tip - To make radical changes to your name formats, beyond the available options, inherit from `GraphNameFormatter` and override the different formatting methods. -::: - -## Execution Options - -### DebugMode - -```csharp -// usage examples -schemaOptions.ExecutionOptions.DebugMode = false; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `false` | `true`, `false` | - -When true, each field and each list member of each field will be executed sequentially with no parallelization. All asynchronous methods will be individually awaited and allowed to throw immediately. A single encountered exception will halt the entire query process. This can be very helpful in preventing a jumping debug cursor. This option will greatly impact performance and can cause inconsistent query results if used in production. It should only be enabled for [debugging](../development/debugging). - - -### EnableMetrics -```csharp -// usage examples -schemaOptions.ExecutionOptions.EnableMetrics = false; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `false` | `true`, `false` | - -When true, metrics and query profiling will be enabled for all queries processed for a given schema. - -> Note: This option DOES NOT control if those metrics are sent to the query requestor, just that they are generated. See [ExposeMetrics](./schema-configuration#exposemetrics) in the response options for that switch. - -### MaxQueryComplexity - -```csharp -// usage examples -schemaOptions.ExecutionOptions.MaxQueryComplexity = 50.0f; -``` - -| Default Value | Acceptable Values | -| ------------- | -------------------- | -| -_not set_- | Float Greater Than 0 | - -The maximum allowed [complexity](../execution/malicious-queries#query-complexity) value of a query. If a query is scored higher than this value it will be rejected. - -### MaxQueryDepth - -```csharp -// usage examples -schemaOptions.ExecutionOptions.MaxQueryDepth = 15; -``` - -| Default Value | Acceptable Values | -| ------------- | ---------------------- | -| -_not set_- | Integer Greater than 0 | - -The maximum allowed [field depth](../execution/malicious-queries#maximum-allowed-field-depth) of any child field within a given query. If a query contains a child that is nested deeper than this value the query will be rejected. - - -### QueryTimeout - -```csharp -// usage examples -schemaOptions.ExecutionOptions.QueryTimeout = TimeSpan.FromMinutes(2); -``` - -| Default Value | Acceptable Values | -| ------------- | -------------------------- | -| -_not set_- | > 10 milliseconds | - -The amount of time an individual query will be given to run before being abandoned and canceled by the runtime. By default, the timeout is disabled and a query will continue to execute as long as the underlying HTTP request is also executing. The minimum allowed amount of time for a query to run is 10ms. - -### ResolverIsolation - -```csharp -// usage examples -schemaOptions.ExecutionOptions.ResolverIsolation = ResolverIsolationOptions.ControllerActions | ResolverIsolation.Properties; -``` - -| Default Value | -| ------------- | -| `ResolverIsolationOptions.None` | - -Resolver types identified in `ResolverIsolation` are guaranteed to be executed independently. This is different than `DebugMode`. In debug mode a single encountered error will end the request whereas errors encountered in isolated resolvers will still be aggregated. This allows the returning partial results which can be useful in some use cases. - -## Response Options - -### AppendServerHeader - -```csharp -// usage examples -schemaOptions.ResponseOptions.AppendServerHeader = true; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `true` | `true`, `false` | - -When true, an `X-GraphQL-AspNet-Server` header with the current library version (e.g. `v1.0.1`) is added to the outgoing response. This option has no effect when a custom `HttpProcessorType` is declared. - - -### ExposeExceptions - -```csharp -// usage examples -schemaOptions.ResponseOptions.ExposeExceptions = false; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `false` | `true`, `false` | - -When true, exception details including message, type and stack trace will be sent to the requestor as part of any error messages. - -:::caution WARNING -Setting this value to true can expose sensitive server details and may be considered a security risk. -::: - -### ExposeMetrics - -```csharp -// usage examples -schemaOptions.ResponseOptions.ExposeMetrics = false; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `false` | `true`, `false` | - -When true, the full set of metrics gathered when a query is executed is sent to the requestor. This value is disregarded unless `ExecutionOptions.EnableMetrics` is set to true. - -> Note: Metrics data for large queries can be quite expansive; double or tripling the size of the json data returned. - -### IndentDocument - -```csharp -// usage examples -schemaOptions.ResponseOptions.IndentDocument = true; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `true` | `true`, `false` | - -When true, the default json response writer will indent and "pretty up" the output response to make it more human-readable. Turning off this setting can result in a smaller output response. - -### MessageSeverityLevel - -```csharp -// usage examples -schemaOptions.ResponseOptions.MessageSeverityLevel = GraphMessageSeverity.Information; -``` -| Default Value | Acceptable Values | -| ---------------------------------- | -------------------------------------- | -| `Information` | \-_any `GraphMessageSeverity` value_\- | - -Indicates which messages generated during a query should be sent to the requestor. Any message with a [severity level](https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet/Execution/GraphMessageSeverity.cs) equal to or greater than the provided level will be delivered. - -#### Message Severity Levels - -|Value | Rank | -|--------|--------| -| - -### TimeStampLocalizer - -```csharp -// usage examples -schemaOptions.ResponseOptions.TimeStampLocalizer = (dtos) => dtos.DateTime; -``` - -|Default Value | Acceptable Value | -| -------------|--------------------------------- | -|_`null`_ | `Func` | - -A function to convert any system-provided timestamp values present in the output into a value of a given timezone. By default, no localization occurs and all times are delivered in their native `UTC-0` format. This localizer does not effect any query field date values. Only those related to internal messaging (e.g. message creation dates, start and stop times for query metrics etc.) are effected. - -## QueryHandler Options - -### AuthenticatedRequestsOnly - -```csharp -// usage examples -schemaOptions.QueryHandler.AuthenticatedRequestsOnly = false; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `false` | `true`, `false` | - -When true, only those requests that are successfully authenticated by the ASP.NET runtime will be passed to GraphQL. Should an unauthenticated request make it to the graphql query processor it will be immediately rejected. - -:::note - This setting acts as a short cut to assigning custom HttpProcessorType. If you provide your own custom [`HttpProcessorType`](#httpprocessortype) this setting has no effect. -::: - - -### DisableDefaultRoute - -```csharp -// usage examples -schemaOptions.QueryHandler.DisableDefaultRoute = false; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `false` | `true`, `false` | - -When set to true the default route and http query processor will **NOT** be registered with the ASP.NET runtime when the application starts. GraphQL queries will not be processed unless manually invoked. - - -### HttpProcessorType - -```csharp -// usage examples -schemaOptions.QueryHandler.HttpProcessorType = typeof(MyProcessorType); -``` - -| Default Value | -| ------------- | -| `null` | - -When set to a `System.Type`, GraphQL will attempt to load the provided type from the configured DI container in order to handle graphql requests. Any class wishing to act as an Http Processor must implement `IGraphQLHttpProcessor`. - -:::tip -It can be easier to extend `DefaultGraphQLHttpProcessor` instead of implementing the interface from scratch if you only need to make minor changes. -::: - -### Route - -```csharp -// usage examples -schemaOptions.QueryHandler.Route = "/graphql"; -``` - -| Default Value | -| ------------- | -| `/graphql` | - -Represents the REST end point where GraphQL will listen for new POST and GET requests. In multi-schema configurations this value will need to be unique per schema type. - -## Subscription Server Options -These options are available to configure a subscription server for a given schema via `.AddSubscriptions(subscriptionOptions)` - -```csharp title="Adding Subscription Configuration Options" -services.AddGraphQL() - .AddSubscriptions(subscriptionOptions => - { - // ************************* - // CONFIGURE YOUR SUBSCRIPTION - // OPTIONS HERE - // ************************* - }); - - -// Be sure to add graphql to the ASP.NET pipeline builder -appBuilder.UseGraphQL(); -``` - -### AuthenticatedRequestsOnly - -```csharp -// usage examples -subscriptionOptions.AuthenticatedRequestsOnly = false; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `false` | `true`, `false` | - -When true, only requests that are successfully authenticated by the ASP.NET runtime will be passed to GraphQL and registered as a subscription client. Connections with unauthenticated sources are immediately closed. - -### ConnectionKeepAliveInterval - -The interval at which the subscription server will send a protocol-specific message to a connected graphql client informing it the connection is still open. - -```csharp -// usage examples -subscriptionOptions.ConnectionKeepAliveInterval = TimeSpan.FromMinutes(2); -``` - -| Default Value | -| ------------- | -| `2 minutes` | - -:::tip -This is an application level keep-alive supported by most graphql messaging protocols. This is a different keep-alive than the web socket specific keep alive provided by ASP.NET -::: - - -### ConnectionInitializationTimeout - -When supported by a messaging protocol, represents a timeframe after the connection is initiated in which a successful initialization handshake must occur. - -```csharp -// usage examples -subscriptionOptions.ConnectionInitializationTimeout = TimeSpan.FromSeconds(30); -``` - -| Default Value | -| ------------- | -| `30 seconds` | - - -> Note: Not all messaging protocols require an explicit timeframe or support an inititalization handshake. - -### DefaultMessageProtocol - -When set, represents a valid and supported messaging protocol that a client should use if it does not specify which protocols it can communicate in. - -```csharp -// usage examples -subscriptionOptions.DefaultMessageProtocol = "my-custom-protocol"; -``` - -| Default Value | -| ------------- | -| `null` | - -> Note: By default, this value is not set and connected clients **MUST** supply a prioritized protocol list. - -### DisableDefaultRoute - -```csharp -// usage examples -subscriptionOptions.DisableDefaultRoute = false; -``` -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `false ` | `true`, `false` | - -When true, GraphQL will not register a component to listen for web socket requests. You must handle the acceptance of web sockets yourself and provision client proxies that can interact with the runtime. If you wish to implement your own web socket middleware handler, viewing [DefaultGraphQLHttpSubscriptionMiddleware<TSchema>](https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet-subscriptions/Engine/DefaultGraphQLHttpSubscriptionMiddleware.cs) may help. - - - -### HttpMiddlewareComponentType - -When set, represents the custom middleware component GraphQL will inject into the ASP.NET pipeline to intercept new web socket connection requests. - -```csharp -// usage examples -subscriptionOptions.HttpMiddlewareComponentType = typeof(MyMiddleware); -``` - -| Default Value | -| ------------- | -| `null` | - -When null, `DefaultGraphQLHttpSubscriptionMiddleware` is used. - -### RequireAuthenticatedConnection - -Deteremines if a web socket request will be accepted in an unauthenticated state or not. - -```csharp -// usage examples -subscriptionOptions.RequiredAuthenticatedConnection = false; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `false ` | `true`, `false` | - -When set to true, the subscription middleware will immediately reject any websocket requests from un-authenticated sources. This option does not include query authorization (i.e. can the user access the fields they are requesting). That occurs after the websocket is established. - -When set to false, the subscription middleware will initially accept all web socket requests. - - -### Route - -Similar to the query/mutation query handler route this represents the path the default subscription middleware will look for when accepting new web sockets. - -```csharp -// usage examples -subscriptionOptions.Route = "/graphql"; -``` - -| Default Value | -| ------------- | -| `/graphql` | - - -Represents the http end point where GraphQL will listen for new web socket requests. In multi-schema configurations this value will need to be unique per schema type. - -:::info -Your subscriptions can share the same route as your general queries for a schema or be different, its up to you. -::: - -### SupportedMessageProtocols - -When populated, represents a list of messaging protocol keys supported by this schema. A connected client MUST be able to communicate in one of the approved -values or it will be dropped. - -```csharp -// usage examples -var myProtocols = new Hashset(); -myProtocols.Add("protocol1"); -myProtocols.Add("protocol2"); -serverOptions.SupportedMessageProtocols = myProtocols; -``` - -| Default Value | -| ------------- | -| `null` | - -> By default, `SupportedMessageProtocols` is null; meaning any server supported protocol will be usable by the target schema. If set to an empty set, then the schema is effectively disabled as no supported protocols will be matched. diff --git a/docs/reference/vocabulary.html b/docs/reference/vocabulary.html new file mode 100644 index 0000000..cab0a2e --- /dev/null +++ b/docs/reference/vocabulary.html @@ -0,0 +1,24 @@ + + + + + +Vocabulary | GraphQL ASP.NET + + + + +
+

Vocabulary

Fields & Resolvers

In GraphQL terms, a field is any requested piece of data (such as an id or name). A resolver fulfills the request for data from a schema field. It takes in a set of input arguments and produces a piece of data that is returned to the client. In GraphQL ASP.NET your controller methods act as resolvers for top level fields in any query.

Graph Type

A graph type is an entity on your object graph; a droid, a donut, a string, a number etc. In GraphQL ASP.NET your model classes, interfaces, enums, controllers etc. are compiled into the various graph types required by the runtime.

Query Document

This is the raw query text submitted by a client. When GraphQL accepts a query it is converted from a string to an internal document format that is parsed and used to fulfill the request.

Queries, Mutations and Subscriptions are all types of query documents.

Root Graph Types

There are three root graph types in GraphQL: Query, Mutation, Subscription. Whenever you make a graphql request, you always specify which query root you are targeting. This documentation will usually refer to all operations as "queries" but this includes mutations and subscriptions as well.

Schema

This is the set of public data types, their fields, input arguments etc. that are exposed on an object graph. When you write a graphql query to return data, the fields you request must all be defined on a schema that graphql will validate your query against.

Your schema is "generated" at runtime by analyzing your model classes, controllers and action methods then populating a GraphSchema container with the appropriate graph types to map graphql requests to your controllers.

note

In GraphQL ASP.NET the schema is generated at runtime directly from your C# controllers and POCOs; there is no additional boilerplate code necessary to define a schema.

+ + + + \ No newline at end of file diff --git a/docs/reference/vocabulary.md b/docs/reference/vocabulary.md deleted file mode 100644 index b276047..0000000 --- a/docs/reference/vocabulary.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -id: vocabulary -title: Vocabulary -sidebar_label: Vocabulary -sidebar_position: 11 ---- - -### Fields & Resolvers -In GraphQL terms, a field is any requested piece of data (such as an id or name). A resolver fulfills the request for data from a schema field. It takes in a set of input arguments and produces a piece of data that is returned to the client. In GraphQL ASP.NET your controller methods act as resolvers for top level fields in any query. - -### Graph Type -A graph type is an entity on your object graph; a droid, a donut, a string, a number etc. In GraphQL ASP.NET your model classes, interfaces, enums, controllers etc. are compiled into the various graph types required by the runtime. - -### Query Document -This is the raw query text submitted by a client. When GraphQL accepts a query it is converted from a string to an internal document format that is parsed and used to fulfill the request. - -> Queries, Mutations and Subscriptions are all types of query documents. - -### Root Graph Types -There are three root graph types in GraphQL: Query, Mutation, Subscription. Whenever you make a graphql request, you always specify which query root you are targeting. This documentation will usually refer to all operations as "queries" but this includes mutations and subscriptions as well. - - -### Schema -This is the set of public data types, their fields, input arguments etc. that are exposed on an object graph. When you write a graphql query to return data, the fields you request must all be defined on a schema that graphql will validate your query against. - -Your schema is "generated" at runtime by analyzing your model classes, controllers and action methods then populating a `GraphSchema` container with the appropriate graph types to map graphql requests to your controllers. - -:::note - In GraphQL ASP.NET the schema is generated at runtime directly from your C# controllers and POCOs; there is no additional boilerplate code necessary to define a schema. -::: \ No newline at end of file diff --git a/docs/server-extensions/_category_.json b/docs/server-extensions/_category_.json deleted file mode 100644 index 52eeb8f..0000000 --- a/docs/server-extensions/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Extensions", - "position": 8, - "collapsed": false -} \ No newline at end of file diff --git a/docs/server-extensions/multipart-requests.html b/docs/server-extensions/multipart-requests.html new file mode 100644 index 0000000..7103dfc --- /dev/null +++ b/docs/server-extensions/multipart-requests.html @@ -0,0 +1,26 @@ + + + + + +Multipart Form Request Extension | GraphQL ASP.NET + + + + +
+

Multipart Form Request Extension

.NET 8+

Multipart Request Specification

GraphQL ASP.NET provides built in support for batch query processing and file uploads via an implementation of the GraphQL Multipart Request Specification.

This extension requires a minimum version of v1.2.0 of the main library and you must target .NET 8 or later. This extension will not work with the .NET standard implementation.

info

This document covers how to submit a batch query and upload files that conform to the above specification. It provides sample curl requests that would be accepted for the given sample code but does not explain in detail the various form fields required to complete a request. It is highly recommended to use a supported client when enabling this server extension.

Enable The Extension

While the multipart form extension is shipped as part of the main library it is disabled by default and must be explicitly enabled on each schema.

Register the Server Extension
// Startup Code
// other code omitted for brevity
services.AddGraphQL(options => {
options.AddMultipartRequestSupport();
});

File Uploads

Files submitted on a post request are automatically routed to your controllers as a custom scalar. Out of the box, any .NET IFormFile and any form field not explicitly declared by the specification will be converted into a file scalar and can be mapped into your query's variables collection.

A Basic Controller

Files are received as a special C# class named FileUpload. Use it in your action methods like you would any other scalar (e.g. int, string etc.). Note that even though it is a class, as opposed to a primitative, GraphQL still handles it as a scalar; much in the same way Uri is also considered a scalar.

Warning: Be sure to dispose of the file stream when you are finished with it.

ExampleFile
public class FileUploadController : GraphController
{
[MutationRoot("singleFileUpload")]
public async Task<int> UploadFile(FileUpload fileRef)
{
using var stream = await fileRef.OpenFileAsync();
// do something with the file stream

return 0;
}
}

The scalar in your schema is named Upload per the specification. Be sure to declare your graphql variables as an Upload type to indicate an uploaded file.

Use the Upload graph type for variables
mutation ($file: Upload) { 
singleFileUpload(file: $file)
}
Sample curl Query
curl localhost:3000/graphql \
-F operations='{ "query": "mutation ($file: Upload) { singleFileUpload(file: $file) }", "variables": { "file": null } }' \
-F map='{ "0": ["variables.file"] }' \
-F 0=@a.txt

Handling Arrays of Files

Arrays of files work just like any other list in GraphQL. When declaring the map variable for the multi-part request, be sure +to indicate which index you are mapping the file to. The extension will not magically append files to an array. Each mapped file must explicitly declare the element index in an array where it is being placed.

Warning: Be sure to dispose of each file stream when you are finished with it.

Example File Upload Controller
using GraphQL.AspNet.ServerExtensions.MultipartRequests;

public class FileUploadController : GraphController
{
[MutationRoot("multiFileUpload")]
public async Task<int> UploadFile(IEnumerable<FileUpload> files)
{
foreach(var file in files)
{
using var stream = await fileRef.OpenFileAsync();
// do something with each file stream
}

return 0;
}
}
Declaring a list of files on a graphql query
mutation ($files: [Upload]) { 
multiFileUpload(file: $files)
}
Sample Curl
curl localhost:3000/graphql \
-F operations='{ "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", "variables": { "files": [null, null] } }' \
-F map='{ "firstFile": ["variables", "files", 0], "secondFile": ["variables", "files", 1] }' \
-F firstFile=@a.txt
-F secondFile=@b.txt

Handling an Unknown Number of Files

There are scenarios where you may ask your users to select a few files to upload without knowing how many they might choose. As long each declaration in your map field points to a position that could be a valid index, the target array will be resized accordingly.

Adding Two Files
curl localhost:3000/graphql \
-F operations='{
"query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }",
"variables": { "files": [] } }' \
-F map='{ "firstFile": ["variables", "files", 0], "secondFile": ["variables", "files", 1] }' \
-F firstFile=@a.txt
-F secondFile=@b.txt

In the above example, the files array will be automatically expanded to include indexes 0 and 1 as requested by the map:

Resultant Operations Object
{
"query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }",
"variables": { "files": [<firstFile>, <secondFile>] }
}

Skipping Array Indexes

If you skip any indexes in your map declaration, the target array will be expanded to to include the out of sequence index. This can produce null values in your array and result in an error if your variable declaration does not allow nulls.

Adding One File To Index 5
# Only one file is supplied but its mapped to index 5
# the final array at 'variables.files` will be 6 elements long with 5 null elements.
curl localhost:3000/graphql \
-F operations='{
"query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }",
"variables": { "files": [] } }' \
-F map='{ "firstFile": ["variables", "files", 5] }' \
-F firstFile=@a.txt
Resultant Operations Object
{
"query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }",
"variables": { "files": [null, null, null, null, null, <firstFile>] }
}

File Uploads on Batched Queries

File uploads work in conjunction with batched queries. When processing a multi-part request as a batch, prefix each of the mapped object-path references with an index of the batch you want the file to apply to. As you might guess this is usually handled by a supported client automatically.

Sample Query
curl localhost:3000/graphql \
-F operations='[
{ "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", "variables": { "files": [] } },
{ "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", "variables": { "files": [] } },
]' \
-F map='{ "firstFile": [0, "variables", "files", 0], "secondFile": [1, "variables", "files", 0] }' \
-F firstFile=@a.txt
-F secondFile=@b.txt

FileUpload Scalar

The following properties on the FileUpload C# class can be useful:

  • FileName - The name of the file that was uploaded. This property will be null if a non-file form field is referenced.
  • MapKey - The key value used to place this file within a variable collection. This is usually the form field name on the multi-part request.
  • ContentType - The supplied content-type value sent with the file. This value will be null for non-file fields.
  • Headers - A collection of all the headers provided with the uploaded file. This value will be null for non-file fields.

Opening a File Stream

When opening a file stream you need to await a call FileUpload.OpenFileAsync(). This method is an abstraction on top of an internal wrapper that standardizes file streams across all implementions (see below for implementing your own file processor). When working with the standard IFormFile interface provided by ASP.NET this call is a simple wrapper for IFormFile.OpenReadStream().

ExampleFile
using GraphQL.AspNet.ServerExtensions.MultipartRequests;

public class FileUploadController : GraphController
{
[MutationRoot("singleFileUpload")]
public async Task<int> UploadFile(FileUpload fileRef)
{
// do something with the file stream
// it is your responsibility to close and dispose of it
using var stream = await fileRef.OpenFileStreamAsync();

return 0;
}
}

Custom File Handling

By default, this extension splits the POST request on an HttpContext and presents the different parts to the query engine in a manner it expects. This means that any uploaded files are consumed under the hood as ASP.NET's built in IFormFile interface. While this is fine for most users, it can be troublesome with regard to timeouts and large file requests. Also, there may be scenarios where you want to save off files prior to executing a query or perhaps you'll need to process the file stream multiple times.

You can implement and register your own IFileUploadScalarValueMaker to add custom processing logic for each file or blob BEFORE graphql gets ahold of it. For instance, some users may want to write incoming files to local disk or cloud storage and present GraphQL with a stream that points to that local reference, rather than the file reference on the http request.

 public interface IFileUploadScalarValueMaker
{
// This overload is used when processing traditional files received as part of a
// multi-part form through ASP.NET's HttpContext
Task<FileUpload> CreateFileScalar(IFormFile aspNetFile);

// This overload is used when processing data received on a
// multi-part form field rather than as a formal file upload.
Task<FileUpload> CreateFileScalar(string mapKey, byte[] blobData);
}
Register Your Custom Value Maker
// other startup code omitted

// register your scalar value maker BEFORE calling .AddGraphQL
services.AddSingleton<IFileUploadScalarValueMaker, MyFileUploadScalarValueMaker>();

services.AddGraphQL(options => {
options.AddMultipartRequestSupport();
});
tip

You can inherit from FileUpload and extend it as needed on your custom maker. However, be sure to declare your method parameters as FileUpload in your controllers so that GraphQL knows what scalar you are requesting.

Take a look at the default upload scalar value maker for some helpful details when trying to implement your own.

Timeouts and File Uploads

Be mindful of any query timeouts you have set for your schemas. ASP.NET may start processing your query before all the file contents are made available to the server as long as it has the initial POST request. This also means that your graphql queries may start executing before the file contents arrive.

While this asysncronicty usually works to your advantage, allowing your queries to begin processing before all the files are uploaded to the server; you may find that your queries pause on .OpenFileStreamAsync() waiting for the file stream to become available if there is a network delay or a large file being uploaded. If you have a custom timeout configured for a schema, it may trigger while waiting for the file. Be sure to set your timeouts to a long enough period of time to avoid this scenario.

Batch Queries

Processing a Batch of Queries

Provide an "operations" form field that represents an array of graphql requests, the engine will automatically detect the array and return an array of responses in the same order as they were received. Each query is processed asyncronously and independently.

Example Batch Query
curl localhost:3000/graphql \
-F operations='[
{ "query": "query { findUser(lastName: \"Smith\") {firstName lastName} }" },
{ "query": "query { findUser(lastName: \"Jones\") {firstName lastName} }" },
]' \
Example Json Serialized Response
[
{
"data": {
"findUser": {
"firstName": "Baily",
"lastName": "Smith"
}
}
},
{
"data": {
"findUser": {
"firstName": "Caleb",
"lastName": "Jones"
}
}
}
]

Processing a Single Query

Provide an "operations" form field that represents a single query and the engine will automatically detect and return a normal graphql response.

Example Batch Query
curl localhost:3000/graphql \
-F operations='{ "query": "query { findUser(lastName: \"Smith\") {firstName lastName} }" }' \
Example Json Serialized Response
{
"data": {
"findUser": {
"firstName": "Baily",
"lastName": "Smith"
}
}
}
info

The extension is backwards compatible with standard graphql http request processing. If a request is recieved that is not a multi-part form POST request, normal graphql processing will occur.

Batch Execution Order is Never Guaranteed

While the order of the results is guaranteed to be the same order in which the queries were received, there is no guarantee that the queries are executed in any specific order. This means if you submit a batch of 5 requests, each requests may complete in a randomized order. If the same batch is submitted 3 times, its possible that the execution order will be different each time.

For queries this is usally not an issue, but if you are batching mutations, make sure you don't have any unexpected dependencies or side effects between queries. If your controllers perform business logic against an existing object and that object is modified by more than of your mutations its highly possible that the state of the object may be unexpectedly modified in some executions but not in others.

Take this controller and query:

Example Controller
public class FileUploadController : GraphController
{
[MutationRoot("addMoney")]
public async Task<Item> AddMoney(int itemId, int dollarsToAdd)
{
var item =await _service.RetrieveItem(itemId);
item.CurrentTotal += dollarsToAdd;

await _service.UpdateItem(item);
return item;
}
}
Example Batch Query
curl localhost:3000/graphql \
-F operations='[
{ "query": "mutation { addMoney(itemId: 34, dollarsToAdd: 5) {id currentTotal} }" },
{ "query": "mutation { addThreeDollars(itemId: 34, , dollarsToAdd: 3) {id currentTotal} }" },
]' \

Assuming that the initial value of currentTotal was 0, all three of these responses are equally likely to occur depending on the order in which the execution engine decides to process the queries.

Sample
// When the queries are executed in declared order
[
{
"data": {
"addMoney": {
"id": 34,
"currentTotal": 5
}
}
},
{
"data": {
"addMoney": {
"id": 34,
"currentTotal": 8
}
}
},
]

// When the queries are executed in reverse order
[
{
"data": {
"addMoney": {
"id": 34,
"currentTotal": 8
}
}
},
{
"data": {
"addMoney": {
"id": 34,
"currentTotal": 3
}
}
},
]

// When the queries are executed simultaniously
// The final result updated to the datastore is unknown
[
{
"data": {
"addMoney": {
"id": 34,
"currentTotal": 5
}
}
},
{
"data": {
"addMoney": {
"id": 34,
"currentTotal": 3
}
}
},
]

Under the hood, the batch process will parse and submit all queries to the engine simultaniously and wait for them to finish before structuring a result object.

caution

Ensure there are no dependencies between queries in a batch. An expected order of execution is never guaranteed.

Configuration

There are several configuration settings specific to extension. They can all be toggled when the extension is registered. Each configuration is specific +to the targeted schema.

Configuring the Server Extension
// Startup Code
// other code omitted for brevity
services.AddGraphQL(options => {
options.AddMultipartRequestSupport(mpOptions => {
// set mpOptions here
});
});

MapMode

// usage example
mpOptions.MapMode = MultipartRequestMapHandlingMode.Default;

A bitwise flag enumeration allowing the inclusion of different types of values for the map field dictated by the specification. Both options are enabled by default.

OptionDescription
AllowStringPathsWhen enabled, the short-hand syntax for object-path, which uses a dot-delimited string instead of an array to indicate a json path, is acceptable for a map value.
SplitDotDelimitedSingleElementArraysWhen enabled, the extension will examine single element arrays and, if that element is a string, treat it as a string, allowing it to be split as a dot-delimited string if the option is enabled. When not enabled, single element arrays are treated as a single path value.

RequestMode

// usage example
mpOptions.RequestMode = MultipartRequestMode.Default;

A bitwise flag enumeration that controls which parts of the multi-part request extension are enabled. By default, both batch queries and file uploads are enabled.

OptionDescription
FileUploadsWhen enabled, the server extension will process file uploads. When disabled, any included files or form fields treated as files will cause the request to be rejected.
BatchQueriesWhen enabled, the extension will attempt to process properly formatted batch queries. When disabled, any attempt to submit a batch query will cause the request to be rejected.

MaxFileCount

// usage example
mpOptions.MaxFileCount = 15;
Default ValueAcceptable Values
nullnull, number

When set, the extension will process, at most, the indicated amount of files. If more files appear on the request than the value indicated the request is automatically rejected. By default this value is set to null or no limit.

MaxBlobCount

// usage example
mpOptions.MaxBlobCount = 15;
Default ValueAcceptable Values
nullnull, number

When set, the extension will process, at most, the indicated amount of additional, non-spec form fields (e.g. additional text blobs). If more blobs appear on the request than the value indicated the request is automatically rejected. By default this value is set to null or no limit.

RegisterMultipartRequestHttpProcessor

// usage example
mpOptions.RegisterMultipartRequestHttpProcessor = true;
Default ValueAcceptable Values
truetrue, false

Determines if, when registering the extension, the default multipart http processor is registered. When set to true, the extension will attempt to replace any other registered http processor(i.e. the object that is handed an HttpContext via a route). When false, no processor is registered. You are expected to provide your own handling for multipart requests. The extension will always register its other required objects (the form parser, the custom scalar etc.).

RequireMultipartRequestHttpProcessor

// usage example
mpOptions.RequireMultipartRequestHttpProcessor = true;
Default ValueAcceptable Values
truetrue, false

Determines if, when starting up the application, the extension will check that the required http processor is registered. When set to true, if the required processor is not registered a configuration exception will be thrown and the server will fail to start. This can be helpful when registering multiple extensions to ensure that a valid processor is registered such that multipart-form requests will be handled correctly.

Demo Project

See the demo projects for a sample project utilizing jaydenseric's apollo-upload-client as a front end for performing file uploads against this extension.

+ + + + \ No newline at end of file diff --git a/docs/server-extensions/multipart-requests.md b/docs/server-extensions/multipart-requests.md deleted file mode 100644 index 9262df6..0000000 --- a/docs/server-extensions/multipart-requests.md +++ /dev/null @@ -1,514 +0,0 @@ ---- -id: multipart-requests -title: Multipart Form Request Extension -sidebar_label: File Uploads & Batching -sidebar_position: 0 ---- - -.NET 8+ - -## Multipart Request Specification -GraphQL ASP.NET provides built in support for batch query processing and file uploads via an implementation of the [GraphQL Multipart Request Specification](https://github.com/jaydenseric/graphql-multipart-request-spec). - -This extension requires a minimum version of `v1.2.0` of the main library and you must target .NET 8 or later. This extension will not work with the .NET standard implementation. - -:::info -This document covers how to submit a batch query and upload files that conform to the above specification. It provides sample curl requests that would be accepted for the given sample code but does not explain in detail the various form fields required to complete a request. It is highly recommended to use a [supported client](https://github.com/jaydenseric/graphql-multipart-request-spec#client) when enabling this server extension. -::: - -## Enable The Extension - -While the multipart form extension is shipped as part of the main library it is disabled by default and must be explicitly enabled on each schema. - -```csharp title='Register the Server Extension' -// Startup Code -// other code omitted for brevity -services.AddGraphQL(options => { - options.AddMultipartRequestSupport(); -}); -``` - -## File Uploads -Files submitted on a post request are automatically routed to your controllers as a custom scalar. Out of the box, any .NET `IFormFile` and any form field not explicitly declared by the specification will be converted into a file scalar and can be mapped into your query's variables collection. - -### A Basic Controller - -Files are received as a special C# class named `FileUpload`. Use it in your action methods like you would any other scalar (e.g. int, string etc.). Note that even though it is a class, as opposed to a primitative, GraphQL still handles it as a scalar; much in the same way `Uri` is also considered a scalar. - -Warning: Be sure to dispose of the file stream when you are finished with it. -
-
- -```csharp title=ExampleFile Upload Controller -public class FileUploadController : GraphController -{ - [MutationRoot("singleFileUpload")] - // highlight-next-line - public async Task UploadFile(FileUpload fileRef) - { - using var stream = await fileRef.OpenFileAsync(); - // do something with the file stream - - return 0; - } -} -``` - -The scalar in your schema is named `Upload` per the specification. Be sure to declare your graphql variables as an `Upload` type to indicate an uploaded file. - -```graphql title="Use the Upload graph type for variables" -mutation ($file: Upload) { - singleFileUpload(file: $file) -} -``` - -```bash title="Sample curl Query" -curl localhost:3000/graphql \ - # highlight-next-line - -F operations='{ "query": "mutation ($file: Upload) { singleFileUpload(file: $file) }", "variables": { "file": null } }' \ - -F map='{ "0": ["variables.file"] }' \ - -F 0=@a.txt -``` - -### Handling Arrays of Files - -Arrays of files work just like any other list in GraphQL. When declaring the map variable for the multi-part request, be sure -to indicate which index you are mapping the file to. The extension will not magically append files to an array. Each mapped file must explicitly declare the element index in an array where it is being placed. - -Warning: Be sure to dispose of each file stream when you are finished with it. -
-
- -```csharp title="Example File Upload Controller" -using GraphQL.AspNet.ServerExtensions.MultipartRequests; - -public class FileUploadController : GraphController -{ - [MutationRoot("multiFileUpload")] - // highlight-next-line - public async Task UploadFile(IEnumerable files) - { - foreach(var file in files) - { - using var stream = await fileRef.OpenFileAsync(); - // do something with each file stream - } - - return 0; - } -} -``` - -```graphql title="Declaring a list of files on a graphql query" -# highlight-next-line -mutation ($files: [Upload]) { - multiFileUpload(file: $files) -} -``` - -```bash title="Sample Curl" -curl localhost:3000/graphql \ -# highlight-next-line - -F operations='{ "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", "variables": { "files": [null, null] } }' \ - -F map='{ "firstFile": ["variables", "files", 0], "secondFile": ["variables", "files", 1] }' \ - -F firstFile=@a.txt - -F secondFile=@b.txt -``` - -### Handling an Unknown Number of Files -There are scenarios where you may ask your users to select a few files to upload without knowing how many they might choose. As long each declaration in your `map` field points to a position that _could be_ a valid index, the target array will be resized accordingly. - -```bash title="Adding Two Files" -curl localhost:3000/graphql \ - -F operations='{ - "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", - # highlight-next-line - "variables": { "files": [] } }' \ - -F map='{ "firstFile": ["variables", "files", 0], "secondFile": ["variables", "files", 1] }' \ - -F firstFile=@a.txt - -F secondFile=@b.txt -``` - - -In the above example, the `files` array will be automatically expanded to include indexes 0 and 1 as requested by the `map`: - -```json title="Resultant Operations Object" -{ - "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", - "variables": { "files": [, ] } -} -``` - -### Skipping Array Indexes -If you skip any indexes in your `map` declaration, the target array will be expanded to to include the out of sequence index. This can produce null values in your array and result in an error if your variable declaration does not allow nulls. - -```bash title="Adding One File To Index 5" -# Only one file is supplied but its mapped to index 5 -# the final array at 'variables.files` will be 6 elements long with 5 null elements. -curl localhost:3000/graphql \ - -F operations='{ - "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", - # highlight-next-line - "variables": { "files": [] } }' \ - # highlight-next-line - -F map='{ "firstFile": ["variables", "files", 5] }' \ - -F firstFile=@a.txt -``` - -```json title="Resultant Operations Object" -{ - "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", - // highlight-next-line - "variables": { "files": [null, null, null, null, null, ] } -} -``` - -### File Uploads on Batched Queries -File uploads work in conjunction with batched queries. When processing a multi-part request as a batch, prefix each of the mapped object-path references with an index of the batch you want the file to apply to. As you might guess this is usually handled by a supported client automatically. - -```bash title="Sample Query" -curl localhost:3000/graphql \ - -F operations='[ - { "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", "variables": { "files": [] } }, - { "query": "mutation ($files: [Upload]) { multiFileUpload(files: $files) }", "variables": { "files": [] } }, - ]' \ - # highlight-next-line - -F map='{ "firstFile": [0, "variables", "files", 0], "secondFile": [1, "variables", "files", 0] }' \ - -F firstFile=@a.txt - -F secondFile=@b.txt -``` - -### FileUpload Scalar -The following properties on the `FileUpload` C# class can be useful: - -* `FileName` - The name of the file that was uploaded. This property will be null if a non-file form field is referenced. -* `MapKey` - The key value used to place this file within a variable collection. This is usually the form field name on the multi-part request. -* `ContentType` - The supplied `content-type` value sent with the file. This value will be null for non-file fields. -* `Headers` - A collection of all the headers provided with the uploaded file. This value will be null for non-file fields. - -### Opening a File Stream -When opening a file stream you need to await a call `FileUpload.OpenFileAsync()`. This method is an abstraction on top of an internal wrapper that standardizes file streams across all implementions (see below for implementing your own file processor). When working with the standard `IFormFile` interface provided by ASP.NET this call is a simple wrapper for `IFormFile.OpenReadStream()`. - -```csharp title=ExampleFile Upload Controller -using GraphQL.AspNet.ServerExtensions.MultipartRequests; - -public class FileUploadController : GraphController -{ - [MutationRoot("singleFileUpload")] - public async Task UploadFile(FileUpload fileRef) - { - // do something with the file stream - // it is your responsibility to close and dispose of it - // highlight-next-line - using var stream = await fileRef.OpenFileStreamAsync(); - - return 0; - } -} -``` - -### Custom File Handling -By default, this extension splits the POST request on an `HttpContext` and presents the different parts to the query engine in a manner it expects. This means that any uploaded files are consumed under the hood as ASP.NET's built in `IFormFile` interface. While this is fine for most users, it can be troublesome with regard to timeouts and large file requests. Also, there may be scenarios where you want to save off files prior to executing a query or perhaps you'll need to process the file stream multiple times. - -You can implement and register your own `IFileUploadScalarValueMaker` to add custom processing logic for each file or blob BEFORE graphql gets ahold of it. For instance, some users may want to write incoming files to local disk or cloud storage and present GraphQL with a stream that points to that local reference, rather than the file reference on the http request. - -```csharp - public interface IFileUploadScalarValueMaker -{ - // This overload is used when processing traditional files received as part of a - // multi-part form through ASP.NET's HttpContext - Task CreateFileScalar(IFormFile aspNetFile); - - // This overload is used when processing data received on a - // multi-part form field rather than as a formal file upload. - Task CreateFileScalar(string mapKey, byte[] blobData); -} -``` - - -```csharp title="Register Your Custom Value Maker" -// other startup code omitted - -// register your scalar value maker BEFORE calling .AddGraphQL -services.AddSingleton(); - -services.AddGraphQL(options => { - options.AddMultipartRequestSupport(); -}); -``` - -:::tip -You can inherit from `FileUpload` and extend it as needed on your custom maker. However, be sure to declare your method parameters as `FileUpload` in your controllers so that GraphQL knows what scalar you are requesting. -::: - -Take a look at the [default upload scalar value maker](https://github.com/graphql-aspnet/graphql-aspnet/blob/master/src/graphql-aspnet/ServerExtensions/MultipartRequests/Engine/TypeMakers/DefaultFileUploadScalarValueMaker.cs) for some helpful details when trying to implement your own. - -### Timeouts and File Uploads - -Be mindful of any query timeouts you have set for your schemas. ASP.NET may start processing your query before all the file contents are made available to the server as long as it has the initial POST request. This also means that your graphql queries may start executing before the file contents arrive. - -While this asysncronicty usually works to your advantage, allowing your queries to begin processing before all the files are uploaded to the server; you may find that your queries pause on `.OpenFileStreamAsync()` waiting for the file stream to become available if there is a network delay or a large file being uploaded. If you have a [custom timeout](../reference/schema-configuration.md#querytimeout) configured for a schema, it may trigger while waiting for the file. Be sure to set your timeouts to a long enough period of time to avoid this scenario. - - -## Batch Queries - -### Processing a Batch of Queries -Provide an "operations" form field that represents an array of graphql requests, the engine will automatically detect the array and return an array of responses in the same order as they were received. Each query is processed asyncronously and independently. - -```bash title="Example Batch Query" -curl localhost:3000/graphql \ - #highlight-start - -F operations='[ - { "query": "query { findUser(lastName: \"Smith\") {firstName lastName} }" }, - { "query": "query { findUser(lastName: \"Jones\") {firstName lastName} }" }, - ]' \ - # highlight-end -``` - -```json title="Example Json Serialized Response" -[ - { - "data": { - "findUser": { - "firstName": "Baily", - "lastName": "Smith" - } - } - }, - { - "data": { - "findUser": { - "firstName": "Caleb", - "lastName": "Jones" - } - } - } -] -``` - -### Processing a Single Query -Provide an "operations" form field that represents a single query and the engine will automatically detect and return a normal graphql response. - -```bash title="Example Batch Query" -curl localhost:3000/graphql \ - #highlight-next-line - -F operations='{ "query": "query { findUser(lastName: \"Smith\") {firstName lastName} }" }' \ -``` - -```json title="Example Json Serialized Response" -{ - "data": { - "findUser": { - "firstName": "Baily", - "lastName": "Smith" - } - } -} -``` - -:::info -The extension is backwards compatible with standard graphql http request processing. If a request is recieved that is not a multi-part form POST request, normal graphql processing will occur. -::: - -### Batch Execution Order is Never Guaranteed -While the order of the results is guaranteed to be the same order in which the queries were received, there is no guarantee that the queries are executed in any specific order. This means if you submit a batch of 5 requests, each requests may complete in a randomized order. If the same batch is submitted 3 times, its possible that the execution order will be different each time. - -For queries this is usally not an issue, but if you are batching mutations, make sure you don't have any unexpected dependencies or side effects between queries. If your controllers perform business logic against an existing object and that object is modified by more than of your mutations its highly possible that the state of the object may be unexpectedly modified in some executions but not in others. - -Take this controller and query: -```csharp title="Example Controller" -public class FileUploadController : GraphController -{ - [MutationRoot("addMoney")] - // highlight-next-line - public async Task AddMoney(int itemId, int dollarsToAdd) - { - var item =await _service.RetrieveItem(itemId); - item.CurrentTotal += dollarsToAdd; - - await _service.UpdateItem(item); - return item; - } -} -``` - -```bash title="Example Batch Query" -curl localhost:3000/graphql \ - #highlight-start - -F operations='[ - { "query": "mutation { addMoney(itemId: 34, dollarsToAdd: 5) {id currentTotal} }" }, - { "query": "mutation { addThreeDollars(itemId: 34, , dollarsToAdd: 3) {id currentTotal} }" }, - ]' \ - # highlight-end -``` - -Assuming that the initial value of `currentTotal` was 0, all three of these responses are equally likely to occur depending on the order in which the execution engine decides to process the queries. -```json title=Sample Json Results -// When the queries are executed in declared order -[ - { - "data": { - "addMoney": { - "id": 34, - "currentTotal": 5 - } - } - }, - { - "data": { - "addMoney": { - "id": 34, - "currentTotal": 8 - } - } - }, -] - -// When the queries are executed in reverse order -[ - { - "data": { - "addMoney": { - "id": 34, - "currentTotal": 8 - } - } - }, - { - "data": { - "addMoney": { - "id": 34, - "currentTotal": 3 - } - } - }, -] - -// When the queries are executed simultaniously -// The final result updated to the datastore is unknown -[ - { - "data": { - "addMoney": { - "id": 34, - "currentTotal": 5 - } - } - }, - { - "data": { - "addMoney": { - "id": 34, - "currentTotal": 3 - } - } - }, -] -``` - -Under the hood, the batch process will parse and submit all queries to the engine simultaniously and wait for them to finish before structuring a result object. -:::caution -Ensure there are no dependencies between queries in a batch. An expected order of execution is never guaranteed. -::: - - -## Configuration -There are several configuration settings specific to extension. They can all be toggled when the extension is registered. Each configuration is specific -to the targeted schema. - -```csharp title='Configuring the Server Extension' -// Startup Code -// other code omitted for brevity -services.AddGraphQL(options => { - // highlight-start - options.AddMultipartRequestSupport(mpOptions => { - // set mpOptions here - }); - // highlight-end -}); -``` - -### MapMode -```csharp -// usage example -mpOptions.MapMode = MultipartRequestMapHandlingMode.Default; -``` - -A bitwise flag enumeration allowing the inclusion of different types of values for the `map` field dictated by the specification. Both options are enabled by default. - -| Option | Description | -| ------------- | ----------------- | -| `AllowStringPaths` | When enabled, the short-hand syntax for `object-path`, which uses a dot-delimited string instead of an array to indicate a json path, is acceptable for a map value. | -| `SplitDotDelimitedSingleElementArrays` | When enabled, the extension will examine single element arrays and, if that element is a string, treat it as a string, allowing it to be split as a dot-delimited string if the option is enabled. When not enabled, single element arrays are treated as a single path value. | - -### RequestMode - -```csharp -// usage example -mpOptions.RequestMode = MultipartRequestMode.Default; -``` - -A bitwise flag enumeration that controls which parts of the multi-part request extension are enabled. By default, both batch queries and file uploads are enabled. - -| Option | Description | -| ------------- | ----------------- | -| `FileUploads` | When enabled, the server extension will process file uploads. When disabled, any included files or form fields treated as files will cause the request to be rejected. | -| `BatchQueries` | When enabled, the extension will attempt to process properly formatted batch queries. When disabled, any attempt to submit a batch query will cause the request to be rejected. | - -### MaxFileCount - -```csharp -// usage example -mpOptions.MaxFileCount = 15; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `null` | `null`, number | - -When set, the extension will process, at most, the indicated amount of files. If more files appear on the request than the value indicated the request is automatically rejected. By default this value is set to `null` or no limit. - -### MaxBlobCount - -```csharp -// usage example -mpOptions.MaxBlobCount = 15; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `null` | `null`, number | - -When set, the extension will process, at most, the indicated amount of additional, non-spec form fields (e.g. additional text blobs). If more blobs appear on the request than the value indicated the request is automatically rejected. By default this value is set to `null` or no limit. - -### RegisterMultipartRequestHttpProcessor - -```csharp -// usage example -mpOptions.RegisterMultipartRequestHttpProcessor = true; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `true` | `true`, `false` | - -Determines if, when registering the extension, the default multipart http processor is registered. When set to true, the extension will attempt to replace any other registered http processor(i.e. the object that is handed an HttpContext via a route). When false, no processor is registered. You are expected to provide your own handling for multipart requests. The extension will always register its other required objects (the form parser, the custom scalar etc.). - - - - -### RequireMultipartRequestHttpProcessor - -```csharp -// usage example -mpOptions.RequireMultipartRequestHttpProcessor = true; -``` - -| Default Value | Acceptable Values | -| ------------- | ----------------- | -| `true` | `true`, `false` | - -Determines if, when starting up the application, the extension will check that the required http processor is registered. When set to true, if the required processor is not registered a configuration exception will be thrown and the server will fail to start. This can be helpful when registering multiple extensions to ensure that a valid processor is registered such that multipart-form requests will be handled correctly. - -## Demo Project -See the [demo projects](../reference/demo-projects.md) for a sample project utilizing [jaydenseric's apollo-upload-client](https://github.com/jaydenseric/apollo-upload-client) as a front end for performing file uploads against this extension. \ No newline at end of file diff --git a/docs/types/_category_.json b/docs/types/_category_.json deleted file mode 100644 index 7aba579..0000000 --- a/docs/types/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Graph Type Definitions", - "position": 3, - "collapsed": false -} \ No newline at end of file diff --git a/docs/types/enums.html b/docs/types/enums.html new file mode 100644 index 0000000..f196fe4 --- /dev/null +++ b/docs/types/enums.html @@ -0,0 +1,24 @@ + + + + + +Enums | GraphQL ASP.NET + + + + +
+

Enums

The ENUM graph type is represented by an enum type in .NET. The naming and exclusion rules used with object types apply in the same manner to enums.

By Default:

  • An ENUM graph type will have the same name as its enum type in your code.
  • All declared enum values are included, including compound values.
DonutType.cs
public enum DonutType
{
Glazed,
Cake,
Custard,
Jelly,
SugarCoated,
}
DonutType Type Definition
enum DonutType {
GLAZED
CAKE
CUSTARD
JELLY
SUGARCOATED
}

Compound Values are represented as their own enum value option.

DonutType.cs
public enum DonutType
{
Glazed,
Cake,
Custard,
Jelly,
SugarCoated,
Filled = Custard | Jelly
}
DonutType Type Definition
enum DonutType {
GLAZED
CAKE
CUSTARD
JELLY
SUGARCOATED
FILLED
}

Including an Enum in Your Schema

An enum graph type is automatically created from a .NET enum if it is:

  • Used as a parameter to an action method
  • Used as a return value of an action method
  • Used as a parameter or return type of any property or method of any included class or struct.
DonutController.cs
public class DonutController : GraphController 
{
[QueryRoot]
public int RetrieveDonutCount(DonutType donutType)
{
/* ... */
}
}

You can also explicitly add an enum at start up:

Startup code
services.AddGraphQL(options => 
{
options.AddGraphType<DonutType>();
});

Excluding an Enum Value

Use the [GraphSkip] attribute to omit a value from the schema. A query will be rejected if it attempts to submit an omitted enum value.

DonutType.cs
public enum DonutType
{
Glazed,
Cake,
Custard,
Jelly,

[GraphSkip]
SugarCoated,
}
DonutType Type Definition
# Sugar Coated is not part of the enum type
enum DonutType {
GLAZED
CAKE
CUSTARD
JELLY
}
caution

An excluded enum value is not just hidden, its NOT part of the schema. Any attempt to use it as a value in a query, a variable or as a result from a field resolution will cause a validation error.

Custom Type Name

Like with other graph types, use the [GraphType] attribute to indicate a custom name for the enum in the schema.

DonutType.cs
[GraphType("Donut_Type")]
public enum DonutType
{
Glazed,
Cake,
Custard,
Jelly,
SugarCoated,
}
DonutType Type Definition
enum Donut_Type {
GLAZED
CAKE
CUSTARD
JELLY
SUGARCOATED
}

Custom Value Names

Use [GraphEnumValue] to declare a custom name for the enum value and GraphQL will automatically handle the name translation when parsing a query document. A target schema's naming format rules will be applied and enforced on the value provided.

DonutType.cs
public enum DonutType
{
Glazed,
Cake,
Custard,
Jelly,

[GraphEnumValue("Sugar_Coated")]
SugarCoated,
}
DonutType Type Definition
enum DonutType {
GLAZED
CAKE
CUSTARD
JELLY
SUGAR_COATED
}

Value Name Formatting

By default, enum values are rendered in all CAPITAL LETTERS. This is the standard convention for GraphQL. If, however; you'd prefer something different you can override the defaults by creating a new GraphNameFormatter on your schema configuration.

Startup Code
services.AddGraphQL(o => {
var customFormatter = new GraphNameFormatter(enumValueStrategy: GraphNameFormatStrategy.ProperCase);
o.DeclarationOptions.GraphNamingFormatter = customFormatter;
})
Sample Formatting
enum DonutType {
Glazed
Cake
Custard
Jelly
}
tip

If you need something even more exotic, inherit from GraphNameFormatter and override the various methods as you see fit.

+ + + + \ No newline at end of file diff --git a/docs/types/enums.md b/docs/types/enums.md deleted file mode 100644 index 328e8d2..0000000 --- a/docs/types/enums.md +++ /dev/null @@ -1,206 +0,0 @@ ---- -id: enums -title: Enums -sidebar_label: Enums -sidebar_position: 4 ---- - -The `ENUM` graph type is represented by an `enum` type in .NET. The naming and exclusion rules used with [object types](./objects) apply in the same manner to enums. - -By Default: - -- An `ENUM` graph type will have the same name as its `enum` type in your code. -- All declared enum values are included, including compound values. - -```csharp title="DonutType.cs" -public enum DonutType -{ - Glazed, - Cake, - Custard, - Jelly, - SugarCoated, -} -``` - -```graphql title="DonutType Type Definition" -enum DonutType { - GLAZED - CAKE - CUSTARD - JELLY - SUGARCOATED -} -``` - -Compound Values are represented as their own enum value option. - - -```csharp title="DonutType.cs" -public enum DonutType -{ - Glazed, - Cake, - Custard, - Jelly, - SugarCoated, - // highlight-next-line - Filled = Custard | Jelly -} -``` - -```graphql title="DonutType Type Definition" -enum DonutType { - GLAZED - CAKE - CUSTARD - JELLY - SUGARCOATED - // highlight-next-line - FILLED -} -``` - -## Including an Enum in Your Schema - -An enum graph type is automatically created from a .NET enum if it is: - -* Used as a parameter to an action method -* Used as a return value of an action method -* Used as a parameter or return type of any property or method of any included class or struct. - -```csharp title="DonutController.cs" -public class DonutController : GraphController -{ - [QueryRoot] - // highlight-next-line - public int RetrieveDonutCount(DonutType donutType) - { - /* ... */ - } -} -``` - -You can also explicitly add an enum at start up: - -```csharp title="Startup code" -services.AddGraphQL(options => -{ - options.AddGraphType(); -}); -``` - -## Excluding an Enum Value - -Use the `[GraphSkip]` attribute to omit a value from the schema. A query will be rejected if it attempts to submit an omitted enum value. - - -```csharp title="DonutType.cs" -public enum DonutType -{ - Glazed, - Cake, - Custard, - Jelly, - - // highlight-next-line - [GraphSkip] - SugarCoated, -} -``` - -```graphql title="DonutType Type Definition" -# Sugar Coated is not part of the enum type -enum DonutType { - GLAZED - CAKE - CUSTARD - JELLY -} -``` - -:::caution -An excluded enum value is not just hidden, its NOT part of the schema. Any attempt to use it as a value in a query, a variable or as a result from a field resolution will cause a validation error. -::: - -## Custom Type Name - -Like with other graph types, use the `[GraphType]` attribute to indicate a custom name for the enum in the schema. - - -```csharp title="DonutType.cs" -// highlight-next-line -[GraphType("Donut_Type")] -public enum DonutType -{ - Glazed, - Cake, - Custard, - Jelly, - SugarCoated, -} -``` - -```graphql title="DonutType Type Definition" -// highlight-next-line -enum Donut_Type { - GLAZED - CAKE - CUSTARD - JELLY - SUGARCOATED -} -``` - -## Custom Value Names - -Use `[GraphEnumValue]` to declare a custom name for the enum value and GraphQL will automatically handle the name translation when parsing a query document. A target schema's naming format rules will be applied and enforced on the value provided. - -```csharp title="DonutType.cs" -public enum DonutType -{ - Glazed, - Cake, - Custard, - Jelly, - - [GraphEnumValue("Sugar_Coated")] - SugarCoated, -} -``` - - -```graphql title="DonutType Type Definition" -enum DonutType { - GLAZED - CAKE - CUSTARD - JELLY - SUGAR_COATED -} -``` - -## Value Name Formatting - -By default, enum values are rendered in all CAPITAL LETTERS. This is the standard convention for GraphQL. If, however; you'd prefer something different you can override the defaults by creating a new `GraphNameFormatter` on your [schema configuration](../reference/schema-configuration.md#graphnamingformatter). - -```csharp title="Startup Code" -services.AddGraphQL(o => { - // highlight-start - var customFormatter = new GraphNameFormatter(enumValueStrategy: GraphNameFormatStrategy.ProperCase); - o.DeclarationOptions.GraphNamingFormatter = customFormatter; - // highlight-end -}) -``` - -```graphql title="Sample Formatting" -enum DonutType { - Glazed - Cake - Custard - Jelly -} -``` -:::tip -If you need something even more exotic, inherit from `GraphNameFormatter` and override the various methods as you see fit. -::: \ No newline at end of file diff --git a/docs/types/input-objects.html b/docs/types/input-objects.html new file mode 100644 index 0000000..295d317 --- /dev/null +++ b/docs/types/input-objects.html @@ -0,0 +1,26 @@ + + + + + +Input Objects | GraphQL ASP.NET + + + + +
+

Input Objects

INPUT_OBJECT graph types (a.k.a. input objects) represent complex data supplied to arguments on fields or directives. Anytime you want to pass more data than a single string or a number, perhaps an Address or a new Employee record, you use an INPUT_OBJECT to represent that entity in GraphQL. When the system scans your controllers, if it comes across a class or struct used as a parameter to a method it will attempt to generate the appropriate input type definition to represent that class.

The rules surrounding naming, field declarations, exclusions, use of [GraphSkip] etc. apply to input objects but with a few key differences:

  • Unless overridden, an input object is named the same as its class name, prefixed with Input_ (e.g. Input_Address, Input_Employee)
  • Only public properties with a get and set will be included.
    • Property return types cannot be Task<T>, an interface and cannot implement IGraphUnionProxy or IGraphActionResult. Such properties are always skipped.
  • Methods are always skipped.

Customized Type Names

Input objects can be given customized names, just like with object types, using the [GraphType] attribute.

Customized Input Object Type Name
[GraphType(InputName = "NewDonutModel")]
public class Donut
{
public int Id { get; set; }
public string Name { get; set; }
public DonutType Type { get; set; }
public decimal Price { get; set; }
}
Donut Type Definition
input NewDonutModel {
id: Int! = 0
name: String = null
type: DonutType! = FROSTED
price: Decimal! = 0
}

Note the specific callout to InputName in the attribution.

Use an Empty Constructor

When GraphQL executes a query it will attempt to create an instance of your input object then assign the key/value pairs received on the query to the properties. In order to do the initial instantiation it requires a public parameterless constructor to do so.

"Input
public class BakeryController : GraphController
{
[Mutation("createDonut")]
public bool CreateNewDonut(DonutModel donut)
{/*....*/}
}

// DonutModel.cs
public class DonutModel
{
//Use a public parameterless constructor
public DonutModel()
{
}

public int Id { get; set; }
public string Name { get; set; }
}

Because of this consturctor restriction it can be helpful to separate your classes between "input" and "output" types much is the same way we do with ViewModel vs. BindingModel objects with REST queries in ASP.NET. This is optional, mix and match as needed by your use case.

Properties Must Have a Public Setter

Properties without a setter are ignored. At runtime, GraphQL compiles an expression tree with the set assignments declared on the graph type, it won't attempt to sneakily reflect and invoke a private or protected setter.

Properties must Have a Public Setter
public class Donut
{
public int Id { get; }
public string Name { get; set; }
public DonutType Type { get; set; }
public decimal Price { get; set; }
}
Donut Type Definition
# Id field is not included 

input Input_Donut {
name: String = null
type: DonutType! = FROSTED
price: Decimal! = 0
}

Methods are Ignored

While its possible to have methods be exposed as resolvable fields on regular OBJECT types, they are ignored for input types regardless of the declaration rules applied to the type.

Methods Are Ignored on Input Objects
public class Donut
{
[GraphField("salesTax")]
public decimal CalculateSalesTax(decimal taxPercentage)
{
return this.Price * taxPercentage;
}

public int Id { get; set; }
public string Name { get; set; }
public DonutType Type { get; set; }
public decimal Price { get; set; }
}
Donut Type Definition
# CalculateSalesTax is not included
input Input_Donut {
id: Int! = 0
name: String = null
type: DonutType! = FROSTED
price: Decimal! = 0
}

Non-Nullability

By default, all properties that are reference types (i.e. classes) are nullable and all value types (primatives, structs etc.) are non-nullable

Recipe can be null, it is a reference type
public class Donut
{
public Recipe Recipe { get; set; } // reference type
public int Quantity { get; set; } // value type
}
Input Donut Definition
input Input_Donut {
recipe: Input_Recipe = null # nullable
quantity: Int! = 0 # not nullable
}

If you want to force a reference type to be non-null you can use the [GraphField] attribute to augment the field's type expression.

Force Recipe to be non-null
public class Donut
{
public Donut()
{
// we must supply a non-null default value
// for a non-nullable field
this.Recipe = new Recipe("Flour, Sugar, Salt");
}

[GraphField(TypeExpression = "Type!")]
public Recipe Recipe { get; set; } // reference type
public int Quantity { get; set; } // value type
}
Input Donut Definition
input Input_Donut {
recipe: Input_Recipe! = {ingredients : "Flour, Sugar, Salt" }
quantity: Int! = 0
}
Did You Notice?

We assigned a recipe in the class's constructor to use as the default value.

Any non-nullable field, that does not have the [Required] attribute (see below), MUST have a default value assigned to it that is not null. A GraphTypeDeclarationException will be thrown at startup if this is not the case.

Required Fields And Default Values

Add [Required] (from System.ComponentModel) to any property to force a user to supply the field in a query document.

Any non-required field will automatically be assigned a default value if not supplied on a query. This default value is equivilant to the property value of the object when its instantiated via its public, parameterless constructor.

Add the Required attribute for force a query to define a value
public class Donut
{
public Donut()
{
// set custom defaults if needed
this.Type = DonutType.Frosted;
this.IsAvailable = true;
}

[Required]
public int Id { get; set; }

public string Name { get; set; }
public DonutType Type { get; set; }
public Bakery Bakery { get;set; }
public bool IsAvailable { get; set; }
public int SkuNumber { get; set; }
}
Donut Type Definition
input Input_Donut {
id: Int! # No Default Value on Id
name: String = null
type: DonutType! = FROSTED
bakery: Input_Bakery = null
isAvailable: Boolean! = true
skuNumber: Int! = 0
}

Nullable Fields are Never Required

By Definition (spec § 5.6.4), a nullable field without a +declared default value is still considered optional and does not need to be supplied. That is to say that if a query does not include a field that is nullable, it will default to null regardless of the use of the [Required] attribute.

These two property declarations for an input object are identical as far as graphql is concerned:

Example Input object Fields
public InputEmployee
{
public string FirstName { get; set; }

[Required]
public string LastName { get; set; }
}

Both FirstName and LastName are of type string, which is nullable. GraphQL will ignore the required attribute and still allow a query to process even if neither value is supplied on a query.

Required Reference Types

Combine the [Required] attribute with a non-nullable type expression to force an otherwise nullable field to be required.

Force Owner to be non-null And Required
public class Bakery
{
[Required]
[GraphField(TypeExpression = "Type!")]
public Person Owner { get; set; }
}
Donut Type Definition
# No Default Value is supplied. 
# the 'owner' must be supplied on a query
input Input_Bakery {
owner: Input_Person!
}

Owner is of type Person, which is a reference type, which is nullable by default. By augmenting its type expression to be non-null and adding the [Required] attribute graphql will not supply a default value require it to be supplied on a query.

Enum Fields and Coercability

Any default value declared for an input field must be coercible by its target graph type in the target schema. Because of this there is a small got'cha situation with enum values.

Take a look at this example of an enum and input object:

Using an Enum as a field type
public class Donut 
{
public string Name { get; set; }
public DonutFlavor Flavor { get; set; }
}

public enum DonutFlavor
{
[GraphSkip]
Vanilla = 0,
Chocolate = 1,
}

When Donut is instantiated the value of Flavor will be Vanilla because +thats the default value (0) of the enum. However, the enum value Vanilla is marked as being skipped in the schema.

Because of this mismatch, a GraphTypeDeclarationException will be thrown when the introspection data for your schema is built. As a result, the server will fail to start until the problem is corrected.

You can get around this by setting an included enum value in the consturctor:

Using an Enum as a field type
public class Donut 
{
public Donut()
{
// set the value of flavor to an enum value
// included in the graph
this.Flavor = DonutFlavor.Chocolate;
}

public string Name { get; set; }
public DonutFlavor Flavor { get; set; }
}

caution

Enum values used for the default value of input object properties MUST also exist as values in the schema or an exception will be thrown.

+ + + + \ No newline at end of file diff --git a/docs/types/input-objects.md b/docs/types/input-objects.md deleted file mode 100644 index 918da0e..0000000 --- a/docs/types/input-objects.md +++ /dev/null @@ -1,300 +0,0 @@ ---- -id: input-objects -title: Input Objects -sidebar_label: Input Objects -sidebar_position: 1 ---- - -`INPUT_OBJECT` graph types (a.k.a. input objects) represent complex data supplied to arguments on fields or directives. Anytime you want to pass more data than a single string or a number, perhaps an Address or a new Employee record, you use an INPUT_OBJECT to represent that entity in GraphQL. When the system scans your controllers, if it comes across a class or struct used as a parameter to a method it will attempt to generate the appropriate input type definition to represent that class. - -The rules surrounding naming, field declarations, exclusions, use of `[GraphSkip]` etc. apply to input objects but with a few key differences: - -- Unless overridden, an input object is named the same as its class name, prefixed with `Input_` (e.g. `Input_Address`, `Input_Employee`) -- Only public properties with a `get` and `set` will be included. - - Property return types cannot be `Task`, an `interface` and cannot implement `IGraphUnionProxy` or `IGraphActionResult`. Such properties are always skipped. -- Methods are always skipped. - -## Customized Type Names - -Input objects can be given customized names, just like with object types, using the `[GraphType]` attribute. - -```csharp title="Customized Input Object Type Name" -[GraphType(InputName = "NewDonutModel")] -public class Donut -{ - public int Id { get; set; } - public string Name { get; set; } - public DonutType Type { get; set; } - public decimal Price { get; set; } -} -``` - -```graphql title="Donut Type Definition" -input NewDonutModel { - id: Int! = 0 - name: String = null - type: DonutType! = FROSTED - price: Decimal! = 0 -} -``` - ->Note the specific callout to `InputName` in the attribution. - -## Use an Empty Constructor - -When GraphQL executes a query it will attempt to create an instance of your input object then assign the key/value pairs received on the query to the properties. In order to do the initial instantiation it requires a public parameterless constructor to do so. - -```csharp title="Input Objects MUST have a Public, Parameterless Constructor -public class BakeryController : GraphController -{ - [Mutation("createDonut")] - public bool CreateNewDonut(DonutModel donut) - {/*....*/} -} - -// DonutModel.cs -public class DonutModel -{ - //Use a public parameterless constructor - public DonutModel() - { - } - - public int Id { get; set; } - public string Name { get; set; } -} -``` - -> Because of this consturctor restriction it can be helpful to separate your classes between "input" and "output" types much is the same way we do with `ViewModel` vs. `BindingModel` objects with REST queries in ASP.NET. This is optional, mix and match as needed by your use case. - -## Properties Must Have a Public Setter - -Properties without a setter are ignored. At runtime, GraphQL compiles an expression tree with the set assignments declared on the graph type, it won't attempt to sneakily reflect and invoke a private or protected setter. - -```csharp title="Properties must Have a Public Setter" -public class Donut -{ - public int Id { get; } - public string Name { get; set; } - public DonutType Type { get; set; } - public decimal Price { get; set; } -} -``` - -```graphql title="Donut Type Definition" -# Id field is not included - -input Input_Donut { - name: String = null - type: DonutType! = FROSTED - price: Decimal! = 0 -} -``` - - -## Methods are Ignored - -While its possible to have methods be exposed as resolvable fields on regular `OBJECT` types, they are ignored for input types regardless of the declaration rules applied to the type. - -```csharp title="Methods Are Ignored on Input Objects" -public class Donut -{ - [GraphField("salesTax")] - public decimal CalculateSalesTax(decimal taxPercentage) - { - return this.Price * taxPercentage; - } - - public int Id { get; set; } - public string Name { get; set; } - public DonutType Type { get; set; } - public decimal Price { get; set; } -} -``` - -```graphql title="Donut Type Definition" -# CalculateSalesTax is not included -input Input_Donut { - id: Int! = 0 - name: String = null - type: DonutType! = FROSTED - price: Decimal! = 0 -} -``` - -## Non-Nullability -By default, all properties that are reference types (i.e. classes) are nullable and all value types (primatives, structs etc.) are non-nullable - -```csharp title="Recipe can be null, it is a reference type" -public class Donut -{ - public Recipe Recipe { get; set; } // reference type - public int Quantity { get; set; } // value type -} -``` - -```graphql title="Input Donut Definition" -input Input_Donut { - recipe: Input_Recipe = null # nullable - quantity: Int! = 0 # not nullable -} -``` - - -If you want to force a reference type to be non-null you can use the `[GraphField]` attribute to augment the field's type expression. - -```csharp title="Force Recipe to be non-null" -public class Donut -{ - public Donut() - { - // we must supply a non-null default value - // for a non-nullable field - this.Recipe = new Recipe("Flour, Sugar, Salt"); - } - - [GraphField(TypeExpression = "Type!")] - public Recipe Recipe { get; set; } // reference type - public int Quantity { get; set; } // value type -} -``` - -```graphql title="Input Donut Definition" -input Input_Donut { - recipe: Input_Recipe! = {ingredients : "Flour, Sugar, Salt" } - quantity: Int! = 0 -} -``` - -:::info Did You Notice? - We assigned a recipe in the class's constructor to use as the default value. - - Any non-nullable field, that does not have the `[Required]` attribute (see below), MUST have a default value assigned to it that is not `null`. A `GraphTypeDeclarationException` will be thrown at startup if this is not the case. -::: - - -## Required Fields And Default Values -Add `[Required]` (from System.ComponentModel) to any property to force a user to supply the field in a query document. - -Any non-required field will automatically be assigned a default value if not supplied on a query. This default value is equivilant to the property value of the object when its instantiated via its public, parameterless constructor. - -```csharp title="Add the Required attribute for force a query to define a value" -public class Donut -{ - public Donut() - { - // set custom defaults if needed - this.Type = DonutType.Frosted; - this.IsAvailable = true; - } - - [Required] - public int Id { get; set; } - - public string Name { get; set; } - public DonutType Type { get; set; } - public Bakery Bakery { get;set; } - public bool IsAvailable { get; set; } - public int SkuNumber { get; set; } -} -``` - -```graphql title="Donut Type Definition" -input Input_Donut { - id: Int! # No Default Value on Id - name: String = null - type: DonutType! = FROSTED - bakery: Input_Bakery = null - isAvailable: Boolean! = true - skuNumber: Int! = 0 -} -``` - -### Nullable Fields are Never Required -By Definition (spec § [5.6.4](https://spec.graphql.org/October2021/#sec-Input-Object-Required-Fields)), a nullable field without a -declared default value is still considered optional and does not need to be supplied. That is to say that if a query does not include a field that is nullable, it will default to `null` regardless of the use of the `[Required]` attribute. - -These two property declarations for an input object are identical as far as graphql is concerned: - -```csharp title='Example Input object Fields' -public InputEmployee -{ - public string FirstName { get; set; } - - [Required] - public string LastName { get; set; } -} -``` - -Both `FirstName` and `LastName` are of type `string`, which is nullable. GraphQL will ignore the required attribute and still allow a query to process even if neither value is supplied on a query. - -### Required Reference Types -Combine the [Required] attribute with a non-nullable type expression to force an otherwise nullable field to be required. - -```csharp title="Force Owner to be non-null And Required" -public class Bakery -{ - [Required] - [GraphField(TypeExpression = "Type!")] - public Person Owner { get; set; } -} -``` - -```graphql title="Donut Type Definition" -# No Default Value is supplied. -# the 'owner' must be supplied on a query -input Input_Bakery { - owner: Input_Person! -} -``` - -`Owner` is of type Person, which is a reference type, which is nullable by default. By augmenting its type expression to be non-null and adding the `[Required]` attribute graphql will not supply a default value require it to be supplied on a query. - -## Enum Fields and Coercability - -Any default value declared for an input field must be coercible by its target graph type in the target schema. Because of this there is a small got'cha situation with enum values. - -Take a look at this example of an enum and input object: - -```csharp title="Using an Enum as a field type" -public class Donut -{ - public string Name { get; set; } - public DonutFlavor Flavor { get; set; } -} - -public enum DonutFlavor -{ - [GraphSkip] - Vanilla = 0, - Chocolate = 1, -} -``` - -When `Donut` is instantiated the value of Flavor will be `Vanilla` because -thats the default value (0) of the enum. However, the enum value `Vanilla` is marked as being skipped in the schema. - -Because of this mismatch, a `GraphTypeDeclarationException` will be thrown when the introspection data for your schema is built. As a result, the server will fail to start until the problem is corrected. - -You can get around this by setting an included enum value in the consturctor: - - -```csharp title="Using an Enum as a field type" -public class Donut -{ - public Donut() - { - // set the value of flavor to an enum value - // included in the graph - this.Flavor = DonutFlavor.Chocolate; - } - - public string Name { get; set; } - public DonutFlavor Flavor { get; set; } -} - -``` - -:::caution - Enum values used for the default value of input object properties MUST also exist as values in the schema or an exception will be thrown. -::: \ No newline at end of file diff --git a/docs/types/interfaces.html b/docs/types/interfaces.html new file mode 100644 index 0000000..48b8c93 --- /dev/null +++ b/docs/types/interfaces.html @@ -0,0 +1,24 @@ + + + + + +Interfaces | GraphQL ASP.NET + + + + +
+

Interfaces

Interfaces in GraphQL work like interfaces in C#, for the most part. They provide a contract for a set of common fields amongst different objects. When it comes to declaring them, the INTERFACE graph type works exactly like object types.

By default, when creating an interface graph type, the library:

  • Will name the interface the same as its C# type name.
  • Will include all properties that have a getter.
  • Will ignore any methods.
IPastry.cs
public interface IPastry
{
int Id { get; set; }
string Name { get; set; }
}
IPastry Type Definition
interface IPastry {
id: Int!
name: String
}

You can override the default settings in your schema configuration or by use of the GraphType and GraphField attributes.

Inheritance and Implmentations

The section on working with interfaces with action methods provides a great discussion on proper usage but its worth pointing out here as well.

You must let GraphQL know of the possible object types which implement your interface. If your action method returns IPastry and you return a Donut, but didn't let GraphQL know about the Donut class, it won't be able to continue to resolve the requested fields as it won't know which resolvers to call. This is especially true if you use type restricted fragments or spreads.

BakeryController.cs
public class BakeryController : GraphController
{
[QueryRoot(typeof(Donut), typeof(Cake))]
public IPastry SearchPastries(string name)
{/* ... */}
}
Sample Query
query {
searchPastries(name: "chocolate*") {
id
name

...on Donut {
isFilled
}

...on Cake {
icingFlavor
}
}
}

Use it to Include it

Unless an interface is actually referenced as a return value of a field, be it from an action method or a model property, it won't be added to your schema and won't be visible to introspection queries.

That is to say that when you register Donut, unless you specifically return IPastry from your application, GraphQL will leave it out of the schema. This goes a long ways in preventing security vulnerabilities and reducing clutter in your schema with all the interfaces you may declare internally. For instance, while common in .NET, its doubtful that you ever want to expose IEnumerable to your graph.

IPastry is never used
public class BakeryController : GraphController
{
[QueryRoot]
public Donut FindDonut(string name)
{/* ... */}
}

public class Donut : IPastry
{/*...*/}

// IPastry will be excluded from the schema since
// its not referenced in any controllers or other object properties.
public interface IPastry
{/*...*/}
Type Definitions
# Donut is published on the schema
# but IPastry is not included
type Donut {
id: Int!
name: String
...
}
tip

Use schemaOptions.AddGraphType<IPastry>() during schema configuration at startup to force GraphQL to publish the interface, even if its never used in the graph. This is true for any graph type.

Implmenting Other Interfaces

Interfaces implementing other interfaces worksa bit differently than it does in .NET. Take for example, these two interfaces:

C# Interface Inheritance
public interface IPastry
{
int Id { get; set; }
string Name { get; set; }
}

public interface IDonut : IPastry
{
string Flavor{ get; set; }
}

In .NET IDonut, by virtue of implementing IPastry, grants "access" to the Id and Name fields for any object that implements IDonut since said object must implement both interfaces to compile correctly. However, this is not the case with interfaces in your graphql schema. As said above, since interfaces are not automatically parsed the fields they define are also not automatically included in child interfaces.

Startup Code
services.AddGraphQL(o => 
{
// only include IDonut in the schema
o.AddGraphType<IDonut>();
});
IDonut Type Definition
# IDonut DOES NOT contain name or id
# because IPastry is not part of the schema
interface IDonut {
flavor: String
}

However, GraphQL does support interface inheritance. As long as both interfaces are included as part of the schema then the fields will wire up as you'd expect.

Startup Code
services.AddGraphQL(o => 
{
// Include both interfaces
o.AddGraphType<IPastry>();
o.AddGraphType<IDonut>();
});
Type Definitions
interface IPastry {  
id: Int!
name: String
}

# IDonut DOES contain all the expected fields
# Since IPastry is included
interface IDonut implements IPastry {
id: Int!
name: String
flavor: String
}
info

GraphQL will NOT attempt to include inherited fields unless the interface they are declared on is part the schema.

Interfaces are not Input Objects

The GraphQL specification states that "interfaces are never valid inputs" [Spec § 3.7]. The runtime will reject any attempts to use an interface as a parameter to a method (i.e. a field argument) that is exposed on the graph.

Interfaces cannot be used as input arguments
public class BakeryController : GraphController
{
// ERROR!
// A GraphTypeDeclarationException will be thrown
[Mutation]
public Donut AddNewDonut(IPastry newPastry)
{/* ... */}
}

Interface Names

Like with other graph types use the [GraphType] attribute to indicate a custom name for the interface in the object graph.

Interface Custom Name
[GraphType("Pastry")]
public interface IPastry
{
int Id { get; set; }
string Name { get; set; }
}
Graph Type Definition
interface Pastry {
id: Int!
name: String
}

Methods as Fields

By default, interface methods are excluded from being fields on the graph but can be added by tagging the method with [GraphField].

Including a POCO method as a field
public interface IPastry
{
[GraphField("salesTax")]
decimal CalculateSalesTax(decimal taxPercentage);

int Id { get; set; }
string Name { get; set; }
decimal Price { get; set; }
}
IPastry Type Definition
inteface IPastry {
salesTax (taxPercentage: Decimal!): Decimal!
id: Int!
name: String
price: Decimal!
}

Just as with controller actions, GraphQL will analyze the signature of the method to determine its return type, expression requirements and input arguments.

Methods on interfaces lack many of the features of controllers such as being able to perform model state validation or provide access to this.User and this.Request.

Excluding Fields

To exclude a single property that you don't want to expose to GraphQL add the [GraphSkip] attribute to it:

Excluding a property
public interface IPastry
{
int Id { get; set; }
string Name { get; set; }

[GraphSkip]
decimal Price { get; set; }
}
IPastry Type Definition
interface IPastry {
id: Int!
name: String
# price is not included
}

Or force GraphQL to skip all fields except those you explicitly define with a [GraphField] attribute:

Require explicit declarations for this type
[GraphType(FieldDeclarationRequirements = TemplateDeclarationRequirements.RequireAll)]
public interface IPastry
{
[GraphField]
int Id { get; set; }

[GraphField]
string Name { get; set; }

decimal Price { get; set; }
}
IPastry Type Definition
# only id and name are included
interface IPastry {
id: Int!
name: String
}

Or set a schema-wide option during startup:

Set Field Declaration Requirements at Startup
services.AddGraphQL(options =>
{
options.DeclarationOptions.FieldDeclarationRequirements = TemplateDeclarationRequirements.RequireAll;
});

Your schema will follow a cascading model of inclusion rules in order of increasing priority from schema -> class -> field level declarations. This can be useful in multi-schema setups where a class may be shared but you don't want the exposed fields to be different or if there is a secure field that you want to guarantee is not exposed regardless of the schema.

Forced Interface Exclusions

Perhaps there exists an interface in a shared assembly used amongst multiple work teams that contains some utility classes that absolutely, positively CANNOT be exposed to GraphQL at any cost.

In these cases, add [GraphSkip] to an interface and GraphQL will throw a GraphTypeDeclarationException if its ever asked to include it in a schema.

Prevent a Type from EVER Being Included in the Graph
// ERROR, GraphTypeDeclarationException will be thrown!
[GraphSkip]
public interface IPastry
{
int Id { get; set; }
string Name { get; set; }
decimal Price { get; set; }
}

This rule is enforced at the template level and is applied to the System.Type. Its not specific to the INTERFACE graph type. Any class, interface, enum etc. with the [GraphSkip] attribute will be permanantly skipped.

+ + + + \ No newline at end of file diff --git a/docs/types/interfaces.md b/docs/types/interfaces.md deleted file mode 100644 index 5978c46..0000000 --- a/docs/types/interfaces.md +++ /dev/null @@ -1,320 +0,0 @@ ---- -id: interfaces -title: Interfaces -sidebar_label: Interfaces -sidebar_position: 2 ---- - -Interfaces in GraphQL work like interfaces in C#, for the most part. They provide a contract for a set of common fields amongst different objects. When it comes to declaring them, the `INTERFACE` graph type works exactly like [object types](./objects). - -By default, when creating an interface graph type, the library: - -- Will name the interface the same as its C# type name. -- Will include all properties that have a getter. -- Will ignore any methods. - - -```csharp title="IPastry.cs" -public interface IPastry -{ - int Id { get; set; } - string Name { get; set; } -} -``` -```graphql title="IPastry Type Definition" -interface IPastry { - id: Int! - name: String -} -``` -> You can override the default settings in your [schema configuration](../reference/schema-configuration.md#fielddeclarationrequirements) or by use of the [GraphType](../reference/attributes.md#graphtype) and [GraphField](../reference/attributes.md#graphfield) attributes. - -## Inheritance and Implmentations - -The section on working with interfaces with [action methods](../controllers/actions#working-with-interfaces) provides a great discussion on proper usage but its worth pointing out here as well. - -You must let GraphQL know of the possible object types which implement your interface. If your action method returns `IPastry` and you return a `Donut`, but didn't let GraphQL know about the `Donut` class, it won't be able to continue to resolve the requested fields as it won't know which resolvers to call. This is especially true if you use type restricted fragments or spreads. - -```csharp title="BakeryController.cs" -public class BakeryController : GraphController -{ - // highlight-next-line - [QueryRoot(typeof(Donut), typeof(Cake))] - public IPastry SearchPastries(string name) - {/* ... */} -} -``` - -```graphql title="Sample Query" -query { - searchPastries(name: "chocolate*") { - id - name - - ...on Donut { - isFilled - } - - ...on Cake { - icingFlavor - } - } -} -``` - -### Use it to Include it - -Unless an interface is actually referenced as a return value of a field, be it from an action method or a model property, it won't be added to your schema and won't be visible to introspection queries. - -That is to say that when you register `Donut`, unless you specifically return `IPastry` from your application, GraphQL will leave it out of the schema. This goes a long ways in preventing security vulnerabilities and reducing clutter in your schema with all the interfaces you may declare internally. For instance, while common in .NET, its doubtful that you ever want to expose `IEnumerable` to your graph. - -```csharp title="IPastry is never used" -public class BakeryController : GraphController -{ - [QueryRoot] - public Donut FindDonut(string name) - {/* ... */} -} - -public class Donut : IPastry -{/*...*/} - -// IPastry will be excluded from the schema since -// its not referenced in any controllers or other object properties. -public interface IPastry -{/*...*/} -``` - - -```graphql title="Type Definitions" -# Donut is published on the schema -# but IPastry is not included -type Donut { - id: Int! - name: String - ... -} -``` - -:::tip - Use `schemaOptions.AddGraphType()` during [schema configuration](../reference/schema-configuration) at startup to force GraphQL to publish the interface, even if its never used in the graph. This is true for any graph type. -::: - -### Implmenting Other Interfaces - -Interfaces implementing other interfaces worksa bit differently than it does in .NET. Take for example, these two interfaces: - -```csharp title="C# Interface Inheritance" -public interface IPastry -{ - int Id { get; set; } - string Name { get; set; } -} - -public interface IDonut : IPastry -{ - string Flavor{ get; set; } -} -``` - -In .NET `IDonut`, by virtue of implementing `IPastry`, grants "access" to the `Id` and `Name` fields for any object that implements IDonut since said object must implement both interfaces to compile correctly. However, this is not the case with interfaces in your graphql schema. As said above, since interfaces are not automatically parsed the fields they define are also not automatically included in child interfaces. - -```csharp title="Startup Code" -services.AddGraphQL(o => -{ - // only include IDonut in the schema - // highlight-next-line - o.AddGraphType(); -}); -``` - -```graphql title="IDonut Type Definition" -# IDonut DOES NOT contain name or id -# because IPastry is not part of the schema -interface IDonut { - flavor: String -} -``` - -However, GraphQL does support interface inheritance. As long as both interfaces are included as part of the schema then the fields will wire up as you'd expect. - - -```csharp title="Startup Code" -services.AddGraphQL(o => -{ - // Include both interfaces - // highlight-start - o.AddGraphType(); - o.AddGraphType(); - // highlight-end -}); -``` - - -```graphql title="Type Definitions" -interface IPastry { - id: Int! - name: String -} - -# IDonut DOES contain all the expected fields -# Since IPastry is included -// highlight-next-line -interface IDonut implements IPastry { - id: Int! - name: String - flavor: String -} -``` - -:::info -GraphQL will NOT attempt to include inherited fields unless the interface they are declared on is part the schema. -::: - -## Interfaces are not Input Objects - -The GraphQL specification states that "interfaces are never valid inputs" [[Spec § 3.7](https://graphql.github.io/graphql-spec/October2021/#sec-Interfaces)]. The runtime will reject any attempts to use an interface as a parameter to a method (i.e. a field argument) that is exposed on the graph. - -```csharp title="Interfaces cannot be used as input arguments" -public class BakeryController : GraphController -{ - // ERROR! - // A GraphTypeDeclarationException will be thrown - [Mutation] - // highlight-next-line - public Donut AddNewDonut(IPastry newPastry) - {/* ... */} -} -``` - -## Interface Names - -Like with other graph types use the `[GraphType]` attribute to indicate a custom name for the interface in the object graph. - - -```csharp title="Interface Custom Name" -// highlight-next-line -[GraphType("Pastry")] -public interface IPastry -{ - int Id { get; set; } - string Name { get; set; } -} -``` - -```graphql title="Graph Type Definition" -interface Pastry { - id: Int! - name: String -} -``` - - -## Methods as Fields - -By default, interface methods are excluded from being fields on the graph but can be added by tagging the method with `[GraphField]`. - - -```csharp title="Including a POCO method as a field" -public interface IPastry -{ - // highlight-next-line - [GraphField("salesTax")] - decimal CalculateSalesTax(decimal taxPercentage); - - int Id { get; set; } - string Name { get; set; } - decimal Price { get; set; } -} -``` - -```graphql title="IPastry Type Definition" -inteface IPastry { - // highlight-next-line - salesTax (taxPercentage: Decimal!): Decimal! - id: Int! - name: String - price: Decimal! -} -``` -Just as with [controller actions](../controllers/actions), GraphQL will analyze the signature of the method to determine its return type, expression requirements and input arguments. - -> Methods on interfaces lack many of the features of controllers such as being able to perform [model state](../controllers/model-state) validation or provide access to `this.User` and `this.Request`. - -## Excluding Fields - -To exclude a single property that you don't want to expose to GraphQL add the `[GraphSkip]` attribute to it: - -```csharp title="Excluding a property" -public interface IPastry -{ - int Id { get; set; } - string Name { get; set; } - - // highlight-next-line - [GraphSkip] - decimal Price { get; set; } -} -``` - -```graphql title="IPastry Type Definition" -interface IPastry { - id: Int! - name: String - # price is not included -} -``` -Or force GraphQL to skip all fields except those you explicitly define with a `[GraphField]` attribute: - -```csharp title="Require explicit declarations for this type" -// highlight-next-line -[GraphType(FieldDeclarationRequirements = TemplateDeclarationRequirements.RequireAll)] -public interface IPastry -{ - [GraphField] - int Id { get; set; } - - [GraphField] - string Name { get; set; } - - decimal Price { get; set; } -} -``` - -```graphql title="IPastry Type Definition" -# only id and name are included -interface IPastry { - id: Int! - name: String -} -``` - -Or set a schema-wide option during startup: - -```csharp title="Set Field Declaration Requirements at Startup" -services.AddGraphQL(options => - { - options.DeclarationOptions.FieldDeclarationRequirements = TemplateDeclarationRequirements.RequireAll; - }); -``` - -Your schema will follow a cascading model of inclusion rules in order of increasing priority from `schema -> class -> field` level declarations. This can be useful in multi-schema setups where a class may be shared but you don't want the exposed fields to be different or if there is a secure field that you want to guarantee is not exposed regardless of the schema. - -## Forced Interface Exclusions - -Perhaps there exists an interface in a shared assembly used amongst multiple work teams that contains some utility classes that absolutely, positively CANNOT be exposed to GraphQL at any cost. - -In these cases, add `[GraphSkip]` to an interface and GraphQL will throw a `GraphTypeDeclarationException` if its ever asked to include it in a schema. - -```csharp title="Prevent a Type from EVER Being Included in the Graph" -// ERROR, GraphTypeDeclarationException will be thrown! -[GraphSkip] -public interface IPastry -{ - int Id { get; set; } - string Name { get; set; } - decimal Price { get; set; } -} -``` - -> This rule is enforced at the template level and is applied to the `System.Type`. Its not specific to the `INTERFACE` graph type. Any class, interface, enum etc. with the `[GraphSkip]` attribute will be permanantly skipped. \ No newline at end of file diff --git a/docs/types/list-non-null.html b/docs/types/list-non-null.html new file mode 100644 index 0000000..e98ff7d --- /dev/null +++ b/docs/types/list-non-null.html @@ -0,0 +1,27 @@ + + + + + +List & Non-Null | GraphQL ASP.NET + + + + +
+

List & Non-Null

In addition to the six fundamental graph types, GraphQL contains two meta graph types: LIST and NON_NULL.

  • NON_NULL : Indicates that the Graph Type its describing must not be a null value, be that as an input argument or returned from a field
  • LIST: Indicates that GraphQL should expect a collection of objects instead of just a single item.

These meta types aren't anything concrete like a scalar or an enum. Instead they "wrap" another graph type (such as int or Donut). They are used to describe the usage of a graph type in a field or input argument:

For example, we would say:

  • "A field that returns a Float number."
  • "A field that must return a Person."
  • "An input argument that must be a Date."

We can even describe complex scenarios:

  • "A field that might return a collection of persons but when returned, each person must be a valid reference."
  • "An input argument that must be a list that contains lists of integers." (e.g. [[1, 2], [5, 15]])

Type Expressions

Together these "wrappers" make up a field's Type Expression. GraphQL ASP.NET will automatically infer a type expression for every field and every input argument when generating your schema.

The following assumptions about your data are made when creating type expressions:

✅ Reference types can be null
+✅ Value types cannot be null
+✅ Nullable value types (e.g. int?) can be null
+✅ When a reference type implements IEnumerable<TType> it will be expressed as a "list of TType"

Type Expressions are commonly shown in the GraphQL schema syntax for field definitions. Here are a few examples of a .NET type and its equivalent type expression in schema syntax.

.NET TypeType Expression
intInt!
float?Float
IEnumerable<Person>[Person]
Person[][Person]
List<bool>[Boolean!]
IReadOnlyList<long>[Long!]
IReadOnlyList<long?>[Long]
IEnumerable<List<ICollection<Donut>>>[[[Donut]]]

The ! indicates NON_NULL and [] for a LIST.

Overriding Type Expressions

You may need to override the default behavior from time to time. For instance, a string, which is a reference type, is nullable by default but you may want to enforce non-nullability at the query level and declare that null is not valid for a given argument. Or, perhaps, an object implements IEnumerable but you don't want graphql to treat it as a list.

You can override the default type expression of any field or argument by defining a custom type expression when needed.

+ + + + \ No newline at end of file diff --git a/docs/types/list-non-null.md b/docs/types/list-non-null.md deleted file mode 100644 index b61a94a..0000000 --- a/docs/types/list-non-null.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -id: list-non-null -title: List & Non-Null -sidebar_label: List & Non-Null -sidebar_position: 6 ---- - -In addition to the six fundamental graph types, GraphQL contains two meta graph types: [LIST and NON_NULL](https://graphql.org/learn/schema/#lists-and-non-null). - -- `NON_NULL` : Indicates that the Graph Type its describing must not be a null value, be that as an input argument or returned from a field -- `LIST`: Indicates that GraphQL should expect a collection of objects instead of just a single item. - -These meta types aren't anything concrete like a scalar or an enum. Instead they "wrap" another graph type (such as `int` or `Donut`). They are used to describe the usage of a graph type in a field or input argument: - -For example, we would say: - -- "A field that returns a `Float` number." -- "A field that must return a `Person`." -- "An input argument that must be a `Date`." - -We can even describe complex scenarios: - -- "A field that **might** return a collection of `persons` but when returned, each person **must** be a valid reference." -- "An input argument that **must** be a list that contains lists of `integers`." (e.g. `[[1, 2], [5, 15]]`) - -## Type Expressions - -Together these "wrappers" make up a field's `Type Expression`. GraphQL ASP.NET will automatically infer a type expression for every field and every input argument when generating your schema. - -The following assumptions about your data are made when creating type expressions: - -✅ Reference types **can be** null
-✅ Value types **cannot be** null
-✅ Nullable value types (e.g. `int?`) **can be** null
-✅ When a reference type implements `IEnumerable` it will be expressed as a "list of `TType`" - -Type Expressions are commonly shown in the GraphQL schema syntax for field definitions. Here are a few examples of a .NET type and its equivalent type expression in schema syntax. - -| .NET Type | Type Expression | -| --------------------------------------- | --------------- | -| int | Int! | -| float? | Float | -| IEnumerable<Person> | [Person] | -| Person[] | [Person] | -| List<bool> | [Boolean!] | -| IReadOnlyList<long> | [Long!] | -| IReadOnlyList<long?> | [Long] | -| IEnumerable<List<ICollection<Donut>>> | [[[Donut]]] | - -> The `!` indicates NON_NULL and `[]` for a LIST. - -### Overriding Type Expressions - -You may need to override the default behavior from time to time. For instance, a `string`, which is a reference type, is nullable by default but you may want to enforce non-nullability at the query level and declare that null is not valid for a given argument. Or, perhaps, an object implements `IEnumerable` but you don't want graphql to treat it as a list. - -You can override the default type expression of any field or argument by defining a [custom type expression](../advanced/type-expressions) when needed. - diff --git a/docs/types/objects.html b/docs/types/objects.html new file mode 100644 index 0000000..8ae1c4a --- /dev/null +++ b/docs/types/objects.html @@ -0,0 +1,24 @@ + + + + + +The Object Graph Type | GraphQL ASP.NET + + + + +
+

The Object Graph Type

The OBJECT graph type is one of six fundamental types defined by GraphQL. We can think of a graph query like a tree and if scalar values, such as string and int, are the leafs then objects are the branches.

✅ Use a class or struct to identify an object type in a schema.

Here we've defined a Donut model class. The runtime will convert it, automatically, into an object graph type. If you're familiar with GraphQL's own type definition language the equivalent expression is shown below.

Donut.cs
public class Donut
{
public int Id { get; set; }
public string Name { get; set; }
public DonutType Type { get; set; }
public decimal Price { get; set; }
}
Donut Type Definition
type Donut {
id: Int!
name: String
type: DonutType!
price: Decimal!
}

By Default, object graph types:

  • Are named the same as the class or struct name
  • Have all public properties with a get statement included as fields
    • The return type of a property must be of an acceptable type or it will be skipped

You can override the default settings in your schema configuration or by use of the GraphType and GraphField attributes.

Custom Naming

Give the object a different name in the graph from that of your C# class with the [GraphType] attribute.

Declaring a Custom Type Name
[GraphType("Doughnut")]
public class Donut
{
public int Id { get; set; }
public string Name { get; set; }
public DonutType Type { get; set; }
public decimal Price { get; set; }
}
Donut Type Definition
type Doughnut {
id: Int!
name: String
type: DonutType!
price: Decimal!
}

Methods as Fields

By default, POCO class methods are excluded from being fields on the graph but can be added by tagging the method with [GraphField].

Including a POCO method as a field
public class Donut
{
[GraphField("salesTax")]
public decimal CalculateSalesTax(decimal taxPercentage)
{
return this.Price * taxPercentage;
}

public int Id { get; set; }
public string Name { get; set; }
public DonutType Type { get; set; }
public decimal Price { get; set; }
}
Donut Type Definition
type Donut {
salesTax (taxPercentage: Decimal!): Decimal!
id: Int!
name: String
type: DonutType!
price: Decimal!
}

Just as with controller actions, GraphQL will analyze the signature of the method to determine its return type, expression requirements and input arguments.

Methods on POCO classes lack many of the features of controllers such as being able to perform model state validation or provide access to this.User and this.Request.

Excluding Fields

To exclude a single property that you don't want to expose to GraphQL add the [GraphSkip] attribute to it:

Excluding a property
public class Donut
{
public int Id { get; set; }
public string Name { get; set; }
public DonutType Type { get; set; }

[GraphSkip]
public decimal Price { get; set; }
}
Donut Type Definition
type Donut {
id: Int!
name: String
type: DonutType!
# price is not included
}

Or force GraphQL to skip all fields except those you explicitly define with a [GraphField] attribute:

Require explicit declarations for this type
[GraphType(FieldDeclarationRequirements = TemplateDeclarationRequirements.RequireAll)]
public class Donut
{
[GraphField]
public int Id { get; set; }

[GraphField]
public string Name { get; set; }

public DonutType Type { get; set; }
public decimal Price { get; set; }
}
Donut Type Definition
# only id and name are included
type Donut {
id: Int!
name: String
}

Or set a schema-wide option during startup:

Set Field Declaration Requirements at Startup
services.AddGraphQL(options =>
{
options.DeclarationOptions.FieldDeclarationRequirements = TemplateDeclarationRequirements.RequireAll;
});

Your schema will follow a cascading model of inclusion rules in order of increasing priority from schema -> class -> field level declarations. This can be useful in multi-schema setups where a class may be shared but you don't want the exposed fields to be different or if there is a secure field that you want to guarantee is not exposed regardless of the schema.

Excluding A Class

By Default, GraphQL won't include your class in a schema unless:

  • Its referenced in a controller OR
  • Referenced by a graph type that is referenced in a controller OR
  • Tagged with [GraphType].

But certian schema configurations can override this behavior and allow GraphQL to greedily include classes that it'll never use. This can expose them in an introspection query unintentionally. You can flag a class such that it skipped unless GraphQL can determine that the object is required to fulfill a request to the schema.

This is also helpful to prevent objects that are only used as an INPUT_OBJECT from being accidentally added as an OBJECT and can reduce the clutter in your schema.

Prevent the Type From Being Auto Included
[GraphType(PreventAutoInclusion = true)]
public class Donut
{
[GraphField]
public int Id { get; set; }

[GraphField]
public string Name { get; set; }

public DonutType Type { get; set; }
public decimal Price { get; set; }
}

Forced Class Exclusions

There are times where preventing auto-inclusion is not enough. Perhaps there is a shared assembly amongst work teams that contains some graph types and some utility classes that absolutely, positively CANNOT be exposed to GraphQL at any cost. Yet those classes are required for the graph types to function.

In these cases, add [GraphSkip] to the class itself and GraphQL will throw a GraphTypeDeclarationException if its ever asked to include the class in a schema. Be that as an explicit reference or through discovery in a controller. This will occur when the schema is first initialized, rendering your application dead. But better a crash when a developer is testing a new change vs. unknowingly leaking sensitive information.

Prevent a Type from EVER Being Included in the Graph
// ERROR, GraphTypeDeclarationException will be thrown!
[GraphSkip]
public class SuperSensitiveData
{
public int SecretSaltPhrase => "123456";
}

This rule is enforced at the template level and is applied to the System.Type across the board. Any class, interface, enum etc. with the [GraphSkip] attribute will be permanantly skipped.

Structs as Objects

The usage of struct types as an OBJECT graph type is fully supported. The same rules listed above that apply to class types also apply to struct types. The only difference is that since structs are value types there are non-nullable by default.

Using a Coffee struct
public class CoffeeController: GraphController
{
[QueryRoot]
public Coffee RetrieveCoffee(string flavor){ /*...*/}
}

public struct Coffee
{
public string Flavor{ get; set; }
}
GraphQL Type Definition
# Coffee must be returned
type Query {
retrieveCoffee(flavor: String) : Coffee!
}

Use the standard Nullable<T> syntax to make them nullable (e.g. Coffee?):

Using a 'Nullable' Coffee struct
public class CoffeeController: GraphController
{
[QueryRoot]
public Coffee? RetrieveCoffee(string flavor){ /*...*/}
}

public struct Coffee
{
public string Flavor{ get; set; }
}
GraphQL Type Definition
# Coffee or null may be returned
type Query {
retrieveCoffee(flavor: String) : Coffee
}

Reuse as Input Objects

Both class and struct types can be used as an INPUT_OBJECT and an OBJECT graph type. See the section on input objects for some of the key differences and requirements.

Object Inheritance

Class inheritance as we think of it in .NET is not concept in GraphQL. As a result, there is no association between two objects in the graph even if they share an inheritance structure in .NET.

C# Class Inheritance
public class Pastry
{
public int Id { get; set; }
public string Name { get; set; }
}

public class Donut : Pastry
{
string Flavor{ get; set; }
}
GraphQL Type Definitions
# Pastry and Donut have similar fields 
# but are NOT related in the Graph
type Pastry {
id: Int!
name: String
}

type Donut {
id: Int!
name: String
flavor: String
}
tip

GraphQL ASP.NET is smart enough to figure out your intent with object use (i.e. Liskov Subsitutions). If you return a Donut where a Pastry is indicated by the graph. The library will happily use your donut as a pastry for any field resolutions.

Implementing Interfaces

Read the section on interfaces for details on how to use them.

Graphql will not attempt to "auto include" the interfaces implemented by your objects in your schema. This is both a security measure to prevent information from leaking as well as a decluttering technique. Its unlikely that your schema cares about the myriad of interfaces you may declare on your business objects.

However, when an interface is included in schema, graphql will sense the inclusion and automatically wire up the necessary information on your objects that implement.

Including a Pastry
public class PastryController: GraphController
{
[QueryRoot("retrievePastry", typeof(Donut))]
public IPastry RetrievePastry(string id){/* ... */}
}
GraphQL Type Definition
interface IPastry {
id: String!
name: String
}

# donut will automatically declare that it implements IPastry when its
# included in the schema
type Donut implements IPastry {
id: String!
name: String
flavor: DonutFlavor
}
+ + + + \ No newline at end of file diff --git a/docs/types/objects.md b/docs/types/objects.md deleted file mode 100644 index b593ad3..0000000 --- a/docs/types/objects.md +++ /dev/null @@ -1,325 +0,0 @@ ---- -id: objects -title: The Object Graph Type -sidebar_label: Objects -sidebar_position: 0 ---- - -The `OBJECT` graph type is one of six fundamental types defined by GraphQL. We can think of a graph query like a tree and if [scalar values](./scalars), such as `string` and `int`, are the leafs then objects are the branches. - -✅ Use a `class` or `struct` to identify an object type in a schema. - -Here we've defined a `Donut` model class. The runtime will convert it, automatically, into an object graph type. If you're familiar with GraphQL's own type definition language the equivalent expression is shown below. - -```csharp title="Donut.cs" -public class Donut -{ - public int Id { get; set; } - public string Name { get; set; } - public DonutType Type { get; set; } - public decimal Price { get; set; } -} -``` - -```graphql title="Donut Type Definition" -type Donut { - id: Int! - name: String - type: DonutType! - price: Decimal! -} -``` - -By Default, object graph types: - -- Are named the same as the `class` or `struct` name -- Have all public properties with a `get` statement included as fields - - The return type of a property must be of an acceptable type or it will be skipped - -> You can override the default settings in your [schema configuration](../reference/schema-configuration.md#fielddeclarationrequirements) or by use of the [GraphType](../reference/attributes.md#graphtype) and [GraphField](../reference/attributes.md#graphfield) attributes. - -## Custom Naming - -Give the object a different name in the graph from that of your C# class with the `[GraphType]` attribute. - -```csharp title="Declaring a Custom Type Name" -[GraphType("Doughnut")] -public class Donut -{ - public int Id { get; set; } - public string Name { get; set; } - public DonutType Type { get; set; } - public decimal Price { get; set; } -} -``` - -```graphql title="Donut Type Definition" -type Doughnut { - id: Int! - name: String - type: DonutType! - price: Decimal! -} -``` - -## Methods as Fields - -By default, POCO class methods are excluded from being fields on the graph but can be added by tagging the method with `[GraphField]`. - - -```csharp title="Including a POCO method as a field" -public class Donut -{ - // highlight-next-line - [GraphField("salesTax")] - public decimal CalculateSalesTax(decimal taxPercentage) - { - return this.Price * taxPercentage; - } - - public int Id { get; set; } - public string Name { get; set; } - public DonutType Type { get; set; } - public decimal Price { get; set; } -} -``` - -```graphql title="Donut Type Definition" -type Donut { - // highlight-next-line - salesTax (taxPercentage: Decimal!): Decimal! - id: Int! - name: String - type: DonutType! - price: Decimal! -} -``` - -Just as with [controller actions](../controllers/actions), GraphQL will analyze the signature of the method to determine its return type, expression requirements and input arguments. - -> Methods on POCO classes lack many of the features of controllers such as being able to perform [model state](../controllers/model-state) validation or provide access to `this.User` and `this.Request`. - -## Excluding Fields - -To exclude a single property that you don't want to expose to GraphQL add the `[GraphSkip]` attribute to it: - -```csharp title="Excluding a property" -public class Donut -{ - public int Id { get; set; } - public string Name { get; set; } - public DonutType Type { get; set; } - - // highlight-next-line - [GraphSkip] - public decimal Price { get; set; } -} -``` - -```graphql title="Donut Type Definition" -type Donut { - id: Int! - name: String - type: DonutType! - # price is not included -} -``` -Or force GraphQL to skip all fields except those you explicitly define with a `[GraphField]` attribute: - -```csharp title="Require explicit declarations for this type" -// highlight-next-line -[GraphType(FieldDeclarationRequirements = TemplateDeclarationRequirements.RequireAll)] -public class Donut -{ - [GraphField] - public int Id { get; set; } - - [GraphField] - public string Name { get; set; } - - public DonutType Type { get; set; } - public decimal Price { get; set; } -} -``` - -```graphql title="Donut Type Definition" -# only id and name are included -type Donut { - id: Int! - name: String -} -``` - -Or set a schema-wide option during startup: - -```csharp title="Set Field Declaration Requirements at Startup" -services.AddGraphQL(options => - { - options.DeclarationOptions.FieldDeclarationRequirements = TemplateDeclarationRequirements.RequireAll; - }); -``` - -Your schema will follow a cascading model of inclusion rules in order of increasing priority from `schema -> class -> field` level declarations. This can be useful in multi-schema setups where a class may be shared but you don't want the exposed fields to be different or if there is a secure field that you want to guarantee is not exposed regardless of the schema. - -## Excluding A Class - -By Default, GraphQL won't include your class in a schema unless: - -- Its referenced in a controller OR -- Referenced by a graph type that is referenced in a controller OR -- Tagged with `[GraphType]`. - -But certian schema configurations can override this behavior and allow GraphQL to greedily include classes that it'll never use. This can expose them in an introspection query unintentionally. You can flag a class such that it skipped unless GraphQL can determine that the object is required to fulfill a request to the schema. - -This is also helpful to prevent objects that are only used as an `INPUT_OBJECT` from being accidentally added as an `OBJECT` and can reduce the clutter in your schema. - - -```csharp title="Prevent the Type From Being Auto Included" -// highlight-next-line -[GraphType(PreventAutoInclusion = true)] -public class Donut -{ - [GraphField] - public int Id { get; set; } - - [GraphField] - public string Name { get; set; } - - public DonutType Type { get; set; } - public decimal Price { get; set; } -} -``` - -## Forced Class Exclusions - -There are times where preventing auto-inclusion is not enough. Perhaps there is a shared assembly amongst work teams that contains some graph types and some utility classes that absolutely, positively CANNOT be exposed to GraphQL at any cost. Yet those classes are required for the graph types to function. - -In these cases, add `[GraphSkip]` to the class itself and GraphQL will throw a `GraphTypeDeclarationException` if its ever asked to include the class in a schema. Be that as an explicit reference or through discovery in a controller. This will occur when the schema is first initialized, rendering your application dead. But better a crash when a developer is testing a new change vs. unknowingly leaking sensitive information. - - -```csharp title="Prevent a Type from EVER Being Included in the Graph" -// ERROR, GraphTypeDeclarationException will be thrown! -[GraphSkip] -public class SuperSensitiveData -{ - public int SecretSaltPhrase => "123456"; -} -``` - -> This rule is enforced at the template level and is applied to the `System.Type` across the board. Any class, interface, enum etc. with the `[GraphSkip]` attribute will be permanantly skipped. - -## Structs as Objects -The usage of `struct` types as an `OBJECT` graph type is fully supported. The same rules listed above that apply to `class` types also apply to `struct` types. The only difference is that since structs are value types there are non-nullable by default. - -```csharp title="Using a Coffee struct" -public class CoffeeController: GraphController -{ - [QueryRoot] - public Coffee RetrieveCoffee(string flavor){ /*...*/} -} - -public struct Coffee -{ - public string Flavor{ get; set; } -} -``` - -```graphql title="GraphQL Type Definition" -# Coffee must be returned -type Query { - retrieveCoffee(flavor: String) : Coffee! -} -``` - -Use the standard `Nullable` syntax to make them nullable (e.g. `Coffee?`): -```csharp title="Using a 'Nullable' Coffee struct" -public class CoffeeController: GraphController -{ - [QueryRoot] - public Coffee? RetrieveCoffee(string flavor){ /*...*/} -} - -public struct Coffee -{ - public string Flavor{ get; set; } -} -``` - -```graphql title="GraphQL Type Definition" -# Coffee or null may be returned -type Query { - retrieveCoffee(flavor: String) : Coffee -} -``` - -## Reuse as Input Objects - -Both `class` and `struct` types can be used as an `INPUT_OBJECT` and an `OBJECT` graph type. See the section on [input objects](./input-objects) for some of the key differences and requirements. - - -## Object Inheritance - -Class inheritance as we think of it in .NET is not concept in GraphQL. As a result, there is no association between two objects in the graph even if they share an inheritance structure in .NET. - -```csharp title="C# Class Inheritance" -public class Pastry -{ - public int Id { get; set; } - public string Name { get; set; } -} - -public class Donut : Pastry -{ - string Flavor{ get; set; } -} -``` - -```graphql title="GraphQL Type Definitions" -# Pastry and Donut have similar fields -# but are NOT related in the Graph -type Pastry { - id: Int! - name: String -} - -type Donut { - id: Int! - name: String - flavor: String -} -``` - -:::tip -GraphQL ASP.NET is smart enough to figure out your intent with object use (i.e. [Liskov Subsitutions](https://en.wikipedia.org/wiki/Liskov_substitution_principle)). If you return a `Donut` where a `Pastry` is indicated by the graph. The library will happily use your donut as a pastry for any field resolutions. -::: - -## Implementing Interfaces - -> Read the section on [interfaces](./interfaces.md) for details on how to use them. - -Graphql will not attempt to "auto include" the interfaces implemented by your objects in your schema. This is both a security measure to prevent information from leaking as well as a decluttering technique. Its unlikely that your schema cares about the myriad of interfaces you may declare on your business objects. - -However, when an interface is included in schema, graphql will sense the inclusion and automatically wire up the necessary information on your objects that implement. - -```csharp title="Including a Pastry" -public class PastryController: GraphController -{ - [QueryRoot("retrievePastry", typeof(Donut))] - public IPastry RetrievePastry(string id){/* ... */} -} -``` -```graphql title="GraphQL Type Definition" -interface IPastry { - id: String! - name: String -} - -# donut will automatically declare that it implements IPastry when its -# included in the schema -// highlight-next-line -type Donut implements IPastry { - id: String! - name: String - flavor: DonutFlavor -} -``` diff --git a/docs/types/scalars.html b/docs/types/scalars.html new file mode 100644 index 0000000..0a4bf9e --- /dev/null +++ b/docs/types/scalars.html @@ -0,0 +1,24 @@ + + + + + +Scalars | GraphQL ASP.NET + + + + +
+

Scalars

Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being enums). You can extend GraphQL with your own custom scalars when needed.

GraphQL ASP.NET has 20 built in scalar types.

Scalar Name.NET TypeAllowed Input Value
BooleanSystem.BooleanBoolean
ByteSystem.ByteNumber
DateOnlySystem.DateOnlyString or Number
DateTimeSystem.DateTimeString or Number
DateTimeOffsetSystem.DateTimeOffsetString or Number
DecimalSystem.DecimalNumber
DoubleSystem.DoubleNumber
FloatSystem.SingleNumber
GuidSystem.GuidString
IDGraphQL.AspNet.GraphIdString or Number
IntSystem.Int32Number
LongSystem.Int64Number
ShortSystem.Int16Number
StringSystem.StringString
SignedByteSystem.SByteNumber
TimeOnlySystem.TimeOnlyString
UIntSystem.UInt32Number
ULongSystem.UInt64Number
UriSystem.UriString
UShortSystem.UInt16Number
info

You must target .NET 8.0 or later to use DateOnly and TimeOnly

Input Value Resolution

When a scalar value is resolved, it's read from the query document (or variable collection) in one of three ways:

  • String : A string of characters, delimited by "quotes"
  • Boolean The value true or false without quotes
  • Number A sequence of numbers with an optional decimal point, negative sign or the letter e without quotes
    • example: -123.456e78
    • GraphQL numbers must conform to the IEEE 754 standard [Spec § 3.5.2]

Scalars used as input arguments require that any supplied value match at least one supported input format before they will attempt to convert the value into the related .NET type. If the value read from the document doesn't match an approved format it is rejected before conversion is attempted.

For example, the library will accept dates as numbers or strings. If you try to supply a boolean value, true, the query is rejected outright and no parsing attempt is made. This can come in handy for custom scalar types that may have multiple serialization options.

See the table above for the list of allowed formats per scalar type.

Working With Dates

Date valued scalars (e.g. DateTime, DateTimeOffset, DateOnly) can be supplied as an RFC 3339 compliant string value or as a number representing the amount of time from the Unix Epoch.

Examples:

Supplied ValueParsed Date
"2022-12-30T18:30:38.259+00:00"Dec. 30, 2022 @ 6:30:38.259 PM (UTC - 0)
"2022-12-30"Dec. 30, 2022 @ 00:00:00.000 AM (UTC - 0)
1586965574234April 15, 2020 @ 3:46:14.234 AM (UTC - 0)
1586940374April 15, 2020 @ 3:46:14.000 AM (UTC - 0)
Epoch Values

Dates supplied as an epoch number can be supplied with or without milliseconds.

Note: If a time component is supplied to a DateOnly scalar, it will be truncated and only the date portion will be used.

By Default, the library will serialize all dates as an RFC 3339 compliant string.

Scalar Names Are Fixed

Unlike other graph types, scalar names are fixed across all schemas. The name defined above (including casing), is how they appear in your schema's introspection queries. These names conform to the accepted standard for graphql type names. This is true for any custom scalars you may build as well.

Nullable<T>

int?, float? etc.

For the value types listed above, GraphQL will automatically coerce values into the appropriate Nullable<T> as required by an argument's type expression.

ID Scalar

GraphQL defines a special scalar value value called ID which is defined as:

a unique identifier, often used to refetch an object or as the key for a cache" [Spec § 3.5.5].

GraphQL ASP.NET maintains a struct, GraphQL.AspNet.GraphId to hold this value and will always serialize it to a string. However, per the specification, when supplying values on a query document, ID can accept strings or integers as input values. Floating point numbers and boolean values are not allowed.

  • Valid ID values
    • "abc"
    • 34
    • -200
  • Invalid ID Values
    • true
    • 4.0
Integer Value Range

When using an integer value for an ID scalar, the minimum allowed value is long.MinValue and the maximum allowed value is ulong.MaxValue

You can perform an implicit and explicit conversion between GraphId and System.String as well.

Converting GraphId
GraphId id = new GraphId("abc");
string str = id;
// str == "abc"

string str = "abc";
GraphId id = (GraphId)str;
// id.Value == "abc"

Custom Scalars

See the section on custom scalars for details on creating your own scalar types.

Working with Structs

Structs, by default, will be treated like object graph types. Sometimes it may make sense to create a custom scalar out of a struct, for example, the default scalar for Guid. Use your best judgement when determining if a struct should be a scalar or not. But always try to opt for fewer scalars when possible.

+ + + + \ No newline at end of file diff --git a/docs/types/scalars.md b/docs/types/scalars.md deleted file mode 100644 index 2b735ec..0000000 --- a/docs/types/scalars.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -id: scalars -title: Scalars -sidebar_label: Scalars -sidebar_position: 5 ---- - -Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being [enums](./enums)). You can extend GraphQL with your own [custom scalars](../advanced/custom-scalars) when needed. - -GraphQL ASP.NET has 20 built in scalar types. - -| Scalar Name | .NET Type | Allowed Input Value | -| -------------- | ---------------------- | ------------------- | -| Boolean | System.Boolean | Boolean | -| Byte | System.Byte | Number | -| DateOnly | System.DateOnly | String or Number | -| DateTime | System.DateTime | String or Number | -| DateTimeOffset | System.DateTimeOffset | String or Number | -| Decimal | System.Decimal | Number | -| Double | System.Double | Number | -| Float | System.Single | Number | -| Guid | System.Guid | String | -| ID | GraphQL.AspNet.GraphId | String or Number | -| Int | System.Int32 | Number | -| Long | System.Int64 | Number | -| Short | System.Int16 | Number | -| String | System.String | String | -| SignedByte | System.SByte | Number | -| TimeOnly | System.TimeOnly | String | -| UInt | System.UInt32 | Number | -| ULong | System.UInt64 | Number | -| Uri | System.Uri | String | -| UShort | System.UInt16 | Number | - -:::info - You must target .NET 8.0 or later to use `DateOnly` and `TimeOnly` -::: - -## Input Value Resolution - -When a scalar value is resolved, it's read from the query document (or variable collection) in one of three ways: - -- **String** : A string of characters, delimited by `"quotes"` -- **Boolean** The value `true` or `false` without quotes -- **Number** A sequence of numbers with an optional decimal point, negative sign or the letter `e` without quotes - - example: `-123.456e78` - - GraphQL numbers must conform to the [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) standard [Spec § [3.5.2](https://graphql.github.io/graphql-spec/October2021/#sec-Float)] - -Scalars used as input arguments require that any supplied value match at least one supported input format before they will attempt to convert the value into the related .NET type. If the value read from the document doesn't match an approved format it is rejected before conversion is attempted. - -For example, the library will accept dates as numbers or strings. If you try to supply a boolean value, `true`, the query is rejected outright and no parsing attempt is made. This can come in handy for custom scalar types that may have multiple serialization options. - -See the table above for the list of allowed formats per scalar type. - -#### Working With Dates - -Date valued scalars (e.g. `DateTime`, `DateTimeOffset`, `DateOnly`) can be supplied as an [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339) compliant string value or as a number representing the amount of time from the [Unix Epoch](https://en.wikipedia.org/wiki/Unix_time). - -Examples: - -| Supplied Value | Parsed Date | -|-----------------|-------------| -|`"2022-12-30T18:30:38.259+00:00"` |Dec. 30, 2022 @ 6:30:38.259 PM (UTC - 0) | -|`"2022-12-30"` |Dec. 30, 2022 @ 00:00:00.000 AM (UTC - 0) | -|`1586965574234` | April 15, 2020 @ 3:46:14.234 AM (UTC - 0) | -|`1586940374` | April 15, 2020 @ 3:46:14.000 AM (UTC - 0) | - -:::tip Epoch Values -Dates supplied as an epoch number can be supplied with or without milliseconds. -::: - -> Note: If a time component is supplied to a `DateOnly` scalar, it will be truncated and only the date portion will be used. - -By Default, the library will serialize all dates as an `RFC 3339` compliant string. - - -## Scalar Names Are Fixed - -Unlike other graph types, scalar names are fixed across all schemas. The name defined above (including casing), is how they appear in your schema's introspection queries. These names conform to the accepted standard for graphql type names. This is true for any custom scalars you may build as well. - -#### Nullable<T> - -`int?`, `float?` etc. - -For the value types listed above, GraphQL will automatically coerce values into the appropriate `Nullable` as required by an argument's type expression. - -## ID Scalar - -GraphQL defines a special scalar value value called `ID` which is defined as: - -> _a unique identifier, often used to refetch an object or as the key for a cache_" [Spec § [3.5.5](https://graphql.github.io/graphql-spec/October2021/#sec-ID)]. - -GraphQL ASP.NET maintains a struct, `GraphQL.AspNet.GraphId` to hold this value and will always serialize it to a string. However, per the specification, when supplying values on a query document, ID can accept strings or integers as input values. Floating point numbers and boolean values are not allowed. - -- Valid ID values - - `"abc"` - - `34` - - `-200` -- Invalid ID Values - - `true` - - `4.0` - -:::note Integer Value Range -When using an integer value for an ID scalar, the minimum allowed value is `long.MinValue` and the maximum allowed value is `ulong.MaxValue` -::: - -You can perform an implicit and explicit conversion between `GraphId` and `System.String` as well. - -```csharp title="Converting GraphId" -GraphId id = new GraphId("abc"); -string str = id; -// str == "abc" - -string str = "abc"; -GraphId id = (GraphId)str; -// id.Value == "abc" -``` - -## Custom Scalars -See the section on [custom scalars](../advanced/custom-scalars.md) for details on creating your own scalar types. - -### Working with Structs - -Structs, by default, will be treated like [object graph types](./objects.md). Sometimes it may make sense to create a custom scalar out of a struct, for example, the default scalar for `Guid`. Use your best judgement when determining if a struct should be a scalar or not. But always try to opt for fewer scalars when possible. \ No newline at end of file diff --git a/docs/types/unions.html b/docs/types/unions.html new file mode 100644 index 0000000..67a5543 --- /dev/null +++ b/docs/types/unions.html @@ -0,0 +1,24 @@ + + + + + +Unions | GraphQL ASP.NET + + + + +
+

Unions

Unions are an aggregate graph type representing multiple, different OBJECT types with no guaranteed fields or interfaces in common; for instance, Salad or House. Because of this, unions define no fields themselves but provide a common way to query the fields of the union members when one is encountered.

Unlike other graph types there is no concrete representation of unions. Where a class is an object graph type or a .NET enum is an enum graph type there is no analog for unions. Instead unions are semi-virtual types that are created from proxy classes that represent them at design time.

Declaring a Union

You can declare a union in your action method using one of the many overloads to the query and mutation attributes:

Declaring an 'inline' Union on an Action Method
public class DataController : GraphController
{
[QueryRoot("search", "SaladOrHouse", typeof(Salad), typeof(House))]
public ????? SearchData(string name)
{/* ... */}
}
Example Query
query {
search(name: "green*") {
...on Salad {
name
hasCroutons
}

...on House {
postalCode
squareFeet
}
}
}

In this example we :

  • Declared an action method named SearchData with a graph field name of search
  • Declared a union type on our graph named SaladOrHouse
  • Included two object types in the union: Salad and House
tip

Unlike with interfaces where the possible types returned from an action method can be declared else where, you MUST provide all of the types to include in the union in the declaration.

What to Return for a Union

Notice we have a big question mark on what the action method returns in the above example. From a C# perspective, in this example, there is no IDataItem interface shared between Salad and House. This represents a problem for static-typed languages like C#. Since unions are virtual types there exists no common type that you can return for generated data. System.Object might work but it tends be too general and the runtime will reject it as a safe guard.

So what do you do? Return an IGraphActionResult instead and let the runtime handle the details.

Return IGraphActionResult When Working With Unions
public class DataController : GraphController
{
// service injection omitted for brevity

[QueryRoot("search", "SaladOrHouse", typeof(Salad), typeof(House))]
public async Task<IGraphActionResult> SearchData(string text)
{
if(name.Contains("green"))
{
Salad salad = await _saladService.FindSalad(text);
return this.Ok(salad);
}
else
{
House house = await _houses.FindHouse(text);
return this.Ok(house);
}
}
}
info

Any controller action that declares a union MUST return an IGraphActionResult

Returning a List of Objects

Perhaps the most complex scenario when working with unions is returning a list of objects. Since there there is no way to declare a List<T> that the library could analyze we have to explicitly declare the field to let GraphQL what is going on.

Return a List of Objects
public class DataController : GraphController
{
// service injection omitted for brevity
[QueryRoot("search", "SaladOrHouse", typeof(Salad), typeof(House), TypeExpression = "[Type]")]
public async Task<IGraphActionResult> SearchData(string text)
{
Salad salad = await _saladService.FindSalad(text);
House house = await _houses.FindHouse(text);

var dataItems = new List<object>();
dataItems.Add(salad);
dataItems.Add(house);

return this.Ok(dataItems);
}
}

Here we've added a custom type expression to tell GraphQL that this field returns a list of objects. GraphQL will then process each item on the list according to the rules of the union.

Union Proxies

In the example above, we declare the union inline on the query attribute. But what if we wanted to reuse the SaladOrHouse union in multiple places. You could declare the union exactly the same on each method or use a union proxy.

Create a class that implements IGraphUnionProxy or inherits from GraphUnionProxy to encapsulate the details, then add that as a reference in your controller methods instead of the individual types. This can also be handy for uncluttering your code if you have a lot of possible types for the union. The return type of your method will still need to be IGraphActionResult. You cannot return a proxy as a value.

Example Using IGraphUnionProxy
public class KitchenController : GraphController
{
[QueryRoot("searchFood", typeof(SaladOrHouse))]
public async Task<IGraphActionResult> SearchFood(string name)
{/* ... */}
}

public class SaladOrHouse : GraphUnionProxy
{
public SaladOrHouse()
{
this.Name = "SaladOrHouse";
this.AddType(typeof(Salad));
this.AddType(typeof(House));
}
}

If you don't supply a name, graphql will use the class name of the proxy as the name of the union.

Union Name Uniqueness

Union names must be unique in a schema. If you do declare a union in multiple action methods without a proxy, GraphQL will attempt to merge the references by name and included types. As long as all declarations are the same, that is the name and the set of included types, then graphql will accept the union. Otherwise, a GraphTypeDeclarationException will be thrown at startup.

An Invalid Union Declaration
public class KitchenController : GraphController
{
[QueryRoot("search", "SaladOrHouse", typeof(Salad), typeof(House))]
public async Task<IGraphActionResult> SearchData(string name)
{/* ... */}

// ERROR: Union members for 'SaladOrHouse' are different
// -----------------
[QueryRoot("fetch", "SaladOrHouse", typeof(Salad), typeof(House), typeof(GameConsole))]
public async Task<IGraphActionResult> RetrieveItem(int id)
{/* ... */}
}

Liskov Substitutions

Liskov substitutions (the L in SOLID) are an important part of object oriented programming. To be able to have one class masquerade as another allows us to easily extend our code's capabilities without any rework.

For Example, the Oven object below can bake any type of bread!

Liskov Substitution Example
public class Bread
{}

public class Roll : Bread
{}

public class Bagel : Roll
{}

public class Oven
{
public void Bake(Bread bread)
{
// We can pass in Bread, Roll or Bagel to the oven.
}
}

However, this presents a problem when when dealing with UNIONs and GraphQL:

BakeryController.cs
public class BakeryController : GraphController
{
[QueryRoot("searchFood", "RollOrBread", typeof(Roll), typeof(Bread))]
public IGraphActionResult SearchFood(string name)
{
// Should GraphQL treat a bagel
// as a Roll or Bread ??
var myBagel = new Bagel();
return this.Ok(myBagel);
}
}
Sample Query
query {
searchFood(name: "Everything"){
... on Bread {
name,
type
}
... on Roll {
name,
hardness
}
}
}

Most of the time, graphql can correctly interpret the correct type of a returned data object and continue processing the query. However, in the above example, we declare a union, RollOrBread, that is of types Roll or Bread yet we return a Bagel from the action method.

Since Bagel is both a Roll and Bread which type should graphql match against when executing the inline fragments? Since it could be either, graphql will be unable to determine which type to use and can't advance the query to select the appropriate fields. The query result is said to be indeterminate.

IGraphUnionProxy.MapType

Luckily there is a way to allow you to take control of your unions and make the determination on your own. The MapType method provided by IGraphUnionProxy will be called whenever a query result is indeterminate, allowing you to choose which of your union's allowed types should be used.

Using a Custom Type Mapper
public class RollOrBread : GraphUnionProxy
{
public RollOrBread()
{
this.AddType(typeof(Roll));
this.AddType(typeof(Bread));
}

public override Type MapType(Type runtimeObjectType)
{
if (runtimeObjectType == typeof(Bagel))
return typeof(Roll);
else
return typeof(Bread);
}
}

The query will now interpret all Bagels as Rolls and be able to process the query correctly.

If, via your logic you are unable to determine which of your Union's types to use then return null and GraphQL will supply the caller with an appropriate error message stating the query was indeterminate. Also, returning any type other than one that was formally declared as part of your Union will result in the same indeterminate state.

Most of the time GraphQL ASP.NET will never call MapType on your union proxy. If your union types do not share an inheritance chain, for instance, the method will never be called.

caution

The MapType() function is not based on a resolved value, but only on the System.Type that was encountered. This is by design to guarantee consistency in query execution.

+ + + + \ No newline at end of file diff --git a/docs/types/unions.md b/docs/types/unions.md deleted file mode 100644 index 75e8718..0000000 --- a/docs/types/unions.md +++ /dev/null @@ -1,255 +0,0 @@ ---- -id: unions -title: Unions -sidebar_label: Unions -sidebar_position: 3 ---- - -Unions are an aggregate graph type representing multiple, different `OBJECT` types with no guaranteed fields or interfaces in common; for instance, `Salad` or `House`. Because of this, unions define no fields themselves but provide a common way to query the fields of the union members when one is encountered. - -Unlike other graph types there is no concrete representation of unions. Where a `class` is an object graph type or a .NET `enum` is an enum graph type there is no analog for unions. Instead unions are semi-virtual types that are created from proxy classes that represent them at design time. - -## Declaring a Union - -You can declare a union in your action method using one of the many overloads to the query and mutation attributes: - -```csharp title="Declaring an 'inline' Union on an Action Method" -public class DataController : GraphController -{ - // highlight-next-line - [QueryRoot("search", "SaladOrHouse", typeof(Salad), typeof(House))] - public ????? SearchData(string name) - {/* ... */} -} -``` - -```graphql title="Example Query" -query { - search(name: "green*") { - ...on Salad { - name - hasCroutons - } - - ...on House { - postalCode - squareFeet - } - } -} -``` - -In this example we : - -- Declared an action method named `SearchData` with a graph field name of `search` -- Declared a union type on our graph named `SaladOrHouse` -- Included two object types in the union: `Salad` and `House` - -:::tip -Unlike with [interfaces](./interfaces) where the possible types returned from an action method can be declared else where, you MUST provide all of the types to include in the union in the declaration. -::: - -### What to Return for a Union - -Notice we have a big question mark on what the action method returns in the above example. From a C# perspective, in this example, there is no `IDataItem` interface shared between `Salad` and `House`. This represents a problem for static-typed languages like C#. Since unions are virtual types there exists no common type that you can return for generated data. `System.Object` might work but it tends be too general and the runtime will reject it as a safe guard. - -So what do you do? Return an `IGraphActionResult` instead and let the runtime handle the details. - -```csharp title="Return IGraphActionResult When Working With Unions" -public class DataController : GraphController -{ - // service injection omitted for brevity - - [QueryRoot("search", "SaladOrHouse", typeof(Salad), typeof(House))] - // highlight-next-line - public async Task SearchData(string text) - { - if(name.Contains("green")) - { - Salad salad = await _saladService.FindSalad(text); - return this.Ok(salad); - } - else - { - House house = await _houses.FindHouse(text); - return this.Ok(house); - } - } -} -``` - -:::info -Any controller action that declares a union MUST return an `IGraphActionResult` -::: - -#### Returning a List of Objects -Perhaps the most complex scenario when working with unions is returning a list of objects. Since there there is no way to declare a `List` that the library could analyze we have to explicitly declare the field to let GraphQL what is going on. - - -```csharp title="Return a List of Objects" -public class DataController : GraphController -{ - // service injection omitted for brevity - // highlight-next-line - [QueryRoot("search", "SaladOrHouse", typeof(Salad), typeof(House), TypeExpression = "[Type]")] - public async Task SearchData(string text) - { - Salad salad = await _saladService.FindSalad(text); - House house = await _houses.FindHouse(text); - - var dataItems = new List(); - dataItems.Add(salad); - dataItems.Add(house); - - return this.Ok(dataItems); - } -} -``` -> Here we've added a custom type expression to tell GraphQL that this field returns a list of objects. GraphQL will then process each item on the list according to the rules of the union. - -## Union Proxies - -In the example above, we declare the union inline on the query attribute. But what if we wanted to reuse the `SaladOrHouse` union in multiple places. You could declare the union exactly the same on each method or use a union proxy. - -Create a class that implements `IGraphUnionProxy` or inherits from `GraphUnionProxy` to encapsulate the details, then add that as a reference in your controller methods instead of the individual types. This can also be handy for uncluttering your code if you have a lot of possible types for the union. The return type of your method will still need to be `IGraphActionResult`. You cannot return a proxy as a value. - -```csharp title="Example Using IGraphUnionProxy" -public class KitchenController : GraphController -{ - // highlight-next-line - [QueryRoot("searchFood", typeof(SaladOrHouse))] - public async Task SearchFood(string name) - {/* ... */} -} - -// highlight-next-line -public class SaladOrHouse : GraphUnionProxy -{ - public SaladOrHouse() - { - this.Name = "SaladOrHouse"; - this.AddType(typeof(Salad)); - this.AddType(typeof(House)); - } -} -``` - -> If you don't supply a name, graphql will use the class name of the proxy as the name of the union. - -## Union Name Uniqueness - -Union names must be unique in a schema. If you do declare a union in multiple action methods without a proxy, GraphQL will attempt to merge the references by name and included types. As long as all declarations are the same, that is the name and the set of included types, then graphql will accept the union. Otherwise, a `GraphTypeDeclarationException` will be thrown at startup. - -```csharp title="An Invalid Union Declaration" -public class KitchenController : GraphController -{ - // highlight-next-line - [QueryRoot("search", "SaladOrHouse", typeof(Salad), typeof(House))] - public async Task SearchData(string name) - {/* ... */} - - // ERROR: Union members for 'SaladOrHouse' are different - // ----------------- - // highlight-next-line - [QueryRoot("fetch", "SaladOrHouse", typeof(Salad), typeof(House), typeof(GameConsole))] - public async Task RetrieveItem(int id) - {/* ... */} -} -``` - -## Liskov Substitutions - -[Liskov substitutions](https://en.wikipedia.org/wiki/Liskov_substitution_principle) (the L in [SOLID](https://en.wikipedia.org/wiki/SOLID)) are an important part of object oriented programming. To be able to have one class masquerade as another allows us to easily extend our code's capabilities without any rework. - -For Example, the Oven object below can bake any type of bread! - -```csharp title="Liskov Substitution Example" -public class Bread -{} - -public class Roll : Bread -{} - -public class Bagel : Roll -{} - -public class Oven -{ - // highlight-next-line - public void Bake(Bread bread) - { - // We can pass in Bread, Roll or Bagel to the oven. - } -} -``` - -However, this presents a problem when when dealing with UNIONs and GraphQL: - -```csharp title="BakeryController.cs" -public class BakeryController : GraphController -{ - // highlight-next-line - [QueryRoot("searchFood", "RollOrBread", typeof(Roll), typeof(Bread))] - public IGraphActionResult SearchFood(string name) - { - // Should GraphQL treat a bagel - // as a Roll or Bread ?? - // highlight-next-line - var myBagel = new Bagel(); - return this.Ok(myBagel); - } -} -``` -```graphql title="Sample Query" -query { - searchFood(name: "Everything"){ - ... on Bread { - name, - type - } - ... on Roll { - name, - hardness - } - } -} -``` - -Most of the time, graphql can correctly interpret the correct type of a returned data object and continue processing the query. However, in the above example, we declare a union, `RollOrBread`, that is of types `Roll` or `Bread` yet we return a `Bagel` from the action method. - -Since `Bagel` is both a `Roll` and `Bread` which type should graphql match against when executing the inline fragments? Since it could be either, graphql will be unable to determine which type to use and can't advance the query to select the appropriate fields. The query result is said to be indeterminate. - -#### IGraphUnionProxy.MapType -Luckily there is a way to allow you to take control of your unions and make the determination on your own. The `MapType` method provided by `IGraphUnionProxy` will be called whenever a query result is indeterminate, allowing you to choose which of your union's allowed types should be used. - - -```csharp title="Using a Custom Type Mapper" -public class RollOrBread : GraphUnionProxy -{ - public RollOrBread() - { - this.AddType(typeof(Roll)); - this.AddType(typeof(Bread)); - } - - // highlight-start - public override Type MapType(Type runtimeObjectType) - { - if (runtimeObjectType == typeof(Bagel)) - return typeof(Roll); - else - return typeof(Bread); - } - // highlight-end -} -``` - -The query will now interpret all `Bagels` as `Rolls` and be able to process the query correctly. - -If, via your logic you are unable to determine which of your Union's types to use then return `null` and GraphQL will supply the caller with an appropriate error message stating the query was indeterminate. Also, returning any type other than one that was formally declared as part of your Union will result in the same indeterminate state. - -Most of the time GraphQL ASP.NET will never call `MapType` on your union proxy. If your union types do not share an inheritance chain, for instance, the method will never be called. - -:::caution - The `MapType()` function is not based on a resolved value, but only on the `System.Type` that was encountered. This is by design to guarantee consistency in query execution. -::: diff --git a/docusaurus.config.js b/docusaurus.config.js deleted file mode 100644 index e1a515e..0000000 --- a/docusaurus.config.js +++ /dev/null @@ -1,144 +0,0 @@ -// @ts-check -// Note: type annotations allow type checking and IDEs autocompletion - -const lightCodeTheme = require('prism-react-renderer/themes/github'); -const darkCodeTheme = require('prism-react-renderer/themes/palenight'); - -/** @type {import('@docusaurus/types').Config} */ -const config = { - title: 'GraphQL ASP.NET', - tagline: 'v1.0.0', - url: 'https://graphql-aspnet.github.io', - baseUrl: '/', - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', - favicon: 'img/favicon.ico', - - - // GitHub pages deployment config. - // If you aren't using GitHub pages, you don't need these. - organizationName: 'graphql-aspnet', // Usually your GitHub org/user name. - projectName: 'graphql-aspnet.github.io', // Usually your repo name. - trailingSlash: false, - - // Even if you don't use internalization, you can use this field to set useful - // metadata like html lang. For example, if your site is Chinese, you may want - // to replace "en" with "zh-Hans". - i18n: { - defaultLocale: 'en', - locales: ['en'], - }, - - scripts: [ - { - src: 'https://buttons.github.io/buttons.js', - async: true, - type: "text/javascript", - }, - ], - - presets: [ - [ - 'classic', - /** @type {import('@docusaurus/preset-classic').Options} */ - ({ - docs: { - sidebarPath: require.resolve('./sidebars.js'), - sidebarCollapsible: false - }, - - theme: { - customCss: require.resolve('./src/css/custom.css'), - }, - }), - ], - ], - - themeConfig: - /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ - ({ - colorMode: { - defaultMode: 'dark', - disableSwitch: false, - respectPrefersColorScheme: true - }, - navbar: { - title: 'GraphQL ASP.NET', - logo: { - alt: 'My Site Logo', - src: 'img/logo-128.png', - }, - items: [ - { - to: 'https://github.com/graphql-aspnet/graphql-aspnet', - position: 'right', - className: 'header-github-link', - 'aria-label': 'GitHub repository', - } - ], - }, - footer: { - links: [ - { - title: 'Docs', - items: [ - { - label: 'Made for ASP.NET developers', - to: '/docs/introduction/made-for-aspnet-developers', - }, - { - label: 'Code Examples', - to: '/docs/quick/code-examples', - }, - ], - }, - { - title: ' ', - items: [], - }, - { - title: 'More', - items: [ - { - html: ` - - Star on Github - - ` - }, - { - html: ` - - GraphQL.org - - ` - }, - ], - }, - ], - copyright: `Copyright © ${new Date().getFullYear()} GraphQL ASP.NET`, - }, - prism: { - darkTheme: darkCodeTheme, - theme: lightCodeTheme, - additionalLanguages: ['csharp', 'powershell'], - }, - docs: { - sidebar: { - autoCollapseCategories: false - } - }, - }), -}; - -module.exports = config; diff --git a/static/img/ef-core-error.png b/img/ef-core-error.png similarity index 100% rename from static/img/ef-core-error.png rename to img/ef-core-error.png diff --git a/static/img/favicon.ico b/img/favicon.ico similarity index 100% rename from static/img/favicon.ico rename to img/favicon.ico diff --git a/static/img/logo-128.png b/img/logo-128.png similarity index 100% rename from static/img/logo-128.png rename to img/logo-128.png diff --git a/static/img/logo-32.png b/img/logo-32.png similarity index 100% rename from static/img/logo-32.png rename to img/logo-32.png diff --git a/static/img/logo-64.png b/img/logo-64.png similarity index 100% rename from static/img/logo-64.png rename to img/logo-64.png diff --git a/index.html b/index.html new file mode 100644 index 0000000..dd2ff08 --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + +GraphQL ASP.NET + + + + +
+

GraphQL ASP.NET .NET 8+

Write a Controller

// C# Controller 
[GraphRoute("groceryStore/bakery")]
public class BakeryController : GraphController
{
[Query("pastries/search")]
public IEnumerable<IPastry> SearchPastries(string text)
{ /* ... */ }

[Query("pastries/recipe")]
public async Task<Recipe> RetrieveRecipe(int id)
{ /* ... */ }

[Query("breadCounter/orders")]
public IEnumerable<BreadOrder> FindOrders(int id)
{ /* ... */ }
}

Execute a Query

# GraphQL Query 
query SearchGroceryStore($pastryName: String!) {
groceryStore {
bakery {
pastries {
search(text: $pastryName) {
name
type
}
recipe(id: 15) {
name
ingredients {
name
}
}
}
}
breadCounter {
orders(id:36) {
id
items {
id
quantity
}
}
}
}
}
+ + + + \ No newline at end of file diff --git a/package.json b/package.json deleted file mode 100644 index d0cf1af..0000000 --- a/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "my-website", - "version": "0.0.0", - "private": true, - "scripts": { - "docusaurus": "docusaurus", - "start": "docusaurus start", - "build": "docusaurus build", - "swizzle": "docusaurus swizzle", - "deploy": "docusaurus deploy", - "clear": "docusaurus clear", - "serve": "docusaurus serve", - "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids" - }, - "dependencies": { - "@docusaurus/core": "^2.4.0", - "@docusaurus/preset-classic": "^2.4.0", - "@mdx-js/react": "^1.6.22", - "clsx": "^1.2.1", - "prism-react-renderer": "^1.3.5", - "react": "^17.0.2", - "react-dom": "^17.0.2" - }, - "devDependencies": { - "@docusaurus/module-type-aliases": "^2.4.0" - }, - "browserslist": { - "production": [ - ">0.5%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "engines": { - "node": ">=16.14" - } -} diff --git a/sidebars.js b/sidebars.js deleted file mode 100644 index 9ab54c2..0000000 --- a/sidebars.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Creating a sidebar enables you to: - - create an ordered group of docs - - render a sidebar for each doc of that group - - provide next/previous navigation - - The sidebars can be generated from the filesystem, or explicitly defined here. - - Create as many sidebars as you want. - */ - -// @ts-check - -/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const sidebars = { - // By default, Docusaurus generates a sidebar from the docs folder structure - tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], - - // But you can create a sidebar manually - /* - tutorialSidebar: [ - 'intro', - 'hello', - { - type: 'category', - label: 'Tutorial', - items: ['tutorial-basics/create-a-document'], - }, - ], - */ -}; - -module.exports = sidebars; diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..5f7b45a --- /dev/null +++ b/sitemap.xml @@ -0,0 +1 @@ +https://graphql-aspnet.github.io/docs/advanced/custom-scalarsweekly0.5https://graphql-aspnet.github.io/docs/advanced/directivesweekly0.5https://graphql-aspnet.github.io/docs/advanced/graph-action-resultsweekly0.5https://graphql-aspnet.github.io/docs/advanced/multi-schema-supportweekly0.5https://graphql-aspnet.github.io/docs/advanced/subscriptionsweekly0.5https://graphql-aspnet.github.io/docs/advanced/type-expressionsweekly0.5https://graphql-aspnet.github.io/docs/controllers/actionsweekly0.5https://graphql-aspnet.github.io/docs/controllers/authorizationweekly0.5https://graphql-aspnet.github.io/docs/controllers/batch-operationsweekly0.5https://graphql-aspnet.github.io/docs/controllers/field-pathsweekly0.5https://graphql-aspnet.github.io/docs/controllers/model-stateweekly0.5https://graphql-aspnet.github.io/docs/controllers/type-extensionsweekly0.5https://graphql-aspnet.github.io/docs/development/debuggingweekly0.5https://graphql-aspnet.github.io/docs/development/entity-frameworkweekly0.5https://graphql-aspnet.github.io/docs/development/unit-testingweekly0.5https://graphql-aspnet.github.io/docs/execution/malicious-queriesweekly0.5https://graphql-aspnet.github.io/docs/execution/metricsweekly0.5https://graphql-aspnet.github.io/docs/introduction/made-for-aspnet-developersweekly0.5https://graphql-aspnet.github.io/docs/introduction/what-is-graphqlweekly0.5https://graphql-aspnet.github.io/docs/logging/standard-eventsweekly0.5https://graphql-aspnet.github.io/docs/logging/structured-loggingweekly0.5https://graphql-aspnet.github.io/docs/logging/subscription-eventsweekly0.5https://graphql-aspnet.github.io/docs/quick/code-examplesweekly0.5https://graphql-aspnet.github.io/docs/quick/create-appweekly0.5https://graphql-aspnet.github.io/docs/quick/overviewweekly0.5https://graphql-aspnet.github.io/docs/reference/attributesweekly0.5https://graphql-aspnet.github.io/docs/reference/demo-projectsweekly0.5https://graphql-aspnet.github.io/docs/reference/global-configurationweekly0.5https://graphql-aspnet.github.io/docs/reference/graph-controllerweekly0.5https://graphql-aspnet.github.io/docs/reference/graph-directiveweekly0.5https://graphql-aspnet.github.io/docs/reference/how-it-worksweekly0.5https://graphql-aspnet.github.io/docs/reference/http-processorweekly0.5https://graphql-aspnet.github.io/docs/reference/middlewareweekly0.5https://graphql-aspnet.github.io/docs/reference/performanceweekly0.5https://graphql-aspnet.github.io/docs/reference/query-cacheweekly0.5https://graphql-aspnet.github.io/docs/reference/schema-configurationweekly0.5https://graphql-aspnet.github.io/docs/reference/vocabularyweekly0.5https://graphql-aspnet.github.io/docs/server-extensions/multipart-requestsweekly0.5https://graphql-aspnet.github.io/docs/types/enumsweekly0.5https://graphql-aspnet.github.io/docs/types/input-objectsweekly0.5https://graphql-aspnet.github.io/docs/types/interfacesweekly0.5https://graphql-aspnet.github.io/docs/types/list-non-nullweekly0.5https://graphql-aspnet.github.io/docs/types/objectsweekly0.5https://graphql-aspnet.github.io/docs/types/scalarsweekly0.5https://graphql-aspnet.github.io/docs/types/unionsweekly0.5https://graphql-aspnet.github.io/weekly0.5 \ No newline at end of file diff --git a/src/css/custom.css b/src/css/custom.css deleted file mode 100644 index d7cff02..0000000 --- a/src/css/custom.css +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Any CSS included here will be global. The classic template - * bundles Infima by default. Infima is a CSS framework designed to - * work well for content-centric websites. - */ - -/* You can override the default Infima variables here. */ -:root { - --ifm-color-primary: #2e8555; - --ifm-color-primary-dark: #29784c; - --ifm-color-primary-darker: #277148; - --ifm-color-primary-darkest: #205d3b; - --ifm-color-primary-light: #33925d; - --ifm-color-primary-lighter: #359962; - --ifm-color-primary-lightest: #3cad6e; - --ifm-code-font-size: 95%; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); -} - -/* For readability concerns, you should choose a lighter palette in dark mode. */ -[data-theme='dark'] { - --ifm-color-primary: #25c2a0; - --ifm-color-primary-dark: #21af90; - --ifm-color-primary-darker: #1fa588; - --ifm-color-primary-darkest: #1a8870; - --ifm-color-primary-light: #29d5b0; - --ifm-color-primary-lighter: #32d8b4; - --ifm-color-primary-lightest: #4fddbf; - --docusaurus-highlighted-code-line-bg: rgba(103, 101, 101, 0.3); -} - -.theme-doc-sidebar-item-category-level-1 .menu__list-item-collapsible a:first-of-type { - color: #a3a3a3; - font-size: 1.1em; -} - -.header-github-link::before { - content: ''; - width: 24px; - height: 24px; - display: flex; - background: 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") no-repeat; - padding-right: 5px -} - -[data-theme='dark'] .header-github-link::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; -} - -.pill { - background-color: rgb(56, 73, 223); - border: none; - color: rgb(255, 255, 255); - padding: 5px 10px; - text-align: center; - text-decoration: none; - display: inline-block; - margin: 4px 2px; - border-radius: 8px; -} - - -.pill-small { - background-color: rgb(56, 73, 223); - border: none; - font-size: 14px; - color: rgb(255, 255, 255); - padding: 3px 5px; - text-align: center; - text-decoration: none; - display: inline-block; - margin: 4px 2px; - cursor: pointer; - border-radius: 3px; -} - -@media only screen and (min-device-width: 320px) and (max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2) { - .pill-header { - display: none; - } -} \ No newline at end of file diff --git a/src/pages/index.js b/src/pages/index.js deleted file mode 100644 index 535134a..0000000 --- a/src/pages/index.js +++ /dev/null @@ -1,125 +0,0 @@ -import React from 'react'; -import clsx from 'clsx'; -import Link from '@docusaurus/Link'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import Layout from '@theme/Layout'; -import CodeBlock from '@theme/CodeBlock'; - - -import styles from './index.module.css'; - -function HomepageHeader() { - const { siteConfig } = useDocusaurusContext(); - return ( -
-
-

{siteConfig.title} .NET 8+

-
-
-
- - Documentation - -
-
-
-
- -
- ); -} - -function WriteAController() { - return ( - <> -

Write a Controller

- - {"// C# Controller \n" + - "[GraphRoute(\"groceryStore/bakery\")]\n" + - "public class BakeryController : GraphController \n" + - "{ \n" + - " [Query(\"pastries/search\")]\n" + - " public IEnumerable SearchPastries(string text)\n" + - " { /* ... */ }\n" + - "\n" + - " [Query(\"pastries/recipe\")]\n" + - " public async Task RetrieveRecipe(int id)\n" + - " { /* ... */ }\n" + - "\n" + - " [Query(\"breadCounter/orders\")]\n" + - " public IEnumerable FindOrders(int id)\n" + - " { /* ... */ }\n" + - "}" - } - - - ); -} - -function ExecuteAQuery() { - return ( - <> -

Execute a Query

- - { - "# GraphQL Query \n" + - "query SearchGroceryStore($pastryName: String!) { \n" + - " groceryStore {\n" + - " bakery {\n" + - " pastries {\n" + - " search(text: $pastryName) {\n" + - " name\n" + - " type\n" + - " }\n" + - " recipe(id: 15) {\n" + - " name\n" + - " ingredients {\n" + - " name\n" + - " }\n" + - " } \n" + - " } \n" + - " } \n" + - " breadCounter {\n" + - " orders(id:36) {\n" + - " id \n" + - " items { \n" + - " id \n" + - " quantity \n" + - " } \n" + - " } \n" + - " } \n" + - " } \n" + - "}" - - } - - - ); -} - -export default function Home() { - const { siteConfig } = useDocusaurusContext(); - return ( - - -
-
-
-
-
- -
-
- -
-
-
-
-
-
- ); -} diff --git a/src/pages/index.module.css b/src/pages/index.module.css deleted file mode 100644 index e352d04..0000000 --- a/src/pages/index.module.css +++ /dev/null @@ -1,37 +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; -} - - -.main-buttons { - column-gap: 15px; -} - -@media screen and (max-width: 996px) { - .heroBanner { - padding: 2rem; - } - -} - -@media (max-width: 480px) { - - .main-buttons { - justify-content: space-around; - row-gap: 5px; - } -} - -.buttons { - display: flex; - align-items: center; - justify-content: center; -} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 8938ff9..0000000 --- a/yarn.lock +++ /dev/null @@ -1,7941 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@algolia/autocomplete-core@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.7.2.tgz#8abbed88082f611997538760dffcb43b33b1fd1d" - integrity sha512-eclwUDC6qfApNnEfu1uWcL/rudQsn59tjEoUYZYE2JSXZrHLRjBUGMxiCoknobU2Pva8ejb0eRxpIYDtVVqdsw== - dependencies: - "@algolia/autocomplete-shared" "1.7.2" - -"@algolia/autocomplete-preset-algolia@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.2.tgz#9cd4f64b3d64399657ee2dc2b7e0a939e0713a26" - integrity sha512-+RYEG6B0QiGGfRb2G3MtPfyrl0dALF3cQNTWBzBX6p5o01vCCGTTinAm2UKG3tfc2CnOMAtnPLkzNZyJUpnVJw== - dependencies: - "@algolia/autocomplete-shared" "1.7.2" - -"@algolia/autocomplete-shared@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.2.tgz#daa23280e78d3b42ae9564d12470ae034db51a89" - integrity sha512-QCckjiC7xXHIUaIL3ektBtjJ0w7tTA3iqKcAE/Hjn1lZ5omp7i3Y4e09rAr9ZybqirL7AbxCLLq0Ra5DDPKeug== - -"@algolia/cache-browser-local-storage@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.14.3.tgz#b9e0da012b2f124f785134a4d468ee0841b2399d" - integrity sha512-hWH1yCxgG3+R/xZIscmUrWAIBnmBFHH5j30fY/+aPkEZWt90wYILfAHIOZ1/Wxhho5SkPfwFmT7ooX2d9JeQBw== - dependencies: - "@algolia/cache-common" "4.14.3" - -"@algolia/cache-common@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.14.3.tgz#a78e9faee3dfec018eab7b0996e918e06b476ac7" - integrity sha512-oZJofOoD9FQOwiGTzyRnmzvh3ZP8WVTNPBLH5xU5JNF7drDbRT0ocVT0h/xB2rPHYzOeXRrLaQQBwRT/CKom0Q== - -"@algolia/cache-in-memory@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.14.3.tgz#96cefb942aeb80e51e6a7e29f25f4f7f3439b736" - integrity sha512-ES0hHQnzWjeioLQf5Nq+x1AWdZJ50znNPSH3puB/Y4Xsg4Av1bvLmTJe7SY2uqONaeMTvL0OaVcoVtQgJVw0vg== - dependencies: - "@algolia/cache-common" "4.14.3" - -"@algolia/client-account@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.14.3.tgz#6d7d032a65c600339ce066505c77013d9a9e4966" - integrity sha512-PBcPb0+f5Xbh5UfLZNx2Ow589OdP8WYjB4CnvupfYBrl9JyC1sdH4jcq/ri8osO/mCZYjZrQsKAPIqW/gQmizQ== - dependencies: - "@algolia/client-common" "4.14.3" - "@algolia/client-search" "4.14.3" - "@algolia/transporter" "4.14.3" - -"@algolia/client-analytics@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.14.3.tgz#ca409d00a8fff98fdcc215dc96731039900055dc" - integrity sha512-eAwQq0Hb/aauv9NhCH5Dp3Nm29oFx28sayFN2fdOWemwSeJHIl7TmcsxVlRsO50fsD8CtPcDhtGeD3AIFLNvqw== - dependencies: - "@algolia/client-common" "4.14.3" - "@algolia/client-search" "4.14.3" - "@algolia/requester-common" "4.14.3" - "@algolia/transporter" "4.14.3" - -"@algolia/client-common@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.14.3.tgz#c44e48652b2121a20d7a40cfd68d095ebb4191a8" - integrity sha512-jkPPDZdi63IK64Yg4WccdCsAP4pHxSkr4usplkUZM5C1l1oEpZXsy2c579LQ0rvwCs5JFmwfNG4ahOszidfWPw== - dependencies: - "@algolia/requester-common" "4.14.3" - "@algolia/transporter" "4.14.3" - -"@algolia/client-personalization@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.14.3.tgz#8f71325035aa2a5fa7d1d567575235cf1d6c654f" - integrity sha512-UCX1MtkVNgaOL9f0e22x6tC9e2H3unZQlSUdnVaSKpZ+hdSChXGaRjp2UIT7pxmPqNCyv51F597KEX5WT60jNg== - dependencies: - "@algolia/client-common" "4.14.3" - "@algolia/requester-common" "4.14.3" - "@algolia/transporter" "4.14.3" - -"@algolia/client-search@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.14.3.tgz#cf1e77549f5c3e73408ffe6441ede985fde69da0" - integrity sha512-I2U7xBx5OPFdPLA8AXKUPPxGY3HDxZ4r7+mlZ8ZpLbI8/ri6fnu6B4z3wcL7sgHhDYMwnAE8Xr0AB0h3Hnkp4A== - dependencies: - "@algolia/client-common" "4.14.3" - "@algolia/requester-common" "4.14.3" - "@algolia/transporter" "4.14.3" - -"@algolia/events@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" - integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== - -"@algolia/logger-common@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.14.3.tgz#87d4725e7f56ea5a39b605771b7149fff62032a7" - integrity sha512-kUEAZaBt/J3RjYi8MEBT2QEexJR2kAE2mtLmezsmqMQZTV502TkHCxYzTwY2dE7OKcUTxi4OFlMuS4GId9CWPw== - -"@algolia/logger-console@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.14.3.tgz#1f19f8f0a5ef11f01d1f9545290eb6a89b71fb8a" - integrity sha512-ZWqAlUITktiMN2EiFpQIFCJS10N96A++yrexqC2Z+3hgF/JcKrOxOdT4nSCQoEPvU4Ki9QKbpzbebRDemZt/hw== - dependencies: - "@algolia/logger-common" "4.14.3" - -"@algolia/requester-browser-xhr@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.14.3.tgz#bcf55cba20f58fd9bc95ee55793b5219f3ce8888" - integrity sha512-AZeg2T08WLUPvDncl2XLX2O67W5wIO8MNaT7z5ii5LgBTuk/rU4CikTjCe2xsUleIZeFl++QrPAi4Bdxws6r/Q== - dependencies: - "@algolia/requester-common" "4.14.3" - -"@algolia/requester-common@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.14.3.tgz#2d02fbe01afb7ae5651ae8dfe62d6c089f103714" - integrity sha512-RrRzqNyKFDP7IkTuV3XvYGF9cDPn9h6qEDl595lXva3YUk9YSS8+MGZnnkOMHvjkrSCKfoLeLbm/T4tmoIeclw== - -"@algolia/requester-node-http@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.14.3.tgz#72389e1c2e5d964702451e75e368eefe85a09d8f" - integrity sha512-O5wnPxtDRPuW2U0EaOz9rMMWdlhwP0J0eSL1Z7TtXF8xnUeeUyNJrdhV5uy2CAp6RbhM1VuC3sOJcIR6Av+vbA== - dependencies: - "@algolia/requester-common" "4.14.3" - -"@algolia/transporter@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.14.3.tgz#5593036bd9cf2adfd077fdc3e81d2e6118660a7a" - integrity sha512-2qlKlKsnGJ008exFRb5RTeTOqhLZj0bkMCMVskxoqWejs2Q2QtWmsiH98hDfpw0fmnyhzHEt0Z7lqxBYp8bW2w== - dependencies: - "@algolia/cache-common" "4.14.3" - "@algolia/logger-common" "4.14.3" - "@algolia/requester-common" "4.14.3" - -"@ampproject/remapping@^2.1.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== - dependencies: - "@jridgewell/gen-mapping" "^0.1.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.8.3": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/code-frame@^7.22.13": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" - integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== - dependencies: - "@babel/highlight" "^7.22.13" - chalk "^2.4.2" - -"@babel/code-frame@^7.26.2": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" - integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== - dependencies: - "@babel/helper-validator-identifier" "^7.25.9" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.5.tgz#86f172690b093373a933223b4745deeb6049e733" - integrity sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g== - -"@babel/core@7.12.9": - version "7.12.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8" - integrity sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.12.5" - "@babel/helper-module-transforms" "^7.12.1" - "@babel/helpers" "^7.12.5" - "@babel/parser" "^7.12.7" - "@babel/template" "^7.12.7" - "@babel/traverse" "^7.12.9" - "@babel/types" "^7.12.7" - 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/core@^7.18.6", "@babel/core@^7.19.6": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.5.tgz#45e2114dc6cd4ab167f81daf7820e8fa1250d113" - integrity sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ== - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.5" - "@babel/helper-compilation-targets" "^7.20.0" - "@babel/helper-module-transforms" "^7.20.2" - "@babel/helpers" "^7.20.5" - "@babel/parser" "^7.20.5" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.5" - "@babel/types" "^7.20.5" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.1" - semver "^6.3.0" - -"@babel/generator@^7.12.5", "@babel/generator@^7.18.7", "@babel/generator@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95" - integrity sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA== - dependencies: - "@babel/types" "^7.20.5" - "@jridgewell/gen-mapping" "^0.3.2" - jsesc "^2.5.1" - -"@babel/generator@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" - integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== - dependencies: - "@babel/types" "^7.23.0" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-annotate-as-pure@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" - integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" - integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== - dependencies: - "@babel/helper-explode-assignable-expression" "^7.18.6" - "@babel/types" "^7.18.9" - -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" - integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ== - dependencies: - "@babel/compat-data" "^7.20.0" - "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.21.3" - semver "^6.3.0" - -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.2", "@babel/helper-create-class-features-plugin@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz#327154eedfb12e977baa4ecc72e5806720a85a06" - integrity sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-member-expression-to-functions" "^7.18.9" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.19.1" - "@babel/helper-split-export-declaration" "^7.18.6" - -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz#5ea79b59962a09ec2acf20a963a01ab4d076ccca" - integrity sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.2.1" - -"@babel/helper-define-polyfill-provider@^0.3.3": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" - integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== - dependencies: - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - semver "^6.1.2" - -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-explode-assignable-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" - integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== - dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-member-expression-to-functions@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" - integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== - dependencies: - "@babel/types" "^7.18.9" - -"@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz#ac53da669501edd37e658602a21ba14c08748712" - integrity sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.20.2" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.1" - "@babel/types" "^7.20.2" - -"@babel/helper-optimise-call-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" - integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-plugin-utils@7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" - integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" - integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== - -"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" - integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-wrap-function" "^7.18.9" - "@babel/types" "^7.18.9" - -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" - integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.18.9" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.19.1" - "@babel/types" "^7.19.0" - -"@babel/helper-simple-access@^7.19.4", "@babel/helper-simple-access@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" - integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== - dependencies: - "@babel/types" "^7.20.2" - -"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" - integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== - dependencies: - "@babel/types" "^7.20.0" - -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== - -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== - -"@babel/helper-string-parser@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" - integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== - -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== - -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helper-validator-identifier@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" - integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== - -"@babel/helper-validator-option@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" - integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== - -"@babel/helper-wrap-function@^7.18.9": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz#75e2d84d499a0ab3b31c33bcfe59d6b8a45f62e3" - integrity sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q== - dependencies: - "@babel/helper-function-name" "^7.19.0" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.5" - "@babel/types" "^7.20.5" - -"@babel/helpers@^7.12.5", "@babel/helpers@^7.20.5": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.0.tgz#53d156098defa8243eab0f32fa17589075a1b808" - integrity sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg== - dependencies: - "@babel/template" "^7.27.0" - "@babel/types" "^7.27.0" - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/highlight@^7.22.13": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" - integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - -"@babel/parser@^7.12.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.8", "@babel/parser@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8" - integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA== - -"@babel/parser@^7.22.15", "@babel/parser@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" - integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== - -"@babel/parser@^7.27.0": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.0.tgz#3d7d6ee268e41d2600091cbd4e145ffee85a44ec" - integrity sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg== - dependencies: - "@babel/types" "^7.27.0" - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" - integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50" - integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" - -"@babel/plugin-proposal-async-generator-functions@^7.20.1": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz#352f02baa5d69f4e7529bdac39aaa02d41146af9" - integrity sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-remap-async-to-generator" "^7.18.9" - "@babel/plugin-syntax-async-generators" "^7.8.4" - -"@babel/plugin-proposal-class-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" - integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-proposal-class-static-block@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz#8aa81d403ab72d3962fc06c26e222dacfc9b9020" - integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - -"@babel/plugin-proposal-dynamic-import@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94" - integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - -"@babel/plugin-proposal-export-namespace-from@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" - integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-proposal-json-strings@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b" - integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-json-strings" "^7.8.3" - -"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz#8148cbb350483bf6220af06fa6db3690e14b2e23" - integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" - integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - -"@babel/plugin-proposal-numeric-separator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" - integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-proposal-object-rest-spread@7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz#def9bd03cea0f9b72283dac0ec22d289c7691069" - integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA== - dependencies: - "@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-object-rest-spread@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz#a556f59d555f06961df1e572bb5eca864c84022d" - integrity sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ== - dependencies: - "@babel/compat-data" "^7.20.1" - "@babel/helper-compilation-targets" "^7.20.0" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.20.1" - -"@babel/plugin-proposal-optional-catch-binding@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" - integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - -"@babel/plugin-proposal-optional-chaining@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" - integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - -"@babel/plugin-proposal-private-methods@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" - integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-proposal-private-property-in-object@^7.18.6": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz#309c7668f2263f1c711aa399b5a9a6291eef6135" - integrity sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.20.5" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - -"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" - integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-import-assertions@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" - integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== - dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz#9d9d357cc818aa7ae7935917c1257f67677a0926" - integrity sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-jsx@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" - integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@7.8.3", "@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz#4e9a0cfc769c85689b77a2e642d24e9f697fc8c7" - integrity sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - -"@babel/plugin-transform-arrow-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" - integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-async-to-generator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615" - integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== - dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-remap-async-to-generator" "^7.18.6" - -"@babel/plugin-transform-block-scoped-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" - integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-block-scoping@^7.20.2": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.5.tgz#401215f9dc13dc5262940e2e527c9536b3d7f237" - integrity sha512-WvpEIW9Cbj9ApF3yJCjIEEf1EiNJLtXagOrL5LNWEZOo3jv8pmPoYTSNJQvqej8OavVlgOoOPw6/htGZro6IkA== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-classes@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz#c0033cf1916ccf78202d04be4281d161f6709bb2" - integrity sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.20.0" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-replace-supers" "^7.19.1" - "@babel/helper-split-export-declaration" "^7.18.6" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" - integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-destructuring@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz#c23741cfa44ddd35f5e53896e88c75331b8b2792" - integrity sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" - integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-duplicate-keys@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" - integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-exponentiation-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" - integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-for-of@^7.18.8": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" - integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" - integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== - dependencies: - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" - integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-member-expression-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" - integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-modules-amd@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz#aca391801ae55d19c4d8d2ebfeaa33df5f2a2cbd" - integrity sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg== - dependencies: - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" - -"@babel/plugin-transform-modules-commonjs@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c" - integrity sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ== - dependencies: - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-simple-access" "^7.19.4" - -"@babel/plugin-transform-modules-systemjs@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz#59e2a84064b5736a4471b1aa7b13d4431d327e0d" - integrity sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ== - dependencies: - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-validator-identifier" "^7.19.1" - -"@babel/plugin-transform-modules-umd@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" - integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== - dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8" - integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.20.5" - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-new-target@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" - integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-object-super@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" - integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.6" - -"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.20.1": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.5.tgz#f8f9186c681d10c3de7620c916156d893c8a019e" - integrity sha512-h7plkOmcndIUWXZFLgpbrh2+fXAi47zcUX7IrOQuZdLD0I0KvjJ6cvo3BEcAOsDOcZhVKGJqv07mkSqK0y2isQ== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-property-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" - integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-react-constant-elements@^7.18.12": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.20.2.tgz#3f02c784e0b711970d7d8ccc96c4359d64e27ac7" - integrity sha512-KS/G8YI8uwMGKErLFOHS/ekhqdHhpEloxs43NecQHVgo2QuQSyJhGIY1fL8UGl9wy5ItVwwoUL4YxVqsplGq2g== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-react-display-name@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz#8b1125f919ef36ebdfff061d664e266c666b9415" - integrity sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-react-jsx-development@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz#dbe5c972811e49c7405b630e4d0d2e1380c0ddc5" - integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA== - dependencies: - "@babel/plugin-transform-react-jsx" "^7.18.6" - -"@babel/plugin-transform-react-jsx@^7.18.6": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9" - integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/plugin-syntax-jsx" "^7.18.6" - "@babel/types" "^7.19.0" - -"@babel/plugin-transform-react-pure-annotations@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz#561af267f19f3e5d59291f9950fd7b9663d0d844" - integrity sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-regenerator@^7.18.6": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz#57cda588c7ffb7f4f8483cc83bdcea02a907f04d" - integrity sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - regenerator-transform "^0.15.1" - -"@babel/plugin-transform-reserved-words@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" - integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-runtime@^7.18.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz#9d2a9dbf4e12644d6f46e5e75bfbf02b5d6e9194" - integrity sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw== - dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" - babel-plugin-polyfill-corejs2 "^0.3.3" - babel-plugin-polyfill-corejs3 "^0.6.0" - babel-plugin-polyfill-regenerator "^0.4.1" - semver "^6.3.0" - -"@babel/plugin-transform-shorthand-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" - integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-spread@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" - integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== - dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - -"@babel/plugin-transform-sticky-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" - integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-template-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" - integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-typeof-symbol@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" - integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-typescript@^7.18.6": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.2.tgz#91515527b376fc122ba83b13d70b01af8fe98f3f" - integrity sha512-jvS+ngBfrnTUBfOQq8NfGnSbF9BrqlR6hjJ2yVxMkmO5nL/cdifNbI30EfjRlN4g5wYWNnMPyj5Sa6R1pbLeag== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.20.2" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-typescript" "^7.20.0" - -"@babel/plugin-transform-unicode-escapes@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" - integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-unicode-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" - integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/preset-env@^7.18.6", "@babel/preset-env@^7.19.4": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.20.2.tgz#9b1642aa47bb9f43a86f9630011780dab7f86506" - integrity sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg== - dependencies: - "@babel/compat-data" "^7.20.1" - "@babel/helper-compilation-targets" "^7.20.0" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-async-generator-functions" "^7.20.1" - "@babel/plugin-proposal-class-properties" "^7.18.6" - "@babel/plugin-proposal-class-static-block" "^7.18.6" - "@babel/plugin-proposal-dynamic-import" "^7.18.6" - "@babel/plugin-proposal-export-namespace-from" "^7.18.9" - "@babel/plugin-proposal-json-strings" "^7.18.6" - "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" - "@babel/plugin-proposal-numeric-separator" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.20.2" - "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-private-methods" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object" "^7.18.6" - "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.20.0" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@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-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.18.6" - "@babel/plugin-transform-async-to-generator" "^7.18.6" - "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.20.2" - "@babel/plugin-transform-classes" "^7.20.2" - "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.20.2" - "@babel/plugin-transform-dotall-regex" "^7.18.6" - "@babel/plugin-transform-duplicate-keys" "^7.18.9" - "@babel/plugin-transform-exponentiation-operator" "^7.18.6" - "@babel/plugin-transform-for-of" "^7.18.8" - "@babel/plugin-transform-function-name" "^7.18.9" - "@babel/plugin-transform-literals" "^7.18.9" - "@babel/plugin-transform-member-expression-literals" "^7.18.6" - "@babel/plugin-transform-modules-amd" "^7.19.6" - "@babel/plugin-transform-modules-commonjs" "^7.19.6" - "@babel/plugin-transform-modules-systemjs" "^7.19.6" - "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" - "@babel/plugin-transform-new-target" "^7.18.6" - "@babel/plugin-transform-object-super" "^7.18.6" - "@babel/plugin-transform-parameters" "^7.20.1" - "@babel/plugin-transform-property-literals" "^7.18.6" - "@babel/plugin-transform-regenerator" "^7.18.6" - "@babel/plugin-transform-reserved-words" "^7.18.6" - "@babel/plugin-transform-shorthand-properties" "^7.18.6" - "@babel/plugin-transform-spread" "^7.19.0" - "@babel/plugin-transform-sticky-regex" "^7.18.6" - "@babel/plugin-transform-template-literals" "^7.18.9" - "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.18.10" - "@babel/plugin-transform-unicode-regex" "^7.18.6" - "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.20.2" - babel-plugin-polyfill-corejs2 "^0.3.3" - babel-plugin-polyfill-corejs3 "^0.6.0" - babel-plugin-polyfill-regenerator "^0.4.1" - core-js-compat "^3.25.1" - semver "^6.3.0" - -"@babel/preset-modules@^0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" - integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== - dependencies: - "@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@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d" - integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-transform-react-display-name" "^7.18.6" - "@babel/plugin-transform-react-jsx" "^7.18.6" - "@babel/plugin-transform-react-jsx-development" "^7.18.6" - "@babel/plugin-transform-react-pure-annotations" "^7.18.6" - -"@babel/preset-typescript@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" - integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-transform-typescript" "^7.18.6" - -"@babel/runtime-corejs3@^7.18.6": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.26.10.tgz#5a3185ca2813f8de8ae68622572086edf5cf51f2" - integrity sha512-uITFQYO68pMEYR46AHgQoyBg7KPPJDAbGn4jUTIRgCFJIp88MIBUianVOplhZDEec07bp9zIyr4Kp0FCyQzmWg== - dependencies: - core-js-pure "^3.30.2" - regenerator-runtime "^0.14.0" - -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.6", "@babel/runtime@^7.8.4": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762" - integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/template@^7.12.7", "@babel/template@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" - integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.10" - "@babel/types" "^7.18.10" - -"@babel/template@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/template@^7.27.0": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.0.tgz#b253e5406cc1df1c57dcd18f11760c2dbf40c0b4" - integrity sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA== - dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/parser" "^7.27.0" - "@babel/types" "^7.27.0" - -"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" - integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.0" - "@babel/types" "^7.23.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.12.7", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.4.4": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84" - integrity sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg== - dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - -"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" - integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" - -"@babel/types@^7.27.0": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.0.tgz#ef9acb6b06c3173f6632d993ecb6d4ae470b4559" - integrity sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg== - dependencies: - "@babel/helper-string-parser" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@docsearch/css@3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.3.0.tgz#d698e48302d12240d7c2f7452ccb2d2239a8cd80" - integrity sha512-rODCdDtGyudLj+Va8b6w6Y85KE85bXRsps/R4Yjwt5vueXKXZQKYw0aA9knxLBT6a/bI/GMrAcmCR75KYOM6hg== - -"@docsearch/react@^3.1.1": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.3.0.tgz#b8ac8e7f49b9bf2f96d34c24bc1cfd097ec0eead" - integrity sha512-fhS5adZkae2SSdMYEMVg6pxI5a/cE+tW16ki1V0/ur4Fdok3hBRkmN/H8VvlXnxzggkQIIRIVvYPn00JPjen3A== - dependencies: - "@algolia/autocomplete-core" "1.7.2" - "@algolia/autocomplete-preset-algolia" "1.7.2" - "@docsearch/css" "3.3.0" - algoliasearch "^4.0.0" - -"@docusaurus/core@2.4.0", "@docusaurus/core@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.4.0.tgz#a12c175cb2e5a7e4582e65876a50813f6168913d" - integrity sha512-J55/WEoIpRcLf3afO5POHPguVZosKmJEQWKBL+K7TAnfuE7i+Y0NPLlkKtnWCehagGsgTqClfQEexH/UT4kELA== - dependencies: - "@babel/core" "^7.18.6" - "@babel/generator" "^7.18.7" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.18.6" - "@babel/preset-env" "^7.18.6" - "@babel/preset-react" "^7.18.6" - "@babel/preset-typescript" "^7.18.6" - "@babel/runtime" "^7.18.6" - "@babel/runtime-corejs3" "^7.18.6" - "@babel/traverse" "^7.18.8" - "@docusaurus/cssnano-preset" "2.4.0" - "@docusaurus/logger" "2.4.0" - "@docusaurus/mdx-loader" "2.4.0" - "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/utils" "2.4.0" - "@docusaurus/utils-common" "2.4.0" - "@docusaurus/utils-validation" "2.4.0" - "@slorber/static-site-generator-webpack-plugin" "^4.0.7" - "@svgr/webpack" "^6.2.1" - autoprefixer "^10.4.7" - babel-loader "^8.2.5" - babel-plugin-dynamic-import-node "^2.3.3" - boxen "^6.2.1" - chalk "^4.1.2" - chokidar "^3.5.3" - clean-css "^5.3.0" - cli-table3 "^0.6.2" - combine-promises "^1.1.0" - commander "^5.1.0" - copy-webpack-plugin "^11.0.0" - core-js "^3.23.3" - css-loader "^6.7.1" - css-minimizer-webpack-plugin "^4.0.0" - cssnano "^5.1.12" - del "^6.1.1" - detect-port "^1.3.0" - escape-html "^1.0.3" - eta "^2.0.0" - file-loader "^6.2.0" - fs-extra "^10.1.0" - html-minifier-terser "^6.1.0" - html-tags "^3.2.0" - html-webpack-plugin "^5.5.0" - import-fresh "^3.3.0" - leven "^3.1.0" - lodash "^4.17.21" - mini-css-extract-plugin "^2.6.1" - postcss "^8.4.14" - postcss-loader "^7.0.0" - prompts "^2.4.2" - react-dev-utils "^12.0.1" - react-helmet-async "^1.3.0" - react-loadable "npm:@docusaurus/react-loadable@5.5.2" - react-loadable-ssr-addon-v5-slorber "^1.0.1" - react-router "^5.3.3" - react-router-config "^5.1.1" - react-router-dom "^5.3.3" - rtl-detect "^1.0.4" - semver "^7.3.7" - serve-handler "^6.1.3" - shelljs "^0.8.5" - terser-webpack-plugin "^5.3.3" - tslib "^2.4.0" - update-notifier "^5.1.0" - url-loader "^4.1.1" - wait-on "^6.0.1" - webpack "^5.73.0" - webpack-bundle-analyzer "^4.5.0" - webpack-dev-server "^4.9.3" - webpack-merge "^5.8.0" - webpackbar "^5.0.2" - -"@docusaurus/cssnano-preset@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.4.0.tgz#9213586358e0cce517f614af041eb7d184f8add6" - integrity sha512-RmdiA3IpsLgZGXRzqnmTbGv43W4OD44PCo+6Q/aYjEM2V57vKCVqNzuafE94jv0z/PjHoXUrjr69SaRymBKYYw== - dependencies: - cssnano-preset-advanced "^5.3.8" - postcss "^8.4.14" - postcss-sort-media-queries "^4.2.1" - tslib "^2.4.0" - -"@docusaurus/logger@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.4.0.tgz#393d91ad9ecdb9a8f80167dd6a34d4b45219b835" - integrity sha512-T8+qR4APN+MjcC9yL2Es+xPJ2923S9hpzDmMtdsOcUGLqpCGBbU1vp3AAqDwXtVgFkq+NsEk7sHdVsfLWR/AXw== - dependencies: - chalk "^4.1.2" - tslib "^2.4.0" - -"@docusaurus/mdx-loader@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.4.0.tgz#c6310342904af2f203e7df86a9df623f86840f2d" - integrity sha512-GWoH4izZKOmFoC+gbI2/y8deH/xKLvzz/T5BsEexBye8EHQlwsA7FMrVa48N063bJBH4FUOiRRXxk5rq9cC36g== - dependencies: - "@babel/parser" "^7.18.8" - "@babel/traverse" "^7.18.8" - "@docusaurus/logger" "2.4.0" - "@docusaurus/utils" "2.4.0" - "@mdx-js/mdx" "^1.6.22" - escape-html "^1.0.3" - file-loader "^6.2.0" - fs-extra "^10.1.0" - image-size "^1.0.1" - mdast-util-to-string "^2.0.0" - remark-emoji "^2.2.0" - stringify-object "^3.3.0" - tslib "^2.4.0" - unified "^9.2.2" - unist-util-visit "^2.0.3" - url-loader "^4.1.1" - webpack "^5.73.0" - -"@docusaurus/module-type-aliases@2.4.0", "@docusaurus/module-type-aliases@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.4.0.tgz#6961605d20cd46f86163ed8c2d83d438b02b4028" - integrity sha512-YEQO2D3UXs72qCn8Cr+RlycSQXVGN9iEUyuHwTuK4/uL/HFomB2FHSU0vSDM23oLd+X/KibQ3Ez6nGjQLqXcHg== - dependencies: - "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/types" "2.4.0" - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-router-config" "*" - "@types/react-router-dom" "*" - react-helmet-async "*" - react-loadable "npm:@docusaurus/react-loadable@5.5.2" - -"@docusaurus/plugin-content-blog@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.4.0.tgz#50dbfbc7b51f152ae660385fd8b34076713374c3" - integrity sha512-YwkAkVUxtxoBAIj/MCb4ohN0SCtHBs4AS75jMhPpf67qf3j+U/4n33cELq7567hwyZ6fMz2GPJcVmctzlGGThQ== - dependencies: - "@docusaurus/core" "2.4.0" - "@docusaurus/logger" "2.4.0" - "@docusaurus/mdx-loader" "2.4.0" - "@docusaurus/types" "2.4.0" - "@docusaurus/utils" "2.4.0" - "@docusaurus/utils-common" "2.4.0" - "@docusaurus/utils-validation" "2.4.0" - cheerio "^1.0.0-rc.12" - feed "^4.2.2" - fs-extra "^10.1.0" - lodash "^4.17.21" - reading-time "^1.5.0" - tslib "^2.4.0" - unist-util-visit "^2.0.3" - utility-types "^3.10.0" - webpack "^5.73.0" - -"@docusaurus/plugin-content-docs@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.4.0.tgz#36e235adf902325735b873b4f535205884363728" - integrity sha512-ic/Z/ZN5Rk/RQo+Io6rUGpToOtNbtPloMR2JcGwC1xT2riMu6zzfSwmBi9tHJgdXH6CB5jG+0dOZZO8QS5tmDg== - dependencies: - "@docusaurus/core" "2.4.0" - "@docusaurus/logger" "2.4.0" - "@docusaurus/mdx-loader" "2.4.0" - "@docusaurus/module-type-aliases" "2.4.0" - "@docusaurus/types" "2.4.0" - "@docusaurus/utils" "2.4.0" - "@docusaurus/utils-validation" "2.4.0" - "@types/react-router-config" "^5.0.6" - combine-promises "^1.1.0" - fs-extra "^10.1.0" - import-fresh "^3.3.0" - js-yaml "^4.1.0" - lodash "^4.17.21" - tslib "^2.4.0" - utility-types "^3.10.0" - webpack "^5.73.0" - -"@docusaurus/plugin-content-pages@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.4.0.tgz#6169909a486e1eae0ddffff0b1717ce4332db4d4" - integrity sha512-Pk2pOeOxk8MeU3mrTU0XLIgP9NZixbdcJmJ7RUFrZp1Aj42nd0RhIT14BGvXXyqb8yTQlk4DmYGAzqOfBsFyGw== - dependencies: - "@docusaurus/core" "2.4.0" - "@docusaurus/mdx-loader" "2.4.0" - "@docusaurus/types" "2.4.0" - "@docusaurus/utils" "2.4.0" - "@docusaurus/utils-validation" "2.4.0" - fs-extra "^10.1.0" - tslib "^2.4.0" - webpack "^5.73.0" - -"@docusaurus/plugin-debug@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.4.0.tgz#1ad513fe9bcaf017deccf62df8b8843faeeb7d37" - integrity sha512-KC56DdYjYT7Txyux71vXHXGYZuP6yYtqwClvYpjKreWIHWus5Zt6VNi23rMZv3/QKhOCrN64zplUbdfQMvddBQ== - dependencies: - "@docusaurus/core" "2.4.0" - "@docusaurus/types" "2.4.0" - "@docusaurus/utils" "2.4.0" - fs-extra "^10.1.0" - react-json-view "^1.21.3" - tslib "^2.4.0" - -"@docusaurus/plugin-google-analytics@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.4.0.tgz#8062d7a09d366329dfd3ce4e8a619da8624b6cc3" - integrity sha512-uGUzX67DOAIglygdNrmMOvEp8qG03X20jMWadeqVQktS6nADvozpSLGx4J0xbkblhJkUzN21WiilsP9iVP+zkw== - dependencies: - "@docusaurus/core" "2.4.0" - "@docusaurus/types" "2.4.0" - "@docusaurus/utils-validation" "2.4.0" - tslib "^2.4.0" - -"@docusaurus/plugin-google-gtag@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.4.0.tgz#a8efda476f971410dfb3aab1cfe1f0f7d269adc5" - integrity sha512-adj/70DANaQs2+TF/nRdMezDXFAV/O/pjAbUgmKBlyOTq5qoMe0Tk4muvQIwWUmiUQxFJe+sKlZGM771ownyOg== - dependencies: - "@docusaurus/core" "2.4.0" - "@docusaurus/types" "2.4.0" - "@docusaurus/utils-validation" "2.4.0" - tslib "^2.4.0" - -"@docusaurus/plugin-google-tag-manager@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-2.4.0.tgz#9a94324ac496835fc34e233cc60441df4e04dfdd" - integrity sha512-E66uGcYs4l7yitmp/8kMEVQftFPwV9iC62ORh47Veqzs6ExwnhzBkJmwDnwIysHBF1vlxnzET0Fl2LfL5fRR3A== - dependencies: - "@docusaurus/core" "2.4.0" - "@docusaurus/types" "2.4.0" - "@docusaurus/utils-validation" "2.4.0" - tslib "^2.4.0" - -"@docusaurus/plugin-sitemap@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.4.0.tgz#ba0eb43565039fe011bdd874b5c5d7252b19d709" - integrity sha512-pZxh+ygfnI657sN8a/FkYVIAmVv0CGk71QMKqJBOfMmDHNN1FeDeFkBjWP49ejBqpqAhjufkv5UWq3UOu2soCw== - dependencies: - "@docusaurus/core" "2.4.0" - "@docusaurus/logger" "2.4.0" - "@docusaurus/types" "2.4.0" - "@docusaurus/utils" "2.4.0" - "@docusaurus/utils-common" "2.4.0" - "@docusaurus/utils-validation" "2.4.0" - fs-extra "^10.1.0" - sitemap "^7.1.1" - tslib "^2.4.0" - -"@docusaurus/preset-classic@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.4.0.tgz#92fdcfab35d8d0ffb8c38bcbf439e4e1cb0566a3" - integrity sha512-/5z5o/9bc6+P5ool2y01PbJhoGddEGsC0ej1MF6mCoazk8A+kW4feoUd68l7Bnv01rCnG3xy7kHUQP97Y0grUA== - dependencies: - "@docusaurus/core" "2.4.0" - "@docusaurus/plugin-content-blog" "2.4.0" - "@docusaurus/plugin-content-docs" "2.4.0" - "@docusaurus/plugin-content-pages" "2.4.0" - "@docusaurus/plugin-debug" "2.4.0" - "@docusaurus/plugin-google-analytics" "2.4.0" - "@docusaurus/plugin-google-gtag" "2.4.0" - "@docusaurus/plugin-google-tag-manager" "2.4.0" - "@docusaurus/plugin-sitemap" "2.4.0" - "@docusaurus/theme-classic" "2.4.0" - "@docusaurus/theme-common" "2.4.0" - "@docusaurus/theme-search-algolia" "2.4.0" - "@docusaurus/types" "2.4.0" - -"@docusaurus/react-loadable@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" - integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== - dependencies: - "@types/react" "*" - prop-types "^15.6.2" - -"@docusaurus/theme-classic@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.4.0.tgz#a5404967b00adec3472efca4c3b3f6a5e2021c78" - integrity sha512-GMDX5WU6Z0OC65eQFgl3iNNEbI9IMJz9f6KnOyuMxNUR6q0qVLsKCNopFUDfFNJ55UU50o7P7o21yVhkwpfJ9w== - dependencies: - "@docusaurus/core" "2.4.0" - "@docusaurus/mdx-loader" "2.4.0" - "@docusaurus/module-type-aliases" "2.4.0" - "@docusaurus/plugin-content-blog" "2.4.0" - "@docusaurus/plugin-content-docs" "2.4.0" - "@docusaurus/plugin-content-pages" "2.4.0" - "@docusaurus/theme-common" "2.4.0" - "@docusaurus/theme-translations" "2.4.0" - "@docusaurus/types" "2.4.0" - "@docusaurus/utils" "2.4.0" - "@docusaurus/utils-common" "2.4.0" - "@docusaurus/utils-validation" "2.4.0" - "@mdx-js/react" "^1.6.22" - clsx "^1.2.1" - copy-text-to-clipboard "^3.0.1" - infima "0.2.0-alpha.43" - lodash "^4.17.21" - nprogress "^0.2.0" - postcss "^8.4.14" - prism-react-renderer "^1.3.5" - prismjs "^1.28.0" - react-router-dom "^5.3.3" - rtlcss "^3.5.0" - tslib "^2.4.0" - utility-types "^3.10.0" - -"@docusaurus/theme-common@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.4.0.tgz#626096fe9552d240a2115b492c7e12099070cf2d" - integrity sha512-IkG/l5f/FLY6cBIxtPmFnxpuPzc5TupuqlOx+XDN+035MdQcAh8wHXXZJAkTeYDeZ3anIUSUIvWa7/nRKoQEfg== - dependencies: - "@docusaurus/mdx-loader" "2.4.0" - "@docusaurus/module-type-aliases" "2.4.0" - "@docusaurus/plugin-content-blog" "2.4.0" - "@docusaurus/plugin-content-docs" "2.4.0" - "@docusaurus/plugin-content-pages" "2.4.0" - "@docusaurus/utils" "2.4.0" - "@docusaurus/utils-common" "2.4.0" - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-router-config" "*" - clsx "^1.2.1" - parse-numeric-range "^1.3.0" - prism-react-renderer "^1.3.5" - tslib "^2.4.0" - use-sync-external-store "^1.2.0" - utility-types "^3.10.0" - -"@docusaurus/theme-search-algolia@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.0.tgz#07d297d50c44446d6bc5a37be39afb8f014084e1" - integrity sha512-pPCJSCL1Qt4pu/Z0uxBAuke0yEBbxh0s4fOvimna7TEcBLPq0x06/K78AaABXrTVQM6S0vdocFl9EoNgU17hqA== - dependencies: - "@docsearch/react" "^3.1.1" - "@docusaurus/core" "2.4.0" - "@docusaurus/logger" "2.4.0" - "@docusaurus/plugin-content-docs" "2.4.0" - "@docusaurus/theme-common" "2.4.0" - "@docusaurus/theme-translations" "2.4.0" - "@docusaurus/utils" "2.4.0" - "@docusaurus/utils-validation" "2.4.0" - algoliasearch "^4.13.1" - algoliasearch-helper "^3.10.0" - clsx "^1.2.1" - eta "^2.0.0" - fs-extra "^10.1.0" - lodash "^4.17.21" - tslib "^2.4.0" - utility-types "^3.10.0" - -"@docusaurus/theme-translations@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.4.0.tgz#62dacb7997322f4c5a828b3ab66177ec6769eb33" - integrity sha512-kEoITnPXzDPUMBHk3+fzEzbopxLD3fR5sDoayNH0vXkpUukA88/aDL1bqkhxWZHA3LOfJ3f0vJbOwmnXW5v85Q== - dependencies: - fs-extra "^10.1.0" - tslib "^2.4.0" - -"@docusaurus/types@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.4.0.tgz#f94f89a0253778b617c5d40ac6f16b17ec55ce41" - integrity sha512-xaBXr+KIPDkIaef06c+i2HeTqVNixB7yFut5fBXPGI2f1rrmEV2vLMznNGsFwvZ5XmA3Quuefd4OGRkdo97Dhw== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - commander "^5.1.0" - joi "^17.6.0" - react-helmet-async "^1.3.0" - utility-types "^3.10.0" - webpack "^5.73.0" - webpack-merge "^5.8.0" - -"@docusaurus/utils-common@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.4.0.tgz#eb2913871860ed32e73858b4c7787dd820c5558d" - integrity sha512-zIMf10xuKxddYfLg5cS19x44zud/E9I7lj3+0bv8UIs0aahpErfNrGhijEfJpAfikhQ8tL3m35nH3hJ3sOG82A== - dependencies: - tslib "^2.4.0" - -"@docusaurus/utils-validation@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.4.0.tgz#1ed92bfab5da321c4a4d99cad28a15627091aa90" - integrity sha512-IrBsBbbAp6y7mZdJx4S4pIA7dUyWSA0GNosPk6ZJ0fX3uYIEQgcQSGIgTeSC+8xPEx3c16o03en1jSDpgQgz/w== - dependencies: - "@docusaurus/logger" "2.4.0" - "@docusaurus/utils" "2.4.0" - joi "^17.6.0" - js-yaml "^4.1.0" - tslib "^2.4.0" - -"@docusaurus/utils@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.4.0.tgz#fdf0c3545819e48bb57eafc5057495fd4d50e900" - integrity sha512-89hLYkvtRX92j+C+ERYTuSUK6nF9bGM32QThcHPg2EDDHVw6FzYQXmX6/p+pU5SDyyx5nBlE4qXR92RxCAOqfg== - dependencies: - "@docusaurus/logger" "2.4.0" - "@svgr/webpack" "^6.2.1" - escape-string-regexp "^4.0.0" - file-loader "^6.2.0" - fs-extra "^10.1.0" - github-slugger "^1.4.0" - globby "^11.1.0" - gray-matter "^4.0.3" - js-yaml "^4.1.0" - lodash "^4.17.21" - micromatch "^4.0.5" - resolve-pathname "^3.0.0" - shelljs "^0.8.5" - tslib "^2.4.0" - url-loader "^4.1.1" - webpack "^5.73.0" - -"@hapi/hoek@^9.0.0": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" - integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== - -"@hapi/topo@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" - integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@jest/schemas@^29.0.0": - version "29.0.0" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a" - integrity sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA== - dependencies: - "@sinclair/typebox" "^0.24.1" - -"@jest/types@^29.3.1": - version "29.3.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.3.1.tgz#7c5a80777cb13e703aeec6788d044150341147e3" - integrity sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA== - dependencies: - "@jest/schemas" "^29.0.0" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== - dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== - -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/source-map@^0.3.3": - version "0.3.6" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" - integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - -"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" - -"@jridgewell/trace-mapping@^0.3.17": - version "0.3.20" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" - integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@leichtgewicht/ip-codec@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" - integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== - -"@mdx-js/mdx@^1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" - integrity sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA== - dependencies: - "@babel/core" "7.12.9" - "@babel/plugin-syntax-jsx" "7.12.1" - "@babel/plugin-syntax-object-rest-spread" "7.8.3" - "@mdx-js/util" "1.6.22" - babel-plugin-apply-mdx-type-prop "1.6.22" - babel-plugin-extract-import-names "1.6.22" - camelcase-css "2.0.1" - detab "2.0.4" - hast-util-raw "6.0.1" - lodash.uniq "4.5.0" - mdast-util-to-hast "10.0.1" - remark-footnotes "2.0.0" - remark-mdx "1.6.22" - 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" - -"@mdx-js/react@^1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573" - integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== - -"@mdx-js/util@1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" - integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@polka/url@^1.0.0-next.20": - version "1.0.0-next.21" - resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" - integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== - -"@sideway/address@^4.1.3": - version "4.1.4" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" - integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@sideway/formula@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" - integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== - -"@sideway/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" - integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== - -"@sinclair/typebox@^0.24.1": - version "0.24.51" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" - integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== - -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@slorber/static-site-generator-webpack-plugin@^4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.7.tgz#fc1678bddefab014e2145cbe25b3ce4e1cfc36f3" - integrity sha512-Ug7x6z5lwrz0WqdnNFOMYrDQNTPAprvHLSh6+/fmml3qUiz6l5eq+2MzLKWtn/q5K5NpSiFsZTP/fck/3vjSxA== - dependencies: - eval "^0.1.8" - p-map "^4.0.0" - webpack-sources "^3.2.2" - -"@svgr/babel-plugin-add-jsx-attribute@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz#74a5d648bd0347bda99d82409d87b8ca80b9a1ba" - integrity sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ== - -"@svgr/babel-plugin-remove-jsx-attribute@*": - version "6.5.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-6.5.0.tgz#652bfd4ed0a0699843585cda96faeb09d6e1306e" - integrity sha512-8zYdkym7qNyfXpWvu4yq46k41pyNM9SOstoWhKlm+IfdCE1DdnRKeMUPsWIEO/DEkaWxJ8T9esNdG3QwQ93jBA== - -"@svgr/babel-plugin-remove-jsx-empty-expression@*": - version "6.5.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-6.5.0.tgz#4b78994ab7d39032c729903fc2dd5c0fa4565cb8" - integrity sha512-NFdxMq3xA42Kb1UbzCVxplUc0iqSyM9X8kopImvFnB+uSDdzIHOdbs1op8ofAvVRtbg4oZiyRl3fTYeKcOe9Iw== - -"@svgr/babel-plugin-replace-jsx-attribute-value@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz#fb9d22ea26d2bc5e0a44b763d4c46d5d3f596c60" - integrity sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg== - -"@svgr/babel-plugin-svg-dynamic-title@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz#01b2024a2b53ffaa5efceaa0bf3e1d5a4c520ce4" - integrity sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw== - -"@svgr/babel-plugin-svg-em-dimensions@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz#dd3fa9f5b24eb4f93bcf121c3d40ff5facecb217" - integrity sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA== - -"@svgr/babel-plugin-transform-react-native-svg@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz#1d8e945a03df65b601551097d8f5e34351d3d305" - integrity sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg== - -"@svgr/babel-plugin-transform-svg-component@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz#48620b9e590e25ff95a80f811544218d27f8a250" - integrity sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ== - -"@svgr/babel-preset@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.5.1.tgz#b90de7979c8843c5c580c7e2ec71f024b49eb828" - integrity sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw== - dependencies: - "@svgr/babel-plugin-add-jsx-attribute" "^6.5.1" - "@svgr/babel-plugin-remove-jsx-attribute" "*" - "@svgr/babel-plugin-remove-jsx-empty-expression" "*" - "@svgr/babel-plugin-replace-jsx-attribute-value" "^6.5.1" - "@svgr/babel-plugin-svg-dynamic-title" "^6.5.1" - "@svgr/babel-plugin-svg-em-dimensions" "^6.5.1" - "@svgr/babel-plugin-transform-react-native-svg" "^6.5.1" - "@svgr/babel-plugin-transform-svg-component" "^6.5.1" - -"@svgr/core@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.5.1.tgz#d3e8aa9dbe3fbd747f9ee4282c1c77a27410488a" - integrity sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw== - dependencies: - "@babel/core" "^7.19.6" - "@svgr/babel-preset" "^6.5.1" - "@svgr/plugin-jsx" "^6.5.1" - camelcase "^6.2.0" - cosmiconfig "^7.0.1" - -"@svgr/hast-util-to-babel-ast@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz#81800bd09b5bcdb968bf6ee7c863d2288fdb80d2" - integrity sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw== - dependencies: - "@babel/types" "^7.20.0" - entities "^4.4.0" - -"@svgr/plugin-jsx@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz#0e30d1878e771ca753c94e69581c7971542a7072" - integrity sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw== - dependencies: - "@babel/core" "^7.19.6" - "@svgr/babel-preset" "^6.5.1" - "@svgr/hast-util-to-babel-ast" "^6.5.1" - svg-parser "^2.0.4" - -"@svgr/plugin-svgo@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz#0f91910e988fc0b842f88e0960c2862e022abe84" - integrity sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ== - dependencies: - cosmiconfig "^7.0.1" - deepmerge "^4.2.2" - svgo "^2.8.0" - -"@svgr/webpack@^6.2.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.5.1.tgz#ecf027814fc1cb2decc29dc92f39c3cf691e40e8" - integrity sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA== - dependencies: - "@babel/core" "^7.19.6" - "@babel/plugin-transform-react-constant-elements" "^7.18.12" - "@babel/preset-env" "^7.19.4" - "@babel/preset-react" "^7.18.6" - "@babel/preset-typescript" "^7.18.6" - "@svgr/core" "^6.5.1" - "@svgr/plugin-jsx" "^6.5.1" - "@svgr/plugin-svgo" "^6.5.1" - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - -"@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/bonjour@^3.5.9": - version "3.5.10" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" - integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== - dependencies: - "@types/node" "*" - -"@types/connect-history-api-fallback@^1.3.5": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" - integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== - dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - -"@types/eslint-scope@^3.7.7": - version "3.7.7" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" - integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.4.10" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.10.tgz#19731b9685c19ed1552da7052b6f668ed7eb64bb" - integrity sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== - -"@types/estree@^1.0.6": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" - integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.31": - version "4.17.31" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz#a1139efeab4e7323834bb0226e62ac019f474b2f" - integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express@*", "@types/express@^4.17.13": - version "4.17.15" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.15.tgz#9290e983ec8b054b65a5abccb610411953d417ff" - integrity sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.31" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/hast@^2.0.0": - version "2.3.4" - resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc" - integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g== - dependencies: - "@types/unist" "*" - -"@types/history@^4.7.11": - version "4.7.11" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" - integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== - -"@types/html-minifier-terser@^6.0.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" - integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== - -"@types/http-proxy@^1.17.8": - version "1.17.9" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" - integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== - -"@types/mdast@^3.0.0": - version "3.0.10" - resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" - integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA== - dependencies: - "@types/unist" "*" - -"@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - -"@types/node@*": - version "18.11.17" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.17.tgz#5c009e1d9c38f4a2a9d45c0b0c493fe6cdb4bcb5" - integrity sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng== - -"@types/node@^17.0.5": - version "17.0.45" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" - integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== - -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -"@types/parse5@^5.0.0": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" - integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== - -"@types/prop-types@*": - version "15.7.5" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== - -"@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/react-router-config@*", "@types/react-router-config@^5.0.6": - version "5.0.6" - resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.6.tgz#87c5c57e72d241db900d9734512c50ccec062451" - integrity sha512-db1mx37a1EJDf1XeX8jJN7R3PZABmJQXR8r28yUjVMFSjkmnQo6X6pOEEmNl+Tp2gYQOGPdYbFIipBtdElZ3Yg== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-router" "*" - -"@types/react-router-dom@*": - version "5.3.3" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" - integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-router" "*" - -"@types/react-router@*": - version "5.1.19" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.19.tgz#9b404246fba7f91474d7008a3d48c17b6e075ad6" - integrity sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - -"@types/react@*": - version "18.0.26" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917" - integrity sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/retry@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - -"@types/sax@^1.2.1": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.4.tgz#8221affa7f4f3cb21abd22f244cfabfa63e6a69e" - integrity sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw== - dependencies: - "@types/node" "*" - -"@types/scheduler@*": - version "0.16.2" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" - integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== - -"@types/serve-index@^1.9.1": - version "1.9.1" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" - integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== - dependencies: - "@types/express" "*" - -"@types/serve-static@*", "@types/serve-static@^1.13.10": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" - integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== - dependencies: - "@types/mime" "*" - "@types/node" "*" - -"@types/sockjs@^0.3.33": - version "0.3.33" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" - integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== - dependencies: - "@types/node" "*" - -"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" - integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== - -"@types/ws@^8.5.1": - version "8.5.3" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" - integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== - dependencies: - "@types/node" "*" - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^17.0.8": - version "17.0.17" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.17.tgz#5672e5621f8e0fca13f433a8017aae4b7a2a03e7" - integrity sha512-72bWxFKTK6uwWJAVT+3rF6Jo6RTojiJ27FQo8Rf60AL+VZbzoVPnMFhKsUnbjR8A3BTCYQ7Mv3hnl8T0A+CX9g== - dependencies: - "@types/yargs-parser" "*" - -"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" - integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== - dependencies: - "@webassemblyjs/helper-numbers" "1.13.2" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - -"@webassemblyjs/floating-point-hex-parser@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" - integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== - -"@webassemblyjs/helper-api-error@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" - integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== - -"@webassemblyjs/helper-buffer@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" - integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== - -"@webassemblyjs/helper-numbers@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" - integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.13.2" - "@webassemblyjs/helper-api-error" "1.13.2" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" - integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== - -"@webassemblyjs/helper-wasm-section@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" - integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/wasm-gen" "1.14.1" - -"@webassemblyjs/ieee754@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" - integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" - integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" - integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== - -"@webassemblyjs/wasm-edit@^1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" - integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/helper-wasm-section" "1.14.1" - "@webassemblyjs/wasm-gen" "1.14.1" - "@webassemblyjs/wasm-opt" "1.14.1" - "@webassemblyjs/wasm-parser" "1.14.1" - "@webassemblyjs/wast-printer" "1.14.1" - -"@webassemblyjs/wasm-gen@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" - integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/ieee754" "1.13.2" - "@webassemblyjs/leb128" "1.13.2" - "@webassemblyjs/utf8" "1.13.2" - -"@webassemblyjs/wasm-opt@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" - integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/wasm-gen" "1.14.1" - "@webassemblyjs/wasm-parser" "1.14.1" - -"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" - integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-api-error" "1.13.2" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/ieee754" "1.13.2" - "@webassemblyjs/leb128" "1.13.2" - "@webassemblyjs/utf8" "1.13.2" - -"@webassemblyjs/wast-printer@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" - integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@xtuc/long" "4.2.2" - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-walk@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - -acorn@^8.0.4, acorn@^8.5.0: - version "8.8.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" - integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== - -acorn@^8.14.0, acorn@^8.8.2: - version "8.14.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" - integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== - -address@^1.0.1, address@^1.1.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" - integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv-keywords@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== - dependencies: - fast-deep-equal "^3.1.3" - -ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.0, ajv@^8.8.0: - version "8.11.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.2.tgz#aecb20b50607acf2569b6382167b65a96008bb78" - integrity sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -algoliasearch-helper@^3.10.0: - version "3.11.1" - resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.11.1.tgz#d83ab7f1a2a374440686ef7a144b3c288b01188a" - integrity sha512-mvsPN3eK4E0bZG0/WlWJjeqe/bUD2KOEVOl0GyL/TGXn6wcpZU8NOuztGHCUKXkyg5gq6YzUakVTmnmSSO5Yiw== - dependencies: - "@algolia/events" "^4.0.1" - -algoliasearch@^4.0.0, algoliasearch@^4.13.1: - version "4.14.3" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.14.3.tgz#f02a77a4db17de2f676018938847494b692035e7" - integrity sha512-GZTEuxzfWbP/vr7ZJfGzIl8fOsoxN916Z6FY2Egc9q2TmZ6hvq5KfAxY89pPW01oW/2HDEKA8d30f9iAH9eXYg== - dependencies: - "@algolia/cache-browser-local-storage" "4.14.3" - "@algolia/cache-common" "4.14.3" - "@algolia/cache-in-memory" "4.14.3" - "@algolia/client-account" "4.14.3" - "@algolia/client-analytics" "4.14.3" - "@algolia/client-common" "4.14.3" - "@algolia/client-personalization" "4.14.3" - "@algolia/client-search" "4.14.3" - "@algolia/logger-common" "4.14.3" - "@algolia/logger-console" "4.14.3" - "@algolia/requester-browser-xhr" "4.14.3" - "@algolia/requester-common" "4.14.3" - "@algolia/requester-node-http" "4.14.3" - "@algolia/transporter" "4.14.3" - -ansi-align@^3.0.0, ansi-align@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" - integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== - dependencies: - string-width "^4.1.0" - -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -arg@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" - integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -array-flatten@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== - -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - -autoprefixer@^10.4.12, autoprefixer@^10.4.7: - version "10.4.13" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.13.tgz#b5136b59930209a321e9fa3dca2e7c4d223e83a8" - integrity sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg== - dependencies: - browserslist "^4.21.4" - caniuse-lite "^1.0.30001426" - fraction.js "^4.2.0" - normalize-range "^0.1.2" - picocolors "^1.0.0" - postcss-value-parser "^4.2.0" - -axios@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" - integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== - dependencies: - follow-redirects "^1.14.7" - -babel-loader@^8.2.5: - version "8.3.0" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8" - integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q== - dependencies: - find-cache-dir "^3.3.1" - loader-utils "^2.0.0" - make-dir "^3.1.0" - schema-utils "^2.6.5" - -babel-plugin-apply-mdx-type-prop@1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz#d216e8fd0de91de3f1478ef3231e05446bc8705b" - integrity sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ== - dependencies: - "@babel/helper-plugin-utils" "7.10.4" - "@mdx-js/util" "1.6.22" - -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== - dependencies: - object.assign "^4.1.0" - -babel-plugin-extract-import-names@1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz#de5f9a28eb12f3eb2578bf74472204e66d1a13dc" - integrity sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ== - dependencies: - "@babel/helper-plugin-utils" "7.10.4" - -babel-plugin-polyfill-corejs2@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" - integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== - dependencies: - "@babel/compat-data" "^7.17.7" - "@babel/helper-define-polyfill-provider" "^0.3.3" - semver "^6.1.1" - -babel-plugin-polyfill-corejs3@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a" - integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.3" - core-js-compat "^3.25.1" - -babel-plugin-polyfill-regenerator@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" - integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.3" - -bail@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" - integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base16@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" - integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ== - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -body-parser@1.20.3: - version "1.20.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -bonjour-service@^1.0.11: - version "1.0.14" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.0.14.tgz#c346f5bc84e87802d08f8d5a60b93f758e514ee7" - integrity sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ== - dependencies: - array-flatten "^2.1.2" - dns-equal "^1.0.0" - fast-deep-equal "^3.1.3" - multicast-dns "^7.2.5" - -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -boxen@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" - integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^6.2.0" - chalk "^4.1.0" - cli-boxes "^2.2.1" - string-width "^4.2.2" - type-fest "^0.20.2" - widest-line "^3.1.0" - wrap-ansi "^7.0.0" - -boxen@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-6.2.1.tgz#b098a2278b2cd2845deef2dff2efc38d329b434d" - integrity sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw== - dependencies: - ansi-align "^3.0.1" - camelcase "^6.2.0" - chalk "^4.1.2" - cli-boxes "^3.0.0" - string-width "^5.0.1" - type-fest "^2.5.0" - widest-line "^4.0.1" - wrap-ansi "^8.0.1" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.3, braces@~3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browserslist@^4.0.0, browserslist@^4.16.6, browserslist@^4.18.1, browserslist@^4.21.3, browserslist@^4.21.4: - version "4.21.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" - integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== - dependencies: - caniuse-lite "^1.0.30001400" - electron-to-chromium "^1.4.251" - node-releases "^2.0.6" - update-browserslist-db "^1.0.9" - -browserslist@^4.24.0: - version "4.24.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" - integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== - dependencies: - caniuse-lite "^1.0.30001669" - electron-to-chromium "^1.5.41" - node-releases "^2.0.18" - update-browserslist-db "^1.1.1" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - 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" - -call-bind-apply-helpers@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" - integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -call-bind@^1.0.7: - version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" - integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-define-property "^1.0.0" - get-intrinsic "^1.2.4" - set-function-length "^1.2.2" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camel-case@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" - integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== - dependencies: - pascal-case "^3.1.2" - tslib "^2.0.3" - -camelcase-css@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" - integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001426: - version "1.0.30001439" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz#ab7371faeb4adff4b74dad1718a6fd122e45d9cb" - integrity sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A== - -caniuse-lite@^1.0.30001669: - version "1.0.30001687" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz#d0ac634d043648498eedf7a3932836beba90ebae" - integrity sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ== - -ccount@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" - integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== - -chalk@^2.0.0, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -character-entities-legacy@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" - integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== - -character-entities@^1.0.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" - integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== - -character-reference-invalid@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" - integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== - -cheerio-select@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" - integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== - dependencies: - boolbase "^1.0.0" - css-select "^5.1.0" - css-what "^6.1.0" - domelementtype "^2.3.0" - domhandler "^5.0.3" - domutils "^3.0.1" - -cheerio@^1.0.0-rc.12: - version "1.0.0-rc.12" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" - integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== - dependencies: - cheerio-select "^2.1.0" - dom-serializer "^2.0.0" - domhandler "^5.0.3" - domutils "^3.0.1" - htmlparser2 "^8.0.1" - parse5 "^7.0.0" - parse5-htmlparser2-tree-adapter "^7.0.0" - -chokidar@^3.4.2, chokidar@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== - -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -ci-info@^3.2.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.7.0.tgz#6d01b3696c59915b6ce057e4aa4adfc2fa25f5ef" - integrity sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog== - -clean-css@^5.2.2, clean-css@^5.3.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.1.tgz#d0610b0b90d125196a2894d35366f734e5d7aa32" - integrity sha512-lCr8OHhiWCTw4v8POJovCoh4T7I9U11yVsPjMWWnnMmp9ZowCxyad1Pathle/9HjaDp+fdQKjO9fQydE6RHTZg== - dependencies: - source-map "~0.6.0" - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-boxes@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" - integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== - -cli-boxes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" - integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== - -cli-table3@^0.6.2: - version "0.6.3" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" - integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== - dependencies: - string-width "^4.2.0" - optionalDependencies: - "@colors/colors" "1.5.0" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -clone-response@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" - integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== - dependencies: - mimic-response "^1.0.0" - -clsx@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" - integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== - -collapse-white-space@^1.0.2: - version "1.0.6" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" - integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colord@^2.9.1: - version "2.9.3" - resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" - integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== - -colorette@^2.0.10: - version "2.0.19" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== - -combine-promises@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.1.0.tgz#72db90743c0ca7aab7d0d8d2052fd7b0f674de71" - integrity sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg== - -comma-separated-tokens@^1.0.0: - version "1.0.8" - resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" - integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== - -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -commander@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== - -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - 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" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - 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" - -connect-history-api-fallback@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" - integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== - -consola@^2.15.3: - version "2.15.3" - resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" - integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== - -content-disposition@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -convert-source-map@^1.7.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" - integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== - -copy-text-to-clipboard@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c" - integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q== - -copy-webpack-plugin@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" - integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== - dependencies: - fast-glob "^3.2.11" - glob-parent "^6.0.1" - globby "^13.1.1" - normalize-path "^3.0.0" - schema-utils "^4.0.0" - serialize-javascript "^6.0.0" - -core-js-compat@^3.25.1: - version "3.26.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.26.1.tgz#0e710b09ebf689d719545ac36e49041850f943df" - integrity sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A== - dependencies: - browserslist "^4.21.4" - -core-js-pure@^3.30.2: - version "3.41.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.41.0.tgz#349fecad168d60807a31e83c99d73d786fe80811" - integrity sha512-71Gzp96T9YPk63aUvE5Q5qP+DryB4ZloUZPSOebGM88VNw8VNfvdA7z6kGA8iGOTEzAomsRidp4jXSmUIJsL+Q== - -core-js@^3.23.3: - version "3.26.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.26.1.tgz#7a9816dabd9ee846c1c0fe0e8fcad68f3709134e" - integrity sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== - dependencies: - "@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" - -cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" - integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" - -cross-fetch@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" - integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== - dependencies: - node-fetch "2.6.7" - -cross-spawn@^7.0.3: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - -css-declaration-sorter@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz#be5e1d71b7a992433fb1c542c7a1b835e45682ec" - integrity sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w== - -css-loader@^6.7.1: - version "6.7.3" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.3.tgz#1e8799f3ccc5874fdd55461af51137fcc5befbcd" - integrity sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ== - dependencies: - icss-utils "^5.1.0" - postcss "^8.4.19" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.2.0" - semver "^7.3.8" - -css-minimizer-webpack-plugin@^4.0.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz#79f6199eb5adf1ff7ba57f105e3752d15211eb35" - integrity sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA== - dependencies: - cssnano "^5.1.8" - jest-worker "^29.1.2" - postcss "^8.4.17" - schema-utils "^4.0.0" - serialize-javascript "^6.0.0" - source-map "^0.6.1" - -css-select@^4.1.3: - version "4.3.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" - integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== - dependencies: - boolbase "^1.0.0" - css-what "^6.0.1" - domhandler "^4.3.1" - domutils "^2.8.0" - nth-check "^2.0.1" - -css-select@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" - integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== - dependencies: - boolbase "^1.0.0" - css-what "^6.1.0" - domhandler "^5.0.2" - domutils "^3.0.1" - nth-check "^2.0.1" - -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - -css-what@^6.0.1, css-what@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" - integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-advanced@^5.3.8: - version "5.3.9" - resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.9.tgz#99e1cdf81a467a5e6c366cfc6d874a166c4d9a67" - integrity sha512-njnh4pp1xCsibJcEHnWZb4EEzni0ePMqPuPNyuWT4Z+YeXmsgqNuTPIljXFEXhxGsWs9183JkXgHxc1TcsahIg== - dependencies: - autoprefixer "^10.4.12" - cssnano-preset-default "^5.2.13" - postcss-discard-unused "^5.1.0" - postcss-merge-idents "^5.1.1" - postcss-reduce-idents "^5.2.0" - postcss-zindex "^5.1.0" - -cssnano-preset-default@^5.2.13: - version "5.2.13" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.13.tgz#e7353b0c57975d1bdd97ac96e68e5c1b8c68e990" - integrity sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ== - dependencies: - css-declaration-sorter "^6.3.1" - cssnano-utils "^3.1.0" - postcss-calc "^8.2.3" - postcss-colormin "^5.3.0" - postcss-convert-values "^5.1.3" - postcss-discard-comments "^5.1.2" - postcss-discard-duplicates "^5.1.0" - postcss-discard-empty "^5.1.1" - postcss-discard-overridden "^5.1.0" - postcss-merge-longhand "^5.1.7" - postcss-merge-rules "^5.1.3" - postcss-minify-font-values "^5.1.0" - postcss-minify-gradients "^5.1.1" - postcss-minify-params "^5.1.4" - postcss-minify-selectors "^5.2.1" - postcss-normalize-charset "^5.1.0" - postcss-normalize-display-values "^5.1.0" - postcss-normalize-positions "^5.1.1" - postcss-normalize-repeat-style "^5.1.1" - postcss-normalize-string "^5.1.0" - postcss-normalize-timing-functions "^5.1.0" - postcss-normalize-unicode "^5.1.1" - postcss-normalize-url "^5.1.0" - postcss-normalize-whitespace "^5.1.1" - postcss-ordered-values "^5.1.3" - postcss-reduce-initial "^5.1.1" - postcss-reduce-transforms "^5.1.0" - postcss-svgo "^5.1.0" - postcss-unique-selectors "^5.1.1" - -cssnano-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" - integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== - -cssnano@^5.1.12, cssnano@^5.1.8: - version "5.1.14" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.14.tgz#07b0af6da73641276fe5a6d45757702ebae2eb05" - integrity sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw== - dependencies: - cssnano-preset-default "^5.2.13" - lilconfig "^2.0.3" - yaml "^1.10.2" - -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - -csstype@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" - integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== - -debug@2.6.9, debug@^2.6.0: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4, debug@^4.1.0, debug@^4.1.1: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== - dependencies: - mimic-response "^1.0.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - -default-gateway@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" - integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== - dependencies: - execa "^5.0.0" - -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - -define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - -define-properties@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" - integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== - dependencies: - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -del@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" - integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== - dependencies: - globby "^11.0.1" - graceful-fs "^4.2.4" - is-glob "^4.0.1" - is-path-cwd "^2.2.0" - is-path-inside "^3.0.2" - p-map "^4.0.0" - rimraf "^3.0.2" - slash "^3.0.0" - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detab@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.4.tgz#b927892069aff405fbb9a186fe97a44a92a94b43" - integrity sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g== - dependencies: - repeat-string "^1.5.4" - -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - -detect-port-alt@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" - integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== - dependencies: - address "^1.0.1" - debug "^2.6.0" - -detect-port@^1.3.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.5.1.tgz#451ca9b6eaf20451acb0799b8ab40dff7718727b" - integrity sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ== - dependencies: - address "^1.0.1" - debug "4" - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== - -dns-packet@^5.2.2: - version "5.4.0" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.4.0.tgz#1f88477cf9f27e78a213fb6d118ae38e759a879b" - integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g== - dependencies: - "@leichtgewicht/ip-codec" "^2.0.1" - -dom-converter@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" - integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== - dependencies: - utila "~0.4" - -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - -dom-serializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" - integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== - dependencies: - domelementtype "^2.3.0" - domhandler "^5.0.2" - entities "^4.2.0" - -domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - -domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" - integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== - dependencies: - domelementtype "^2.3.0" - -domutils@^2.5.2, domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - -domutils@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c" - integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q== - dependencies: - dom-serializer "^2.0.0" - domelementtype "^2.3.0" - domhandler "^5.0.1" - -dot-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" - integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - -dunder-proto@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.0.tgz#c2fce098b3c8f8899554905f4377b6d85dabaa80" - integrity sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-errors "^1.3.0" - gopd "^1.2.0" - -duplexer3@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" - integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== - -duplexer@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" - integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.4.251: - version "1.4.284" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" - integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== - -electron-to-chromium@^1.5.41: - version "1.5.72" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.72.tgz#a732805986d3a5b5fedd438ddf4616c7d78ac2df" - integrity sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -emoticon@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/emoticon/-/emoticon-3.2.0.tgz#c008ca7d7620fac742fe1bf4af8ff8fed154ae7f" - integrity sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -encodeurl@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enhanced-resolve@^5.17.1: - version "5.17.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" - integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - -entities@^4.2.0, entities@^4.3.0, entities@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" - integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-define-property@^1.0.0, es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-module-lexer@^1.2.1: - version "1.5.4" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" - integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - -escape-html@^1.0.3, escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -eta@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/eta/-/eta-2.0.1.tgz#199e675359cb6e19d38f29e1f405e1ba0e79a6df" - integrity sha512-46E2qDPDm7QA+usjffUWz9KfXsxVZclPOuKsXs4ZWZdI/X1wpDF7AO424pt7fdYohCzWsIkXAhNGXSlwo5naAg== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eval@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.8.tgz#2b903473b8cc1d1989b83a1e7923f883eb357f85" - integrity sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw== - dependencies: - "@types/node" "*" - require-like ">= 0.1.1" - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - 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" - -express@^4.17.3: - version "4.21.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" - integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.3" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.7.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.3.1" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.3" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.12" - proxy-addr "~2.0.7" - qs "6.13.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.19.0" - serve-static "1.16.2" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== - dependencies: - is-extendable "^0.1.0" - -extend@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-glob@^3.2.11, fast-glob@^3.2.9: - version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-url-parser@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" - integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== - dependencies: - punycode "^1.3.2" - -fastq@^1.6.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.14.0.tgz#107f69d7295b11e0fccc264e1fc6389f623731ce" - integrity sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg== - dependencies: - reusify "^1.0.4" - -faye-websocket@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - -fbemitter@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3" - integrity sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw== - dependencies: - fbjs "^3.0.0" - -fbjs-css-vars@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" - integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== - -fbjs@^3.0.0, fbjs@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6" - integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ== - dependencies: - cross-fetch "^3.1.5" - fbjs-css-vars "^1.0.0" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.30" - -feed@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e" - integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ== - dependencies: - xml-js "^1.6.11" - -file-loader@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" - integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -filesize@^8.0.6: - version "8.0.7" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" - integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" - integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== - dependencies: - debug "2.6.9" - encodeurl "~2.0.0" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -find-cache-dir@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" - integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== - dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flux@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.3.tgz#573b504a24982c4768fdfb59d8d2ea5637d72ee7" - integrity sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw== - dependencies: - fbemitter "^3.0.0" - fbjs "^3.0.1" - -follow-redirects@^1.0.0, follow-redirects@^1.14.7: - version "1.15.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== - -fork-ts-checker-webpack-plugin@^6.5.0: - version "6.5.2" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz#4f67183f2f9eb8ba7df7177ce3cf3e75cdafb340" - integrity sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA== - dependencies: - "@babel/code-frame" "^7.8.3" - "@types/json-schema" "^7.0.5" - chalk "^4.1.0" - chokidar "^3.4.2" - cosmiconfig "^6.0.0" - deepmerge "^4.2.2" - fs-extra "^9.0.0" - glob "^7.1.6" - memfs "^3.1.2" - minimatch "^3.0.4" - schema-utils "2.7.0" - semver "^7.3.2" - tapable "^1.0.0" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fraction.js@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" - integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-extra@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-extra@^9.0.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-monkey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" - integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" - integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.3" - -get-intrinsic@^1.2.4: - version "1.2.5" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.5.tgz#dfe7dd1b30761b464fe51bf4bb00ac7c37b681e7" - integrity sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg== - dependencies: - call-bind-apply-helpers "^1.0.0" - dunder-proto "^1.0.0" - es-define-property "^1.0.1" - es-errors "^1.3.0" - function-bind "^1.1.2" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - -get-own-enumerable-property-symbols@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" - integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== - -get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -github-slugger@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.5.0.tgz#17891bbc73232051474d68bd867a34625c955f7d" - integrity sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw== - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-dirs@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" - integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== - dependencies: - ini "2.0.0" - -global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globby@^11.0.1, globby@^11.0.4, globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -globby@^13.1.1: - version "13.1.3" - resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.3.tgz#f62baf5720bcb2c1330c8d4ef222ee12318563ff" - integrity sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw== - dependencies: - dir-glob "^3.0.1" - fast-glob "^3.2.11" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^4.0.0" - -gopd@^1.0.1, gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@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@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - -graceful-fs@^4.2.11: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -gray-matter@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" - integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== - dependencies: - js-yaml "^3.13.1" - kind-of "^6.0.2" - section-matter "^1.0.0" - strip-bom-string "^1.0.0" - -gzip-size@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" - integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== - dependencies: - duplexer "^0.1.2" - -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== - dependencies: - get-intrinsic "^1.1.1" - -has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -hast-to-hyperscript@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz#9b67fd188e4c81e8ad66f803855334173920218d" - integrity sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA== - dependencies: - "@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@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz#554e34abdeea25ac76f5bd950a1f0180e0b3bc2a" - integrity sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA== - dependencies: - "@types/parse5" "^5.0.0" - hastscript "^6.0.0" - property-information "^5.0.0" - vfile "^4.0.0" - vfile-location "^3.2.0" - web-namespaces "^1.0.0" - -hast-util-parse-selector@^2.0.0: - version "2.2.5" - resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" - integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== - -hast-util-raw@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-6.0.1.tgz#973b15930b7529a7b66984c98148b46526885977" - integrity sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig== - dependencies: - "@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@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479" - integrity sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ== - dependencies: - 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@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" - integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w== - dependencies: - "@types/hast" "^2.0.0" - 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@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -history@^4.9.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" - integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== - dependencies: - "@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" - -hoist-non-react-statics@^3.1.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" - integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== - dependencies: - react-is "^16.7.0" - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -html-entities@^2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" - integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA== - -html-minifier-terser@^6.0.2, html-minifier-terser@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" - integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== - dependencies: - camel-case "^4.1.2" - clean-css "^5.2.2" - commander "^8.3.0" - he "^1.2.0" - param-case "^3.0.4" - relateurl "^0.2.7" - terser "^5.10.0" - -html-tags@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961" - integrity sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg== - -html-void-elements@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" - integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== - -html-webpack-plugin@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" - integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== - dependencies: - "@types/html-minifier-terser" "^6.0.0" - html-minifier-terser "^6.0.2" - lodash "^4.17.21" - pretty-error "^4.0.0" - tapable "^2.0.0" - -htmlparser2@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" - integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - domutils "^2.5.2" - entities "^2.0.0" - -htmlparser2@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.1.tgz#abaa985474fcefe269bc761a779b544d7196d010" - integrity sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA== - dependencies: - domelementtype "^2.3.0" - domhandler "^5.0.2" - domutils "^3.0.1" - entities "^4.3.0" - -http-cache-semantics@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - -http-proxy-middleware@^2.0.3: - version "2.0.9" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef" - integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q== - dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - -ignore@^5.2.0: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== - -image-size@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.2.tgz#d778b6d0ab75b2737c1556dd631652eb963bc486" - integrity sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg== - dependencies: - queue "6.0.2" - -immer@^9.0.7: - version "9.0.16" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.16.tgz#8e7caab80118c2b54b37ad43e05758cdefad0198" - integrity sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ== - -import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A== - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -infima@0.2.0-alpha.43: - version "0.2.0-alpha.43" - resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.43.tgz#f7aa1d7b30b6c08afef441c726bac6150228cbe0" - integrity sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - -ini@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - -ini@^1.3.5, ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -inline-style-parser@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" - integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== - -interpret@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" - integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== - -is-alphabetical@1.0.4, is-alphabetical@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" - integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== - -is-alphanumerical@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" - integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== - dependencies: - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-buffer@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" - integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== - dependencies: - has "^1.0.3" - -is-decimal@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" - integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== - -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extendable@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-hexadecimal@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" - integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== - -is-installed-globally@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== - dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" - -is-npm@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" - integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-cwd@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== - -is-path-inside@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-plain-obj@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" - integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== - -is-root@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" - integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - -is-whitespace-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" - integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== - -is-word-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" - integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jest-util@^29.3.1: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.3.1.tgz#1dda51e378bbcb7e3bc9d8ab651445591ed373e1" - integrity sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ== - dependencies: - "@jest/types" "^29.3.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest-worker@^29.1.2: - version "29.3.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.3.1.tgz#e9462161017a9bb176380d721cab022661da3d6b" - integrity sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw== - dependencies: - "@types/node" "*" - jest-util "^29.3.1" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -joi@^17.6.0: - version "17.7.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.0.tgz#591a33b1fe1aca2bc27f290bcad9b9c1c570a6b3" - integrity sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg== - dependencies: - "@hapi/hoek" "^9.0.0" - "@hapi/topo" "^5.0.0" - "@sideway/address" "^4.1.3" - "@sideway/formula" "^3.0.0" - "@sideway/pinpoint" "^2.0.0" - -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== - -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== - -json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json5@^2.1.2, json5@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.2.tgz#64471c5bdcc564c18f7c1d4df2e2297f2457c5ab" - integrity sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ== - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -klona@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" - integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== - -latest-version@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -lilconfig@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" - integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -loader-runner@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" - integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== - -loader-utils@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" - integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -loader-utils@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576" - integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw== - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.curry@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" - integrity sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA== - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== - -lodash.flow@^3.3.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" - integrity sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw== - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.uniq@4.5.0, lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== - -lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -lower-case@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" - integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== - dependencies: - tslib "^2.0.3" - -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -markdown-escapes@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" - integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== - -mdast-squeeze-paragraphs@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz#7c4c114679c3bee27ef10b58e2e015be79f1ef97" - integrity sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ== - dependencies: - unist-util-remove "^2.0.0" - -mdast-util-definitions@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2" - integrity sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ== - dependencies: - unist-util-visit "^2.0.0" - -mdast-util-to-hast@10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb" - integrity sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA== - dependencies: - "@types/mdast" "^3.0.0" - "@types/unist" "^2.0.0" - mdast-util-definitions "^4.0.0" - mdurl "^1.0.0" - unist-builder "^2.0.0" - unist-util-generated "^1.0.0" - unist-util-position "^3.0.0" - unist-util-visit "^2.0.0" - -mdast-util-to-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b" - integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w== - -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - -mdurl@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memfs@^3.1.2, memfs@^3.4.3: - version "3.4.12" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.12.tgz#d00f8ad8dab132dc277c659dc85bfd14b07d03bd" - integrity sha512-BcjuQn6vfqP+k100e0E9m61Hyqa//Brp+I3f0OBmN0ATHlFA8vx3Lt8z57R3u2bPqe3WGDBC+nF72fTH7isyEw== - dependencies: - fs-monkey "^1.0.3" - -merge-descriptors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" - integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" - integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== - -mime-types@2.1.18: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" - integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== - dependencies: - mime-db "~1.33.0" - -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -mini-css-extract-plugin@^2.6.1: - version "2.7.2" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.2.tgz#e049d3ea7d3e4e773aad585c6cb329ce0c7b72d7" - integrity sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw== - dependencies: - schema-utils "^4.0.0" - -minimalistic-assert@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimatch@3.1.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" - integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== - -mrmime@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" - integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multicast-dns@^7.2.5: - version "7.2.5" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" - integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== - dependencies: - dns-packet "^5.2.2" - thunky "^1.0.2" - -nanoid@^3.3.6: - version "3.3.8" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" - integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -no-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" - integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== - dependencies: - lower-case "^2.0.2" - tslib "^2.0.3" - -node-emoji@^1.10.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" - integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== - dependencies: - lodash "^4.17.21" - -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -node-forge@^1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== - -node-releases@^2.0.18: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== - -node-releases@^2.0.6: - version "2.0.8" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.8.tgz#0f349cdc8fcfa39a92ac0be9bc48b7706292b9ae" - integrity sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== - -normalize-url@^4.1.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" - integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== - -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -nprogress@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" - integrity sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA== - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - -object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.1: - version "1.13.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" - integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -open@^8.0.9, open@^8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" - integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -opener@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" - integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== - -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - -p-limit@^2.0.0, p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-retry@^4.5.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" - integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== - dependencies: - "@types/retry" "0.12.0" - retry "^0.13.1" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - -param-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" - integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-entities@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" - integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== - dependencies: - 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@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@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@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" - integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== - -parse5-htmlparser2-tree-adapter@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" - integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== - dependencies: - domhandler "^5.0.2" - parse5 "^7.0.0" - -parse5@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - -parse5@^7.0.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" - integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== - dependencies: - entities "^4.4.0" - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascal-case@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" - integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-is-inside@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" - integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== - -path-to-regexp@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" - integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== - -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picocolors@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pkg-dir@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -pkg-up@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== - dependencies: - find-up "^3.0.0" - -postcss-calc@^8.2.3: - version "8.2.4" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" - integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== - dependencies: - postcss-selector-parser "^6.0.9" - postcss-value-parser "^4.2.0" - -postcss-colormin@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a" - integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - colord "^2.9.1" - postcss-value-parser "^4.2.0" - -postcss-convert-values@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393" - integrity sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA== - dependencies: - browserslist "^4.21.4" - postcss-value-parser "^4.2.0" - -postcss-discard-comments@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" - integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== - -postcss-discard-duplicates@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" - integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== - -postcss-discard-empty@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" - integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== - -postcss-discard-overridden@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" - integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== - -postcss-discard-unused@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz#8974e9b143d887677304e558c1166d3762501142" - integrity sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-loader@^7.0.0: - version "7.0.2" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.0.2.tgz#b53ff44a26fba3688eee92a048c7f2d4802e23bb" - integrity sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg== - dependencies: - cosmiconfig "^7.0.0" - klona "^2.0.5" - semver "^7.3.8" - -postcss-merge-idents@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-5.1.1.tgz#7753817c2e0b75d0853b56f78a89771e15ca04a1" - integrity sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw== - dependencies: - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-merge-longhand@^5.1.7: - version "5.1.7" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16" - integrity sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ== - dependencies: - postcss-value-parser "^4.2.0" - stylehacks "^5.1.1" - -postcss-merge-rules@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.3.tgz#8f97679e67cc8d08677a6519afca41edf2220894" - integrity sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA== - dependencies: - browserslist "^4.21.4" - caniuse-api "^3.0.0" - cssnano-utils "^3.1.0" - postcss-selector-parser "^6.0.5" - -postcss-minify-font-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" - integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-minify-gradients@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" - integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== - dependencies: - colord "^2.9.1" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-params@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352" - integrity sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw== - dependencies: - browserslist "^4.21.4" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-selectors@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" - integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-normalize-charset@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" - integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== - -postcss-normalize-display-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" - integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-positions@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" - integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-repeat-style@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" - integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-string@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" - integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-timing-functions@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" - integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-unicode@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030" - integrity sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA== - dependencies: - browserslist "^4.21.4" - postcss-value-parser "^4.2.0" - -postcss-normalize-url@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" - integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== - dependencies: - normalize-url "^6.0.1" - postcss-value-parser "^4.2.0" - -postcss-normalize-whitespace@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" - integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-ordered-values@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" - integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ== - dependencies: - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-reduce-idents@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz#c89c11336c432ac4b28792f24778859a67dfba95" - integrity sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-reduce-initial@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.1.tgz#c18b7dfb88aee24b1f8e4936541c29adbd35224e" - integrity sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w== - dependencies: - browserslist "^4.21.4" - caniuse-api "^3.0.0" - -postcss-reduce-transforms@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" - integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: - version "6.0.11" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" - integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-sort-media-queries@^4.2.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-4.3.0.tgz#f48a77d6ce379e86676fc3f140cf1b10a06f6051" - integrity sha512-jAl8gJM2DvuIJiI9sL1CuiHtKM4s5aEIomkU8G3LFvbP+p8i7Sz8VV63uieTgoewGqKbi+hxBTiOKJlB35upCg== - dependencies: - sort-css-media-queries "2.1.0" - -postcss-svgo@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" - integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== - dependencies: - postcss-value-parser "^4.2.0" - svgo "^2.7.0" - -postcss-unique-selectors@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" - integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss-zindex@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.1.0.tgz#4a5c7e5ff1050bd4c01d95b1847dfdcc58a496ff" - integrity sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A== - -postcss@^8.3.11, postcss@^8.4.14, postcss@^8.4.17, postcss@^8.4.19: - version "8.4.31" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" - integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== - dependencies: - nanoid "^3.3.6" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== - -pretty-error@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" - integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== - dependencies: - lodash "^4.17.20" - renderkid "^3.0.0" - -pretty-time@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" - integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== - -prism-react-renderer@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz#786bb69aa6f73c32ba1ee813fbe17a0115435085" - integrity sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg== - -prismjs@^1.28.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9" - integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw== - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - -prompts@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -prop-types@^15.6.2, prop-types@^15.7.2: - version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - -property-information@^5.0.0, property-information@^5.3.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" - integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== - dependencies: - xtend "^4.0.0" - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^1.3.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -pupa@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" - integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== - dependencies: - escape-goat "^2.0.0" - -pure-color@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" - integrity sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA== - -qs@6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -queue@6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" - integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== - dependencies: - inherits "~2.0.3" - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -rc@1.2.8, rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -react-base16-styling@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c" - integrity sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ== - dependencies: - base16 "^1.0.0" - lodash.curry "^4.0.1" - lodash.flow "^3.3.0" - pure-color "^1.2.0" - -react-dev-utils@^12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73" - integrity sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ== - dependencies: - "@babel/code-frame" "^7.16.0" - address "^1.1.2" - browserslist "^4.18.1" - chalk "^4.1.2" - cross-spawn "^7.0.3" - detect-port-alt "^1.1.6" - escape-string-regexp "^4.0.0" - filesize "^8.0.6" - find-up "^5.0.0" - fork-ts-checker-webpack-plugin "^6.5.0" - global-modules "^2.0.0" - globby "^11.0.4" - gzip-size "^6.0.0" - immer "^9.0.7" - is-root "^2.1.0" - loader-utils "^3.2.0" - open "^8.4.0" - pkg-up "^3.1.0" - prompts "^2.4.2" - react-error-overlay "^6.0.11" - recursive-readdir "^2.2.2" - shell-quote "^1.7.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - -react-dom@^17.0.2: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" - integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler "^0.20.2" - -react-error-overlay@^6.0.11: - version "6.0.11" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" - integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== - -react-fast-compare@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" - integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== - -react-helmet-async@*, react-helmet-async@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.3.0.tgz#7bd5bf8c5c69ea9f02f6083f14ce33ef545c222e" - integrity sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg== - dependencies: - "@babel/runtime" "^7.12.5" - invariant "^2.2.4" - prop-types "^15.7.2" - react-fast-compare "^3.2.0" - shallowequal "^1.1.0" - -react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - -react-json-view@^1.21.3: - version "1.21.3" - resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475" - integrity sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw== - dependencies: - flux "^4.0.1" - react-base16-styling "^0.6.0" - react-lifecycles-compat "^3.0.4" - react-textarea-autosize "^8.3.2" - -react-lifecycles-compat@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== - -react-loadable-ssr-addon-v5-slorber@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz#2cdc91e8a744ffdf9e3556caabeb6e4278689883" - integrity sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A== - dependencies: - "@babel/runtime" "^7.10.3" - -"react-loadable@npm:@docusaurus/react-loadable@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" - integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== - dependencies: - "@types/react" "*" - prop-types "^15.6.2" - -react-router-config@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" - integrity sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg== - dependencies: - "@babel/runtime" "^7.1.2" - -react-router-dom@^5.3.3: - version "5.3.4" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.4.tgz#2ed62ffd88cae6db134445f4a0c0ae8b91d2e5e6" - integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ== - dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - loose-envify "^1.3.1" - prop-types "^15.6.2" - react-router "5.3.4" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-router@5.3.4, react-router@^5.3.3: - version "5.3.4" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.4.tgz#8ca252d70fcc37841e31473c7a151cf777887bb5" - integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA== - dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - 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" - -react-textarea-autosize@^8.3.2: - version "8.4.0" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.4.0.tgz#4d0244d6a50caa897806b8c44abc0540a69bfc8c" - integrity sha512-YrTFaEHLgJsi8sJVYHBzYn+mkP3prGkmP2DKb/tm0t7CLJY5t1Rxix8070LAKb0wby7bl/lf2EeHkuMihMZMwQ== - dependencies: - "@babel/runtime" "^7.10.2" - use-composed-ref "^1.3.0" - use-latest "^1.2.1" - -react@^17.0.2: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" - integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - -readable-stream@^2.0.1: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - 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" - -readable-stream@^3.0.6: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -reading-time@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb" - integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg== - -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== - dependencies: - resolve "^1.1.6" - -recursive-readdir@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz#e726f328c0d69153bcabd5c322d3195252379372" - integrity sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA== - dependencies: - minimatch "^3.0.5" - -regenerate-unicode-properties@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" - integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - -regenerator-runtime@^0.14.0: - version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" - integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== - -regenerator-transform@^0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" - integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== - dependencies: - "@babel/runtime" "^7.8.4" - -regexpu-core@^5.2.1: - version "5.2.2" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.2.tgz#3e4e5d12103b64748711c3aad69934d7718e75fc" - integrity sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw== - dependencies: - regenerate "^1.4.2" - regenerate-unicode-properties "^10.1.0" - regjsgen "^0.7.1" - regjsparser "^0.9.1" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" - -registry-auth-token@^4.0.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.2.tgz#f02d49c3668884612ca031419491a13539e21fac" - integrity sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg== - dependencies: - rc "1.2.8" - -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - -regjsgen@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.7.1.tgz#ee5ef30e18d3f09b7c369b76e7c2373ed25546f6" - integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA== - -regjsparser@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" - integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== - dependencies: - jsesc "~0.5.0" - -relateurl@^0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" - integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== - -remark-emoji@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-2.2.0.tgz#1c702090a1525da5b80e15a8f963ef2c8236cac7" - integrity sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w== - dependencies: - emoticon "^3.2.0" - node-emoji "^1.10.0" - unist-util-visit "^2.0.3" - -remark-footnotes@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-2.0.0.tgz#9001c4c2ffebba55695d2dd80ffb8b82f7e6303f" - integrity sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ== - -remark-mdx@1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-1.6.22.tgz#06a8dab07dcfdd57f3373af7f86bd0e992108bbd" - integrity sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ== - dependencies: - "@babel/core" "7.12.9" - "@babel/helper-plugin-utils" "7.10.4" - "@babel/plugin-proposal-object-rest-spread" "7.12.1" - "@babel/plugin-syntax-jsx" "7.12.1" - "@mdx-js/util" "1.6.22" - is-alphabetical "1.0.4" - remark-parse "8.0.3" - unified "9.2.0" - -remark-parse@8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1" - integrity sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q== - dependencies: - 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@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz#76eb0e085295131c84748c8e43810159c5653ead" - integrity sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw== - dependencies: - mdast-squeeze-paragraphs "^4.0.0" - -renderkid@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" - integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== - dependencies: - css-select "^4.1.3" - dom-converter "^0.2.0" - htmlparser2 "^6.1.0" - lodash "^4.17.21" - strip-ansi "^6.0.1" - -repeat-string@^1.5.4: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -"require-like@>= 0.1.1": - version "0.1.2" - resolved "https://registry.yarnpkg.com/require-like/-/require-like-0.1.2.tgz#ad6f30c13becd797010c468afa775c0c0a6b47fa" - integrity sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-pathname@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" - integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== - -resolve@^1.1.6, resolve@^1.14.2, resolve@^1.3.2: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== - dependencies: - lowercase-keys "^1.0.0" - -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rtl-detect@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.0.4.tgz#40ae0ea7302a150b96bc75af7d749607392ecac6" - integrity sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ== - -rtlcss@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-3.5.0.tgz#c9eb91269827a102bac7ae3115dd5d049de636c3" - integrity sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A== - dependencies: - find-up "^5.0.0" - picocolors "^1.0.0" - postcss "^8.3.11" - strip-json-comments "^3.1.1" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@^7.5.4: - version "7.8.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" - integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== - dependencies: - tslib "^2.1.0" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -scheduler@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" - integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - -schema-utils@2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - -schema-utils@^2.6.5: - version "2.7.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" - integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== - dependencies: - "@types/json-schema" "^7.0.5" - ajv "^6.12.4" - ajv-keywords "^3.5.2" - -schema-utils@^3.0.0, schema-utils@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" - integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.8.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.0.0" - -section-matter@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" - integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA== - dependencies: - extend-shallow "^2.0.1" - kind-of "^6.0.0" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -selfsigned@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61" - integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== - dependencies: - node-forge "^1" - -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - -semver@^5.4.1: - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.2, semver@^7.3.4, semver@^7.3.7, semver@^7.3.8: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" - integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== - dependencies: - randombytes "^2.1.0" - -serve-handler@^6.1.3: - version "6.1.5" - resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.5.tgz#a4a0964f5c55c7e37a02a633232b6f0d6f068375" - integrity sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg== - dependencies: - bytes "3.0.0" - content-disposition "0.5.2" - fast-url-parser "1.1.3" - mime-types "2.1.18" - minimatch "3.1.2" - path-is-inside "1.0.2" - path-to-regexp "2.2.1" - range-parser "1.2.0" - -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== - dependencies: - 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" - -serve-static@1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== - dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.19.0" - -set-function-length@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shallowequal@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" - integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.7.3: - version "1.7.4" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8" - integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw== - -shelljs@^0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" - integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - -side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" - -signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -sirv@^1.0.7: - version "1.0.19" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49" - integrity sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ== - dependencies: - "@polka/url" "^1.0.0-next.20" - mrmime "^1.0.0" - totalist "^1.0.0" - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -sitemap@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.1.1.tgz#eeed9ad6d95499161a3eadc60f8c6dce4bea2bef" - integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg== - dependencies: - "@types/node" "^17.0.5" - "@types/sax" "^1.2.1" - arg "^5.0.0" - sax "^1.2.4" - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== - -sockjs@^0.3.24: - version "0.3.24" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== - dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" - -sort-css-media-queries@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.1.0.tgz#7c85e06f79826baabb232f5560e9745d7a78c4ce" - integrity sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA== - -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.5.0: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -space-separated-tokens@^1.0.0: - version "1.1.5" - resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" - integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - 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" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - -state-toggle@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" - integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -std-env@^3.0.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.3.1.tgz#93a81835815e618c8aa75e7c8a4dc04f7c314e29" - integrity sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q== - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -stringify-object@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" - integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== - dependencies: - get-own-enumerable-property-symbols "^3.0.0" - is-obj "^1.0.1" - is-regexp "^1.0.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" - integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== - dependencies: - ansi-regex "^6.0.1" - -strip-bom-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" - integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - -style-to-object@0.3.0, style-to-object@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" - integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== - dependencies: - inline-style-parser "0.1.1" - -stylehacks@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9" - integrity sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw== - dependencies: - browserslist "^4.21.4" - postcss-selector-parser "^6.0.4" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -svg-parser@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" - integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== - -svgo@^2.7.0, svgo@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - -tapable@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== - -tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -terser-webpack-plugin@^5.3.10: - version "5.3.10" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" - integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== - dependencies: - "@jridgewell/trace-mapping" "^0.3.20" - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.1" - terser "^5.26.0" - -terser-webpack-plugin@^5.3.3: - version "5.3.6" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz#5590aec31aa3c6f771ce1b1acca60639eab3195c" - integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ== - dependencies: - "@jridgewell/trace-mapping" "^0.3.14" - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.0" - terser "^5.14.1" - -terser@^5.10.0, terser@^5.14.1: - version "5.16.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.1.tgz#5af3bc3d0f24241c7fb2024199d5c461a1075880" - integrity sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw== - dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -terser@^5.26.0: - version "5.37.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.37.0.tgz#38aa66d1cfc43d0638fab54e43ff8a4f72a21ba3" - integrity sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.8.2" - commander "^2.20.0" - source-map-support "~0.5.20" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - -tiny-invariant@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" - integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== - -tiny-warning@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" - integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -totalist@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" - integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -trim-trailing-lines@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" - integrity sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ== - -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - integrity sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ== - -trough@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" - integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== - -tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" - integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^2.5.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" - integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -ua-parser-js@^0.7.30: - version "0.7.33" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532" - integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw== - -unherit@^1.0.4: - version "1.1.3" - resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" - integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== - dependencies: - inherits "^2.0.0" - xtend "^4.0.0" - -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" - integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" - integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== - -unified@9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8" - integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== - dependencies: - 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" - -unified@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975" - integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ== - dependencies: - 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" - -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - -unist-builder@2.0.3, unist-builder@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" - integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== - -unist-util-generated@^1.0.0: - version "1.1.6" - resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" - integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg== - -unist-util-is@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" - integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== - -unist-util-position@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" - integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== - -unist-util-remove-position@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" - integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== - dependencies: - unist-util-visit "^2.0.0" - -unist-util-remove@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.1.0.tgz#b0b4738aa7ee445c402fda9328d604a02d010588" - integrity sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q== - dependencies: - unist-util-is "^4.0.0" - -unist-util-stringify-position@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" - integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== - dependencies: - "@types/unist" "^2.0.2" - -unist-util-visit-parents@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" - integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^4.0.0" - -unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" - integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^4.0.0" - unist-util-visit-parents "^3.0.0" - -universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -update-browserslist-db@^1.0.9: - version "1.0.10" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -update-browserslist-db@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" - integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.0" - -update-notifier@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" - integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== - dependencies: - boxen "^5.0.0" - chalk "^4.1.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.4.0" - is-npm "^5.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.1.0" - pupa "^2.1.1" - semver "^7.3.4" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -url-loader@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" - integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== - dependencies: - loader-utils "^2.0.0" - mime-types "^2.1.27" - schema-utils "^3.0.0" - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== - dependencies: - prepend-http "^2.0.0" - -use-composed-ref@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" - integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== - -use-isomorphic-layout-effect@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" - integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== - -use-latest@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2" - integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== - dependencies: - use-isomorphic-layout-effect "^1.1.1" - -use-sync-external-store@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" - integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== - -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -utila@~0.4: - version "0.4.0" - resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" - integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== - -utility-types@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" - integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -value-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" - integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -vfile-location@^3.0.0, vfile-location@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c" - integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA== - -vfile-message@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" - integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== - dependencies: - "@types/unist" "^2.0.0" - unist-util-stringify-position "^2.0.0" - -vfile@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" - integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== - dependencies: - "@types/unist" "^2.0.0" - is-buffer "^2.0.0" - unist-util-stringify-position "^2.0.0" - vfile-message "^2.0.0" - -wait-on@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.1.tgz#16bbc4d1e4ebdd41c5b4e63a2e16dbd1f4e5601e" - integrity sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw== - dependencies: - axios "^0.25.0" - joi "^17.6.0" - lodash "^4.17.21" - minimist "^1.2.5" - rxjs "^7.5.4" - -watchpack@^2.4.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" - integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - -web-namespaces@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" - integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -webpack-bundle-analyzer@^4.5.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz#33c1c485a7fcae8627c547b5c3328b46de733c66" - integrity sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg== - dependencies: - acorn "^8.0.4" - acorn-walk "^8.0.0" - chalk "^4.1.0" - commander "^7.2.0" - gzip-size "^6.0.0" - lodash "^4.17.20" - opener "^1.5.2" - sirv "^1.0.7" - ws "^7.3.1" - -webpack-dev-middleware@^5.3.1: - version "5.3.4" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" - integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== - dependencies: - colorette "^2.0.10" - memfs "^3.4.3" - mime-types "^2.1.31" - range-parser "^1.2.1" - schema-utils "^4.0.0" - -webpack-dev-server@^4.9.3: - version "4.11.1" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz#ae07f0d71ca0438cf88446f09029b92ce81380b5" - integrity sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw== - dependencies: - "@types/bonjour" "^3.5.9" - "@types/connect-history-api-fallback" "^1.3.5" - "@types/express" "^4.17.13" - "@types/serve-index" "^1.9.1" - "@types/serve-static" "^1.13.10" - "@types/sockjs" "^0.3.33" - "@types/ws" "^8.5.1" - ansi-html-community "^0.0.8" - bonjour-service "^1.0.11" - chokidar "^3.5.3" - colorette "^2.0.10" - compression "^1.7.4" - connect-history-api-fallback "^2.0.0" - default-gateway "^6.0.3" - express "^4.17.3" - graceful-fs "^4.2.6" - html-entities "^2.3.2" - http-proxy-middleware "^2.0.3" - ipaddr.js "^2.0.1" - open "^8.0.9" - p-retry "^4.5.0" - rimraf "^3.0.2" - schema-utils "^4.0.0" - selfsigned "^2.1.1" - serve-index "^1.9.1" - sockjs "^0.3.24" - spdy "^4.0.2" - webpack-dev-middleware "^5.3.1" - ws "^8.4.2" - -webpack-merge@^5.8.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" - integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== - dependencies: - clone-deep "^4.0.1" - wildcard "^2.0.0" - -webpack-sources@^3.2.2, webpack-sources@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - -webpack@^5.73.0: - version "5.97.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.97.1.tgz#972a8320a438b56ff0f1d94ade9e82eac155fa58" - integrity sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg== - dependencies: - "@types/eslint-scope" "^3.7.7" - "@types/estree" "^1.0.6" - "@webassemblyjs/ast" "^1.14.1" - "@webassemblyjs/wasm-edit" "^1.14.1" - "@webassemblyjs/wasm-parser" "^1.14.1" - acorn "^8.14.0" - browserslist "^4.24.0" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.1" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.11" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.2.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.3.10" - watchpack "^2.4.1" - webpack-sources "^3.2.3" - -webpackbar@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-5.0.2.tgz#d3dd466211c73852741dfc842b7556dcbc2b0570" - integrity sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ== - dependencies: - chalk "^4.1.0" - consola "^2.15.3" - pretty-time "^1.1.0" - std-env "^3.0.1" - -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - -widest-line@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" - integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== - dependencies: - string-width "^5.0.1" - -wildcard@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" - integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.0.1.tgz#2101e861777fec527d0ea90c57c6b03aac56a5b3" - integrity sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -ws@^7.3.1: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== - -ws@^8.4.2: - version "8.11.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" - integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== - -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - -xml-js@^1.6.11: - version "1.6.11" - resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" - integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== - dependencies: - sax "^1.2.4" - -xtend@^4.0.0, xtend@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zwitch@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" - integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==