diff --git a/404.html b/404.html index 78b0cab02..8f4798c0e 100644 --- a/404.html +++ b/404.html @@ -4,13 +4,13 @@ Page Not Found | gql - - + +
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/assets/js/018b0880.bd88ff5a.js b/assets/js/018b0880.a157b9e5.js similarity index 99% rename from assets/js/018b0880.bd88ff5a.js rename to assets/js/018b0880.a157b9e5.js index f10d30798..239ffafda 100644 --- a/assets/js/018b0880.bd88ff5a.js +++ b/assets/js/018b0880.a157b9e5.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[227],{3905:(e,n,t)=>{t.d(n,{Zo:()=>c,kt:()=>u});var a=t(7294);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 s(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 s=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var l=a.createContext({}),o=function(e){var n=a.useContext(l),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},c=function(e){var n=o(e.components);return a.createElement(l.Provider,{value:n},e.children)},m={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,s=e.originalType,l=e.parentName,c=p(e,["components","mdxType","originalType","parentName"]),y=o(t),u=r,d=y["".concat(l,".").concat(u)]||y[u]||m[u]||s;return t?a.createElement(d,i(i({ref:n},c),{},{components:t})):a.createElement(d,i({ref:n},c))}));function u(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var s=t.length,i=new Array(s);i[0]=y;var p={};for(var l in n)hasOwnProperty.call(n,l)&&(p[l]=n[l]);p.originalType=e,p.mdxType="string"==typeof e?e:r,i[1]=p;for(var o=2;o{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>i,default:()=>m,frontMatter:()=>s,metadata:()=>p,toc:()=>o});var a=t(7462),r=(t(7294),t(3905));const s={title:"Structuring large applications"},i=void 0,p={unversionedId:"server/schema/structuring_apps",id:"server/schema/structuring_apps",title:"Structuring large applications",description:"The documentation explores smaller examples.",source:"@site/docs/server/schema/structuring_apps.md",sourceDirName:"server/schema",slug:"/server/schema/structuring_apps",permalink:"/gql/docs/server/schema/structuring_apps",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/schema/structuring_apps.md",tags:[],version:"current",frontMatter:{title:"Structuring large applications"},sidebar:"docs",previous:{title:"Extending schemas",permalink:"/gql/docs/server/schema/extending"},next:{title:"Planning",permalink:"/gql/docs/server/execution/planning"}},l={},o=[{value:"Seperating domains",id:"seperating-domains",level:2},{value:"Mutually recursive domains",id:"mutually-recursive-domains",level:2},{value:"Call by name constructor parameters",id:"call-by-name-constructor-parameters",level:3},{value:"Cake",id:"cake",level:3}],c={toc:o};function m(e){let{components:n,...t}=e;return(0,r.kt)("wrapper",(0,a.Z)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"The documentation explores smaller examples.\nTo host larger graphs there are some considerations that must be addressed."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"What up-front work can be done to minimize the overhead in introducing new types."),(0,r.kt)("li",{parentName:"ul"},"How is (mutual) recursion handled between different domains.")),(0,r.kt)("p",null,"Recursive datatypes are notoriously difficult to deal with.\nIn functional programming lazyness is often exploited as a solution to introduce cyclic data, but can easily accidentally introduce infinite recursion."),(0,r.kt)("h2",{id:"seperating-domains"},"Seperating domains"),(0,r.kt)("p",null,"Partially applying all needed dependencies can be expressed with a class."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'import cats.effect._\nimport gql._\nimport gql.ast._\nimport gql.dsl._\n\nfinal case class Organization(\n id: String,\n name: String\n)\n\nfinal case class User(\n id: String,\n name: String,\n organizationId: String\n)\n\ntrait Repo {\n def getUser(id: String): IO[User]\n def getOrganization(id: String): IO[Organization]\n def getOrganizationUsers(organizationId: String): IO[List[User]]\n}\n\nclass UserTypes(repo: Repo) {\n // notice how we bind the effect (IO) so that we can omit this parameter in the dsl\n val dsl = new GqlDsl[IO] {}\n import dsl._\n\n implicit val organization: Type[IO, Organization] = \n tpe[Organization](\n "Organization",\n "id" -> lift(_.id),\n "name" -> lift(_.name),\n "users" -> eff(x => repo.getOrganizationUsers(x.id))\n )\n\n implicit val user: Type[IO, User] =\n tpe[User](\n "User",\n "id" -> lift(_.id),\n "name" -> lift(_.name),\n "organization" -> eff(x => repo.getOrganization(x.organizationId))\n )\n}\n')),(0,r.kt)("details",null,(0,r.kt)("summary",null,"You can also extend the dsl if you prefer a more object oriented style."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"class UserTypes(repo: Repo) extends GqlDsl[IO] {\n // ...\n}\n"))),(0,r.kt)("h2",{id:"mutually-recursive-domains"},"Mutually recursive domains"),(0,r.kt)("p",null,"Subgraphs can neatly packaged into classes, but that does not address the issue of recursion between different domains."),(0,r.kt)("h3",{id:"call-by-name-constructor-parameters"},"Call by name constructor parameters"),(0,r.kt)("p",null,"A compositional approach is to use call by name constructor parameters to lazily pass mutually recursive dependencies."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"class UserTypes(paymentTypes: => PaymentTypes) {\n lazy val p = paymentTypes\n import p._\n // ...\n}\n\nclass PaymentTypes(userTypes: => UserTypes) {\n lazy val u = userTypes\n import u._\n // ...\n}\n\nlazy val userTypes: UserTypes = new UserTypes(paymentTypes)\nlazy val paymentTypes: PaymentTypes = new PaymentTypes(userTypes)\n")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"When domain types are defined in seperate projects, OOP interfaces can be used to implement mutual recursion."),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"// core project\ntrait User\ntrait UserTypes {\n // we can also choose to only expose the datatypes that are necessary\n implicit def userType: Type[IO, User]\n}\ntrait Payment\ntrait PaymentTypes {\n implicit def paymentType: Type[IO, Payment]\n}\n\n// user project\nclass UserTypesImpl(paymentTypes: => PaymentTypes) extends UserTypes {\n lazy val p = paymentTypes\n import p._\n def userType: Type[IO, User] = ???\n}\n\n// payment project\nclass PaymentTypesImpl(userTypes: => UserTypes) extends PaymentTypes {\n lazy val u = userTypes\n import u._\n def paymentType: Type[IO, Payment] = ???\n}\n\n// main project\nlazy val userTypes: UserTypes = new UserTypesImpl(paymentTypes)\nlazy val paymentTypes: PaymentTypes = new PaymentTypesImpl(userTypes)\n"))),(0,r.kt)("h3",{id:"cake"},"Cake"),(0,r.kt)("p",null,"The cake pattern can also be used to define mutually recursive dependencies, at the cost of composability."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"// core project\ntrait User\ntrait UserTypes {\n // we can also choose to only expose the datatypes that are necessary\n implicit def userType: Type[IO, User]\n}\ntrait Payment\ntrait PaymentTypes {\n implicit def paymentType: Type[IO, Payment]\n}\n\n// user project\ntrait UserTypesImpl extends UserTypes { self: PaymentTypes =>\n import self._\n def userType: Type[IO, User] = ???\n}\n\n// payment project\ntrait PaymentTypesImpl extends PaymentTypes { self: UserTypes =>\n import self._\n def paymentType: Type[IO, Payment] = ???\n}\n\n// main project\nval allTypes = new UserTypesImpl with PaymentTypesImpl { }\n// allTypes: AnyRef with UserTypesImpl with PaymentTypesImpl = repl.MdocSession$MdocApp$$anon$2@341db0d9\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[227],{3905:(e,n,t)=>{t.d(n,{Zo:()=>c,kt:()=>u});var a=t(7294);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 s(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 s=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var l=a.createContext({}),o=function(e){var n=a.useContext(l),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},c=function(e){var n=o(e.components);return a.createElement(l.Provider,{value:n},e.children)},m={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,s=e.originalType,l=e.parentName,c=p(e,["components","mdxType","originalType","parentName"]),y=o(t),u=r,d=y["".concat(l,".").concat(u)]||y[u]||m[u]||s;return t?a.createElement(d,i(i({ref:n},c),{},{components:t})):a.createElement(d,i({ref:n},c))}));function u(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var s=t.length,i=new Array(s);i[0]=y;var p={};for(var l in n)hasOwnProperty.call(n,l)&&(p[l]=n[l]);p.originalType=e,p.mdxType="string"==typeof e?e:r,i[1]=p;for(var o=2;o{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>i,default:()=>m,frontMatter:()=>s,metadata:()=>p,toc:()=>o});var a=t(7462),r=(t(7294),t(3905));const s={title:"Structuring large applications"},i=void 0,p={unversionedId:"server/schema/structuring_apps",id:"server/schema/structuring_apps",title:"Structuring large applications",description:"The documentation explores smaller examples.",source:"@site/docs/server/schema/structuring_apps.md",sourceDirName:"server/schema",slug:"/server/schema/structuring_apps",permalink:"/gql/docs/server/schema/structuring_apps",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/schema/structuring_apps.md",tags:[],version:"current",frontMatter:{title:"Structuring large applications"},sidebar:"docs",previous:{title:"Extending schemas",permalink:"/gql/docs/server/schema/extending"},next:{title:"Planning",permalink:"/gql/docs/server/execution/planning"}},l={},o=[{value:"Seperating domains",id:"seperating-domains",level:2},{value:"Mutually recursive domains",id:"mutually-recursive-domains",level:2},{value:"Call by name constructor parameters",id:"call-by-name-constructor-parameters",level:3},{value:"Cake",id:"cake",level:3}],c={toc:o};function m(e){let{components:n,...t}=e;return(0,r.kt)("wrapper",(0,a.Z)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"The documentation explores smaller examples.\nTo host larger graphs there are some considerations that must be addressed."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"What up-front work can be done to minimize the overhead in introducing new types."),(0,r.kt)("li",{parentName:"ul"},"How is (mutual) recursion handled between different domains.")),(0,r.kt)("p",null,"Recursive datatypes are notoriously difficult to deal with.\nIn functional programming lazyness is often exploited as a solution to introduce cyclic data, but can easily accidentally introduce infinite recursion."),(0,r.kt)("h2",{id:"seperating-domains"},"Seperating domains"),(0,r.kt)("p",null,"Partially applying all needed dependencies can be expressed with a class."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'import cats.effect._\nimport gql._\nimport gql.ast._\nimport gql.dsl._\n\nfinal case class Organization(\n id: String,\n name: String\n)\n\nfinal case class User(\n id: String,\n name: String,\n organizationId: String\n)\n\ntrait Repo {\n def getUser(id: String): IO[User]\n def getOrganization(id: String): IO[Organization]\n def getOrganizationUsers(organizationId: String): IO[List[User]]\n}\n\nclass UserTypes(repo: Repo) {\n // notice how we bind the effect (IO) so that we can omit this parameter in the dsl\n val dsl = new GqlDsl[IO] {}\n import dsl._\n\n implicit val organization: Type[IO, Organization] = \n tpe[Organization](\n "Organization",\n "id" -> lift(_.id),\n "name" -> lift(_.name),\n "users" -> eff(x => repo.getOrganizationUsers(x.id))\n )\n\n implicit val user: Type[IO, User] =\n tpe[User](\n "User",\n "id" -> lift(_.id),\n "name" -> lift(_.name),\n "organization" -> eff(x => repo.getOrganization(x.organizationId))\n )\n}\n')),(0,r.kt)("details",null,(0,r.kt)("summary",null,"You can also extend the dsl if you prefer a more object oriented style."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"class UserTypes(repo: Repo) extends GqlDsl[IO] {\n // ...\n}\n"))),(0,r.kt)("h2",{id:"mutually-recursive-domains"},"Mutually recursive domains"),(0,r.kt)("p",null,"Subgraphs can neatly packaged into classes, but that does not address the issue of recursion between different domains."),(0,r.kt)("h3",{id:"call-by-name-constructor-parameters"},"Call by name constructor parameters"),(0,r.kt)("p",null,"A compositional approach is to use call by name constructor parameters to lazily pass mutually recursive dependencies."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"class UserTypes(paymentTypes: => PaymentTypes) {\n lazy val p = paymentTypes\n import p._\n // ...\n}\n\nclass PaymentTypes(userTypes: => UserTypes) {\n lazy val u = userTypes\n import u._\n // ...\n}\n\nlazy val userTypes: UserTypes = new UserTypes(paymentTypes)\nlazy val paymentTypes: PaymentTypes = new PaymentTypes(userTypes)\n")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"When domain types are defined in seperate projects, OOP interfaces can be used to implement mutual recursion."),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"// core project\ntrait User\ntrait UserTypes {\n // we can also choose to only expose the datatypes that are necessary\n implicit def userType: Type[IO, User]\n}\ntrait Payment\ntrait PaymentTypes {\n implicit def paymentType: Type[IO, Payment]\n}\n\n// user project\nclass UserTypesImpl(paymentTypes: => PaymentTypes) extends UserTypes {\n lazy val p = paymentTypes\n import p._\n def userType: Type[IO, User] = ???\n}\n\n// payment project\nclass PaymentTypesImpl(userTypes: => UserTypes) extends PaymentTypes {\n lazy val u = userTypes\n import u._\n def paymentType: Type[IO, Payment] = ???\n}\n\n// main project\nlazy val userTypes: UserTypes = new UserTypesImpl(paymentTypes)\nlazy val paymentTypes: PaymentTypes = new PaymentTypesImpl(userTypes)\n"))),(0,r.kt)("h3",{id:"cake"},"Cake"),(0,r.kt)("p",null,"The cake pattern can also be used to define mutually recursive dependencies, at the cost of composability."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"// core project\ntrait User\ntrait UserTypes {\n // we can also choose to only expose the datatypes that are necessary\n implicit def userType: Type[IO, User]\n}\ntrait Payment\ntrait PaymentTypes {\n implicit def paymentType: Type[IO, Payment]\n}\n\n// user project\ntrait UserTypesImpl extends UserTypes { self: PaymentTypes =>\n import self._\n def userType: Type[IO, User] = ???\n}\n\n// payment project\ntrait PaymentTypesImpl extends PaymentTypes { self: UserTypes =>\n import self._\n def paymentType: Type[IO, Payment] = ???\n}\n\n// main project\nval allTypes = new UserTypesImpl with PaymentTypesImpl { }\n// allTypes: AnyRef with UserTypesImpl with PaymentTypesImpl = repl.MdocSession$MdocApp$$anon$2@4585e91d\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0a44bcdb.695e518c.js b/assets/js/0a44bcdb.de31a9a1.js similarity index 99% rename from assets/js/0a44bcdb.695e518c.js rename to assets/js/0a44bcdb.de31a9a1.js index 174920379..9fbcfe656 100644 --- a/assets/js/0a44bcdb.695e518c.js +++ b/assets/js/0a44bcdb.de31a9a1.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[436],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>d});var a=n(7294);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=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=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):l(l({},t),e)),n},c=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},u=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),u=p(n),d=i,f=u["".concat(s,".").concat(d)]||u[d]||m[d]||r;return n?a.createElement(f,l(l({ref:t},c),{},{components:n})):a.createElement(f,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]=u;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o.mdxType="string"==typeof e?e:i,l[1]=o;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>m,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=n(7462),i=(n(7294),n(3905));const r={title:"The DSL"},l=void 0,o={unversionedId:"server/schema/dsl",id:"server/schema/dsl",title:"The DSL",description:"gql's dsl is a lightweight set of smart-constructors.",source:"@site/docs/server/schema/dsl.md",sourceDirName:"server/schema",slug:"/server/schema/dsl",permalink:"/gql/docs/server/schema/dsl",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/schema/dsl.md",tags:[],version:"current",frontMatter:{title:"The DSL"},sidebar:"docs",previous:{title:"Input types",permalink:"/gql/docs/server/schema/input_types"},next:{title:"Monadic Resolver DSL",permalink:"/gql/docs/server/schema/arrow_dsl"}},s={},p=[{value:"Fields",id:"fields",level:2},{value:"Builders",id:"builders",level:3},{value:"Value resolution",id:"value-resolution",level:3},{value:"Unification instances",id:"unification-instances",level:2},{value:"Interface inheritance",id:"interface-inheritance",level:3},{value:"Input types",id:"input-types",level:2},{value:"Other output structures",id:"other-output-structures",level:2},{value:"Covariant effects",id:"covariant-effects",level:3}],c={toc:p};function m(e){let{components:t,...n}=e;return(0,i.kt)("wrapper",(0,a.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"gql's dsl is a lightweight set of smart-constructors.\nIf you have a particular usecase or even coding style that conflicts with the dsl, you can always introduce your own schema definition syntax or build on top of the existing dsl."),(0,i.kt)("p",null,"Lets begin by importing what we need."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"import cats.data._\nimport cats.effect._\nimport cats.implicits._\nimport gql.dsl.all._\nimport gql.ast._\nimport gql.resolver._\n")),(0,i.kt)("h2",{id:"fields"},"Fields"),(0,i.kt)("p",null,"The simplest form of field construction comes from the ",(0,i.kt)("inlineCode",{parentName:"p"},"build.from")," smart constructor.\nIt simply lifts a resolver into a field, given that a gql output type exists for the resolver output."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"def r: Resolver[IO, Int, String] = Resolver.lift(i => i.toString())\n\nval f: Field[IO, Int, String] = build.from(r)\n// f: Field[IO, Int, String] = Field(\n// resolve = gql.resolver.Resolver@18be1b21,\n// output = cats.Always@7e084c97,\n// description = None,\n// attributes = List()\n// )\n")),(0,i.kt)("p",null,"Sometimes type inference cannot find the proper type for a field:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"build.from(Resolver.liftF(i => IO(i.toString())))\n// error: value liftF is not a member of object gql.resolver.Resolver\n// did you mean lift? or perhaps liftFull?\n// build.from(Resolver.liftF(i => IO(i.toString())))\n// ^^^^^^^^^^^^^^\n")),(0,i.kt)("p",null,"The type parameters for ",(0,i.kt)("inlineCode",{parentName:"p"},"build")," are partially applied, such that when type inference isn't enough, types can be supplied explicitly."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"build[IO, Int].from(Resolver.effect(i => IO(i.toString())))\n\nbuild.from(Resolver.effect((i: Int) => IO(i.toString())))\n")),(0,i.kt)("p",null,"For some fields, there is an even more concise syntax.\nInvoking the ",(0,i.kt)("inlineCode",{parentName:"p"},"apply")," method of ",(0,i.kt)("inlineCode",{parentName:"p"},"build"),", takes a higher order function that goes from the identity resolver (",(0,i.kt)("inlineCode",{parentName:"p"},"Resolver[F, A, A]"),") to some output."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"build[IO, Int](_.map(i => i * 2).evalMap(i => IO(i))): Field[IO, Int, Int]\n")),(0,i.kt)("h3",{id:"builders"},"Builders"),(0,i.kt)("p",null,"Complex structures may require many special resolver compositions.\nThe dsl also introduces a something akin to a builder pattern.\nThe ",(0,i.kt)("inlineCode",{parentName:"p"},"build")," function from the previous section, creates a builder that has more constructors than just ",(0,i.kt)("inlineCode",{parentName:"p"},"from")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"apply"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"import gql.dsl.FieldBuilder\nval b: FieldBuilder[IO, Int] = build[IO, Int]\n")),(0,i.kt)("p",null,"Often a builder is only relevant within a scope, thus one can end up having many unused builders in scope.\nThe ",(0,i.kt)("inlineCode",{parentName:"p"},"builder")," makes such code more concise:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"builder[IO, Int]{ (fb: FieldBuilder[IO, Int]) =>\n fb\n}\n")),(0,i.kt)("p",null,"The builder dsl contains most of the field related constructors:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'builder[IO, Int]{ fb =>\n fb.tpe(\n "Query",\n "answer" -> lift(i => i * 0 + 42),\n "pong" -> fb(_.map(_ => "pong"))\n ): Type[IO, Int]\n \n fb.fields(\n "answer" -> fb.lift(i => i * 0 + 42),\n "ping" -> fb.from(Resolver.lift(_ => "pong"))\n )\n}\n')),(0,i.kt)("h3",{id:"value-resolution"},"Value resolution"),(0,i.kt)("p",null,"Wrapping every field in a ",(0,i.kt)("inlineCode",{parentName:"p"},"build")," smart constructor and then defining the resolver seperately is a bit verbose.\nThere are smart constructors for two common variants of field resolvers, that lift a resolver function directly to a ",(0,i.kt)("inlineCode",{parentName:"p"},"Field"),"."),(0,i.kt)("p",null,"We must decide if the field is pure or effectful:"),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"The effect constructor is named ",(0,i.kt)("inlineCode",{parentName:"p"},"eff")," to avoid collisions with cats-effect.")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'final case class Person(\n name: String\n)\n\ntpe[IO, Person](\n "Person",\n "name" -> lift(_.name),\n "nameEffect" -> eff(x => IO(x.name))\n)\n')),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"lift")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"eff")," constructors can also also be supplied with arguments:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'def familyName = arg[String]("familyName")\n\ntpe[IO, Person](\n "Person",\n "name" -> lift(familyName)(_ + _.name),\n "nameEffect" -> eff(familyName)((f, p) => IO(p.name + f))\n)\n')),(0,i.kt)("h2",{id:"unification-instances"},"Unification instances"),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"Union"),"s and ",(0,i.kt)("inlineCode",{parentName:"p"},"Interface"),"s are abstract types that have implementations."),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"Union")," declares it's implementations up-front, like a ",(0,i.kt)("inlineCode",{parentName:"p"},"sealed trait"),".\nHowever, ",(0,i.kt)("inlineCode",{parentName:"p"},"Interface")," implementations are declared on the types that implement the interface, like a ",(0,i.kt)("inlineCode",{parentName:"p"},"trait")," or an ",(0,i.kt)("inlineCode",{parentName:"p"},"abstract class"),"."),(0,i.kt)("p",null,"Before continuing, lets setup the environment."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"trait Vehicle { \n def name: String\n}\nfinal case class Car(name: String) extends Vehicle\nfinal case class Boat(name: String) extends Vehicle\nfinal case class Truck(name: String) extends Vehicle\n\n")),(0,i.kt)("p",null,"For the ",(0,i.kt)("inlineCode",{parentName:"p"},"Union"),", variants can be declared using the ",(0,i.kt)("inlineCode",{parentName:"p"},"variant")," function, which takes a ",(0,i.kt)("inlineCode",{parentName:"p"},"PartialFunction")," from the unifying type to the implementation."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'implicit def car: Type[IO, Car] = ???\nimplicit def boat: Type[IO, Boat] = ???\nimplicit def truck: Type[IO, Truck] = ???\n\nunion[IO, Vehicle]("Vehicle")\n .variant[Car] { case c: Car => c }\n .variant[Boat] { case b: Boat => b }\n .variant[Truck] { case t: Truck => t }\n')),(0,i.kt)("p",null,"A shorthand function exists, if the type of the variant is a subtype of the unifying type."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'union[IO, Vehicle]("Vehicle")\n .subtype[Car] \n .subtype[Boat] \n .subtype[Truck] \n')),(0,i.kt)("p",null,"For an ",(0,i.kt)("inlineCode",{parentName:"p"},"Interface")," the same dsl exists, but is placed on the types that can implement the interface (a ",(0,i.kt)("inlineCode",{parentName:"p"},"Type")," or another ",(0,i.kt)("inlineCode",{parentName:"p"},"Interface"),")."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'implicit lazy val vehicle: Interface[IO, Vehicle] = interface[IO, Vehicle](\n "Vehicle",\n "name" -> abst[IO, String]\n)\n\ntpe[IO, Car]("Car", "name" -> lift(_.name))\n .implements[Vehicle]{ case c: Car => c }\n \ntpe[IO, Boat]("Boat", "name" -> lift(_.name))\n .subtypeOf[Vehicle]\n \ntrait OtherVehicle extends Vehicle {\n def weight: Int\n}\n\ninterface[IO, OtherVehicle](\n "OtherVehicle",\n "weight" -> abst[IO, Int],\n // Since OtherVehicle is a subtype of Vehicle\n // we can directly embed the Vehicle fields\n vehicle.abstractFields: _*\n).implements[Vehicle]\n')),(0,i.kt)("h3",{id:"interface-inheritance"},"Interface inheritance"),(0,i.kt)("p",null,"It can be a bit cumbersome to implement an interface's fields every time it is extended.\nInterfaces accept any field type (abstract or concrete) as input.\nThis is convinient since it allows a safe type of inheritance.\nWhen using the ",(0,i.kt)("inlineCode",{parentName:"p"},"subtypeImpl")," function, all possible fields are added to the type."),(0,i.kt)("admonition",{type:"info"},(0,i.kt)("p",{parentName:"admonition"},"gql's inheritance has some implications:"),(0,i.kt)("ul",{parentName:"admonition"},(0,i.kt)("li",{parentName:"ul"},"If you're working an a ",(0,i.kt)("inlineCode",{parentName:"li"},"Type"),", only concrete fields can be inherited."),(0,i.kt)("li",{parentName:"ul"},"If you're working on an ",(0,i.kt)("inlineCode",{parentName:"li"},"Interface"),", all fields, concrete and abstract can be inherited.")),(0,i.kt)("p",{parentName:"admonition"},"gql picks the best field when you inherit from an interface.\nFor two fields with the same name, gql will always pick the concrete field.\nIf both are concrete, it will prioritize the field from the subtype (the type you're working on).")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'trait Pet {\n def name: String\n def age: Int\n def weight: Double\n}\n\ncase class Dog(name: String, age: Int, weight: Double) extends Pet\n\nimplicit lazy val pet: Interface[IO, Pet] = interface[IO, Pet](\n "Pet",\n "name" -> lift(_.name),\n "age" -> lift(_.age),\n "weight" -> lift(_.weight)\n)\n\nlazy val overwirttenName = lift[Dog](_.name)\n\nimplicit lazy val dog: Type[IO, Dog] = tpe[IO, Dog](\n "Dog",\n "bark" -> lift(_ => "woof!"),\n "name" -> overwirttenName\n).subtypeImpl[Pet]\n\ndog.fields.map{ case (k, _) => k}.mkString_(", ")\n// res13: String = "bark, name, age, weight"\n\n// The Dog type has it\'s own implementation of the name field\ndog.fields.exists{ case (_, v) => v == overwirttenName }\n// res14: Boolean = true\n')),(0,i.kt)("p",null,"To showcase the inheritance a bit further, consider the following invalid schema."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'implicit lazy val pet: Interface[IO, Pet] = interface[IO, Pet](\n "Pet",\n "name" -> lift(_.name),\n "age" -> lift(_.age),\n // Notice that weight is abstract\n "weight" -> abst[IO, Double]\n)\n\nimplicit lazy val dog: Type[IO, Dog] = tpe[IO, Dog](\n "Dog",\n "bark" -> lift(_ => "woof!")\n).subtypeImpl[Pet]\n\n// We are missing the weight field\ndog.fields.map{ case (k, _) => k}.mkString_(", ")\n// res15: String = "bark, name, age"\n')),(0,i.kt)("admonition",{type:"tip"},(0,i.kt)("p",{parentName:"admonition"},(0,i.kt)("a",{parentName:"p",href:"/gql/docs/server/schema/#validation"},"Schema validation")," will catch such errors.")),(0,i.kt)("h2",{id:"input-types"},"Input types"),(0,i.kt)("p",null,"Review the ",(0,i.kt)("a",{parentName:"p",href:"/gql/docs/server/schema/input_types"},"Input types")," section for more information."),(0,i.kt)("h2",{id:"other-output-structures"},"Other output structures"),(0,i.kt)("p",null,"Examples of other structures can be in the ",(0,i.kt)("a",{parentName:"p",href:"/gql/docs/server/schema/output_types"},"Output types")," section."),(0,i.kt)("h3",{id:"covariant-effects"},"Covariant effects"),(0,i.kt)("p",null,"Output types in gql are covariant in ",(0,i.kt)("inlineCode",{parentName:"p"},"F"),", such that output types written in different effects seamlessly weave together.\n",(0,i.kt)("inlineCode",{parentName:"p"},"fs2")," provides a type that we can reuse for pure effects defined as ",(0,i.kt)("inlineCode",{parentName:"p"},"type Pure[A] <: Nothing"),"."),(0,i.kt)("p",null,"With this trick, we can define gql types for trivial cases of our domain:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'final case class Entity(\n name: String,\n age: Int\n)\n\nobject Entity {\n implicit lazy val gqlType: Type[fs2.Pure, Entity] = tpe[fs2.Pure, Entity](\n "Entity",\n "name" -> lift(_.name),\n "age" -> lift(_.age)\n )\n}\n\ntrait Example\n\ntpe[IO, Example](\n "Example",\n "entity" -> lift(_ => Entity("John Doe", 42))\n)\n')))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[436],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>d});var a=n(7294);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=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=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):l(l({},t),e)),n},c=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},u=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),u=p(n),d=i,f=u["".concat(s,".").concat(d)]||u[d]||m[d]||r;return n?a.createElement(f,l(l({ref:t},c),{},{components:n})):a.createElement(f,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]=u;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o.mdxType="string"==typeof e?e:i,l[1]=o;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>m,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=n(7462),i=(n(7294),n(3905));const r={title:"The DSL"},l=void 0,o={unversionedId:"server/schema/dsl",id:"server/schema/dsl",title:"The DSL",description:"gql's dsl is a lightweight set of smart-constructors.",source:"@site/docs/server/schema/dsl.md",sourceDirName:"server/schema",slug:"/server/schema/dsl",permalink:"/gql/docs/server/schema/dsl",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/schema/dsl.md",tags:[],version:"current",frontMatter:{title:"The DSL"},sidebar:"docs",previous:{title:"Input types",permalink:"/gql/docs/server/schema/input_types"},next:{title:"Monadic Resolver DSL",permalink:"/gql/docs/server/schema/arrow_dsl"}},s={},p=[{value:"Fields",id:"fields",level:2},{value:"Builders",id:"builders",level:3},{value:"Value resolution",id:"value-resolution",level:3},{value:"Unification instances",id:"unification-instances",level:2},{value:"Interface inheritance",id:"interface-inheritance",level:3},{value:"Input types",id:"input-types",level:2},{value:"Other output structures",id:"other-output-structures",level:2},{value:"Covariant effects",id:"covariant-effects",level:3}],c={toc:p};function m(e){let{components:t,...n}=e;return(0,i.kt)("wrapper",(0,a.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"gql's dsl is a lightweight set of smart-constructors.\nIf you have a particular usecase or even coding style that conflicts with the dsl, you can always introduce your own schema definition syntax or build on top of the existing dsl."),(0,i.kt)("p",null,"Lets begin by importing what we need."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"import cats.data._\nimport cats.effect._\nimport cats.implicits._\nimport gql.dsl.all._\nimport gql.ast._\nimport gql.resolver._\n")),(0,i.kt)("h2",{id:"fields"},"Fields"),(0,i.kt)("p",null,"The simplest form of field construction comes from the ",(0,i.kt)("inlineCode",{parentName:"p"},"build.from")," smart constructor.\nIt simply lifts a resolver into a field, given that a gql output type exists for the resolver output."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"def r: Resolver[IO, Int, String] = Resolver.lift(i => i.toString())\n\nval f: Field[IO, Int, String] = build.from(r)\n// f: Field[IO, Int, String] = Field(\n// resolve = gql.resolver.Resolver@6a135ede,\n// output = cats.Always@5fe719ba,\n// description = None,\n// attributes = List()\n// )\n")),(0,i.kt)("p",null,"Sometimes type inference cannot find the proper type for a field:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"build.from(Resolver.liftF(i => IO(i.toString())))\n// error: value liftF is not a member of object gql.resolver.Resolver\n// did you mean lift? or perhaps liftFull?\n// build.from(Resolver.liftF(i => IO(i.toString())))\n// ^^^^^^^^^^^^^^\n")),(0,i.kt)("p",null,"The type parameters for ",(0,i.kt)("inlineCode",{parentName:"p"},"build")," are partially applied, such that when type inference isn't enough, types can be supplied explicitly."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"build[IO, Int].from(Resolver.effect(i => IO(i.toString())))\n\nbuild.from(Resolver.effect((i: Int) => IO(i.toString())))\n")),(0,i.kt)("p",null,"For some fields, there is an even more concise syntax.\nInvoking the ",(0,i.kt)("inlineCode",{parentName:"p"},"apply")," method of ",(0,i.kt)("inlineCode",{parentName:"p"},"build"),", takes a higher order function that goes from the identity resolver (",(0,i.kt)("inlineCode",{parentName:"p"},"Resolver[F, A, A]"),") to some output."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"build[IO, Int](_.map(i => i * 2).evalMap(i => IO(i))): Field[IO, Int, Int]\n")),(0,i.kt)("h3",{id:"builders"},"Builders"),(0,i.kt)("p",null,"Complex structures may require many special resolver compositions.\nThe dsl also introduces a something akin to a builder pattern.\nThe ",(0,i.kt)("inlineCode",{parentName:"p"},"build")," function from the previous section, creates a builder that has more constructors than just ",(0,i.kt)("inlineCode",{parentName:"p"},"from")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"apply"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"import gql.dsl.FieldBuilder\nval b: FieldBuilder[IO, Int] = build[IO, Int]\n")),(0,i.kt)("p",null,"Often a builder is only relevant within a scope, thus one can end up having many unused builders in scope.\nThe ",(0,i.kt)("inlineCode",{parentName:"p"},"builder")," makes such code more concise:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"builder[IO, Int]{ (fb: FieldBuilder[IO, Int]) =>\n fb\n}\n")),(0,i.kt)("p",null,"The builder dsl contains most of the field related constructors:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'builder[IO, Int]{ fb =>\n fb.tpe(\n "Query",\n "answer" -> lift(i => i * 0 + 42),\n "pong" -> fb(_.map(_ => "pong"))\n ): Type[IO, Int]\n \n fb.fields(\n "answer" -> fb.lift(i => i * 0 + 42),\n "ping" -> fb.from(Resolver.lift(_ => "pong"))\n )\n}\n')),(0,i.kt)("h3",{id:"value-resolution"},"Value resolution"),(0,i.kt)("p",null,"Wrapping every field in a ",(0,i.kt)("inlineCode",{parentName:"p"},"build")," smart constructor and then defining the resolver seperately is a bit verbose.\nThere are smart constructors for two common variants of field resolvers, that lift a resolver function directly to a ",(0,i.kt)("inlineCode",{parentName:"p"},"Field"),"."),(0,i.kt)("p",null,"We must decide if the field is pure or effectful:"),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"The effect constructor is named ",(0,i.kt)("inlineCode",{parentName:"p"},"eff")," to avoid collisions with cats-effect.")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'final case class Person(\n name: String\n)\n\ntpe[IO, Person](\n "Person",\n "name" -> lift(_.name),\n "nameEffect" -> eff(x => IO(x.name))\n)\n')),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"lift")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"eff")," constructors can also also be supplied with arguments:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'def familyName = arg[String]("familyName")\n\ntpe[IO, Person](\n "Person",\n "name" -> lift(familyName)(_ + _.name),\n "nameEffect" -> eff(familyName)((f, p) => IO(p.name + f))\n)\n')),(0,i.kt)("h2",{id:"unification-instances"},"Unification instances"),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"Union"),"s and ",(0,i.kt)("inlineCode",{parentName:"p"},"Interface"),"s are abstract types that have implementations."),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"Union")," declares it's implementations up-front, like a ",(0,i.kt)("inlineCode",{parentName:"p"},"sealed trait"),".\nHowever, ",(0,i.kt)("inlineCode",{parentName:"p"},"Interface")," implementations are declared on the types that implement the interface, like a ",(0,i.kt)("inlineCode",{parentName:"p"},"trait")," or an ",(0,i.kt)("inlineCode",{parentName:"p"},"abstract class"),"."),(0,i.kt)("p",null,"Before continuing, lets setup the environment."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"trait Vehicle { \n def name: String\n}\nfinal case class Car(name: String) extends Vehicle\nfinal case class Boat(name: String) extends Vehicle\nfinal case class Truck(name: String) extends Vehicle\n\n")),(0,i.kt)("p",null,"For the ",(0,i.kt)("inlineCode",{parentName:"p"},"Union"),", variants can be declared using the ",(0,i.kt)("inlineCode",{parentName:"p"},"variant")," function, which takes a ",(0,i.kt)("inlineCode",{parentName:"p"},"PartialFunction")," from the unifying type to the implementation."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'implicit def car: Type[IO, Car] = ???\nimplicit def boat: Type[IO, Boat] = ???\nimplicit def truck: Type[IO, Truck] = ???\n\nunion[IO, Vehicle]("Vehicle")\n .variant[Car] { case c: Car => c }\n .variant[Boat] { case b: Boat => b }\n .variant[Truck] { case t: Truck => t }\n')),(0,i.kt)("p",null,"A shorthand function exists, if the type of the variant is a subtype of the unifying type."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'union[IO, Vehicle]("Vehicle")\n .subtype[Car] \n .subtype[Boat] \n .subtype[Truck] \n')),(0,i.kt)("p",null,"For an ",(0,i.kt)("inlineCode",{parentName:"p"},"Interface")," the same dsl exists, but is placed on the types that can implement the interface (a ",(0,i.kt)("inlineCode",{parentName:"p"},"Type")," or another ",(0,i.kt)("inlineCode",{parentName:"p"},"Interface"),")."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'implicit lazy val vehicle: Interface[IO, Vehicle] = interface[IO, Vehicle](\n "Vehicle",\n "name" -> abst[IO, String]\n)\n\ntpe[IO, Car]("Car", "name" -> lift(_.name))\n .implements[Vehicle]{ case c: Car => c }\n \ntpe[IO, Boat]("Boat", "name" -> lift(_.name))\n .subtypeOf[Vehicle]\n \ntrait OtherVehicle extends Vehicle {\n def weight: Int\n}\n\ninterface[IO, OtherVehicle](\n "OtherVehicle",\n "weight" -> abst[IO, Int],\n // Since OtherVehicle is a subtype of Vehicle\n // we can directly embed the Vehicle fields\n vehicle.abstractFields: _*\n).implements[Vehicle]\n')),(0,i.kt)("h3",{id:"interface-inheritance"},"Interface inheritance"),(0,i.kt)("p",null,"It can be a bit cumbersome to implement an interface's fields every time it is extended.\nInterfaces accept any field type (abstract or concrete) as input.\nThis is convinient since it allows a safe type of inheritance.\nWhen using the ",(0,i.kt)("inlineCode",{parentName:"p"},"subtypeImpl")," function, all possible fields are added to the type."),(0,i.kt)("admonition",{type:"info"},(0,i.kt)("p",{parentName:"admonition"},"gql's inheritance has some implications:"),(0,i.kt)("ul",{parentName:"admonition"},(0,i.kt)("li",{parentName:"ul"},"If you're working an a ",(0,i.kt)("inlineCode",{parentName:"li"},"Type"),", only concrete fields can be inherited."),(0,i.kt)("li",{parentName:"ul"},"If you're working on an ",(0,i.kt)("inlineCode",{parentName:"li"},"Interface"),", all fields, concrete and abstract can be inherited.")),(0,i.kt)("p",{parentName:"admonition"},"gql picks the best field when you inherit from an interface.\nFor two fields with the same name, gql will always pick the concrete field.\nIf both are concrete, it will prioritize the field from the subtype (the type you're working on).")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'trait Pet {\n def name: String\n def age: Int\n def weight: Double\n}\n\ncase class Dog(name: String, age: Int, weight: Double) extends Pet\n\nimplicit lazy val pet: Interface[IO, Pet] = interface[IO, Pet](\n "Pet",\n "name" -> lift(_.name),\n "age" -> lift(_.age),\n "weight" -> lift(_.weight)\n)\n\nlazy val overwirttenName = lift[Dog](_.name)\n\nimplicit lazy val dog: Type[IO, Dog] = tpe[IO, Dog](\n "Dog",\n "bark" -> lift(_ => "woof!"),\n "name" -> overwirttenName\n).subtypeImpl[Pet]\n\ndog.fields.map{ case (k, _) => k}.mkString_(", ")\n// res13: String = "bark, name, age, weight"\n\n// The Dog type has it\'s own implementation of the name field\ndog.fields.exists{ case (_, v) => v == overwirttenName }\n// res14: Boolean = true\n')),(0,i.kt)("p",null,"To showcase the inheritance a bit further, consider the following invalid schema."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'implicit lazy val pet: Interface[IO, Pet] = interface[IO, Pet](\n "Pet",\n "name" -> lift(_.name),\n "age" -> lift(_.age),\n // Notice that weight is abstract\n "weight" -> abst[IO, Double]\n)\n\nimplicit lazy val dog: Type[IO, Dog] = tpe[IO, Dog](\n "Dog",\n "bark" -> lift(_ => "woof!")\n).subtypeImpl[Pet]\n\n// We are missing the weight field\ndog.fields.map{ case (k, _) => k}.mkString_(", ")\n// res15: String = "bark, name, age"\n')),(0,i.kt)("admonition",{type:"tip"},(0,i.kt)("p",{parentName:"admonition"},(0,i.kt)("a",{parentName:"p",href:"/gql/docs/server/schema/#validation"},"Schema validation")," will catch such errors.")),(0,i.kt)("h2",{id:"input-types"},"Input types"),(0,i.kt)("p",null,"Review the ",(0,i.kt)("a",{parentName:"p",href:"/gql/docs/server/schema/input_types"},"Input types")," section for more information."),(0,i.kt)("h2",{id:"other-output-structures"},"Other output structures"),(0,i.kt)("p",null,"Examples of other structures can be in the ",(0,i.kt)("a",{parentName:"p",href:"/gql/docs/server/schema/output_types"},"Output types")," section."),(0,i.kt)("h3",{id:"covariant-effects"},"Covariant effects"),(0,i.kt)("p",null,"Output types in gql are covariant in ",(0,i.kt)("inlineCode",{parentName:"p"},"F"),", such that output types written in different effects seamlessly weave together.\n",(0,i.kt)("inlineCode",{parentName:"p"},"fs2")," provides a type that we can reuse for pure effects defined as ",(0,i.kt)("inlineCode",{parentName:"p"},"type Pure[A] <: Nothing"),"."),(0,i.kt)("p",null,"With this trick, we can define gql types for trivial cases of our domain:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'final case class Entity(\n name: String,\n age: Int\n)\n\nobject Entity {\n implicit lazy val gqlType: Type[fs2.Pure, Entity] = tpe[fs2.Pure, Entity](\n "Entity",\n "name" -> lift(_.name),\n "age" -> lift(_.age)\n )\n}\n\ntrait Example\n\ntpe[IO, Example](\n "Example",\n "entity" -> lift(_ => Entity("John Doe", 42))\n)\n')))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4f169309.0956e161.js b/assets/js/4f169309.0956e161.js new file mode 100644 index 000000000..e91e23773 --- /dev/null +++ b/assets/js/4f169309.0956e161.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[381],{3905:(e,n,t)=>{t.d(n,{Zo:()=>m,kt:()=>u});var a=t(7294);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=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=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},m=function(e){var n=p(e.components);return a.createElement(s.Provider,{value:n},e.children)},d={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,o=e.originalType,s=e.parentName,m=l(e,["components","mdxType","originalType","parentName"]),c=p(t),u=r,h=c["".concat(s,".").concat(u)]||c[u]||d[u]||o;return t?a.createElement(h,i(i({ref:n},m),{},{components:t})):a.createElement(h,i({ref:n},m))}));function u(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var o=t.length,i=new Array(o);i[0]=c;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l.mdxType="string"==typeof e?e:r,i[1]=l;for(var p=2;p{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>d,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var a=t(7462),r=(t(7294),t(3905));const o={title:"Resolvers"},i=void 0,l={unversionedId:"server/schema/resolvers",id:"server/schema/resolvers",title:"Resolvers",description:"Resolvers are at the core of gql; a resolver Resolver[F, I, O] takes an I and produces an O in effect F.",source:"@site/docs/server/schema/resolvers.md",sourceDirName:"server/schema",slug:"/server/schema/resolvers",permalink:"/gql/docs/server/schema/resolvers",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/schema/resolvers.md",tags:[],version:"current",frontMatter:{title:"Resolvers"},sidebar:"docs",previous:{title:"Monadic Resolver DSL",permalink:"/gql/docs/server/schema/arrow_dsl"},next:{title:"The schema",permalink:"/gql/docs/server/schema/"}},s={},p=[{value:"Resolvers",id:"resolvers",level:2},{value:"Lift",id:"lift",level:3},{value:"Effect",id:"effect",level:3},{value:"Arguments",id:"arguments",level:3},{value:"Meta",id:"meta",level:3},{value:"Errors",id:"errors",level:3},{value:"First",id:"first",level:3},{value:"Batch",id:"batch",level:3},{value:"Batch resolver syntax",id:"batch-resolver-syntax",level:4},{value:"Batchers from elsewhere",id:"batchers-from-elsewhere",level:4},{value:"Inline batch",id:"inline-batch",level:3},{value:"Choice",id:"choice",level:3},{value:"Stream",id:"stream",level:3},{value:"Stream semantics",id:"stream-semantics",level:4},{value:"Steps",id:"steps",level:2}],m={toc:p};function d(e){let{components:n,...t}=e;return(0,r.kt)("wrapper",(0,a.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Resolvers are at the core of gql; a resolver ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, O]")," takes an ",(0,r.kt)("inlineCode",{parentName:"p"},"I")," and produces an ",(0,r.kt)("inlineCode",{parentName:"p"},"O")," in effect ",(0,r.kt)("inlineCode",{parentName:"p"},"F"),".\nResolvers are embedded in fields and act as continuations.\nWhen gql executes a query it first constructs a tree of continueations from your schema and the supplied GraphQL query."),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Resolver"),"s act and compose like functions with combinators such as ",(0,r.kt)("inlineCode",{parentName:"p"},"andThen")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"compose"),"."),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," forms an ",(0,r.kt)("inlineCode",{parentName:"p"},"Arrow")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"Choice"),".")),(0,r.kt)("p",null,"Lets start off with some imports:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"import gql._\nimport gql.dsl.all._\nimport gql.resolver._\nimport gql.ast._\nimport cats.effect._\nimport cats.implicits._\nimport cats.data._\n")),(0,r.kt)("h2",{id:"resolvers"},"Resolvers"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," is a collection of high-level combinators that constructs a tree of ",(0,r.kt)("inlineCode",{parentName:"p"},"Step"),"."),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"If you are familiar with the relationship between ",(0,r.kt)("inlineCode",{parentName:"p"},"fs2.Stream")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"fs2.Pull"),", then the relationship between ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"Step")," should be familiar.")),(0,r.kt)("h3",{id:"lift"},"Lift"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Resolver.lift")," lifts a function ",(0,r.kt)("inlineCode",{parentName:"p"},"I => O")," into ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, O]"),".\n",(0,r.kt)("inlineCode",{parentName:"p"},"lift"),"'s method form is ",(0,r.kt)("inlineCode",{parentName:"p"},"map"),", which for any resolver ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, O]")," produces a new resolver ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, O2]")," given a function ",(0,r.kt)("inlineCode",{parentName:"p"},"O => O2"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"val r = Resolver.lift[IO, Int](_.toLong)\n// r: Resolver[IO, Int, Long] = gql.resolver.Resolver@5c195a20\nr.map(_.toString())\n// res0: Resolver[IO, Int, String] = gql.resolver.Resolver@452a95e8\n")),(0,r.kt)("h3",{id:"effect"},"Effect"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"effect")," like ",(0,r.kt)("inlineCode",{parentName:"p"},"lift")," lifts a function, but instead an effectful one like ",(0,r.kt)("inlineCode",{parentName:"p"},"I => F[O]")," into ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, O]"),".\n",(0,r.kt)("inlineCode",{parentName:"p"},"effect"),"'s method form is ",(0,r.kt)("inlineCode",{parentName:"p"},"evalMap")," (like ",(0,r.kt)("inlineCode",{parentName:"p"},"Resource")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"fs2.Stream"),")."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"val r = Resolver.effect[IO, Int](i => IO(i.toLong))\n// r: Resolver[IO, Int, Long] = gql.resolver.Resolver@12270766\nr.evalMap(l => IO(l.toString()))\n// res1: Resolver[[x]IO[x], Int, String] = gql.resolver.Resolver@313b1b3c\n")),(0,r.kt)("h3",{id:"arguments"},"Arguments"),(0,r.kt)("p",null,"Arguments in gql are provided through resolvers.\nA resolver ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, A]")," can be constructed from an argument ",(0,r.kt)("inlineCode",{parentName:"p"},"Arg[A]"),", through either ",(0,r.kt)("inlineCode",{parentName:"p"},"argument")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"arg")," in method form."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'lazy val ageArg = arg[Int]("age")\nval r = Resolver.argument[IO, Nothing, String](arg[String]("name"))\n// r: Resolver[IO, Nothing, String] = gql.resolver.Resolver@27e58f61\nval r2 = r.arg(ageArg)\n// r2: Resolver[IO, Nothing, (Int, String)] = gql.resolver.Resolver@36f1dba0\nr2.map{ case (age, name) => s"$name is $age years old" }\n// res2: Resolver[IO, Nothing, String] = gql.resolver.Resolver@1cc7e62f\n')),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Arg")," also has an applicative defined for it, so multi-argument resolution can be simplified to."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'val r = Resolver.argument[IO, Nothing, (String, Int)](\n (arg[String]("name"), arg[Int]("age")).tupled\n)\n// r: Resolver[IO, Nothing, (String, Int)] = gql.resolver.Resolver@75ccec70\nr.map{ case (age, name) => s"$name is $age years old" }\n// res3: Resolver[IO, Nothing, String] = gql.resolver.Resolver@7e59fb2f\n')),(0,r.kt)("h3",{id:"meta"},"Meta"),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"meta")," resolver provides metadata regarding query execution, such as the position of query execution, field aliasing and the provided arguments."),(0,r.kt)("p",null,"It also allows the caller to inspect the query ast such that more exotic operations become possible.\nFor instance, arguments can dynamically be inspected."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'lazy val a = arg[Int]("age")\nResolver.meta[IO, String].map(meta => meta.astNode.arg(a))\n// res4: Resolver[IO, String, Option[Int]] = gql.resolver.Resolver@464d039f\n')),(0,r.kt)("p",null,"The ",(0,r.kt)("a",{parentName:"p",href:"/gql/docs/server/integrations/relational"},"relational")," integration makes heavy use of this feature."),(0,r.kt)("h3",{id:"errors"},"Errors"),(0,r.kt)("p",null,"Errors are reported in ",(0,r.kt)("inlineCode",{parentName:"p"},"cats.data.Ior"),"."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"An ",(0,r.kt)("inlineCode",{parentName:"p"},"Ior")," is a non-exclusive ",(0,r.kt)("inlineCode",{parentName:"p"},"Either"),".")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"Ior")," datatype's left side must be ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," and acts as an optional error that will be present in the query result.\ngql can return an error and a result for the same path, given that ",(0,r.kt)("inlineCode",{parentName:"p"},"Ior")," has both it's left and right side defined."),(0,r.kt)("p",null,"Errors are embedded into resolvers via ",(0,r.kt)("inlineCode",{parentName:"p"},"rethrow"),".\nThe extension method ",(0,r.kt)("inlineCode",{parentName:"p"},"rethrow")," is present on any resolver of type ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, Ior[String, O]]"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'val r = Resolver.lift[IO, Int](i => Ior.Both("I will be in the errors :)", i))\n// r: Resolver[IO, Int, Ior.Both[String, Int]] = gql.resolver.Resolver@651863e7\nr.rethrow\n// res5: Resolver[[A]IO[A], Int, Int] = gql.resolver.Resolver@77a5bc58\n')),(0,r.kt)("p",null,"We can also use ",(0,r.kt)("inlineCode",{parentName:"p"},"emap")," to map the current value into an ",(0,r.kt)("inlineCode",{parentName:"p"},"Ior"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'val r = Resolver.id[IO, Int].emap(i => Ior.Both("I will be in the errors :)", i))\n// r: Resolver[IO, Int, Int] = gql.resolver.Resolver@4694a100\n')),(0,r.kt)("h3",{id:"first"},"First"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," also implements ",(0,r.kt)("inlineCode",{parentName:"p"},"first")," (",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, A, B] => Resolver[F, (A, C), (B, C)]"),") which can be convinient for situations where one would usually have to trace a value through an entire computation."),(0,r.kt)("p",null,"Since a ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," does not form a ",(0,r.kt)("inlineCode",{parentName:"p"},"Monad"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"first")," is necessary to implement non-trivial resolver compositions."),(0,r.kt)("p",null,"For instance, maybe your program contains a general resolver compositon that is used many places, like say verifying credentials, but you'd like to trace a value through it without having to keep track of tupling output with input."),(0,r.kt)("p",null,"Assume we'd like to implement a resolver, that when given a person's name, can get a list of the person's friends."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'case class PersonId(value: Int)\n\ncase class Person(id: PersonId, name: String)\n\ndef getFriends(id: PersonId, limit: Int): IO[List[Person]] = ???\n\ndef getPerson(name: String): IO[Person] = ???\n\ndef getPersonResolver = Resolver.effect[IO, String](getPerson)\n\ndef limitResolver = Resolver.argument[IO, Person, Int](arg[Int]("limit"))\n\ndef limitArg = arg[Int]("limit")\ngetPersonResolver\n // \'arg\' tuples the input with the argument value\n .arg(limitArg)\n .evalMap{ case (limit, p) => getFriends(p.id, limit) }\n// res6: Resolver[[x]IO[x], String, List[Person]] = gql.resolver.Resolver@68eb03b6\n')),(0,r.kt)("h3",{id:"batch"},"Batch"),(0,r.kt)("p",null,"Like most other GraphQL implementations, gql also supports batching."),(0,r.kt)("p",null,"Unlike most other GraphQL implementations, gql's batching implementation features a global query planner that lets gql delay field execution until it can be paired with another field."),(0,r.kt)("p",null,"Batch declaration and usage occurs as follows:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Declare a function ",(0,r.kt)("inlineCode",{parentName:"li"},"Set[K] => F[Map[K, V]]"),"."),(0,r.kt)("li",{parentName:"ul"},"Give this function to gql and get back a ",(0,r.kt)("inlineCode",{parentName:"li"},"Resolver[F, Set[K], Map[K, V]]")," in a ",(0,r.kt)("inlineCode",{parentName:"li"},"State")," monad (for unique id generation)."),(0,r.kt)("li",{parentName:"ul"},"Use this new resolver where you want batching.")),(0,r.kt)("p",null,"And now put into practice:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"def getPeopleFromDB(ids: Set[PersonId]): IO[List[Person]] = ???\n\nResolver.batch[IO, PersonId, Person]{ keys => \n getPeopleFromDB(keys).map(_.map(x => x.id -> x).toMap)\n}\n// res7: State[SchemaState[IO], Resolver[IO, Set[PersonId], Map[PersonId, Person]]] = cats.data.IndexedStateT@4eca5217\n")),(0,r.kt)("p",null,"Whenever gql sees this resolver in any composition, it will look for similar resolvers during query planning."),(0,r.kt)("p",null,"Note, however, that you should only declare each batch resolver variant ",(0,r.kt)("strong",{parentName:"p"},"once"),", that is, you should build your schema in ",(0,r.kt)("inlineCode",{parentName:"p"},"State"),".\ngql consideres different batch instantiations incompatible regardless of any type information."),(0,r.kt)("p",null,"State has ",(0,r.kt)("inlineCode",{parentName:"p"},"Monad")," (and transitively ",(0,r.kt)("inlineCode",{parentName:"p"},"Applicative"),") defined for it, so it composes well.\nHere is an example of multiple batchers:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"def b1 = Resolver.batch[IO, Int, Person](_ => ???)\ndef b2 = Resolver.batch[IO, Int, String](_ => ???)\n\n(b1, b2).tupled\n// res8: State[SchemaState[IO], (Resolver[IO, Set[Int], Map[Int, Person]], Resolver[IO, Set[Int], Map[Int, String]])] = cats.data.IndexedStateT@145760ea\n")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"Even if your field doesn't benefit from batching, batching can still do duplicate key elimination.")),(0,r.kt)("h4",{id:"batch-resolver-syntax"},"Batch resolver syntax"),(0,r.kt)("p",null,"When a resolver in a very specific form ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, Set[K], Map[K, V]]"),", then the gql dsl provides some helper methods.\nFor instance, a batcher may be embedded in a singular context (",(0,r.kt)("inlineCode",{parentName:"p"},"K => V"),").\nHere is a showcase of some of the helper methods:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'def pb: Resolver[IO, Set[Int], Map[Int, Person]] = \n // Stub implementation\n Resolver.lift(_ => Map.empty)\n\n// None if a key is missing\npb.all[List]\n// res9: Resolver[[A]IO[A], List[Int], List[Option[Person]]] = gql.resolver.Resolver@77284fc1\n\n// Every key must have an associated value\n// or else raise an error via a custom show-like typeclass\nimplicit lazy val showMissingPersonId =\n ShowMissingKeys.showForKey[Int]("not all people could be found")\npb.traversable[List]\n// res10: Resolver[[A]IO[A], List[Int], List[Person]] = gql.resolver.Resolver@47ee0421\n\n// Maybe there is one value for one key?\npb.opt\n// res11: Resolver[[A]IO[A], Int, Option[Person]] = gql.resolver.Resolver@5ba8bc21\n\n// Same as opt\npb.all[cats.Id]\n// res12: Resolver[[A]IO[A], cats.package.Id[Int], cats.package.Id[Option[Person]]] = gql.resolver.Resolver@74d7d78e\n\n// There is always one value for one key\npb.one\n// res13: Resolver[[A]IO[A], Int, Person] = gql.resolver.Resolver@41b4c892\n\n// You can be more explicit via the `batch` method\npb.batch.all[NonEmptyList]\n// res14: Resolver[[A]IO[A], NonEmptyList[Int], NonEmptyList[Option[Person]]] = gql.resolver.Resolver@764c0389\n')),(0,r.kt)("p",null,"Using ",(0,r.kt)("inlineCode",{parentName:"p"},"batch")," aids with better compiler error messages."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"Resolver.lift[IO, Int](_.toString()).batch.all\n// error: Cannot prove that Set[K] =:= Int.\n// Resolver.lift[IO, Int](_.toString()).batch.all\n// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"For larger programs, consider declaring all your batchers up-front and putting them into some type of collection:"),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"case class MyBatchers(\n personBatcher: Resolver[IO, Set[Int], Map[Int, Person]],\n intStringBatcher: Resolver[IO, Set[Int], Map[Int, String]]\n)\n\n(b1, b2).mapN(MyBatchers.apply)\n// res16: State[SchemaState[IO], MyBatchers] = cats.data.IndexedStateT@28fc87f4\n")),(0,r.kt)("p",{parentName:"admonition"},"For most batchers it is likely that you eventually want to pre-compose them in various ways, for instance requsting args, which this pattern promotes.")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"Sometimes you have multiple groups of fields in the same object where each group have different performance overheads."),(0,r.kt)("p",{parentName:"admonition"},"Say you had a ",(0,r.kt)("inlineCode",{parentName:"p"},"Person")," object in your database.\nThis ",(0,r.kt)("inlineCode",{parentName:"p"},"Person")," object also exists in a remote api.\nThis remote api can tell you, the friends of a ",(0,r.kt)("inlineCode",{parentName:"p"},"Person")," given the object's id and name.\nWritten out a bit more structured we have that:"),(0,r.kt)("ul",{parentName:"admonition"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"PersonId => PersonId")," (identity)"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"PersonId => PersonDB")," (database query)"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"PersonDB => PersonRemoteAPI")," (remote api call)"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"PersonId => PersonRemoteAPI")," (composition of database query and remote api call)")),(0,r.kt)("p",{parentName:"admonition"},"And now put into code:"),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'// We have a trivial id field for our person id\ndef pureFields = fields[IO, PersonId](\n "id" -> lift(id => id)\n)\n\n// If we query our database with a person id, we get a person database object\ncase class PersonDB(\n id: PersonId, \n name: String, \n remoteApiId: String\n)\n\n// SELECT id, name, remote_api_id FROM person WHERE id in (...)\ndef dbBatchResolver: Resolver[IO, PersonId, PersonDB] = ???\n\n// From the db we can get the name and the remote api id\ndef dbFields = fields[IO, PersonDB](\n "name" -> lift(_.name),\n "apiId" -> lift(_.remoteApiId)\n)\n\n// The remote api data can be found given the result of a db query\ncase class PersonRemoteAPI(\n id: PersonId, \n friends: List[PersonId]\n)\n\n// Given a PersonDB we can call the api (via a batched GET or something)\ndef personBatchResolver: Resolver[IO, PersonDB, PersonRemoteAPI] = ???\n\n// We can get the friends from the remote api\ndef remoteApiFields = fields[IO, PersonRemoteAPI](\n "friends" -> lift(_.friends)\n)\n\n// Now we can start composing our fields\n// We can align the types of the db and remote api data to the PersonDB type\n// by composing the remote api resolver on the remote api fields\ndef dbFields2: Fields[IO, PersonDB] = \n remoteApiFields.compose(personBatchResolver) ::: dbFields\n\n// Given a PersonId we have every field\n// If "friends" is selected, gql will first run `dbBatchResolver` and then `personBatchResolver`\ndef allFields = dbFields2.compose(dbBatchResolver) ::: pureFields\n\nimplicit def person: Type[IO, PersonId] = tpeNel[IO, PersonId](\n "Person",\n allFields\n)\n')),(0,r.kt)("p",{parentName:"admonition"},"The general pattern for this decomposition revolves around figuring out what the most basic description of your object is.\nIn this example, every fields can (eventually through various side-effects) be resolved from just ",(0,r.kt)("inlineCode",{parentName:"p"},"PersonId"),".")),(0,r.kt)("h4",{id:"batchers-from-elsewhere"},"Batchers from elsewhere"),(0,r.kt)("p",null,"Most batching implementations have compatible signatures and can be adapted into a gql batcher."),(0,r.kt)("p",null,"For instance, converting ",(0,r.kt)("inlineCode",{parentName:"p"},"fetch")," to gql:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'import fetch._\nobject People extends Data[PersonId, Person] {\n def name = "People"\n\n def source: DataSource[IO, PersonId, Person] = ???\n}\n\nResolver\n .batch[IO, PersonId, Person](_.toList.toNel.traverse(People.source.batch).map(_.getOrElse(Map.empty)))\n// res17: State[SchemaState[IO], Resolver[IO, Set[PersonId], Map[PersonId, Person]]] = cats.data.IndexedStateT@39560b\n')),(0,r.kt)("h3",{id:"inline-batch"},"Inline batch"),(0,r.kt)("p",null,"A batch resolver can also be defined inline with some notable differences to the regular batch resolver:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"It does not need to be defined in state."),(0,r.kt)("li",{parentName:"ul"},"It is not subject to global query planning, and is only ever called with inputs from the same selection.")),(0,r.kt)("p",null,"The inline batch resolver has the same signature as a regular batch resolver; ",(0,r.kt)("inlineCode",{parentName:"p"},"Set[K] => F[Map[K, V]]"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"Resolver.inlineBatch[IO, PersonId, Person](\n _.toList.toNel.traverse(People.source.batch).map(_.getOrElse(Map.empty))\n)\n// res18: Resolver[IO, Set[PersonId], Map[PersonId, Person]] = gql.resolver.Resolver@465687fc\n")),(0,r.kt)("h3",{id:"choice"},"Choice"),(0,r.kt)("p",null,"Resolvers also implement ",(0,r.kt)("inlineCode",{parentName:"p"},"Choice")," via ",(0,r.kt)("inlineCode",{parentName:"p"},"(Resolver[F, A, C], Resolver[F, B, D]) => Resolver[F, Either[A, B], Either[C, D]]"),".\nOn the surface, this combinator may have limited uses, but with a bit of composition we can perform tasks such as caching."),(0,r.kt)("p",null,"For instance, a combinator derived from ",(0,r.kt)("inlineCode",{parentName:"p"},"Choice")," is ",(0,r.kt)("inlineCode",{parentName:"p"},"skippable: Resolver[F, I, O] => Resolver[F, Either[I, O], O]"),', which acts as a variant of "caching".\nIf the right side is present we skip the underlying resolver (',(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, O]"),") altogether."),(0,r.kt)("p",null,"For any resolver in the form ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, Either[L, R]]")," we modify the left side with ",(0,r.kt)("inlineCode",{parentName:"p"},"leftThrough")," and the right with ",(0,r.kt)("inlineCode",{parentName:"p"},"rightThrough"),"."),(0,r.kt)("p",null,"For Instance we can implement caching."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'def getPersonForId(id: PersonId): IO[Person] = ???\n\ntype CachedPerson = Either[PersonId, Person]\ndef cachedPerson = tpe[IO, CachedPerson](\n "Person",\n "id" -> lift(_.map(_.id).merge.value),\n // We\'ll align the left and right side of the choice and then merge the `Either`\n "name" -> build[IO, CachedPerson](_.leftThrough(_.evalMap(getPersonForId)).map(_.merge.name))\n)\n')),(0,r.kt)("p",null,"We can also use some of the ",(0,r.kt)("inlineCode",{parentName:"p"},"compose")," tricks from the ",(0,r.kt)("a",{parentName:"p",href:"#batch-resolver-syntax"},"batch resolver syntax section")," if we have a lot of fields that depend on ",(0,r.kt)("inlineCode",{parentName:"p"},"Person"),". "),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"The query planner treats the choice branches as parallel, such that for two instances of a choice, resolvers in the two branches may be batched together.")),(0,r.kt)("h3",{id:"stream"},"Stream"),(0,r.kt)("p",null,"The stream resolver embeds an ",(0,r.kt)("inlineCode",{parentName:"p"},"fs2.Stream")," and provides the ability to emit a stream of results for a graphql subscription."),(0,r.kt)("h4",{id:"stream-semantics"},"Stream semantics"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"When one or more streams emit, the interpreter will re-evaluate the query from the position that emitted.\nThat is, only the sub-tree that changed will be re-interpreted."),(0,r.kt)("li",{parentName:"ul"},"If two streams emit and one occurs as a child of the other, the child will be ignored since it will be replaced."),(0,r.kt)("li",{parentName:"ul"},"By default, the interpreter will only respect the most-recent emitted data.")),(0,r.kt)("p",null,"This means that by default, gql assumes that your stream should behave like a signal, not sequentially.\nHowever, gql can also adhere sequential semantics."),(0,r.kt)("p",null,"For instance a schema designed like the following, emits incremental updates regarding the price for some symbol:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-graphql"},"type PriceChange {\n difference: Float!\n}\n\ntype Subscription {\n priceChanges(symbolId: ID!): PriceChange!\n}\n")),(0,r.kt)("p",null,"And here is a schema that represents an api that emits updates regarding the current price of a symbol:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-graphql"},"type SymbolState {\n price: Float!\n}\n\ntype Subscription {\n price(symbolId: ID!): SymbolState!\n}\n")),(0,r.kt)("p",null,"Consider the following example where two different evaluation semantics are displayed:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"case class PriceChange(difference: Float)\ndef priceChanges(symbolId: String): fs2.Stream[IO, PriceChange] = ???\n\ncase class SymbolState(price: Float)\ndef price(symbolId: String): fs2.Stream[IO, SymbolState] = ???\n\ndef priceChangesResolver = Resolver.id[IO, String].sequentialStreamMap(priceChanges)\n\ndef priceResolver = Resolver.id[IO, String].streamMap(price)\n")),(0,r.kt)("p",null,"If your stream is sequential, gql will only pull elements when they are needed."),(0,r.kt)("p",null,"The interpreter performs a global re-interpretation of your schema, when one or more streams emit.\nThat is, the interpreter cycles through the following two phases:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Interpret for the current values."),(0,r.kt)("li",{parentName:"ul"},"Await new values (and values that arrived during the previous step).")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"Since gql is free to ignore updates when a stream is a signal, one should prefer ",(0,r.kt)("inlineCode",{parentName:"p"},"evalMap")," on a ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," instead of a stream if possible.")),(0,r.kt)("admonition",{type:"warning"},(0,r.kt)("p",{parentName:"admonition"},"For a given stream it must hold all child resources alive (maybe the child resources are also streams that may emit).\nAs such, for a given stream, gql must await a next element from the stream before releasing any currently held resources sub-tree.\nThis means that gql must be able to pull one element before closing the old one.")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"If you have streams of updates where you are only interested in that something changed (",(0,r.kt)("inlineCode",{parentName:"p"},"Stream[F, Unit]"),") there may be room for significant optimization.\nIn ",(0,r.kt)("inlineCode",{parentName:"p"},"fs2")," you can merge streams with combinators such as ",(0,r.kt)("inlineCode",{parentName:"p"},"parJoin"),", but they have to assume that there may be resources to account for.\nIf you are discarding the output of the stream or you are absolutely sure that the output does not depend on a resource lifetime,\none can write more optimized versions functions for this purpose."),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Some examples of potentially more performant implementations"),(0,r.kt)("p",{parentName:"admonition"},"In a crude benchmarks, these combinators may perform an order of magnitude faster than ",(0,r.kt)("inlineCode",{parentName:"p"},"parJoin")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"merge"),"."),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"import fs2.{Pipe, Stream}\nimport fs2.concurrent._\ndef parListen[A]: Pipe[IO, Stream[IO, A], Unit] =\n streams =>\n for {\n d <- Stream.eval(IO.deferred[Either[Throwable, Unit]])\n c <- Stream.eval(IO.deferred[Unit])\n sigRef <- Stream.eval(SignallingRef[IO, Unit](()))\n\n bg = streams.flatMap { sub =>\n Stream.supervise {\n sub\n .evalMap(_ => sigRef.set(()))\n .compile\n .drain\n .onError(e => d.complete(Left(e)).void)\n .onCancel(c.complete(()).void)\n }.void\n }\n\n listenCancel = (c.get *> IO.canceled).as(Right(()): Either[Throwable, Unit])\n fg = sigRef.discrete.interruptWhen(d).interruptWhen(listenCancel)\n\n _ <- fg.concurrently(bg)\n } yield ()\n\ndef parListenSignal[A]: Pipe[IO, Stream[IO, A], A] =\n streams =>\n Stream.eval(SignallingRef.of[IO, Option[A]](None)).flatMap { sig =>\n sig.discrete.unNone.concurrently {\n streams.parEvalMapUnorderedUnbounded { x =>\n x.evalMap(x => sig.set(Some(x))).compile.drain\n }\n }\n }\n")))),(0,r.kt)("p",null,"Here is an example of some streams in action:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'import scala.concurrent.duration._\nimport cats.effect.unsafe.implicits.global\n\ncase class Streamed(value: Int)\n\nimplicit lazy val streamed: Type[IO, Streamed] = tpe[IO, Streamed](\n "Streamed",\n "value" -> build[IO, Streamed](_.streamMap{ s =>\n fs2.Stream\n .bracket(IO(println(s"allocating $s")))(_ => IO(println(s"releasing $s"))) >>\n fs2.Stream\n .iterate(0)(_ + 1)\n .evalTap(n => IO(println(s"emitting $n for $s")))\n .meteredStartImmediately(((5 - s.value) * 20).millis)\n .as(Streamed(s.value + 1))\n })\n)\n\ndef query = """\n subscription {\n streamed {\n value {\n value { \n value {\n __typename\n }\n }\n }\n }\n }\n"""\n\ndef schema = SchemaShape.unit[IO](\n fields("ping" -> lift(_ => "pong")),\n subscription = Some(fields("streamed" -> lift(_ => Streamed(0))))\n)\n\nSchema.simple(schema)\n .map(Compiler[IO].compile(_, query))\n .flatMap { case Right(Application.Subscription(stream)) => stream.take(4).compile.drain }\n .unsafeRunSync()\n// allocating Streamed(0)\n// emitting 0 for Streamed(0)\n// allocating Streamed(1)\n// emitting 0 for Streamed(1)\n// allocating Streamed(2)\n// emitting 0 for Streamed(2)\n// emitting 1 for Streamed(2)\n// emitting 1 for Streamed(1)\n// emitting 1 for Streamed(0)\n// allocating Streamed(1)\n// allocating Streamed(2)\n// emitting 0 for Streamed(1)\n// emitting 0 for Streamed(2)\n// allocating Streamed(2)\n// emitting 0 for Streamed(2)\n// emitting 2 for Streamed(2)\n// releasing Streamed(0)\n// releasing Streamed(1)\n// releasing Streamed(2)\n// emitting 2 for Streamed(1)\n// releasing Streamed(1)\n// releasing Streamed(2)\n// releasing Streamed(2)\n')),(0,r.kt)("p",null,"gql also allows the user to specify how much time the interpreter may await more stream updates:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"Schema.simple(schema).map(Compiler[IO].compile(_, query, accumulate=Some(10.millis)))\n")),(0,r.kt)("p",null,"furthermore, gql can also emit interpreter information if you want to look into what gql is doing:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"Schema.simple(schema)\n .map(Compiler[IO].compile(_, query, debug=gql.server.interpreter.DebugPrinter[IO](s => IO(println(s)))))\n .flatMap { case Right(Application.Subscription(stream)) => stream.take(3).compile.drain }\n .unsafeRunSync()\n// allocating Streamed(0)\n// emitting 0 for Streamed(0)\n// publishing at index 0 at root.streamed.value\n// allocating Streamed(1)\n// emitting 0 for Streamed(1)\n// publishing at index 0 at root.streamed.value.value\n// allocating Streamed(2)\n// emitting 0 for Streamed(2)\n// publishing at index 0 at root.streamed.value.value.value\n// unconsing with current tree:\n// |- unknown-cats.effect.kernel.Unique$Token@55855354\n// got state, awaiting a non-empty state (publication)\n// emitting 1 for Streamed(2)\n// publishing at index 1 at root.streamed.value.value.value\n// done publishing at index 1 at root.streamed.value.value.value, await? true\n// got non-empty state, awaiting 5 milliseconds\n// unconsed:\n// [\n// ResourceInfo(\n// parentName = root.streamed.value.value.value (signal = true),\n// name = resource-1,\n// open = true,\n// value = StreamData(\n// cont = Continuation.Done(\n// Selection(\n// PreparedSpecification(\n// typename = Streamed,\n// selections = PreparedSelections{\n// PreparedDataField(\n// name = __typename,\n// alias = None,\n// cont = PreparedCont(\n// edges = Lift(...),\n// cont = PreparedLeaf(String)\n// )\n// )\n// }\n// )\n// )\n// ),\n// value = Right(repl.MdocSession$MdocApp$Streamed$1)\n// )\n// )\n// ]\n// emitting 1 for Streamed(1)\n// publishing at index 1 at root.streamed.value.value\n// done publishing at index 1 at root.streamed.value.value, await? true\n// unconsed after removing old children:\n// [\n// ResourceInfo(\n// parentName = root.streamed.value.value.value (signal = true),\n// name = resource-1,\n// open = true,\n// value = ditto\n// )\n// ]\n// tree after unconsing:\n// |- unknown-cats.effect.kernel.Unique$Token@55855354\n// emitting 1 elements from uncons\n// interpreting for 1 inputs\n// done interpreting\n// unconsing with current tree:\n// |- unknown-cats.effect.kernel.Unique$Token@55855354\n// got state, awaiting a non-empty state (publication)\n// got non-empty state, awaiting 5 milliseconds\n// emitting 1 for Streamed(0)\n// publishing at index 1 at root.streamed.value\n// done publishing at index 1 at root.streamed.value, await? true\n// unconsed:\n// [\n// ResourceInfo(\n// parentName = root.streamed.value.value (signal = true),\n// name = resource-1,\n// open = true,\n// value = StreamData(\n// cont = Continuation.Done(\n// Selection(\n// PreparedSpecification(\n// typename = Streamed,\n// selections = PreparedSelections{\n// PreparedDataField(\n// name = value,\n// alias = None,\n// cont = PreparedCont(\n// edges = Compose(\n// left = Compose(left = Lift(...), right = Lift(...)),\n// right = EmbedStream(signal = true)\n// ),\n// cont = Selection(\n// PreparedSpecification(\n// typename = Streamed,\n// selections = PreparedSelections{\n// PreparedDataField(\n// name = __typename,\n// alias = None,\n// cont = PreparedCont(\n// edges = Lift(...),\n// cont = PreparedLeaf(String)\n// )\n// )\n// }\n// )\n// )\n// )\n// )\n// }\n// )\n// )\n// ),\n// value = Right(repl.MdocSession$MdocApp$Streamed$1)\n// )\n// ),\n// ResourceInfo(\n// parentName = root.streamed.value (signal = true),\n// name = resource-1,\n// open = true,\n// value = StreamData(\n// cont = Continuation.Done(\n// Selection(\n// PreparedSpecification(\n// typename = Streamed,\n// selections = PreparedSelections{\n// PreparedDataField(\n// name = value,\n// alias = None,\n// cont = PreparedCont(\n// edges = Compose(\n// left = Compose(left = Lift(...), right = Lift(...)),\n// right = EmbedStream(signal = true)\n// ),\n// cont = Selection(\n// PreparedSpecification(\n// typename = Streamed,\n// selections = PreparedSelections{\n// PreparedDataField(\n// name = value,\n// alias = None,\n// cont = PreparedCont(\n// edges = Compose(\n// left = Compose(\n// left = Lift(...),\n// right = Lift(...)\n// ),\n// right = EmbedStream(signal = true)\n// ),\n// cont = Selection(\n// PreparedSpecification(\n// typename = Streamed,\n// selections = PreparedSelections{\n// PreparedDataField(\n// name = __typename,\n// alias = None,\n// cont = PreparedCont(\n// edges = Lift(...),\n// cont = PreparedLeaf(String)\n// )\n// )\n// }\n// )\n// )\n// )\n// )\n// }\n// )\n// )\n// )\n// )\n// }\n// )\n// )\n// ),\n// value = Right(repl.MdocSession$MdocApp$Streamed$1)\n// )\n// )\n// ]\n// unconsed after removing old children:\n// [\n// ResourceInfo(\n// parentName = root.streamed.value.value (signal = true),\n// name = resource-1,\n// open = true,\n// value = ditto\n// ),\n// ResourceInfo(\n// parentName = root.streamed.value (signal = true),\n// name = resource-1,\n// open = true,\n// value = ditto\n// )\n// ]\n// tree after unconsing:\n// |- unknown-cats.effect.kernel.Unique$Token@55855354\n// emitting 2 elements from uncons\n// interpreting for 2 inputs\n// allocating Streamed(2)\n// allocating Streamed(1)\n// emitting 0 for Streamed(2)\n// emitting 0 for Streamed(1)\n// publishing at index 0 at root.streamed.value.value.value\n// publishing at index 0 at root.streamed.value.value\n// emitting 2 for Streamed(2)\n// allocating Streamed(2)\n// publishing at index 2 at root.streamed.value.value.value\n// emitting 0 for Streamed(2)\n// done publishing at index 2 at root.streamed.value.value.value, await? true\n// publishing at index 0 at root.streamed.value.value.value\n// done interpreting\n// releasing Streamed(0)\n// releasing Streamed(2)\n// releasing Streamed(1)\n// releasing Streamed(2)\n// releasing Streamed(2)\n// releasing Streamed(1)\n")),(0,r.kt)("h2",{id:"steps"},"Steps"),(0,r.kt)("p",null,"A ",(0,r.kt)("inlineCode",{parentName:"p"},"Step")," is the low-level algebra for a resolver, that describes a single step of evaluation for a query.\nThe variants of ",(0,r.kt)("inlineCode",{parentName:"p"},"Step")," are clearly listed in the source code. All variants of step provide orthogonal properties."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4f169309.908663e8.js b/assets/js/4f169309.908663e8.js deleted file mode 100644 index 0f4425d03..000000000 --- a/assets/js/4f169309.908663e8.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[381],{3905:(e,n,t)=>{t.d(n,{Zo:()=>m,kt:()=>u});var a=t(7294);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=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=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},m=function(e){var n=p(e.components);return a.createElement(s.Provider,{value:n},e.children)},d={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,o=e.originalType,s=e.parentName,m=l(e,["components","mdxType","originalType","parentName"]),c=p(t),u=r,h=c["".concat(s,".").concat(u)]||c[u]||d[u]||o;return t?a.createElement(h,i(i({ref:n},m),{},{components:t})):a.createElement(h,i({ref:n},m))}));function u(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var o=t.length,i=new Array(o);i[0]=c;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l.mdxType="string"==typeof e?e:r,i[1]=l;for(var p=2;p{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>d,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var a=t(7462),r=(t(7294),t(3905));const o={title:"Resolvers"},i=void 0,l={unversionedId:"server/schema/resolvers",id:"server/schema/resolvers",title:"Resolvers",description:"Resolvers are at the core of gql; a resolver Resolver[F, I, O] takes an I and produces an O in effect F.",source:"@site/docs/server/schema/resolvers.md",sourceDirName:"server/schema",slug:"/server/schema/resolvers",permalink:"/gql/docs/server/schema/resolvers",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/schema/resolvers.md",tags:[],version:"current",frontMatter:{title:"Resolvers"},sidebar:"docs",previous:{title:"Monadic Resolver DSL",permalink:"/gql/docs/server/schema/arrow_dsl"},next:{title:"The schema",permalink:"/gql/docs/server/schema/"}},s={},p=[{value:"Resolvers",id:"resolvers",level:2},{value:"Lift",id:"lift",level:3},{value:"Effect",id:"effect",level:3},{value:"Arguments",id:"arguments",level:3},{value:"Meta",id:"meta",level:3},{value:"Errors",id:"errors",level:3},{value:"First",id:"first",level:3},{value:"Batch",id:"batch",level:3},{value:"Batch resolver syntax",id:"batch-resolver-syntax",level:4},{value:"Batchers from elsewhere",id:"batchers-from-elsewhere",level:4},{value:"Inline batch",id:"inline-batch",level:3},{value:"Choice",id:"choice",level:3},{value:"Stream",id:"stream",level:3},{value:"Stream semantics",id:"stream-semantics",level:4},{value:"Steps",id:"steps",level:2}],m={toc:p};function d(e){let{components:n,...t}=e;return(0,r.kt)("wrapper",(0,a.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Resolvers are at the core of gql; a resolver ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, O]")," takes an ",(0,r.kt)("inlineCode",{parentName:"p"},"I")," and produces an ",(0,r.kt)("inlineCode",{parentName:"p"},"O")," in effect ",(0,r.kt)("inlineCode",{parentName:"p"},"F"),".\nResolvers are embedded in fields and act as continuations.\nWhen gql executes a query it first constructs a tree of continueations from your schema and the supplied GraphQL query."),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Resolver"),"s act and compose like functions with combinators such as ",(0,r.kt)("inlineCode",{parentName:"p"},"andThen")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"compose"),"."),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," forms an ",(0,r.kt)("inlineCode",{parentName:"p"},"Arrow")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"Choice"),".")),(0,r.kt)("p",null,"Lets start off with some imports:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"import gql._\nimport gql.dsl.all._\nimport gql.resolver._\nimport gql.ast._\nimport cats.effect._\nimport cats.implicits._\nimport cats.data._\n")),(0,r.kt)("h2",{id:"resolvers"},"Resolvers"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," is a collection of high-level combinators that constructs a tree of ",(0,r.kt)("inlineCode",{parentName:"p"},"Step"),"."),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"If you are familiar with the relationship between ",(0,r.kt)("inlineCode",{parentName:"p"},"fs2.Stream")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"fs2.Pull"),", then the relationship between ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"Step")," should be familiar.")),(0,r.kt)("h3",{id:"lift"},"Lift"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Resolver.lift")," lifts a function ",(0,r.kt)("inlineCode",{parentName:"p"},"I => O")," into ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, O]"),".\n",(0,r.kt)("inlineCode",{parentName:"p"},"lift"),"'s method form is ",(0,r.kt)("inlineCode",{parentName:"p"},"map"),", which for any resolver ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, O]")," produces a new resolver ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, O2]")," given a function ",(0,r.kt)("inlineCode",{parentName:"p"},"O => O2"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"val r = Resolver.lift[IO, Int](_.toLong)\n// r: Resolver[IO, Int, Long] = gql.resolver.Resolver@611b3bad\nr.map(_.toString())\n// res0: Resolver[IO, Int, String] = gql.resolver.Resolver@a5a3800\n")),(0,r.kt)("h3",{id:"effect"},"Effect"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"effect")," like ",(0,r.kt)("inlineCode",{parentName:"p"},"lift")," lifts a function, but instead an effectful one like ",(0,r.kt)("inlineCode",{parentName:"p"},"I => F[O]")," into ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, O]"),".\n",(0,r.kt)("inlineCode",{parentName:"p"},"effect"),"'s method form is ",(0,r.kt)("inlineCode",{parentName:"p"},"evalMap")," (like ",(0,r.kt)("inlineCode",{parentName:"p"},"Resource")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"fs2.Stream"),")."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"val r = Resolver.effect[IO, Int](i => IO(i.toLong))\n// r: Resolver[IO, Int, Long] = gql.resolver.Resolver@e894a67\nr.evalMap(l => IO(l.toString()))\n// res1: Resolver[[x]IO[x], Int, String] = gql.resolver.Resolver@7b9b9593\n")),(0,r.kt)("h3",{id:"arguments"},"Arguments"),(0,r.kt)("p",null,"Arguments in gql are provided through resolvers.\nA resolver ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, A]")," can be constructed from an argument ",(0,r.kt)("inlineCode",{parentName:"p"},"Arg[A]"),", through either ",(0,r.kt)("inlineCode",{parentName:"p"},"argument")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"arg")," in method form."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'lazy val ageArg = arg[Int]("age")\nval r = Resolver.argument[IO, Nothing, String](arg[String]("name"))\n// r: Resolver[IO, Nothing, String] = gql.resolver.Resolver@43650f1e\nval r2 = r.arg(ageArg)\n// r2: Resolver[IO, Nothing, (Int, String)] = gql.resolver.Resolver@1824a54a\nr2.map{ case (age, name) => s"$name is $age years old" }\n// res2: Resolver[IO, Nothing, String] = gql.resolver.Resolver@5cba1e5b\n')),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Arg")," also has an applicative defined for it, so multi-argument resolution can be simplified to."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'val r = Resolver.argument[IO, Nothing, (String, Int)](\n (arg[String]("name"), arg[Int]("age")).tupled\n)\n// r: Resolver[IO, Nothing, (String, Int)] = gql.resolver.Resolver@7b2a3f52\nr.map{ case (age, name) => s"$name is $age years old" }\n// res3: Resolver[IO, Nothing, String] = gql.resolver.Resolver@105cd2d6\n')),(0,r.kt)("h3",{id:"meta"},"Meta"),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"meta")," resolver provides metadata regarding query execution, such as the position of query execution, field aliasing and the provided arguments."),(0,r.kt)("p",null,"It also allows the caller to inspect the query ast such that more exotic operations become possible.\nFor instance, arguments can dynamically be inspected."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'lazy val a = arg[Int]("age")\nResolver.meta[IO, String].map(meta => meta.astNode.arg(a))\n// res4: Resolver[IO, String, Option[Int]] = gql.resolver.Resolver@2def2b62\n')),(0,r.kt)("p",null,"The ",(0,r.kt)("a",{parentName:"p",href:"/gql/docs/server/integrations/relational"},"relational")," integration makes heavy use of this feature."),(0,r.kt)("h3",{id:"errors"},"Errors"),(0,r.kt)("p",null,"Errors are reported in ",(0,r.kt)("inlineCode",{parentName:"p"},"cats.data.Ior"),"."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"An ",(0,r.kt)("inlineCode",{parentName:"p"},"Ior")," is a non-exclusive ",(0,r.kt)("inlineCode",{parentName:"p"},"Either"),".")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"Ior")," datatype's left side must be ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," and acts as an optional error that will be present in the query result.\ngql can return an error and a result for the same path, given that ",(0,r.kt)("inlineCode",{parentName:"p"},"Ior")," has both it's left and right side defined."),(0,r.kt)("p",null,"Errors are embedded into resolvers via ",(0,r.kt)("inlineCode",{parentName:"p"},"rethrow"),".\nThe extension method ",(0,r.kt)("inlineCode",{parentName:"p"},"rethrow")," is present on any resolver of type ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, Ior[String, O]]"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'val r = Resolver.lift[IO, Int](i => Ior.Both("I will be in the errors :)", i))\n// r: Resolver[IO, Int, Ior.Both[String, Int]] = gql.resolver.Resolver@21abb042\nr.rethrow\n// res5: Resolver[[A]IO[A], Int, Int] = gql.resolver.Resolver@b51db8a\n')),(0,r.kt)("p",null,"We can also use ",(0,r.kt)("inlineCode",{parentName:"p"},"emap")," to map the current value into an ",(0,r.kt)("inlineCode",{parentName:"p"},"Ior"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'val r = Resolver.id[IO, Int].emap(i => Ior.Both("I will be in the errors :)", i))\n// r: Resolver[IO, Int, Int] = gql.resolver.Resolver@7d6d6254\n')),(0,r.kt)("h3",{id:"first"},"First"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," also implements ",(0,r.kt)("inlineCode",{parentName:"p"},"first")," (",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, A, B] => Resolver[F, (A, C), (B, C)]"),") which can be convinient for situations where one would usually have to trace a value through an entire computation."),(0,r.kt)("p",null,"Since a ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," does not form a ",(0,r.kt)("inlineCode",{parentName:"p"},"Monad"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"first")," is necessary to implement non-trivial resolver compositions."),(0,r.kt)("p",null,"For instance, maybe your program contains a general resolver compositon that is used many places, like say verifying credentials, but you'd like to trace a value through it without having to keep track of tupling output with input."),(0,r.kt)("p",null,"Assume we'd like to implement a resolver, that when given a person's name, can get a list of the person's friends."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'case class PersonId(value: Int)\n\ncase class Person(id: PersonId, name: String)\n\ndef getFriends(id: PersonId, limit: Int): IO[List[Person]] = ???\n\ndef getPerson(name: String): IO[Person] = ???\n\ndef getPersonResolver = Resolver.effect[IO, String](getPerson)\n\ndef limitResolver = Resolver.argument[IO, Person, Int](arg[Int]("limit"))\n\ndef limitArg = arg[Int]("limit")\ngetPersonResolver\n // \'arg\' tuples the input with the argument value\n .arg(limitArg)\n .evalMap{ case (limit, p) => getFriends(p.id, limit) }\n// res6: Resolver[[x]IO[x], String, List[Person]] = gql.resolver.Resolver@290de2a0\n')),(0,r.kt)("h3",{id:"batch"},"Batch"),(0,r.kt)("p",null,"Like most other GraphQL implementations, gql also supports batching."),(0,r.kt)("p",null,"Unlike most other GraphQL implementations, gql's batching implementation features a global query planner that lets gql delay field execution until it can be paired with another field."),(0,r.kt)("p",null,"Batch declaration and usage occurs as follows:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Declare a function ",(0,r.kt)("inlineCode",{parentName:"li"},"Set[K] => F[Map[K, V]]"),"."),(0,r.kt)("li",{parentName:"ul"},"Give this function to gql and get back a ",(0,r.kt)("inlineCode",{parentName:"li"},"Resolver[F, Set[K], Map[K, V]]")," in a ",(0,r.kt)("inlineCode",{parentName:"li"},"State")," monad (for unique id generation)."),(0,r.kt)("li",{parentName:"ul"},"Use this new resolver where you want batching.")),(0,r.kt)("p",null,"And now put into practice:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"def getPeopleFromDB(ids: Set[PersonId]): IO[List[Person]] = ???\n\nResolver.batch[IO, PersonId, Person]{ keys => \n getPeopleFromDB(keys).map(_.map(x => x.id -> x).toMap)\n}\n// res7: State[SchemaState[IO], Resolver[IO, Set[PersonId], Map[PersonId, Person]]] = cats.data.IndexedStateT@55d87e63\n")),(0,r.kt)("p",null,"Whenever gql sees this resolver in any composition, it will look for similar resolvers during query planning."),(0,r.kt)("p",null,"Note, however, that you should only declare each batch resolver variant ",(0,r.kt)("strong",{parentName:"p"},"once"),", that is, you should build your schema in ",(0,r.kt)("inlineCode",{parentName:"p"},"State"),".\ngql consideres different batch instantiations incompatible regardless of any type information."),(0,r.kt)("p",null,"State has ",(0,r.kt)("inlineCode",{parentName:"p"},"Monad")," (and transitively ",(0,r.kt)("inlineCode",{parentName:"p"},"Applicative"),") defined for it, so it composes well.\nHere is an example of multiple batchers:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"def b1 = Resolver.batch[IO, Int, Person](_ => ???)\ndef b2 = Resolver.batch[IO, Int, String](_ => ???)\n\n(b1, b2).tupled\n// res8: State[SchemaState[IO], (Resolver[IO, Set[Int], Map[Int, Person]], Resolver[IO, Set[Int], Map[Int, String]])] = cats.data.IndexedStateT@505072d9\n")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"Even if your field doesn't benefit from batching, batching can still do duplicate key elimination.")),(0,r.kt)("h4",{id:"batch-resolver-syntax"},"Batch resolver syntax"),(0,r.kt)("p",null,"When a resolver in a very specific form ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, Set[K], Map[K, V]]"),", then the gql dsl provides some helper methods.\nFor instance, a batcher may be embedded in a singular context (",(0,r.kt)("inlineCode",{parentName:"p"},"K => V"),").\nHere is a showcase of some of the helper methods:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'def pb: Resolver[IO, Set[Int], Map[Int, Person]] = \n // Stub implementation\n Resolver.lift(_ => Map.empty)\n\n// None if a key is missing\npb.all[List]\n// res9: Resolver[[A]IO[A], List[Int], List[Option[Person]]] = gql.resolver.Resolver@4639c545\n\n// Every key must have an associated value\n// or else raise an error via a custom show-like typeclass\nimplicit lazy val showMissingPersonId =\n ShowMissingKeys.showForKey[Int]("not all people could be found")\npb.traversable[List]\n// res10: Resolver[[A]IO[A], List[Int], List[Person]] = gql.resolver.Resolver@e737c0\n\n// Maybe there is one value for one key?\npb.opt\n// res11: Resolver[[A]IO[A], Int, Option[Person]] = gql.resolver.Resolver@165a6ad9\n\n// Same as opt\npb.all[cats.Id]\n// res12: Resolver[[A]IO[A], cats.package.Id[Int], cats.package.Id[Option[Person]]] = gql.resolver.Resolver@454f1471\n\n// There is always one value for one key\npb.one\n// res13: Resolver[[A]IO[A], Int, Person] = gql.resolver.Resolver@4dd4f270\n\n// You can be more explicit via the `batch` method\npb.batch.all[NonEmptyList]\n// res14: Resolver[[A]IO[A], NonEmptyList[Int], NonEmptyList[Option[Person]]] = gql.resolver.Resolver@7434953d\n')),(0,r.kt)("p",null,"Using ",(0,r.kt)("inlineCode",{parentName:"p"},"batch")," aids with better compiler error messages."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"Resolver.lift[IO, Int](_.toString()).batch.all\n// error: Cannot prove that Set[K] =:= Int.\n// Resolver.lift[IO, Int](_.toString()).batch.all\n// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"For larger programs, consider declaring all your batchers up-front and putting them into some type of collection:"),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"case class MyBatchers(\n personBatcher: Resolver[IO, Set[Int], Map[Int, Person]],\n intStringBatcher: Resolver[IO, Set[Int], Map[Int, String]]\n)\n\n(b1, b2).mapN(MyBatchers.apply)\n// res16: State[SchemaState[IO], MyBatchers] = cats.data.IndexedStateT@4758e200\n")),(0,r.kt)("p",{parentName:"admonition"},"For most batchers it is likely that you eventually want to pre-compose them in various ways, for instance requsting args, which this pattern promotes.")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"Sometimes you have multiple groups of fields in the same object where each group have different performance overheads."),(0,r.kt)("p",{parentName:"admonition"},"Say you had a ",(0,r.kt)("inlineCode",{parentName:"p"},"Person")," object in your database.\nThis ",(0,r.kt)("inlineCode",{parentName:"p"},"Person")," object also exists in a remote api.\nThis remote api can tell you, the friends of a ",(0,r.kt)("inlineCode",{parentName:"p"},"Person")," given the object's id and name.\nWritten out a bit more structured we have that:"),(0,r.kt)("ul",{parentName:"admonition"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"PersonId => PersonId")," (identity)"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"PersonId => PersonDB")," (database query)"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"PersonDB => PersonRemoteAPI")," (remote api call)"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"PersonId => PersonRemoteAPI")," (composition of database query and remote api call)")),(0,r.kt)("p",{parentName:"admonition"},"And now put into code:"),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'// We have a trivial id field for our person id\ndef pureFields = fields[IO, PersonId](\n "id" -> lift(id => id)\n)\n\n// If we query our database with a person id, we get a person database object\ncase class PersonDB(\n id: PersonId, \n name: String, \n remoteApiId: String\n)\n\n// SELECT id, name, remote_api_id FROM person WHERE id in (...)\ndef dbBatchResolver: Resolver[IO, PersonId, PersonDB] = ???\n\n// From the db we can get the name and the remote api id\ndef dbFields = fields[IO, PersonDB](\n "name" -> lift(_.name),\n "apiId" -> lift(_.remoteApiId)\n)\n\n// The remote api data can be found given the result of a db query\ncase class PersonRemoteAPI(\n id: PersonId, \n friends: List[PersonId]\n)\n\n// Given a PersonDB we can call the api (via a batched GET or something)\ndef personBatchResolver: Resolver[IO, PersonDB, PersonRemoteAPI] = ???\n\n// We can get the friends from the remote api\ndef remoteApiFields = fields[IO, PersonRemoteAPI](\n "friends" -> lift(_.friends)\n)\n\n// Now we can start composing our fields\n// We can align the types of the db and remote api data to the PersonDB type\n// by composing the remote api resolver on the remote api fields\ndef dbFields2: Fields[IO, PersonDB] = \n remoteApiFields.compose(personBatchResolver) ::: dbFields\n\n// Given a PersonId we have every field\n// If "friends" is selected, gql will first run `dbBatchResolver` and then `personBatchResolver`\ndef allFields = dbFields2.compose(dbBatchResolver) ::: pureFields\n\nimplicit def person: Type[IO, PersonId] = tpeNel[IO, PersonId](\n "Person",\n allFields\n)\n')),(0,r.kt)("p",{parentName:"admonition"},"The general pattern for this decomposition revolves around figuring out what the most basic description of your object is.\nIn this example, every fields can (eventually through various side-effects) be resolved from just ",(0,r.kt)("inlineCode",{parentName:"p"},"PersonId"),".")),(0,r.kt)("h4",{id:"batchers-from-elsewhere"},"Batchers from elsewhere"),(0,r.kt)("p",null,"Most batching implementations have compatible signatures and can be adapted into a gql batcher."),(0,r.kt)("p",null,"For instance, converting ",(0,r.kt)("inlineCode",{parentName:"p"},"fetch")," to gql:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'import fetch._\nobject People extends Data[PersonId, Person] {\n def name = "People"\n\n def source: DataSource[IO, PersonId, Person] = ???\n}\n\nResolver\n .batch[IO, PersonId, Person](_.toList.toNel.traverse(People.source.batch).map(_.getOrElse(Map.empty)))\n// res17: State[SchemaState[IO], Resolver[IO, Set[PersonId], Map[PersonId, Person]]] = cats.data.IndexedStateT@320de1d6\n')),(0,r.kt)("h3",{id:"inline-batch"},"Inline batch"),(0,r.kt)("p",null,"A batch resolver can also be defined inline with some notable differences to the regular batch resolver:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"It does not need to be defined in state."),(0,r.kt)("li",{parentName:"ul"},"It is not subject to global query planning, and is only ever called with inputs from the same selection.")),(0,r.kt)("p",null,"The inline batch resolver has the same signature as a regular batch resolver; ",(0,r.kt)("inlineCode",{parentName:"p"},"Set[K] => F[Map[K, V]]"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"Resolver.inlineBatch[IO, PersonId, Person](\n _.toList.toNel.traverse(People.source.batch).map(_.getOrElse(Map.empty))\n)\n// res18: Resolver[IO, Set[PersonId], Map[PersonId, Person]] = gql.resolver.Resolver@3a091cb8\n")),(0,r.kt)("h3",{id:"choice"},"Choice"),(0,r.kt)("p",null,"Resolvers also implement ",(0,r.kt)("inlineCode",{parentName:"p"},"Choice")," via ",(0,r.kt)("inlineCode",{parentName:"p"},"(Resolver[F, A, C], Resolver[F, B, D]) => Resolver[F, Either[A, B], Either[C, D]]"),".\nOn the surface, this combinator may have limited uses, but with a bit of composition we can perform tasks such as caching."),(0,r.kt)("p",null,"For instance, a combinator derived from ",(0,r.kt)("inlineCode",{parentName:"p"},"Choice")," is ",(0,r.kt)("inlineCode",{parentName:"p"},"skippable: Resolver[F, I, O] => Resolver[F, Either[I, O], O]"),', which acts as a variant of "caching".\nIf the right side is present we skip the underlying resolver (',(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, O]"),") altogether."),(0,r.kt)("p",null,"For any resolver in the form ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver[F, I, Either[L, R]]")," we modify the left side with ",(0,r.kt)("inlineCode",{parentName:"p"},"leftThrough")," and the right with ",(0,r.kt)("inlineCode",{parentName:"p"},"rightThrough"),"."),(0,r.kt)("p",null,"For Instance we can implement caching."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'def getPersonForId(id: PersonId): IO[Person] = ???\n\ntype CachedPerson = Either[PersonId, Person]\ndef cachedPerson = tpe[IO, CachedPerson](\n "Person",\n "id" -> lift(_.map(_.id).merge.value),\n // We\'ll align the left and right side of the choice and then merge the `Either`\n "name" -> build[IO, CachedPerson](_.leftThrough(_.evalMap(getPersonForId)).map(_.merge.name))\n)\n')),(0,r.kt)("p",null,"We can also use some of the ",(0,r.kt)("inlineCode",{parentName:"p"},"compose")," tricks from the ",(0,r.kt)("a",{parentName:"p",href:"#batch-resolver-syntax"},"batch resolver syntax section")," if we have a lot of fields that depend on ",(0,r.kt)("inlineCode",{parentName:"p"},"Person"),". "),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"The query planner treats the choice branches as parallel, such that for two instances of a choice, resolvers in the two branches may be batched together.")),(0,r.kt)("h3",{id:"stream"},"Stream"),(0,r.kt)("p",null,"The stream resolver embeds an ",(0,r.kt)("inlineCode",{parentName:"p"},"fs2.Stream")," and provides the ability to emit a stream of results for a graphql subscription."),(0,r.kt)("h4",{id:"stream-semantics"},"Stream semantics"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"When one or more streams emit, the interpreter will re-evaluate the query from the position that emitted.\nThat is, only the sub-tree that changed will be re-interpreted."),(0,r.kt)("li",{parentName:"ul"},"If two streams emit and one occurs as a child of the other, the child will be ignored since it will be replaced."),(0,r.kt)("li",{parentName:"ul"},"By default, the interpreter will only respect the most-recent emitted data.")),(0,r.kt)("p",null,"This means that by default, gql assumes that your stream should behave like a signal, not sequentially.\nHowever, gql can also adhere sequential semantics."),(0,r.kt)("p",null,"For instance a schema designed like the following, emits incremental updates regarding the price for some symbol:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-graphql"},"type PriceChange {\n difference: Float!\n}\n\ntype Subscription {\n priceChanges(symbolId: ID!): PriceChange!\n}\n")),(0,r.kt)("p",null,"And here is a schema that represents an api that emits updates regarding the current price of a symbol:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-graphql"},"type SymbolState {\n price: Float!\n}\n\ntype Subscription {\n price(symbolId: ID!): SymbolState!\n}\n")),(0,r.kt)("p",null,"Consider the following example where two different evaluation semantics are displayed:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"case class PriceChange(difference: Float)\ndef priceChanges(symbolId: String): fs2.Stream[IO, PriceChange] = ???\n\ncase class SymbolState(price: Float)\ndef price(symbolId: String): fs2.Stream[IO, SymbolState] = ???\n\ndef priceChangesResolver = Resolver.id[IO, String].sequentialStreamMap(priceChanges)\n\ndef priceResolver = Resolver.id[IO, String].streamMap(price)\n")),(0,r.kt)("p",null,"If your stream is sequential, gql will only pull elements when they are needed."),(0,r.kt)("p",null,"The interpreter performs a global re-interpretation of your schema, when one or more streams emit.\nThat is, the interpreter cycles through the following two phases:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Interpret for the current values."),(0,r.kt)("li",{parentName:"ul"},"Await new values (and values that arrived during the previous step).")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"Since gql is free to ignore updates when a stream is a signal, one should prefer ",(0,r.kt)("inlineCode",{parentName:"p"},"evalMap")," on a ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," instead of a stream if possible.")),(0,r.kt)("admonition",{type:"warning"},(0,r.kt)("p",{parentName:"admonition"},"For a given stream it must hold all child resources alive (maybe the child resources are also streams that may emit).\nAs such, for a given stream, gql must await a next element from the stream before releasing any currently held resources sub-tree.\nThis means that gql must be able to pull one element before closing the old one.")),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"If you have streams of updates where you are only interested in that something changed (",(0,r.kt)("inlineCode",{parentName:"p"},"Stream[F, Unit]"),") there may be room for significant optimization.\nIn ",(0,r.kt)("inlineCode",{parentName:"p"},"fs2")," you can merge streams with combinators such as ",(0,r.kt)("inlineCode",{parentName:"p"},"parJoin"),", but they have to assume that there may be resources to account for.\nIf you are discarding the output of the stream or you are absolutely sure that the output does not depend on a resource lifetime,\none can write more optimized versions functions for this purpose."),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Some examples of potentially more performant implementations"),(0,r.kt)("p",{parentName:"admonition"},"In a crude benchmarks, these combinators may perform an order of magnitude faster than ",(0,r.kt)("inlineCode",{parentName:"p"},"parJoin")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"merge"),"."),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"import fs2.{Pipe, Stream}\nimport fs2.concurrent._\ndef parListen[A]: Pipe[IO, Stream[IO, A], Unit] =\n streams =>\n for {\n d <- Stream.eval(IO.deferred[Either[Throwable, Unit]])\n c <- Stream.eval(IO.deferred[Unit])\n sigRef <- Stream.eval(SignallingRef[IO, Unit](()))\n\n bg = streams.flatMap { sub =>\n Stream.supervise {\n sub\n .evalMap(_ => sigRef.set(()))\n .compile\n .drain\n .onError(e => d.complete(Left(e)).void)\n .onCancel(c.complete(()).void)\n }.void\n }\n\n listenCancel = (c.get *> IO.canceled).as(Right(()): Either[Throwable, Unit])\n fg = sigRef.discrete.interruptWhen(d).interruptWhen(listenCancel)\n\n _ <- fg.concurrently(bg)\n } yield ()\n\ndef parListenSignal[A]: Pipe[IO, Stream[IO, A], A] =\n streams =>\n Stream.eval(SignallingRef.of[IO, Option[A]](None)).flatMap { sig =>\n sig.discrete.unNone.concurrently {\n streams.parEvalMapUnorderedUnbounded { x =>\n x.evalMap(x => sig.set(Some(x))).compile.drain\n }\n }\n }\n")))),(0,r.kt)("p",null,"Here is an example of some streams in action:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'import scala.concurrent.duration._\nimport cats.effect.unsafe.implicits.global\n\ncase class Streamed(value: Int)\n\nimplicit lazy val streamed: Type[IO, Streamed] = tpe[IO, Streamed](\n "Streamed",\n "value" -> build[IO, Streamed](_.streamMap{ s =>\n fs2.Stream\n .bracket(IO(println(s"allocating $s")))(_ => IO(println(s"releasing $s"))) >>\n fs2.Stream\n .iterate(0)(_ + 1)\n .evalTap(n => IO(println(s"emitting $n for $s")))\n .meteredStartImmediately(((5 - s.value) * 20).millis)\n .as(Streamed(s.value + 1))\n })\n)\n\ndef query = """\n subscription {\n streamed {\n value {\n value { \n value {\n __typename\n }\n }\n }\n }\n }\n"""\n\ndef schema = SchemaShape.unit[IO](\n fields("ping" -> lift(_ => "pong")),\n subscription = Some(fields("streamed" -> lift(_ => Streamed(0))))\n)\n\nSchema.simple(schema)\n .map(Compiler[IO].compile(_, query))\n .flatMap { case Right(Application.Subscription(stream)) => stream.take(4).compile.drain }\n .unsafeRunSync()\n// allocating Streamed(0)\n// emitting 0 for Streamed(0)\n// allocating Streamed(1)\n// emitting 0 for Streamed(1)\n// allocating Streamed(2)\n// emitting 0 for Streamed(2)\n// emitting 1 for Streamed(2)\n// emitting 1 for Streamed(1)\n// emitting 1 for Streamed(0)\n// allocating Streamed(2)\n// allocating Streamed(1)\n// emitting 0 for Streamed(2)\n// emitting 0 for Streamed(1)\n// allocating Streamed(2)\n// emitting 0 for Streamed(2)\n// emitting 2 for Streamed(2)\n// releasing Streamed(0)\n// emitting 2 for Streamed(1)\n// releasing Streamed(1)\n// releasing Streamed(2)\n// releasing Streamed(2)\n// releasing Streamed(2)\n// releasing Streamed(1)\n')),(0,r.kt)("p",null,"gql also allows the user to specify how much time the interpreter may await more stream updates:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"Schema.simple(schema).map(Compiler[IO].compile(_, query, accumulate=Some(10.millis)))\n")),(0,r.kt)("p",null,"furthermore, gql can also emit interpreter information if you want to look into what gql is doing:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"Schema.simple(schema)\n .map(Compiler[IO].compile(_, query, debug=gql.server.interpreter.DebugPrinter[IO](s => IO(println(s)))))\n .flatMap { case Right(Application.Subscription(stream)) => stream.take(3).compile.drain }\n .unsafeRunSync()\n// allocating Streamed(0)\n// emitting 0 for Streamed(0)\n// publishing at index 0 at root.streamed.value\n// allocating Streamed(1)\n// emitting 0 for Streamed(1)\n// publishing at index 0 at root.streamed.value.value\n// allocating Streamed(2)\n// emitting 0 for Streamed(2)\n// publishing at index 0 at root.streamed.value.value.value\n// unconsing with current tree:\n// |- unknown-cats.effect.kernel.Unique$Token@58c2bcde\n// got state, awaiting a non-empty state (publication)\n// emitting 1 for Streamed(2)\n// publishing at index 1 at root.streamed.value.value.value\n// done publishing at index 1 at root.streamed.value.value.value, await? true\n// got non-empty state, awaiting 5 milliseconds\n// unconsed:\n// [\n// ResourceInfo(\n// parentName = root.streamed.value.value.value (signal = true),\n// name = resource-1,\n// open = true,\n// value = StreamData(\n// cont = Continuation.Done(\n// Selection(\n// PreparedSpecification(\n// typename = Streamed,\n// selections = PreparedSelections{\n// PreparedDataField(\n// name = __typename,\n// alias = None,\n// cont = PreparedCont(\n// edges = Lift(...),\n// cont = PreparedLeaf(String)\n// )\n// )\n// }\n// )\n// )\n// ),\n// value = Right(repl.MdocSession$MdocApp$Streamed$1)\n// )\n// )\n// ]\n// emitting 1 for Streamed(1)\n// unconsed after removing old children:\n// [\n// ResourceInfo(\n// parentName = root.streamed.value.value.value (signal = true),\n// name = resource-1,\n// open = true,\n// value = ditto\n// )\n// ]\n// publishing at index 1 at root.streamed.value.value\n// done publishing at index 1 at root.streamed.value.value, await? true\n// tree after unconsing:\n// |- unknown-cats.effect.kernel.Unique$Token@58c2bcde\n// emitting 1 elements from uncons\n// interpreting for 1 inputs\n// done interpreting\n// unconsing with current tree:\n// |- unknown-cats.effect.kernel.Unique$Token@58c2bcde\n// got state, awaiting a non-empty state (publication)\n// got non-empty state, awaiting 5 milliseconds\n// emitting 1 for Streamed(0)\n// publishing at index 1 at root.streamed.value\n// done publishing at index 1 at root.streamed.value, await? true\n// unconsed:\n// [\n// ResourceInfo(\n// parentName = root.streamed.value.value (signal = true),\n// name = resource-1,\n// open = true,\n// value = StreamData(\n// cont = Continuation.Done(\n// Selection(\n// PreparedSpecification(\n// typename = Streamed,\n// selections = PreparedSelections{\n// PreparedDataField(\n// name = value,\n// alias = None,\n// cont = PreparedCont(\n// edges = Compose(\n// left = Compose(left = Lift(...), right = Lift(...)),\n// right = EmbedStream(signal = true)\n// ),\n// cont = Selection(\n// PreparedSpecification(\n// typename = Streamed,\n// selections = PreparedSelections{\n// PreparedDataField(\n// name = __typename,\n// alias = None,\n// cont = PreparedCont(\n// edges = Lift(...),\n// cont = PreparedLeaf(String)\n// )\n// )\n// }\n// )\n// )\n// )\n// )\n// }\n// )\n// )\n// ),\n// value = Right(repl.MdocSession$MdocApp$Streamed$1)\n// )\n// )\n// ]\n// unconsed after removing old children:\n// [\n// ResourceInfo(\n// parentName = root.streamed.value.value (signal = true),\n// name = resource-1,\n// open = true,\n// value = ditto\n// )\n// ]\n// tree after unconsing:\n// |- unknown-cats.effect.kernel.Unique$Token@58c2bcde\n// emitting 1 elements from uncons\n// interpreting for 1 inputs\n// allocating Streamed(2)\n// emitting 0 for Streamed(2)\n// publishing at index 0 at root.streamed.value.value.value\n// done interpreting\n// releasing Streamed(0)\n// releasing Streamed(2)\n// releasing Streamed(1)\n// releasing Streamed(2)\n")),(0,r.kt)("h2",{id:"steps"},"Steps"),(0,r.kt)("p",null,"A ",(0,r.kt)("inlineCode",{parentName:"p"},"Step")," is the low-level algebra for a resolver, that describes a single step of evaluation for a query.\nThe variants of ",(0,r.kt)("inlineCode",{parentName:"p"},"Step")," are clearly listed in the source code. All variants of step provide orthogonal properties."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/62af8b26.8f2a4663.js b/assets/js/62af8b26.3d4d563c.js similarity index 58% rename from assets/js/62af8b26.8f2a4663.js rename to assets/js/62af8b26.3d4d563c.js index ee3728b86..2a38a854e 100644 --- a/assets/js/62af8b26.8f2a4663.js +++ b/assets/js/62af8b26.3d4d563c.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[508],{3905:(e,n,a)=>{a.d(n,{Zo:()=>c,kt:()=>m});var t=a(7294);function o(e,n,a){return n in e?Object.defineProperty(e,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[n]=a,e}function l(e,n){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(e);n&&(t=t.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),a.push.apply(a,t)}return a}function i(e){for(var n=1;n=0||(o[a]=e[a]);return o}(e,n);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(o[a]=e[a])}return o}var s=t.createContext({}),p=function(e){var n=t.useContext(s),a=n;return e&&(a="function"==typeof e?e(n):i(i({},n),e)),a},c=function(e){var n=p(e.components);return t.createElement(s.Provider,{value:n},e.children)},u={inlineCode:"code",wrapper:function(e){var n=e.children;return t.createElement(t.Fragment,{},n)}},d=t.forwardRef((function(e,n){var a=e.components,o=e.mdxType,l=e.originalType,s=e.parentName,c=r(e,["components","mdxType","originalType","parentName"]),d=p(a),m=o,g=d["".concat(s,".").concat(m)]||d[m]||u[m]||l;return a?t.createElement(g,i(i({ref:n},c),{},{components:a})):t.createElement(g,i({ref:n},c))}));function m(e,n){var a=arguments,o=n&&n.mdxType;if("string"==typeof e||o){var l=a.length,i=new Array(l);i[0]=d;var r={};for(var s in n)hasOwnProperty.call(n,s)&&(r[s]=n[s]);r.originalType=e,r.mdxType="string"==typeof e?e:o,i[1]=r;for(var p=2;p{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>l,metadata:()=>r,toc:()=>p});var t=a(7462),o=(a(7294),a(3905));const l={title:"Relational"},i=void 0,r={unversionedId:"server/integrations/relational",id:"server/integrations/relational",title:"Relational",description:"This integration is fairly new and sofisticated so it can be subject to change.",source:"@site/docs/server/integrations/relational.md",sourceDirName:"server/integrations",slug:"/server/integrations/relational",permalink:"/gql/docs/server/integrations/relational",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/integrations/relational.md",tags:[],version:"current",frontMatter:{title:"Relational"},sidebar:"docs",previous:{title:"Global object identification",permalink:"/gql/docs/server/integrations/goi"},next:{title:"Query DSL",permalink:"/gql/docs/client/dsl"}},s={},p=[{value:"Skunk example",id:"skunk-example",level:2},{value:"Simplifying relationships",id:"simplifying-relationships",level:3},{value:"Runtime semantics",id:"runtime-semantics",level:2},{value:"Implementing your own integration",id:"implementing-your-own-integration",level:2},{value:"Adding arguments",id:"adding-arguments",level:2},{value:"Sum types",id:"sum-types",level:2},{value:"Declaring complex subqueries",id:"declaring-complex-subqueries",level:2},{value:"Using relational without tables",id:"using-relational-without-tables",level:2},{value:"Running transactions",id:"running-transactions",level:2},{value:"Handling N+1",id:"handling-n1",level:2}],c={toc:p};function u(e){let{components:n,...a}=e;return(0,o.kt)("wrapper",(0,t.Z)({},c,a,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("admonition",{type:"caution"},(0,o.kt)("p",{parentName:"admonition"},"This integration is fairly new and sofisticated so it can be subject to change.")),(0,o.kt)("p",null,"gql also comes with an optional integration for relational databases."),(0,o.kt)("p",null,"The relational integration is library agnostic and is based on query fragments that can be composed into a full query."),(0,o.kt)("p",null,"The relational module ships with two implementations, one for ",(0,o.kt)("inlineCode",{parentName:"p"},"skunk")," and another for ",(0,o.kt)("inlineCode",{parentName:"p"},"doobie"),".\nThey can be found in the ",(0,o.kt)("a",{parentName:"p",href:"../../overview/modules"},"modules")," section."),(0,o.kt)("admonition",{type:"tip"},(0,o.kt)("p",{parentName:"admonition"},"Integrating a new library requires very little code.\nThe skunk integration only spans 18 lines of code.")),(0,o.kt)("h2",{id:"skunk-example"},"Skunk example"),(0,o.kt)("p",null,"For this example we will use ",(0,o.kt)("inlineCode",{parentName:"p"},"skunk"),".\nWe will start off with some imports."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},"import skunk._\nimport skunk.codec.all._\nimport skunk.implicits._\nimport gql.ast._\nimport gql.dsl.all._\nimport gql.relational._\nimport gql.relational.skunk.dsl._\nimport gql.relational.skunk.dsl.algebra.QueryContext\nimport cats._\nimport cats.data._\nimport cats.arrow._\nimport cats.effect._\nimport cats.implicits._\n")),(0,o.kt)("p",null,"Before we start declaring fragments, we need to define our domain."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},"final case class Home(name: String, address: String)\n// many homes belong to many people\nfinal case class Person(name: String, age: Int)\n// a pet has one owner\nfinal case class Pet(name: String, age: Int, owner: Int)\n")),(0,o.kt)("p",null,"The realtional module also ships with a dsl that makes declaration use conscise.\nWe will start off just declaring the home table."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'case class HomeTable(\n // When a table is queried it must have an alias\n alias: String\n) extends SkunkTable {\n // Note that we use only skunk tools to declare the contents of this structure\n\n // We can declare how this table is referenced in sql (or some other query language)\n def table = void"home"\n\n // The SkunkTable trait gives some convinience methods for declaring columns\n val (idCol, id) = sel("id", int4)\n val (nameCol, name) = sel("name", text)\n val (addressCol, address) = sel("address", text)\n\n // The projection that uniquely identifies a row in the table\n def tableKey = id\n}\n// We get some methods if show how given an alias we can get a table\nval homeTable = skunkTable(HomeTable)\n')),(0,o.kt)("p",null,"We will also need to declare the other two tables, this time with less comments."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'case class PersonTable(alias: String) extends SkunkTable {\n def table = void"person"\n\n val (idCol, id) = sel("id", int4)\n val (nameCol, name) = sel("name", text)\n val (ageCol, age) = sel("age", int4)\n\n def tableKey = id\n}\nval personTable = skunkTable(PersonTable)\n\ncase class PetTable(alias: String) extends SkunkTable {\n def table = void"pet"\n\n val (idCol, id) = sel("id", int4)\n val (nameCol, name) = sel("name", text)\n val (ageCol, age) = sel("age", int4)\n val (ownerCol, owner) = sel("owner", int4)\n\n def tableKey = id\n}\nval petTable = skunkTable(PetTable)\n')),(0,o.kt)("p",null,"Since ",(0,o.kt)("inlineCode",{parentName:"p"},"Home")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"Person")," have a many to many relationship, we will have to go through another table table to get the relationship."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'case class HomePersonTable(alias: String) extends SkunkTable {\n def table = void"home_person"\n\n val (homeCol, home) = sel("home_id", int4)\n val (personCol, person) = sel("person_id", int4)\n\n def tableKey = (home, person).tupled\n}\nval homePersonTable = skunkTable(HomePersonTable)\n')),(0,o.kt)("p",null,"Now we can start declaring our graphql schema."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'implicit lazy val pet: Type[IO, QueryContext[PetTable]] = \n tpe[IO, QueryContext[PetTable]](\n "PetTable",\n "name" -> query(_.name), // query is a method that compiles to a projection in the query language (sql)\n "age" -> query(_.age)\n )\n\nimplicit lazy val person: Type[IO, QueryContext[PersonTable]] = \n tpe[IO, QueryContext[PersonTable]](\n "PersonTable",\n "name" -> query(_.name),\n "age" -> query(_.age),\n "pets" -> cont{ person => // cont is a continuation that will create a new table from the current one\n // The join method takes a type parameter that declares the multiplicity of the join\n // If no type parameter is given, the join is assumed to be one to one\n petTable.join[List]{ pet =>\n // Given an instance of the pet table, we can declare a join predicate\n sql"${pet.ownerCol} = ${person.idCol}"\n }\n }\n )\n\nimplicit lazy val home: Type[IO, QueryContext[HomeTable]] = \n tpe[IO, QueryContext[HomeTable]](\n "HomeTable",\n "name" -> query(_.name),\n "address" -> query(_.address),\n "caption" -> query(h => (h.name, h.address).mapN(_ + " at " + _)), // projections form an applicative\n "people" -> cont{ home =>\n // Tables can be flatmapped together\n for {\n hp <- homePersonTable.join[List](hp => sql"${home.idCol} = ${hp.homeCol}")\n p <- personTable.join(p => sql"${hp.personCol} = ${p.idCol}")\n } yield p\n }\n )\n')),(0,o.kt)("p",null,"Now we are done declaring our schema."),(0,o.kt)("p",null,"Before querying it we will need our database up and running."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'import cats.effect.unsafe.implicits.global\nimport natchez.noop._ // needed for skunk connection\nimplicit val trace: natchez.Trace[IO] = NoopTrace[IO]()\n\ndef connection = Session.single[IO](\n host = "127.0.0.1",\n port = 5432,\n user = "postgres",\n database = "postgres"\n)\n')),(0,o.kt)("details",null,(0,o.kt)("summary",null,"We will also need to create our tables and insert some data."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'connection.use{ ses =>\n val queries = List(\n sql"drop table if exists pet",\n sql"drop table if exists home_person",\n sql"drop table if exists person",\n sql"drop table if exists home",\n sql"""create table home_person (\n home_id int not null,\n person_id int not null\n )""",\n sql"""create table pet (\n id int4 primary key,\n name text not null,\n age int not null,\n owner int not null\n )""",\n sql"""create table person (\n id int4 primary key,\n name text not null,\n age int not null\n )""",\n sql"""create table home (\n id int4 primary key,\n name text not null,\n address text not null\n )""",\n sql"""insert into home (id, name, address) values (1, \'Doe Home\', \'123 Main St\')""",\n sql"""insert into person (id, name, age) values (1, \'John Doe\', 42)""",\n sql"""insert into person (id, name, age) values (2, \'Jane Doe\', 40)""",\n sql"""insert into home_person (home_id, person_id) values (1, 1)""", \n sql"""insert into home_person (home_id, person_id) values (1, 2)""",\n sql"""insert into pet (id, name, age, owner) values (1, \'Fluffy\', 2, 1)""",\n )\n\n queries.traverse(x => ses.execute(x.command))\n}.unsafeRunSync()\n// res0: List[..skunk.data.Completion] = List(\n// DropTable,\n// DropTable,\n// DropTable,\n// DropTable,\n// CreateTable,\n// CreateTable,\n// CreateTable,\n// CreateTable,\n// Insert(count = 1),\n// Insert(count = 1),\n// Insert(count = 1),\n// Insert(count = 1),\n// Insert(count = 1),\n// Insert(count = 1)\n// )\n'))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'def schema = gql.Schema.query(\n tpe[IO, Unit](\n "Query",\n "homes" -> runFieldSingle(connection) { (_: Unit) => \n homeTable.join[List](_ => sql"true")\n }\n )\n)\n\ndef q = """\nquery {\n homes {\n name\n address\n caption\n people {\n name\n age\n pets {\n name\n age\n }\n }\n }\n}\n"""\n\nimport io.circe.syntax._\nimport gql.{Compiler, Application}\nschema\n .map(Compiler[IO].compile(_, q))\n .flatMap { case Right(Application.Query(run)) => run.map(_.handleErrors{e => println(e.getMessage()); ""}.asJson.spaces2) }\n .unsafeRunSync()\n// res1: String = """{\n// "data" : {\n// "homes" : [\n// {\n// "address" : "123 Main St",\n// "caption" : "Doe Home at 123 Main St",\n// "name" : "Doe Home",\n// "people" : [\n// {\n// "age" : 42,\n// "name" : "John Doe",\n// "pets" : [\n// {\n// "age" : 2,\n// "name" : "Fluffy"\n// }\n// ]\n// },\n// {\n// "age" : 40,\n// "name" : "Jane Doe",\n// "pets" : [\n// ]\n// }\n// ]\n// }\n// ]\n// }\n// }"""\n')),(0,o.kt)("p",null,"And thats it!"),(0,o.kt)("p",null,"Just for fun, we check out the generated sql."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.relational.skunk._\nimplicit def logQueries[F[_]: MonadCancelThrow]: SkunkIntegration.Queryable[F] = \n new SkunkIntegration.Queryable[F] {\n def apply[A](\n query: AppliedFragment,\n decoder: Decoder[A], \n connection: SkunkIntegration.Connection[F]\n ): F[List[A]] = {\n println(query.fragment.sql)\n SkunkIntegration.skunkQueryable[F].apply(query, decoder, connection)\n }\n}\n\ndef schema = gql.Schema.query(\n tpe[IO, Unit](\n "Query",\n "homes" -> runFieldSingle(connection) { (_: Unit) => \n homeTable.join[List](_ => sql"true")\n }\n )\n)\n\nschema\n .map(Compiler[IO].compile(_, q))\n .flatMap { case Right(Application.Query(run)) => run.void }\n .unsafeRunSync()\n// select t1.id, t1.address, t1.name, t1.address, t1.name, t2.home_id, t2.person_id, t3.id, t3.age, t3.name, t4.id, t4.age, t4.name\n// from home as t1\n// left join home_person as t2 on t1.id = t2.home_id\n// left join person as t3 on t2.person_id = t3.id\n// left join pet as t4 on t4.owner = t3.id\n// where true\n')),(0,o.kt)("h3",{id:"simplifying-relationships"},"Simplifying relationships"),(0,o.kt)("p",null,"The join between ",(0,o.kt)("inlineCode",{parentName:"p"},"home")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"person")," can be a bit daunting, since you have to keep track of multiplicity yourself.\nInstead we can use the database to handle some of the multiplicity for us by generalizing the person table."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'case class SharedPersonTable(alias: String, table: AppliedFragment) extends SkunkTable {\n val (idCol, id) = sel("id", int4)\n val (nameCol, name) = sel("name", text)\n val (ageCol, age) = sel("age", int4)\n\n def tableKey = id\n}\n\nval sharedPersonTable = skunkTable(SharedPersonTable(_, void"person"))\n\nval homePersonQuery = void"(select * from home_person inner join person on home_person.person_id = person.id)"\nval sharedHomePersonTable = skunkTable(SharedPersonTable(_, homePersonQuery))\n\n// And now using our subquery we can simplify the join.\nimplicit lazy val person: Type[IO, QueryContext[SharedPersonTable]] = ???\n\ntpe[IO, QueryContext[HomeTable]](\n "HomeTable",\n "name" -> query(_.name),\n "address" -> query(_.address),\n "caption" -> query(h => (h.name, h.address).mapN(_ + " at " + _)), // projections form an applicative\n "people" -> cont{ h => \n sharedHomePersonTable.join[List](hp => sql"${h.idCol} = ${hp.aliased(sql"home_id")}")\n }\n)\n')),(0,o.kt)("h2",{id:"runtime-semantics"},"Runtime semantics"),(0,o.kt)("admonition",{type:"info"},(0,o.kt)("p",{parentName:"admonition"},"This section is a technical reference, and not necessary to use the library.")),(0,o.kt)("p",null,"Data emitted by SQL is not hierarchical, but instead flat; for it to map well to graphql, which is hierarchical some work must be performed.\nMost use-cases are covered by simply invoking the ",(0,o.kt)("inlineCode",{parentName:"p"},"join")," method with the proper multiplicity parameter."),(0,o.kt)("p",null,"When your AST is inspected to build a query, a recursive AST walk composes a big reassociation function that can translate flat query results into the proper hierarchical structure.\nThis composed function also tracks the visited columns and their decoders."),(0,o.kt)("p",null,"The query algebra has a special operation that lets the caller modify the state however they wish.\nThe dsl uses this state modification for various tasks, such as providing a convinient ",(0,o.kt)("inlineCode",{parentName:"p"},"join")," method that both joins a table and performs the proper reassociation of results.\nConsider the following example that joins a table more explicitly."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},"val q1 = for {\n ht <- homeTable.simpleJoin(_ => void\"true\")\n _ <- reassociate[List](ht.tableKey)\n // some other reassociation criteria\n _ <- reassociate[Option](select(int4, void\"42\"))\n} yield ht\n// q1: algebra.Query[[X]List[Option[X]], HomeTable] = FlatMap(\n// fa = FlatMap(\n// fa = LiftEffect(fa = EitherT(value = cats.data.IndexedStateT@3aeb8301)),\n// f = gql.relational.QueryDsl$$Lambda$14028/0x0000000803785040@268e9cd4\n// ),\n// f = \n// )\n\n// we can perform reassociation before performing the actions in 'q1'\nval q2 = reassociate[Option](select(text, void\"'john doe'\")).flatMap(_ => q1)\n// q2: algebra.Query[[X]Option[List[Option[X]]], HomeTable] = FlatMap(\n// fa = LiftEffect(fa = EitherT(value = cats.data.IndexedStateT@1a31fa0d)),\n// f = \n// )\n\n// we can also change the result structure after performing the actions in 'q2'\nq2.mapK[List](new (\u03bb[X => Option[List[Option[X]]]] ~> List) {\n def apply[A](fa: Option[List[Option[A]]]): List[A] = fa.toList.flatten.flatMap(_.toList)\n})\n// res4: algebra.Query[List, HomeTable] = LiftEffect(\n// fa = EitherT(value = cats.data.IndexedStateT@9113f61)\n// )\n")),(0,o.kt)("p",null,"Accessing the lowlevel state also lets the user perform other tasks such as unique id (new alias) generation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},"for {\n alias1 <- newAlias\n alias2 <- newAlias\n} yield ()\n// res5: algebra.Query[[X]X, Unit] = FlatMap(\n// fa = LiftEffect(fa = EitherT(value = cats.data.IndexedStateT@6f8456c9)),\n// f = \n// )\n")),(0,o.kt)("h2",{id:"implementing-your-own-integration"},"Implementing your own integration"),(0,o.kt)("p",null,"The entire dsl and query compiler is available if you implement a couple of methods."),(0,o.kt)("p",null,"Here is the full skunk integration."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'import _root_.{skunk => sk}\nobject MyIntegration extends QueryAlgebra {\n // What is a fragment\n type Frag = sk.AppliedFragment\n // How do we transform a string into a fragment\n def stringToFrag(s: String): Frag = sql"#${s}".apply(Void)\n // Combine and create empty fragments\n implicit def appliedFragmentMonoid: Monoid[Frag] = sk.AppliedFragment.MonoidAppFragment\n // How do we decode values\n type Decoder[A] = sk.Decoder[A]\n // How can we combine decoders\n implicit def applicativeForDecoder: Applicative[Decoder] = Decoder.ApplicativeDecoder\n // How do we make an optional decoder\n def optDecoder[A](d: Decoder[A]): Decoder[Option[A]] = d.opt\n // What is needed to perform a query\n type Connection[F[_]] = Resource[F, Session[F]]\n // Given a connection, how do we use it\n implicit def skunkQueryable[F[_]: MonadCancelThrow]: Queryable[F] = new Queryable[F] {\n def apply[A](query: AppliedFragment, decoder: Decoder[A], connection: Connection[F]): F[List[A]] =\n connection.use(_.execute(query.fragment.query(decoder))(query.argument))\n }\n}\n')),(0,o.kt)("p",null,"The dsl can be instantiated for any query algebra."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},"object myDsl extends QueryDsl(MyIntegration)\n")),(0,o.kt)("p",null,"you can also add integration specific methods to your dsl."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},"object myDsl extends QueryDsl(MyIntegration) {\n def someOperationSpecificToMyIntegration = ???\n}\n")),(0,o.kt)("h2",{id:"adding-arguments"},"Adding arguments"),(0,o.kt)("p",null,"All field combinators allow arguments to be provided naturally, regardless of where the field is in the query."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'implicit lazy val pt: Type[IO, QueryContext[PersonTable]] = ???\n\ntpe[IO, QueryContext[HomeTable]](\n "HomeTable",\n "people" -> cont(arg[List[Int]]("ids")) { (home, ids) =>\n for {\n hp <- homePersonTable.join[List](hp => sql"${home.idCol} = ${hp.homeCol}")\n p <- personTable.join(p => sql"${hp.personCol} = ${p.idCol} and ${p.idCol} in (${int4.list(ids)})".apply(ids))\n } yield p\n }\n)\n')),(0,o.kt)("h2",{id:"sum-types"},"Sum types"),(0,o.kt)("p",null,"Sum types can naturally be declared also."),(0,o.kt)("details",null,(0,o.kt)("summary",null,"Lets set up some tables for sum types."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'connection.use{ ses =>\n val queries = List(\n sql"drop table if exists owner",\n sql"drop table if exists dog",\n sql"drop table if exists cat",\n sql"""create table owner (\n id int4 primary key\n )""",\n sql"""create table dog (\n id int4 primary key,\n owner_id int4 not null,\n name text not null,\n age int not null\n )""",\n sql"""create table cat (\n id int4 primary key,\n owner_id int4 not null,\n name text not null,\n age int not null\n )""",\n sql"""insert into owner (id) values (1)""",\n sql"""insert into owner (id) values (2)""",\n sql"""insert into dog (id, owner_id, name, age) values (1, 1, \'Dog\', 42)""",\n sql"""insert into cat (id, owner_id, name, age) values (2, 2, \'Cat\', 22)""",\n )\n\n queries.traverse(x => ses.execute(x.command))\n}.unsafeRunSync()\n// res7: List[..skunk.data.Completion] = List(\n// DropTable,\n// DropTable,\n// DropTable,\n// CreateTable,\n// CreateTable,\n// CreateTable,\n// Insert(count = 1),\n// Insert(count = 1),\n// Insert(count = 1),\n// Insert(count = 1)\n// )\n'))),(0,o.kt)("p",null,"And now we can run it."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'sealed trait Animal { \n def name: String\n}\ncase class Dog(owner: String, name: String, age: Int) extends Animal\ncase class Cat(owner: String, name: String, age: Int) extends Animal\n\ntrait OwnerTable extends SkunkTable {\n def table = void"owner"\n val (idCol, id) = sel("id", int4)\n def tableKey = id\n}\ncase class OwnerTableUnion(alias: String) extends OwnerTable\ncase class OwnerTableInterface(alias: String) extends OwnerTable\nval ownerTableUnion = skunkTable(OwnerTableUnion)\n// ownerTableUnion: SkunkTableAlg[OwnerTableUnion] = gql.relational.skunk.dsl$$anon$2@3d3e1fd1\nval ownerTableInterface = skunkTable(OwnerTableInterface)\n// ownerTableInterface: SkunkTableAlg[OwnerTableInterface] = gql.relational.skunk.dsl$$anon$2@284a6be9\n\ncase class DogTable(alias: String) extends SkunkTable {\n def table = void"dog"\n\n val (idCol, id) = sel("id", int4)\n val (ownerCol, owner) = sel("owner_id", int4)\n val (nameCol, name) = sel("name", text)\n val (ageCol, age) = sel("age", int4)\n\n def tableKey = id\n}\nval dogTable = skunkTable(DogTable)\n// dogTable: SkunkTableAlg[DogTable] = gql.relational.skunk.dsl$$anon$2@28a0a3b6\n\ncase class CatTable(alias: String) extends SkunkTable {\n def table = void"cat"\n\n val (idCol, id) = sel("id", int4)\n val (ownerCol, owner) = sel("owner_id", int4)\n val (nameCol, name) = sel("name", text)\n val (ageCol, age) = sel("age", int4)\n\n def tableKey = id\n}\nval catTable = skunkTable(CatTable)\n// catTable: SkunkTableAlg[CatTable] = gql.relational.skunk.dsl$$anon$2@655f9a75\n\nimplicit lazy val animalInterface = interface[IO, QueryContext[OwnerTableInterface]](\n "AnimalInterface",\n "owner" -> abst[IO, String]\n)\n\nimplicit lazy val cat = tpe[IO, QueryContext[CatTable]](\n "Cat",\n "owner" -> query(_.owner),\n "name" -> query(_.name),\n "age" -> query(_.age)\n).contImplements[OwnerTableInterface]{ owner => \n catTable.join[Option](cat => sql"${owner.idCol} = ${cat.ownerCol}")\n}\n\nimplicit lazy val dog = tpe[IO, QueryContext[DogTable]](\n "Dog",\n "owner" -> query(_.owner),\n "name" -> query(_.name),\n "age" -> query(_.age)\n).contImplements[OwnerTableInterface]{ owner => \n dogTable.join[Option](dog => sql"${owner.idCol} = ${dog.ownerCol}")\n}\n\n// we use the builder to create a union type\nimplicit lazy val animal = relBuilder[IO, OwnerTableUnion] { b =>\n b\n .union("Animal")\n .contVariant(owner => dogTable.join[Option](dog => sql"${owner.idCol} = ${dog.ownerCol}"))\n .contVariant(owner => catTable.join[Option](cat => sql"${owner.idCol} = ${cat.ownerCol}"))\n}\n\ndef schema = gql.Schema.query(\n tpe[IO, Unit](\n "Query",\n "animals" -> runFieldSingle(connection) { (_: Unit) =>\n ownerTableUnion.join[List](_ => sql"true")\n },\n "animalInterfaces" -> runFieldSingle(connection) { (_: Unit) =>\n ownerTableInterface.join[List](_ => sql"true")\n }\n )\n)\n\ndef animalQuery = """\n query {\n animals {\n __typename\n ... on Dog {\n owner\n name\n age\n }\n ... on Cat {\n owner\n name\n age\n }\n }\n animalInterfaces {\n __typename\n ... on Dog {\n owner\n name\n age\n }\n ... on Cat {\n owner\n name\n age\n }\n }\n }\n"""\n\nschema\n .map(Compiler[IO].compile(_, animalQuery))\n .flatMap { case Right(Application.Query(run)) => run.map(_.handleErrors{e => println(e.getMessage()); ""}.asJson.spaces2) }\n .unsafeRunSync()\n// select t1.id, t2.id, t2.age, t2.name, t2.owner_id, t3.id, t3.age, t3.name, t3.owner_id\n// from owner as t1\n// left join dog as t2 on t1.id = t2.owner_id\n// left join cat as t3 on t1.id = t3.owner_id\n// where true\n// select t1.id, t2.id, t2.age, t2.name, t2.owner_id, t3.id, t3.age, t3.name, t3.owner_id\n// from owner as t1\n// left join dog as t2 on t1.id = t2.owner_id\n// left join cat as t3 on t1.id = t3.owner_id\n// where true\n// res8: String = """{\n// "data" : {\n// "animalInterfaces" : [\n// {\n// "__typename" : "Cat",\n// "age" : 22,\n// "name" : "Cat",\n// "owner" : 2\n// },\n// {\n// "__typename" : "Dog",\n// "age" : 42,\n// "name" : "Dog",\n// "owner" : 1\n// }\n// ],\n// "animals" : [\n// {\n// "__typename" : "Cat",\n// "age" : 22,\n// "name" : "Cat",\n// "owner" : 2\n// },\n// {\n// "__typename" : "Dog",\n// "age" : 42,\n// "name" : "Dog",\n// "owner" : 1\n// }\n// ]\n// }\n// }"""\n')),(0,o.kt)("h2",{id:"declaring-complex-subqueries"},"Declaring complex subqueries"),(0,o.kt)("p",null,"Sometimes your tables must have complex filtering, limiting, ordering and so on.\nThe most obvious way to declare such parameters is simply to use a subquery."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'case class ParameterizedPersonTable(alias: String, table: AppliedFragment) extends SkunkTable {\n val (idCol, id) = sel("id", int4)\n val (nameCol, name) = sel("name", text)\n val (ageCol, age) = sel("age", int4)\n \n def tableKey = id\n}\ndef parameterizedPersonTable(\n limitOffset: Option[(Int, Int)],\n order: Option[AppliedFragment],\n filter: Option[AppliedFragment]\n) = skunkTable{ alias => \n val filt = filter.foldMap(f => sql"where ${f.fragment}".apply(f.argument))\n val ord = order.foldMap(f => sql"order by ${f.fragment}".apply(f.argument))\n val lim = \n limitOffset.foldMap{ case (limit, offset) => sql"limit ${int4} offset ${int4}".apply((limit, offset))}\n ParameterizedPersonTable(\n alias,\n sql"""|(\n | select *\n | from person\n | ${filt.fragment}\n | ${ord.fragment}\n | ${lim.fragment}\n |)""".stripMargin.apply((filt.argument, ord.argument, lim.argument))\n )\n}\n')),(0,o.kt)("p",null,"And now we can use our new table."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'implicit lazy val ppt: Type[IO, QueryContext[ParameterizedPersonTable]] = ???\n\nval personQueryArgs = (\n arg[Option[Int]]("limit"),\n arg[Option[Int]]("offset"),\n arg[Option[Boolean]]("order"),\n arg[Option[Int]]("ageFilter")\n).tupled\ntpe[IO, QueryContext[HomeTable]](\n "HomeTable",\n "people" -> cont(personQueryArgs) { case (home, (lim, off, ord, af)) =>\n for {\n hp <- homePersonTable.join[List](hp => sql"${home.idCol} = ${hp.homeCol}")\n p <- parameterizedPersonTable(\n limitOffset = (lim, off).tupled,\n order = ord.map{\n case true => void"age desc"\n case false => void"age asc"\n },\n filter = af.map(age => sql"age > ${int4}".apply(age))\n ).join(p => sql"${hp.personCol} = ${p.idCol}")\n } yield p\n }\n)\n')),(0,o.kt)("h2",{id:"using-relational-without-tables"},"Using relational without tables"),(0,o.kt)("p",null,"There is no restriction on how you can implement a table, so you can choose your own strategy.\nFor instance say we just wanted to declare everything up-front and select fields ad-hoc."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.relational.skunk.SkunkIntegration.Query.Select\n\ncase class AdHocTable(\n alias: String, \n table: AppliedFragment,\n tableKey: Select[?],\n) extends SkunkTable\n\ntpe[IO, QueryContext[HomeTable]](\n "HomeTable",\n "people" -> cont(arg[List[Int]]("ids")) { (home, ids) =>\n for {\n hp <- skunkTable(alias => \n AdHocTable(\n alias, \n sql"#${alias}.home_person".apply(Void), \n select(\n int4 ~ int4,\n sql"#${alias}.home_id".apply(Void), \n sql"#${alias}.person_id".apply(Void)\n )\n )\n ).join[List](hp => sql"${home.idCol} = ${hp.aliased(sql"home_id")}")\n p <- personTable.join(p => sql"${hp.aliased(sql".person_id")} = ${p.idCol} and ${p.idCol} in (${int4.list(ids)})".apply(ids))\n } yield p\n }\n)\n')),(0,o.kt)("p",null,"Since there is no dsl for this, constructing the query is a bit gruesome.\nConsider if a dsl is possible for your formulation."),(0,o.kt)("h2",{id:"running-transactions"},"Running transactions"),(0,o.kt)("p",null,"Most usecases involve running all queries in a transaction, but none of the examples so far have introduces this.\nThe implementation of transactions depends on the database library, but many implementations share common properties."),(0,o.kt)("p",null,"If your database library supports opening transactions as a resource then the you can lazily open a transaction.\nHere is an example using skunk."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'trait SessionContext {\n def getSession: Resource[IO, Session[IO]]\n}\n\nobject SessionContext {\n def fromIOLocal(iol: IOLocal[Option[Resource[IO, Session[IO]]]]) = new SessionContext {\n def getSession = Resource.eval(iol.get).flatMap{\n case None => Resource.eval(IO.raiseError(new Exception("No session in context")))\n case Some(sc) => sc\n }\n }\n}\n\ndef myConnection: Resource[IO, Session[IO]] = Session.single[IO](\n host = "127.0.0.1",\n port = 5432,\n user = "postgres",\n database = "postgres"\n)\n\n// The outer resource manages the lifecycle of the connection\n// The inner resource leases the connection, if the inner resource is not closed, the outer waits\ndef lazyConnection: Resource[IO, LazyResource[IO, Session[IO]]] = \n gql.relational.LazyResource.fromResource(myConnection)\n\n// We define our schema as requiring a connection\ndef myQuery(ctx: SessionContext): Type[IO, Unit] = {\n implicit lazy val homeTableTpe: Out[IO, QueryContext[HomeTable]] = ???\n tpe[IO, Unit](\n "Query",\n "homes" -> runFieldSingle(ctx.getSession) { (_: Unit) => \n homeTable.join[List](_ => sql"true")\n }\n )\n}\n\ndef runQuery: IO[String => Compiler.Outcome[IO]] = \n gql.Statistics[IO].flatMap{ stats => \n IOLocal[Option[Resource[IO, Session[IO]]]](None).map{ loc =>\n val sc = SessionContext.fromIOLocal(loc)\n\n val schema = gql.Schema.query(stats)(myQuery(sc))\n\n val setResource = lazyConnection.evalMap(x => loc.set(Some(x.get)))\n\n (query: String) => \n Compiler[IO]\n .compile(schema, q)\n .map{\n case gql.Application.Query(fa) => gql.Application.Query(setResource.surround(fa))\n case gql.Application.Mutation(fa) => gql.Application.Mutation(setResource.surround(fa))\n // Subscription is a bit more complex since we would like to close the transaction on every event\n case gql.Application.Subscription(fa) => \n gql.Application.Subscription{\n fs2.Stream.resource(lazyConnection).flatMap{ x =>\n fs2.Stream.exec(loc.set(Some(x.get))) ++\n fa.evalTap(_ => x.forceClose)\n }\n }\n }\n }\n }\n')),(0,o.kt)("details",null,(0,o.kt)("summary",null,"You can also use MTL for passing the transaction around"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'import cats.mtl._\n\ndef myConnection: Resource[IO, Session[IO]] = Session.single[IO](\n host = "127.0.0.1",\n port = 5432,\n user = "postgres",\n database = "postgres"\n)\n\n// The outer resource manages the lifecycle of the connection\n// The inner resource leases the connection, if the inner resource is not closed, the outer waits\ndef lazyConnection: Resource[IO, LazyResource[IO, Session[IO]]] = \n gql.relational.LazyResource.fromResource(myConnection)\n\nval liftK = Kleisli.liftK[IO, Resource[IO, Session[IO]]]\n\ntype GetConn[F[_]] = Ask[F, Resource[F, Session[F]]]\n\ndef makeConn[F[_]](conn: GetConn[F]): Resource[F, Session[F]] = \n Resource.eval(conn.ask[Resource[F, Session[F]]]).flatten\n\n// We define our schema as requiring a connection\ndef myQuery[F[_]: Async](conn: GetConn[F]): Type[F, Unit] = {\n implicit lazy val homeTableTpe: Type[F, QueryContext[HomeTable]] = ???\n tpe[F, Unit](\n "Query",\n "homes" -> runFieldSingle(makeConn(conn)) { (_: Unit) => \n homeTable.join[List](_ => sql"true")\n }\n )\n}\n\nimplicit def functorForAsk[F[_]]: Functor[Ask[F, *]] = ???\ndef kleisliAsk[F[_]: Applicative, A] = Ask[Kleisli[F, A, *], A]\n\ndef runQuery: IO[String => Compiler.Outcome[IO]] = \n gql.Statistics[IO].map{ stats => \n type G[A] = Kleisli[IO, Resource[IO, Session[IO]], A]\n\n val liftK = Kleisli.liftK[IO, Resource[IO, Session[IO]]]\n\n val ask: Ask[G, Resource[G, Session[G]]] = \n kleisliAsk[IO, Resource[IO, Session[IO]]].map(_.mapK(liftK).map(_.mapK(liftK)))\n\n val schema = gql.Schema.query(stats.mapK(liftK))(myQuery[G](ask))\n\n val oneshot = lazyConnection.map(_.get.flatTap(_.transaction))\n\n (query: String) => \n Compiler[G]\n .compile(schema, q)\n .map{ \n case gql.Application.Query(fa) => gql.Application.Query(oneshot.useKleisli(fa))\n case gql.Application.Mutation(fa) => gql.Application.Mutation(oneshot.useKleisli(fa))\n // Subscription is a bit more complex since we would like to close the transaction on every event\n case gql.Application.Subscription(fa) => \n gql.Application.Subscription{\n fs2.Stream.resource(lazyConnection).flatMap{ lc =>\n fa\n .translate(Kleisli.applyK[IO, Resource[IO, Session[IO]]](lc.get.flatTap(_.transaction)))\n .evalTap(_ => lc.forceClose)\n }\n }\n }\n }\n'))),(0,o.kt)("h2",{id:"handling-n1"},"Handling N+1"),(0,o.kt)("p",null,"The relational module can handle N+1 queries and queries that can cause cartesian products.\nTo solve N+1, the user must use the ",(0,o.kt)("inlineCode",{parentName:"p"},"runField")," method instead of the ",(0,o.kt)("inlineCode",{parentName:"p"},"runFieldSingle"),".\nThe ",(0,o.kt)("inlineCode",{parentName:"p"},"runField")," method takes a list of inputs ",(0,o.kt)("inlineCode",{parentName:"p"},"I")," and produces ",(0,o.kt)("inlineCode",{parentName:"p"},"Query[G, (Select[I], B)]"),", such that query results can be reassociated with the inputs."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'def myBatchedHomeQuery(conn: Resource[IO, Session[IO]]) = {\n case class MyDatatype(homeId: Int)\n\n tpe[IO, MyDatatype](\n "MyDatatype",\n "home" -> runField[IO, List, MyDatatype, HomeTable](conn) { xs => \n val lst = xs.toList.map(_.homeId)\n for {\n ht <- homeTable.join[List](ht => sql"${ht.idCol} in (${int4.list(lst)})".apply(lst))\n } yield (ht.id.fmap(MyDatatype), ht)\n }\n )\n}\n')),(0,o.kt)("p",null,"To solve the query multiplicity explosions you can use the ",(0,o.kt)("inlineCode",{parentName:"p"},"contBoundary")," which works almost like ",(0,o.kt)("inlineCode",{parentName:"p"},"cont"),", except the query will be split up into two queries."),(0,o.kt)("p",null,"The ",(0,o.kt)("inlineCode",{parentName:"p"},"contBoundary")," function takes two interesting parameters.\nThe first parameter will be a projection of the current query, decoded into ",(0,o.kt)("inlineCode",{parentName:"p"},"B"),".\nThe second parameter turns this ",(0,o.kt)("inlineCode",{parentName:"p"},"B")," into another query, which will be the root of the new query."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'def boundaryQuery(conn: Resource[IO, Session[IO]]) = {\n case class MyDatatype(homeId: Int)\n\n relBuilder[IO, HomeTable]{ rb =>\n rb.tpe(\n "HomeTable",\n "people" -> rb.contBoundary(conn){ home =>\n homePersonTable.join[List](hp => sql"${home.idCol} = ${hp.homeCol}").map(_.person)\n }{ (xs: NonEmptyList[Int]) =>\n val lst = xs.toList\n personTable.join(p => sql"${p.idCol} in (${int4.list(lst)})".apply(lst)).map(p => p.id -> p)\n }\n )\n }\n}\n')),(0,o.kt)("admonition",{type:"info"},(0,o.kt)("p",{parentName:"admonition"},"The ",(0,o.kt)("inlineCode",{parentName:"p"},"contBoundary")," is only available in when using the ",(0,o.kt)("inlineCode",{parentName:"p"},"relBuilder"),", since type inference does not work very well."),(0,o.kt)("p",{parentName:"admonition"},"Inference troubles with ",(0,o.kt)("inlineCode",{parentName:"p"},"runField")," can also be alleviated by using the ",(0,o.kt)("inlineCode",{parentName:"p"},"relBuilder"),".")))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[508],{3905:(e,n,a)=>{a.d(n,{Zo:()=>c,kt:()=>m});var t=a(7294);function o(e,n,a){return n in e?Object.defineProperty(e,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[n]=a,e}function l(e,n){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(e);n&&(t=t.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),a.push.apply(a,t)}return a}function i(e){for(var n=1;n=0||(o[a]=e[a]);return o}(e,n);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(o[a]=e[a])}return o}var s=t.createContext({}),p=function(e){var n=t.useContext(s),a=n;return e&&(a="function"==typeof e?e(n):i(i({},n),e)),a},c=function(e){var n=p(e.components);return t.createElement(s.Provider,{value:n},e.children)},u={inlineCode:"code",wrapper:function(e){var n=e.children;return t.createElement(t.Fragment,{},n)}},d=t.forwardRef((function(e,n){var a=e.components,o=e.mdxType,l=e.originalType,s=e.parentName,c=r(e,["components","mdxType","originalType","parentName"]),d=p(a),m=o,g=d["".concat(s,".").concat(m)]||d[m]||u[m]||l;return a?t.createElement(g,i(i({ref:n},c),{},{components:a})):t.createElement(g,i({ref:n},c))}));function m(e,n){var a=arguments,o=n&&n.mdxType;if("string"==typeof e||o){var l=a.length,i=new Array(l);i[0]=d;var r={};for(var s in n)hasOwnProperty.call(n,s)&&(r[s]=n[s]);r.originalType=e,r.mdxType="string"==typeof e?e:o,i[1]=r;for(var p=2;p{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>l,metadata:()=>r,toc:()=>p});var t=a(7462),o=(a(7294),a(3905));const l={title:"Relational"},i=void 0,r={unversionedId:"server/integrations/relational",id:"server/integrations/relational",title:"Relational",description:"This integration is fairly new and sofisticated so it can be subject to change.",source:"@site/docs/server/integrations/relational.md",sourceDirName:"server/integrations",slug:"/server/integrations/relational",permalink:"/gql/docs/server/integrations/relational",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/integrations/relational.md",tags:[],version:"current",frontMatter:{title:"Relational"},sidebar:"docs",previous:{title:"Global object identification",permalink:"/gql/docs/server/integrations/goi"},next:{title:"Query DSL",permalink:"/gql/docs/client/dsl"}},s={},p=[{value:"Skunk example",id:"skunk-example",level:2},{value:"Simplifying relationships",id:"simplifying-relationships",level:3},{value:"Runtime semantics",id:"runtime-semantics",level:2},{value:"Implementing your own integration",id:"implementing-your-own-integration",level:2},{value:"Adding arguments",id:"adding-arguments",level:2},{value:"Sum types",id:"sum-types",level:2},{value:"Declaring complex subqueries",id:"declaring-complex-subqueries",level:2},{value:"Using relational without tables",id:"using-relational-without-tables",level:2},{value:"Running transactions",id:"running-transactions",level:2},{value:"Handling N+1",id:"handling-n1",level:2}],c={toc:p};function u(e){let{components:n,...a}=e;return(0,o.kt)("wrapper",(0,t.Z)({},c,a,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("admonition",{type:"caution"},(0,o.kt)("p",{parentName:"admonition"},"This integration is fairly new and sofisticated so it can be subject to change.")),(0,o.kt)("p",null,"gql also comes with an optional integration for relational databases."),(0,o.kt)("p",null,"The relational integration is library agnostic and is based on query fragments that can be composed into a full query."),(0,o.kt)("p",null,"The relational module ships with two implementations, one for ",(0,o.kt)("inlineCode",{parentName:"p"},"skunk")," and another for ",(0,o.kt)("inlineCode",{parentName:"p"},"doobie"),".\nThey can be found in the ",(0,o.kt)("a",{parentName:"p",href:"../../overview/modules"},"modules")," section."),(0,o.kt)("admonition",{type:"tip"},(0,o.kt)("p",{parentName:"admonition"},"Integrating a new library requires very little code.\nThe skunk integration only spans 18 lines of code.")),(0,o.kt)("h2",{id:"skunk-example"},"Skunk example"),(0,o.kt)("p",null,"For this example we will use ",(0,o.kt)("inlineCode",{parentName:"p"},"skunk"),".\nWe will start off with some imports."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},"import skunk._\nimport skunk.codec.all._\nimport skunk.implicits._\nimport gql.ast._\nimport gql.dsl.all._\nimport gql.relational._\nimport gql.relational.skunk.dsl._\nimport gql.relational.skunk.dsl.algebra.QueryContext\nimport cats._\nimport cats.data._\nimport cats.arrow._\nimport cats.effect._\nimport cats.implicits._\n")),(0,o.kt)("p",null,"Before we start declaring fragments, we need to define our domain."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},"final case class Home(name: String, address: String)\n// many homes belong to many people\nfinal case class Person(name: String, age: Int)\n// a pet has one owner\nfinal case class Pet(name: String, age: Int, owner: Int)\n")),(0,o.kt)("p",null,"The realtional module also ships with a dsl that makes declaration use conscise.\nWe will start off just declaring the home table."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'case class HomeTable(\n // When a table is queried it must have an alias\n alias: String\n) extends SkunkTable {\n // Note that we use only skunk tools to declare the contents of this structure\n\n // We can declare how this table is referenced in sql (or some other query language)\n def table = void"home"\n\n // The SkunkTable trait gives some convinience methods for declaring columns\n val (idCol, id) = sel("id", int4)\n val (nameCol, name) = sel("name", text)\n val (addressCol, address) = sel("address", text)\n\n // The projection that uniquely identifies a row in the table\n def tableKey = id\n}\n// We get some methods if show how given an alias we can get a table\nval homeTable = skunkTable(HomeTable)\n')),(0,o.kt)("p",null,"We will also need to declare the other two tables, this time with less comments."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'case class PersonTable(alias: String) extends SkunkTable {\n def table = void"person"\n\n val (idCol, id) = sel("id", int4)\n val (nameCol, name) = sel("name", text)\n val (ageCol, age) = sel("age", int4)\n\n def tableKey = id\n}\nval personTable = skunkTable(PersonTable)\n\ncase class PetTable(alias: String) extends SkunkTable {\n def table = void"pet"\n\n val (idCol, id) = sel("id", int4)\n val (nameCol, name) = sel("name", text)\n val (ageCol, age) = sel("age", int4)\n val (ownerCol, owner) = sel("owner", int4)\n\n def tableKey = id\n}\nval petTable = skunkTable(PetTable)\n')),(0,o.kt)("p",null,"Since ",(0,o.kt)("inlineCode",{parentName:"p"},"Home")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"Person")," have a many to many relationship, we will have to go through another table table to get the relationship."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'case class HomePersonTable(alias: String) extends SkunkTable {\n def table = void"home_person"\n\n val (homeCol, home) = sel("home_id", int4)\n val (personCol, person) = sel("person_id", int4)\n\n def tableKey = (home, person).tupled\n}\nval homePersonTable = skunkTable(HomePersonTable)\n')),(0,o.kt)("p",null,"Now we can start declaring our graphql schema."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'implicit lazy val pet: Type[IO, QueryContext[PetTable]] = \n tpe[IO, QueryContext[PetTable]](\n "PetTable",\n "name" -> query(_.name), // query is a method that compiles to a projection in the query language (sql)\n "age" -> query(_.age)\n )\n\nimplicit lazy val person: Type[IO, QueryContext[PersonTable]] = \n tpe[IO, QueryContext[PersonTable]](\n "PersonTable",\n "name" -> query(_.name),\n "age" -> query(_.age),\n "pets" -> cont{ person => // cont is a continuation that will create a new table from the current one\n // The join method takes a type parameter that declares the multiplicity of the join\n // If no type parameter is given, the join is assumed to be one to one\n petTable.join[List]{ pet =>\n // Given an instance of the pet table, we can declare a join predicate\n sql"${pet.ownerCol} = ${person.idCol}"\n }\n }\n )\n\nimplicit lazy val home: Type[IO, QueryContext[HomeTable]] = \n tpe[IO, QueryContext[HomeTable]](\n "HomeTable",\n "name" -> query(_.name),\n "address" -> query(_.address),\n "caption" -> query(h => (h.name, h.address).mapN(_ + " at " + _)), // projections form an applicative\n "people" -> cont{ home =>\n // Tables can be flatmapped together\n for {\n hp <- homePersonTable.join[List](hp => sql"${home.idCol} = ${hp.homeCol}")\n p <- personTable.join(p => sql"${hp.personCol} = ${p.idCol}")\n } yield p\n }\n )\n')),(0,o.kt)("p",null,"Now we are done declaring our schema."),(0,o.kt)("p",null,"Before querying it we will need our database up and running."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'import cats.effect.unsafe.implicits.global\nimport natchez.noop._ // needed for skunk connection\nimplicit val trace: natchez.Trace[IO] = NoopTrace[IO]()\n\ndef connection = Session.single[IO](\n host = "127.0.0.1",\n port = 5432,\n user = "postgres",\n database = "postgres"\n)\n')),(0,o.kt)("details",null,(0,o.kt)("summary",null,"We will also need to create our tables and insert some data."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'connection.use{ ses =>\n val queries = List(\n sql"drop table if exists pet",\n sql"drop table if exists home_person",\n sql"drop table if exists person",\n sql"drop table if exists home",\n sql"""create table home_person (\n home_id int not null,\n person_id int not null\n )""",\n sql"""create table pet (\n id int4 primary key,\n name text not null,\n age int not null,\n owner int not null\n )""",\n sql"""create table person (\n id int4 primary key,\n name text not null,\n age int not null\n )""",\n sql"""create table home (\n id int4 primary key,\n name text not null,\n address text not null\n )""",\n sql"""insert into home (id, name, address) values (1, \'Doe Home\', \'123 Main St\')""",\n sql"""insert into person (id, name, age) values (1, \'John Doe\', 42)""",\n sql"""insert into person (id, name, age) values (2, \'Jane Doe\', 40)""",\n sql"""insert into home_person (home_id, person_id) values (1, 1)""", \n sql"""insert into home_person (home_id, person_id) values (1, 2)""",\n sql"""insert into pet (id, name, age, owner) values (1, \'Fluffy\', 2, 1)""",\n )\n\n queries.traverse(x => ses.execute(x.command))\n}.unsafeRunSync()\n// res0: List[..skunk.data.Completion] = List(\n// DropTable,\n// DropTable,\n// DropTable,\n// DropTable,\n// CreateTable,\n// CreateTable,\n// CreateTable,\n// CreateTable,\n// Insert(count = 1),\n// Insert(count = 1),\n// Insert(count = 1),\n// Insert(count = 1),\n// Insert(count = 1),\n// Insert(count = 1)\n// )\n'))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'def schema = gql.Schema.query(\n tpe[IO, Unit](\n "Query",\n "homes" -> runFieldSingle(connection) { (_: Unit) => \n homeTable.join[List](_ => sql"true")\n }\n )\n)\n\ndef q = """\nquery {\n homes {\n name\n address\n caption\n people {\n name\n age\n pets {\n name\n age\n }\n }\n }\n}\n"""\n\nimport io.circe.syntax._\nimport gql.{Compiler, Application}\nschema\n .map(Compiler[IO].compile(_, q))\n .flatMap { case Right(Application.Query(run)) => run.map(_.handleErrors{e => println(e.getMessage()); ""}.asJson.spaces2) }\n .unsafeRunSync()\n// res1: String = """{\n// "data" : {\n// "homes" : [\n// {\n// "address" : "123 Main St",\n// "caption" : "Doe Home at 123 Main St",\n// "name" : "Doe Home",\n// "people" : [\n// {\n// "age" : 42,\n// "name" : "John Doe",\n// "pets" : [\n// {\n// "age" : 2,\n// "name" : "Fluffy"\n// }\n// ]\n// },\n// {\n// "age" : 40,\n// "name" : "Jane Doe",\n// "pets" : [\n// ]\n// }\n// ]\n// }\n// ]\n// }\n// }"""\n')),(0,o.kt)("p",null,"And thats it!"),(0,o.kt)("p",null,"Just for fun, we check out the generated sql."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.relational.skunk._\nimplicit def logQueries[F[_]: MonadCancelThrow]: SkunkIntegration.Queryable[F] = \n new SkunkIntegration.Queryable[F] {\n def apply[A](\n query: AppliedFragment,\n decoder: Decoder[A], \n connection: SkunkIntegration.Connection[F]\n ): F[List[A]] = {\n println(query.fragment.sql)\n SkunkIntegration.skunkQueryable[F].apply(query, decoder, connection)\n }\n}\n\ndef schema = gql.Schema.query(\n tpe[IO, Unit](\n "Query",\n "homes" -> runFieldSingle(connection) { (_: Unit) => \n homeTable.join[List](_ => sql"true")\n }\n )\n)\n\nschema\n .map(Compiler[IO].compile(_, q))\n .flatMap { case Right(Application.Query(run)) => run.void }\n .unsafeRunSync()\n// select t1.id, t1.address, t1.name, t1.address, t1.name, t2.home_id, t2.person_id, t3.id, t3.age, t3.name, t4.id, t4.age, t4.name\n// from home as t1\n// left join home_person as t2 on t1.id = t2.home_id\n// left join person as t3 on t2.person_id = t3.id\n// left join pet as t4 on t4.owner = t3.id\n// where true\n')),(0,o.kt)("h3",{id:"simplifying-relationships"},"Simplifying relationships"),(0,o.kt)("p",null,"The join between ",(0,o.kt)("inlineCode",{parentName:"p"},"home")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"person")," can be a bit daunting, since you have to keep track of multiplicity yourself.\nInstead we can use the database to handle some of the multiplicity for us by generalizing the person table."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'case class SharedPersonTable(alias: String, table: AppliedFragment) extends SkunkTable {\n val (idCol, id) = sel("id", int4)\n val (nameCol, name) = sel("name", text)\n val (ageCol, age) = sel("age", int4)\n\n def tableKey = id\n}\n\nval sharedPersonTable = skunkTable(SharedPersonTable(_, void"person"))\n\nval homePersonQuery = void"(select * from home_person inner join person on home_person.person_id = person.id)"\nval sharedHomePersonTable = skunkTable(SharedPersonTable(_, homePersonQuery))\n\n// And now using our subquery we can simplify the join.\nimplicit lazy val person: Type[IO, QueryContext[SharedPersonTable]] = ???\n\ntpe[IO, QueryContext[HomeTable]](\n "HomeTable",\n "name" -> query(_.name),\n "address" -> query(_.address),\n "caption" -> query(h => (h.name, h.address).mapN(_ + " at " + _)), // projections form an applicative\n "people" -> cont{ h => \n sharedHomePersonTable.join[List](hp => sql"${h.idCol} = ${hp.aliased(sql"home_id")}")\n }\n)\n')),(0,o.kt)("h2",{id:"runtime-semantics"},"Runtime semantics"),(0,o.kt)("admonition",{type:"info"},(0,o.kt)("p",{parentName:"admonition"},"This section is a technical reference, and not necessary to use the library.")),(0,o.kt)("p",null,"Data emitted by SQL is not hierarchical, but instead flat; for it to map well to graphql, which is hierarchical some work must be performed.\nMost use-cases are covered by simply invoking the ",(0,o.kt)("inlineCode",{parentName:"p"},"join")," method with the proper multiplicity parameter."),(0,o.kt)("p",null,"When your AST is inspected to build a query, a recursive AST walk composes a big reassociation function that can translate flat query results into the proper hierarchical structure.\nThis composed function also tracks the visited columns and their decoders."),(0,o.kt)("p",null,"The query algebra has a special operation that lets the caller modify the state however they wish.\nThe dsl uses this state modification for various tasks, such as providing a convinient ",(0,o.kt)("inlineCode",{parentName:"p"},"join")," method that both joins a table and performs the proper reassociation of results.\nConsider the following example that joins a table more explicitly."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},"val q1 = for {\n ht <- homeTable.simpleJoin(_ => void\"true\")\n _ <- reassociate[List](ht.tableKey)\n // some other reassociation criteria\n _ <- reassociate[Option](select(int4, void\"42\"))\n} yield ht\n// q1: algebra.Query[[X]List[Option[X]], HomeTable] = FlatMap(\n// fa = FlatMap(\n// fa = LiftEffect(fa = EitherT(value = cats.data.IndexedStateT@6d3cf9bf)),\n// f = gql.relational.QueryDsl$$Lambda$13964/0x000000080381d040@e7b48c2\n// ),\n// f = \n// )\n\n// we can perform reassociation before performing the actions in 'q1'\nval q2 = reassociate[Option](select(text, void\"'john doe'\")).flatMap(_ => q1)\n// q2: algebra.Query[[X]Option[List[Option[X]]], HomeTable] = FlatMap(\n// fa = LiftEffect(fa = EitherT(value = cats.data.IndexedStateT@66c59358)),\n// f = \n// )\n\n// we can also change the result structure after performing the actions in 'q2'\nq2.mapK[List](new (\u03bb[X => Option[List[Option[X]]]] ~> List) {\n def apply[A](fa: Option[List[Option[A]]]): List[A] = fa.toList.flatten.flatMap(_.toList)\n})\n// res4: algebra.Query[List, HomeTable] = LiftEffect(\n// fa = EitherT(value = cats.data.IndexedStateT@1a7bba46)\n// )\n")),(0,o.kt)("p",null,"Accessing the lowlevel state also lets the user perform other tasks such as unique id (new alias) generation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},"for {\n alias1 <- newAlias\n alias2 <- newAlias\n} yield ()\n// res5: algebra.Query[[X]X, Unit] = FlatMap(\n// fa = LiftEffect(fa = EitherT(value = cats.data.IndexedStateT@108049b7)),\n// f = \n// )\n")),(0,o.kt)("h2",{id:"implementing-your-own-integration"},"Implementing your own integration"),(0,o.kt)("p",null,"The entire dsl and query compiler is available if you implement a couple of methods."),(0,o.kt)("p",null,"Here is the full skunk integration."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'import _root_.{skunk => sk}\nobject MyIntegration extends QueryAlgebra {\n // What is a fragment\n type Frag = sk.AppliedFragment\n // How do we transform a string into a fragment\n def stringToFrag(s: String): Frag = sql"#${s}".apply(Void)\n // Combine and create empty fragments\n implicit def appliedFragmentMonoid: Monoid[Frag] = sk.AppliedFragment.MonoidAppFragment\n // How do we decode values\n type Decoder[A] = sk.Decoder[A]\n // How can we combine decoders\n implicit def applicativeForDecoder: Applicative[Decoder] = Decoder.ApplicativeDecoder\n // How do we make an optional decoder\n def optDecoder[A](d: Decoder[A]): Decoder[Option[A]] = d.opt\n // What is needed to perform a query\n type Connection[F[_]] = Resource[F, Session[F]]\n // Given a connection, how do we use it\n implicit def skunkQueryable[F[_]: MonadCancelThrow]: Queryable[F] = new Queryable[F] {\n def apply[A](query: AppliedFragment, decoder: Decoder[A], connection: Connection[F]): F[List[A]] =\n connection.use(_.execute(query.fragment.query(decoder))(query.argument))\n }\n}\n')),(0,o.kt)("p",null,"The dsl can be instantiated for any query algebra."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},"object myDsl extends QueryDsl(MyIntegration)\n")),(0,o.kt)("p",null,"you can also add integration specific methods to your dsl."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},"object myDsl extends QueryDsl(MyIntegration) {\n def someOperationSpecificToMyIntegration = ???\n}\n")),(0,o.kt)("h2",{id:"adding-arguments"},"Adding arguments"),(0,o.kt)("p",null,"All field combinators allow arguments to be provided naturally, regardless of where the field is in the query."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'implicit lazy val pt: Type[IO, QueryContext[PersonTable]] = ???\n\ntpe[IO, QueryContext[HomeTable]](\n "HomeTable",\n "people" -> cont(arg[List[Int]]("ids")) { (home, ids) =>\n for {\n hp <- homePersonTable.join[List](hp => sql"${home.idCol} = ${hp.homeCol}")\n p <- personTable.join(p => sql"${hp.personCol} = ${p.idCol} and ${p.idCol} in (${int4.list(ids)})".apply(ids))\n } yield p\n }\n)\n')),(0,o.kt)("h2",{id:"sum-types"},"Sum types"),(0,o.kt)("p",null,"Sum types can naturally be declared also."),(0,o.kt)("details",null,(0,o.kt)("summary",null,"Lets set up some tables for sum types."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'connection.use{ ses =>\n val queries = List(\n sql"drop table if exists owner",\n sql"drop table if exists dog",\n sql"drop table if exists cat",\n sql"""create table owner (\n id int4 primary key\n )""",\n sql"""create table dog (\n id int4 primary key,\n owner_id int4 not null,\n name text not null,\n age int not null\n )""",\n sql"""create table cat (\n id int4 primary key,\n owner_id int4 not null,\n name text not null,\n age int not null\n )""",\n sql"""insert into owner (id) values (1)""",\n sql"""insert into owner (id) values (2)""",\n sql"""insert into dog (id, owner_id, name, age) values (1, 1, \'Dog\', 42)""",\n sql"""insert into cat (id, owner_id, name, age) values (2, 2, \'Cat\', 22)""",\n )\n\n queries.traverse(x => ses.execute(x.command))\n}.unsafeRunSync()\n// res7: List[..skunk.data.Completion] = List(\n// DropTable,\n// DropTable,\n// DropTable,\n// CreateTable,\n// CreateTable,\n// CreateTable,\n// Insert(count = 1),\n// Insert(count = 1),\n// Insert(count = 1),\n// Insert(count = 1)\n// )\n'))),(0,o.kt)("p",null,"And now we can run it."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'sealed trait Animal { \n def name: String\n}\ncase class Dog(owner: String, name: String, age: Int) extends Animal\ncase class Cat(owner: String, name: String, age: Int) extends Animal\n\ntrait OwnerTable extends SkunkTable {\n def table = void"owner"\n val (idCol, id) = sel("id", int4)\n def tableKey = id\n}\ncase class OwnerTableUnion(alias: String) extends OwnerTable\ncase class OwnerTableInterface(alias: String) extends OwnerTable\nval ownerTableUnion = skunkTable(OwnerTableUnion)\n// ownerTableUnion: SkunkTableAlg[OwnerTableUnion] = gql.relational.skunk.dsl$$anon$2@44e92e3d\nval ownerTableInterface = skunkTable(OwnerTableInterface)\n// ownerTableInterface: SkunkTableAlg[OwnerTableInterface] = gql.relational.skunk.dsl$$anon$2@efc2aab\n\ncase class DogTable(alias: String) extends SkunkTable {\n def table = void"dog"\n\n val (idCol, id) = sel("id", int4)\n val (ownerCol, owner) = sel("owner_id", int4)\n val (nameCol, name) = sel("name", text)\n val (ageCol, age) = sel("age", int4)\n\n def tableKey = id\n}\nval dogTable = skunkTable(DogTable)\n// dogTable: SkunkTableAlg[DogTable] = gql.relational.skunk.dsl$$anon$2@499208b8\n\ncase class CatTable(alias: String) extends SkunkTable {\n def table = void"cat"\n\n val (idCol, id) = sel("id", int4)\n val (ownerCol, owner) = sel("owner_id", int4)\n val (nameCol, name) = sel("name", text)\n val (ageCol, age) = sel("age", int4)\n\n def tableKey = id\n}\nval catTable = skunkTable(CatTable)\n// catTable: SkunkTableAlg[CatTable] = gql.relational.skunk.dsl$$anon$2@5b4d2dcc\n\nimplicit lazy val animalInterface = interface[IO, QueryContext[OwnerTableInterface]](\n "AnimalInterface",\n "owner" -> abst[IO, String]\n)\n\nimplicit lazy val cat = tpe[IO, QueryContext[CatTable]](\n "Cat",\n "owner" -> query(_.owner),\n "name" -> query(_.name),\n "age" -> query(_.age)\n).contImplements[OwnerTableInterface]{ owner => \n catTable.join[Option](cat => sql"${owner.idCol} = ${cat.ownerCol}")\n}\n\nimplicit lazy val dog = tpe[IO, QueryContext[DogTable]](\n "Dog",\n "owner" -> query(_.owner),\n "name" -> query(_.name),\n "age" -> query(_.age)\n).contImplements[OwnerTableInterface]{ owner => \n dogTable.join[Option](dog => sql"${owner.idCol} = ${dog.ownerCol}")\n}\n\n// we use the builder to create a union type\nimplicit lazy val animal = relBuilder[IO, OwnerTableUnion] { b =>\n b\n .union("Animal")\n .contVariant(owner => dogTable.join[Option](dog => sql"${owner.idCol} = ${dog.ownerCol}"))\n .contVariant(owner => catTable.join[Option](cat => sql"${owner.idCol} = ${cat.ownerCol}"))\n}\n\ndef schema = gql.Schema.query(\n tpe[IO, Unit](\n "Query",\n "animals" -> runFieldSingle(connection) { (_: Unit) =>\n ownerTableUnion.join[List](_ => sql"true")\n },\n "animalInterfaces" -> runFieldSingle(connection) { (_: Unit) =>\n ownerTableInterface.join[List](_ => sql"true")\n }\n )\n)\n\ndef animalQuery = """\n query {\n animals {\n __typename\n ... on Dog {\n owner\n name\n age\n }\n ... on Cat {\n owner\n name\n age\n }\n }\n animalInterfaces {\n __typename\n ... on Dog {\n owner\n name\n age\n }\n ... on Cat {\n owner\n name\n age\n }\n }\n }\n"""\n\nschema\n .map(Compiler[IO].compile(_, animalQuery))\n .flatMap { case Right(Application.Query(run)) => run.map(_.handleErrors{e => println(e.getMessage()); ""}.asJson.spaces2) }\n .unsafeRunSync()\n// select t1.id, t2.id, t2.age, t2.name, t2.owner_id, t3.id, t3.age, t3.name, t3.owner_id\n// from owner as t1\n// left join dog as t2 on t1.id = t2.owner_id\n// left join cat as t3 on t1.id = t3.owner_id\n// where true\n// select t1.id, t2.id, t2.age, t2.name, t2.owner_id, t3.id, t3.age, t3.name, t3.owner_id\n// from owner as t1\n// left join dog as t2 on t1.id = t2.owner_id\n// left join cat as t3 on t1.id = t3.owner_id\n// where true\n// res8: String = """{\n// "data" : {\n// "animalInterfaces" : [\n// {\n// "__typename" : "Cat",\n// "age" : 22,\n// "name" : "Cat",\n// "owner" : 2\n// },\n// {\n// "__typename" : "Dog",\n// "age" : 42,\n// "name" : "Dog",\n// "owner" : 1\n// }\n// ],\n// "animals" : [\n// {\n// "__typename" : "Cat",\n// "age" : 22,\n// "name" : "Cat",\n// "owner" : 2\n// },\n// {\n// "__typename" : "Dog",\n// "age" : 42,\n// "name" : "Dog",\n// "owner" : 1\n// }\n// ]\n// }\n// }"""\n')),(0,o.kt)("h2",{id:"declaring-complex-subqueries"},"Declaring complex subqueries"),(0,o.kt)("p",null,"Sometimes your tables must have complex filtering, limiting, ordering and so on.\nThe most obvious way to declare such parameters is simply to use a subquery."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'case class ParameterizedPersonTable(alias: String, table: AppliedFragment) extends SkunkTable {\n val (idCol, id) = sel("id", int4)\n val (nameCol, name) = sel("name", text)\n val (ageCol, age) = sel("age", int4)\n \n def tableKey = id\n}\ndef parameterizedPersonTable(\n limitOffset: Option[(Int, Int)],\n order: Option[AppliedFragment],\n filter: Option[AppliedFragment]\n) = skunkTable{ alias => \n val filt = filter.foldMap(f => sql"where ${f.fragment}".apply(f.argument))\n val ord = order.foldMap(f => sql"order by ${f.fragment}".apply(f.argument))\n val lim = \n limitOffset.foldMap{ case (limit, offset) => sql"limit ${int4} offset ${int4}".apply((limit, offset))}\n ParameterizedPersonTable(\n alias,\n sql"""|(\n | select *\n | from person\n | ${filt.fragment}\n | ${ord.fragment}\n | ${lim.fragment}\n |)""".stripMargin.apply((filt.argument, ord.argument, lim.argument))\n )\n}\n')),(0,o.kt)("p",null,"And now we can use our new table."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'implicit lazy val ppt: Type[IO, QueryContext[ParameterizedPersonTable]] = ???\n\nval personQueryArgs = (\n arg[Option[Int]]("limit"),\n arg[Option[Int]]("offset"),\n arg[Option[Boolean]]("order"),\n arg[Option[Int]]("ageFilter")\n).tupled\ntpe[IO, QueryContext[HomeTable]](\n "HomeTable",\n "people" -> cont(personQueryArgs) { case (home, (lim, off, ord, af)) =>\n for {\n hp <- homePersonTable.join[List](hp => sql"${home.idCol} = ${hp.homeCol}")\n p <- parameterizedPersonTable(\n limitOffset = (lim, off).tupled,\n order = ord.map{\n case true => void"age desc"\n case false => void"age asc"\n },\n filter = af.map(age => sql"age > ${int4}".apply(age))\n ).join(p => sql"${hp.personCol} = ${p.idCol}")\n } yield p\n }\n)\n')),(0,o.kt)("h2",{id:"using-relational-without-tables"},"Using relational without tables"),(0,o.kt)("p",null,"There is no restriction on how you can implement a table, so you can choose your own strategy.\nFor instance say we just wanted to declare everything up-front and select fields ad-hoc."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.relational.skunk.SkunkIntegration.Query.Select\n\ncase class AdHocTable(\n alias: String, \n table: AppliedFragment,\n tableKey: Select[?],\n) extends SkunkTable\n\ntpe[IO, QueryContext[HomeTable]](\n "HomeTable",\n "people" -> cont(arg[List[Int]]("ids")) { (home, ids) =>\n for {\n hp <- skunkTable(alias => \n AdHocTable(\n alias, \n sql"#${alias}.home_person".apply(Void), \n select(\n int4 ~ int4,\n sql"#${alias}.home_id".apply(Void), \n sql"#${alias}.person_id".apply(Void)\n )\n )\n ).join[List](hp => sql"${home.idCol} = ${hp.aliased(sql"home_id")}")\n p <- personTable.join(p => sql"${hp.aliased(sql".person_id")} = ${p.idCol} and ${p.idCol} in (${int4.list(ids)})".apply(ids))\n } yield p\n }\n)\n')),(0,o.kt)("p",null,"Since there is no dsl for this, constructing the query is a bit gruesome.\nConsider if a dsl is possible for your formulation."),(0,o.kt)("h2",{id:"running-transactions"},"Running transactions"),(0,o.kt)("p",null,"Most usecases involve running all queries in a transaction, but none of the examples so far have introduces this.\nThe implementation of transactions depends on the database library, but many implementations share common properties."),(0,o.kt)("p",null,"If your database library supports opening transactions as a resource then the you can lazily open a transaction.\nHere is an example using skunk."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'trait SessionContext {\n def getSession: Resource[IO, Session[IO]]\n}\n\nobject SessionContext {\n def fromIOLocal(iol: IOLocal[Option[Resource[IO, Session[IO]]]]) = new SessionContext {\n def getSession = Resource.eval(iol.get).flatMap{\n case None => Resource.eval(IO.raiseError(new Exception("No session in context")))\n case Some(sc) => sc\n }\n }\n}\n\ndef myConnection: Resource[IO, Session[IO]] = Session.single[IO](\n host = "127.0.0.1",\n port = 5432,\n user = "postgres",\n database = "postgres"\n)\n\n// The outer resource manages the lifecycle of the connection\n// The inner resource leases the connection, if the inner resource is not closed, the outer waits\ndef lazyConnection: Resource[IO, LazyResource[IO, Session[IO]]] = \n gql.relational.LazyResource.fromResource(myConnection)\n\n// We define our schema as requiring a connection\ndef myQuery(ctx: SessionContext): Type[IO, Unit] = {\n implicit lazy val homeTableTpe: Out[IO, QueryContext[HomeTable]] = ???\n tpe[IO, Unit](\n "Query",\n "homes" -> runFieldSingle(ctx.getSession) { (_: Unit) => \n homeTable.join[List](_ => sql"true")\n }\n )\n}\n\ndef runQuery: IO[String => Compiler.Outcome[IO]] = \n gql.Statistics[IO].flatMap{ stats => \n IOLocal[Option[Resource[IO, Session[IO]]]](None).map{ loc =>\n val sc = SessionContext.fromIOLocal(loc)\n\n val schema = gql.Schema.query(stats)(myQuery(sc))\n\n val setResource = lazyConnection.evalMap(x => loc.set(Some(x.get)))\n\n (query: String) => \n Compiler[IO]\n .compile(schema, q)\n .map{\n case gql.Application.Query(fa) => gql.Application.Query(setResource.surround(fa))\n case gql.Application.Mutation(fa) => gql.Application.Mutation(setResource.surround(fa))\n // Subscription is a bit more complex since we would like to close the transaction on every event\n case gql.Application.Subscription(fa) => \n gql.Application.Subscription{\n fs2.Stream.resource(lazyConnection).flatMap{ x =>\n fs2.Stream.exec(loc.set(Some(x.get))) ++\n fa.evalTap(_ => x.forceClose)\n }\n }\n }\n }\n }\n')),(0,o.kt)("details",null,(0,o.kt)("summary",null,"You can also use MTL for passing the transaction around"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'import cats.mtl._\n\ndef myConnection: Resource[IO, Session[IO]] = Session.single[IO](\n host = "127.0.0.1",\n port = 5432,\n user = "postgres",\n database = "postgres"\n)\n\n// The outer resource manages the lifecycle of the connection\n// The inner resource leases the connection, if the inner resource is not closed, the outer waits\ndef lazyConnection: Resource[IO, LazyResource[IO, Session[IO]]] = \n gql.relational.LazyResource.fromResource(myConnection)\n\nval liftK = Kleisli.liftK[IO, Resource[IO, Session[IO]]]\n\ntype GetConn[F[_]] = Ask[F, Resource[F, Session[F]]]\n\ndef makeConn[F[_]](conn: GetConn[F]): Resource[F, Session[F]] = \n Resource.eval(conn.ask[Resource[F, Session[F]]]).flatten\n\n// We define our schema as requiring a connection\ndef myQuery[F[_]: Async](conn: GetConn[F]): Type[F, Unit] = {\n implicit lazy val homeTableTpe: Type[F, QueryContext[HomeTable]] = ???\n tpe[F, Unit](\n "Query",\n "homes" -> runFieldSingle(makeConn(conn)) { (_: Unit) => \n homeTable.join[List](_ => sql"true")\n }\n )\n}\n\nimplicit def functorForAsk[F[_]]: Functor[Ask[F, *]] = ???\ndef kleisliAsk[F[_]: Applicative, A] = Ask[Kleisli[F, A, *], A]\n\ndef runQuery: IO[String => Compiler.Outcome[IO]] = \n gql.Statistics[IO].map{ stats => \n type G[A] = Kleisli[IO, Resource[IO, Session[IO]], A]\n\n val liftK = Kleisli.liftK[IO, Resource[IO, Session[IO]]]\n\n val ask: Ask[G, Resource[G, Session[G]]] = \n kleisliAsk[IO, Resource[IO, Session[IO]]].map(_.mapK(liftK).map(_.mapK(liftK)))\n\n val schema = gql.Schema.query(stats.mapK(liftK))(myQuery[G](ask))\n\n val oneshot = lazyConnection.map(_.get.flatTap(_.transaction))\n\n (query: String) => \n Compiler[G]\n .compile(schema, q)\n .map{ \n case gql.Application.Query(fa) => gql.Application.Query(oneshot.useKleisli(fa))\n case gql.Application.Mutation(fa) => gql.Application.Mutation(oneshot.useKleisli(fa))\n // Subscription is a bit more complex since we would like to close the transaction on every event\n case gql.Application.Subscription(fa) => \n gql.Application.Subscription{\n fs2.Stream.resource(lazyConnection).flatMap{ lc =>\n fa\n .translate(Kleisli.applyK[IO, Resource[IO, Session[IO]]](lc.get.flatTap(_.transaction)))\n .evalTap(_ => lc.forceClose)\n }\n }\n }\n }\n'))),(0,o.kt)("h2",{id:"handling-n1"},"Handling N+1"),(0,o.kt)("p",null,"The relational module can handle N+1 queries and queries that can cause cartesian products.\nTo solve N+1, the user must use the ",(0,o.kt)("inlineCode",{parentName:"p"},"runField")," method instead of the ",(0,o.kt)("inlineCode",{parentName:"p"},"runFieldSingle"),".\nThe ",(0,o.kt)("inlineCode",{parentName:"p"},"runField")," method takes a list of inputs ",(0,o.kt)("inlineCode",{parentName:"p"},"I")," and produces ",(0,o.kt)("inlineCode",{parentName:"p"},"Query[G, (Select[I], B)]"),", such that query results can be reassociated with the inputs."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'def myBatchedHomeQuery(conn: Resource[IO, Session[IO]]) = {\n case class MyDatatype(homeId: Int)\n\n tpe[IO, MyDatatype](\n "MyDatatype",\n "home" -> runField[IO, List, MyDatatype, HomeTable](conn) { xs => \n val lst = xs.toList.map(_.homeId)\n for {\n ht <- homeTable.join[List](ht => sql"${ht.idCol} in (${int4.list(lst)})".apply(lst))\n } yield (ht.id.fmap(MyDatatype), ht)\n }\n )\n}\n')),(0,o.kt)("p",null,"To solve the query multiplicity explosions you can use the ",(0,o.kt)("inlineCode",{parentName:"p"},"contBoundary")," which works almost like ",(0,o.kt)("inlineCode",{parentName:"p"},"cont"),", except the query will be split up into two queries."),(0,o.kt)("p",null,"The ",(0,o.kt)("inlineCode",{parentName:"p"},"contBoundary")," function takes two interesting parameters.\nThe first parameter will be a projection of the current query, decoded into ",(0,o.kt)("inlineCode",{parentName:"p"},"B"),".\nThe second parameter turns this ",(0,o.kt)("inlineCode",{parentName:"p"},"B")," into another query, which will be the root of the new query."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-scala"},'def boundaryQuery(conn: Resource[IO, Session[IO]]) = {\n case class MyDatatype(homeId: Int)\n\n relBuilder[IO, HomeTable]{ rb =>\n rb.tpe(\n "HomeTable",\n "people" -> rb.contBoundary(conn){ home =>\n homePersonTable.join[List](hp => sql"${home.idCol} = ${hp.homeCol}").map(_.person)\n }{ (xs: NonEmptyList[Int]) =>\n val lst = xs.toList\n personTable.join(p => sql"${p.idCol} in (${int4.list(lst)})".apply(lst)).map(p => p.id -> p)\n }\n )\n }\n}\n')),(0,o.kt)("admonition",{type:"info"},(0,o.kt)("p",{parentName:"admonition"},"The ",(0,o.kt)("inlineCode",{parentName:"p"},"contBoundary")," is only available in when using the ",(0,o.kt)("inlineCode",{parentName:"p"},"relBuilder"),", since type inference does not work very well."),(0,o.kt)("p",{parentName:"admonition"},"Inference troubles with ",(0,o.kt)("inlineCode",{parentName:"p"},"runField")," can also be alleviated by using the ",(0,o.kt)("inlineCode",{parentName:"p"},"relBuilder"),".")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/677daa8b.b489819d.js b/assets/js/677daa8b.a0a8ccfc.js similarity index 72% rename from assets/js/677daa8b.b489819d.js rename to assets/js/677daa8b.a0a8ccfc.js index 5799782e7..3d42252e1 100644 --- a/assets/js/677daa8b.b489819d.js +++ b/assets/js/677daa8b.a0a8ccfc.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[672],{3905:(e,n,a)=>{a.d(n,{Zo:()=>c,kt:()=>u});var t=a(7294);function r(e,n,a){return n in e?Object.defineProperty(e,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[n]=a,e}function i(e,n){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(e);n&&(t=t.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),a.push.apply(a,t)}return a}function l(e){for(var n=1;n=0||(r[a]=e[a]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var s=t.createContext({}),p=function(e){var n=t.useContext(s),a=n;return e&&(a="function"==typeof e?e(n):l(l({},n),e)),a},c=function(e){var n=p(e.components);return t.createElement(s.Provider,{value:n},e.children)},d={inlineCode:"code",wrapper:function(e){var n=e.children;return t.createElement(t.Fragment,{},n)}},m=t.forwardRef((function(e,n){var a=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),m=p(a),u=r,v=m["".concat(s,".").concat(u)]||m[u]||d[u]||i;return a?t.createElement(v,l(l({ref:n},c),{},{components:a})):t.createElement(v,l({ref:n},c))}));function u(e,n){var a=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=a.length,l=new Array(i);l[0]=m;var o={};for(var s in n)hasOwnProperty.call(n,s)&&(o[s]=n[s]);o.originalType=e,o.mdxType="string"==typeof e?e:r,l[1]=o;for(var p=2;p{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var t=a(7462),r=(a(7294),a(3905));const i={title:"Monadic Resolver DSL"},l=void 0,o={unversionedId:"server/schema/arrow_dsl",id:"server/schema/arrow_dsl",title:"Monadic Resolver DSL",description:"Modelling complex evaluation with Resolvers can be tricky.",source:"@site/docs/server/schema/arrow_dsl.md",sourceDirName:"server/schema",slug:"/server/schema/arrow_dsl",permalink:"/gql/docs/server/schema/arrow_dsl",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/schema/arrow_dsl.md",tags:[],version:"current",frontMatter:{title:"Monadic Resolver DSL"},sidebar:"docs",previous:{title:"The DSL",permalink:"/gql/docs/server/schema/dsl"},next:{title:"Resolvers",permalink:"/gql/docs/server/schema/resolvers"}},s={},p=[{value:"Technical details",id:"technical-details",level:3},{value:"Builder extensions",id:"builder-extensions",level:3},{value:"Composition",id:"composition",level:3},{value:"Toplevel expressions",id:"toplevel-expressions",level:4},{value:"Lifting arguments",id:"lifting-arguments",level:2},{value:"Choice",id:"choice",level:2},{value:"Batching example",id:"batching-example",level:2},{value:"Arrowless final?",id:"arrowless-final",level:2}],c={toc:p};function d(e){let{components:n,...a}=e;return(0,r.kt)("wrapper",(0,t.Z)({},c,a,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Modelling complex evaluation with ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver"),"s can be tricky.\nIt often involves using ",(0,r.kt)("inlineCode",{parentName:"p"},"first")," to pair up an arrow's result with it's input and proceeding with ",(0,r.kt)("inlineCode",{parentName:"p"},"map")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"contramap"),"."),(0,r.kt)("p",null,"Gql introduces a in-language monadic arrow dsl that re-writes a monadic arrow expression into a series of ",(0,r.kt)("inlineCode",{parentName:"p"},"map"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"contramap")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"first")," invocations."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"This feature is akin to the ",(0,r.kt)("inlineCode",{parentName:"p"},"proc")," ",(0,r.kt)("a",{parentName:"p",href:"https://en.wikibooks.org/wiki/Haskell/Arrow_tutorial#Arrow_proc_notation"},"notation in Haskell"),".")),(0,r.kt)("p",null,"Using the notation is straightforward, the same (covariant) combinators for ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," exist in the arrow dsl."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"import gql.resolver._\nimport cats.implicits._\nimport cats.effect._\nimport gql.arrow._\n\n// Bind the effect type (IO) to aid with compiler errors and inference\nval d = dsl[IO]\nimport d._\nval r: Resolver[IO, Int, String] = \n proc[Int] { i: Var[Int] =>\n for {\n a <- i.evalMap(x => IO(x + 2))\n b <- a.evalMap(x => IO(x * 3))\n c <- (a, b).tupled.evalMap{ case (aa, bb) => IO(aa + bb) }\n } yield c.map(_.toString)\n }\n")),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Most syntatic extensions don't make much sense unless the arrow type (Resolver) is bound which requires knowing the effect type. The full monadic arrows language is available as toplevel functions also."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"import gql.arrow.{Language => L}\nL.proc[Resolver[IO, *, *], Int, String] { i =>\n for {\n x <- L.declare[Resolver[IO, *, *], Int, Int](i)(Resolver.lift[IO, Int](z => z * 2))\n y <- L.declare[Resolver[IO, *, *], (Int, Int), String]((x, x).tupled)(Resolver.lift[IO, (Int, Int)]{ case (a, b) => (a + b).toString() })\n } yield y\n}\n// res0: Resolver[IO, Int, String] = gql.resolver.Resolver@747a1da1\n"))),(0,r.kt)("p",null,"The underlying arrow is also available for composition via ",(0,r.kt)("inlineCode",{parentName:"p"},"apply"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"proc[Int] { i =>\n for {\n x <- i(_.evalMap(z => IO(z + 1)))\n out <- x.apply(_.map(_.toString))\n } yield out\n}\n")),(0,r.kt)("h3",{id:"technical-details"},"Technical details"),(0,r.kt)("p",null,"The dsl introduces two datatypes, ",(0,r.kt)("inlineCode",{parentName:"p"},"Var")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"Decl"),"."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"Var")," is a reference to a set of variables that occur in the arrow. ",(0,r.kt)("inlineCode",{parentName:"li"},"Var")," forms an ",(0,r.kt)("inlineCode",{parentName:"li"},"Applicative"),"."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"Decl")," is used to re-write the monadic (",(0,r.kt)("inlineCode",{parentName:"li"},"flatMap"),") structure into an arrow. ",(0,r.kt)("inlineCode",{parentName:"li"},"Decl")," forms a ",(0,r.kt)("inlineCode",{parentName:"li"},"Monad"),".")),(0,r.kt)("p",null,"The primary use of ",(0,r.kt)("inlineCode",{parentName:"p"},"Decl")," is to bind variables.\nEvery transformation on a ",(0,r.kt)("inlineCode",{parentName:"p"},"Var"),"iable introduces a new ",(0,r.kt)("inlineCode",{parentName:"p"},"Var"),"iable which is stored in the ",(0,r.kt)("inlineCode",{parentName:"p"},"Decl")," structure."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Since ",(0,r.kt)("inlineCode",{parentName:"p"},"Var")," forms an ",(0,r.kt)("inlineCode",{parentName:"p"},"Applicative")," that implies that ",(0,r.kt)("inlineCode",{parentName:"p"},"map")," is available for ",(0,r.kt)("inlineCode",{parentName:"p"},"Var"),".\n",(0,r.kt)("inlineCode",{parentName:"p"},"map")," for ",(0,r.kt)("inlineCode",{parentName:"p"},"Var")," is not memoized since it does not lift ",(0,r.kt)("inlineCode",{parentName:"p"},"Var")," into ",(0,r.kt)("inlineCode",{parentName:"p"},"Decl"),".\n",(0,r.kt)("inlineCode",{parentName:"p"},"Var")," has an extension ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/typelevel/cats/blob/c8aabcacd6045b9aed5c8626c4bf5308dd3f4912/core/src/main/scala/cats/arrow/Profunctor.scala#L59"},(0,r.kt)("inlineCode",{parentName:"a"},"rmap"))," which introduces a new ",(0,r.kt)("inlineCode",{parentName:"p"},"Var"),"iable that memoizes the result.\nThat is, the following equivalences holds:"),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"declare((v: Var[A]).map(f))(Resolver.id[F, A]) <-> \n (v: Var[A]).rmap(f) <->\n (v: Var[A]).apply(_.map(f))\n"))),(0,r.kt)("p",null,"Closures are illegal in the dsl, as they are refer to variables that are not guaranteed to be available, so prefer invoking ",(0,r.kt)("inlineCode",{parentName:"p"},"proc")," once per ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"println {\n scala.util.Try {\n proc[Int] { i =>\n for {\n x <- i.evalMap(x => IO(x + 2))\n o <- x.andThen(proc[Int]{ _ =>\n x.rmap(y => y + 2)\n })\n } yield o\n }\n }.toEither.leftMap(_.getMessage)\n}\n// Left(Variable closure error.\n// Variable declared at arrow_dsl.md:70.\n// Compilation initiated at arrow_dsl.md:68.\n// Variables that were not declared in this scope may not be referenced.\n// Example:\n// ```\n// proc[Int]{ i =>\n// for {\n// x <- i.apply(_.map(_ + 1))\n// y <- i.apply(_.andThen(proc[Int]{ _ =>\n// // referencing 'x' here is an error\n// x.apply(_.map(_ + 1))\n// }))\n// } yield y\n// }\n// ```)\n")),(0,r.kt)("h3",{id:"builder-extensions"},"Builder extensions"),(0,r.kt)("p",null,"The dsl includes an extension method to ",(0,r.kt)("inlineCode",{parentName:"p"},"FieldBuilder")," that eases construction of ",(0,r.kt)("inlineCode",{parentName:"p"},"Field"),"s.\nThe dsl also enhances any resolver with a ",(0,r.kt)("inlineCode",{parentName:"p"},"proc")," extension method."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.ast._\n\nval gqlDsl = gql.dsl.GqlDsl[IO]\nimport gqlDsl._\n\nbuilder[Unit]{ b =>\n b.tpe(\n "MyType",\n "field" -> b.proc{ i =>\n for {\n x <- i.evalMap(_ => IO(1 + 2))\n y <- x.rmap(_ + 3)\n } yield y\n },\n "otherField" -> b(_.proc{ i =>\n i.evalMap(_ => IO(1 + 2))\n })\n )\n}\n')),(0,r.kt)("h3",{id:"composition"},"Composition"),(0,r.kt)("p",null,"Sharing common sub-arrows is a desirable property.\nThis can is expressed naturally with the dsl."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"def mulDiv(i: Var[Int]): Decl[Var[Int]] = for {\n x <- i.rmap(_ * 2)\n y <- x.rmap(_ / 2)\n} yield y\n\nproc[Int](mulDiv(_) >>= mulDiv)\n// res4: Resolver[IO, Int, Int] = gql.resolver.Resolver@2e1046e7\n\nproc[Int](mulDiv(_) >>= mulDiv >>= mulDiv)\n// res5: Resolver[IO, Int, Int] = gql.resolver.Resolver@389a4e2c\n")),(0,r.kt)("h4",{id:"toplevel-expressions"},"Toplevel expressions"),(0,r.kt)("p",null,"It is recommended to always work in a scope with your effect type (",(0,r.kt)("inlineCode",{parentName:"p"},"F"),") bound, to ease inference and type signatures.\nThere is however support for toplevel proc resolver expressions."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"def toplevelMulDiv[F[_]](i: Var[Int]): ResolverDecl[F, Var[Int]] = {\n val d = dsl[F]\n import d._\n for {\n x <- i.rmap(_ * 2)\n y <- x.rmap(_ / 2)\n } yield y\n}\n")),(0,r.kt)("p",null,"Passing the dsl as an implicit parameter is also an option."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"def toplevelMulDiv[F[_]](i: Var[Int])(implicit d: ResolverArrowDsl[F]): ResolverDecl[F, Var[Int]] = {\n import d._\n for {\n x <- i.rmap(_ * 2)\n y <- x.rmap(_ / 2)\n } yield y\n}\n")),(0,r.kt)("h2",{id:"lifting-arguments"},"Lifting arguments"),(0,r.kt)("p",null,"Request arguments is made easier by the arrow dsl."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'proc[Int] { i =>\n for {\n x <- i.evalMap(x => IO(x + 2))\n y <- argument(arg[Int]("age"))\n z <- (x, y).tupled.evalMap { case (a, b) => IO(a + b) }\n } yield z\n}\n')),(0,r.kt)("h2",{id:"choice"},"Choice"),(0,r.kt)("p",null,"The dsl also covers ",(0,r.kt)("inlineCode",{parentName:"p"},"ArrowChoice"),"'s ",(0,r.kt)("inlineCode",{parentName:"p"},"choice")," combinator."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'proc[Int] { i =>\n for {\n x <- i.rmap(v => if (v > 5) Left(v) else Right(v))\n y <- x.choice(\n l => l.rmap(_ * 2),\n r => for {\n a <- argument(arg[Int]("age"))\n out <- (a, r, i).tupled.rmap{ case (a, b, c) => a + b + c }\n } yield out\n )\n } yield y\n}\n')),(0,r.kt)("h2",{id:"batching-example"},"Batching example"),(0,r.kt)("p",null,"Some steps commonly occur when writing batched resolvers:"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Pulling an id out of the parent datatype."),(0,r.kt)("li",{parentName:"ol"},"Passing the id to a batching resolver."),(0,r.kt)("li",{parentName:"ol"},"Pairing the batched output with the parent datatype.")),(0,r.kt)("p",null,"This pairing requires some clever use of ",(0,r.kt)("inlineCode",{parentName:"p"},"first")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"contramap/lmap"),".\nThis behaviour is much easier to express monadically since we have access to closures."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'def getAddresses(ids: Set[Int]): IO[Map[Int, String]] =\n IO(ids.toList.map(id => id -> s"Address $id").toMap)\n\ncase class DataType(id: Int, name: String)\nproc[DataType] { i =>\n for {\n id <- i.rmap(_.id)\n r = Resolver.inlineBatch[IO, Int, String](getAddresses).opt\n (addr: Var[Option[String]]) <- id.andThen(r)\n p = (i, addr).tupled\n out <- p.rmap{ case (dt, a) => s"${dt.name} @ ${a.getOrElse("")}" }\n } yield out\n}\n')),(0,r.kt)("h2",{id:"arrowless-final"},"Arrowless final?"),(0,r.kt)("p",null,"Expressions can be declared for any arrow, not just ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver"),".\nThe usefullness of this property is not significant, but an interesting property nonetheless."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"import cats.free._\nimport cats.arrow._\ndef mulDiv[F2[_, _]](v: Var[Int]): Free[DeclAlg[F2, *], Var[Int]] = {\n val d = new Language[F2] {}\n import d._\n // We can ask for the arrow evidence that must occur when some proc compiles us\n askArrow.flatMap{ implicit arrow: Arrow[F2] =>\n for {\n x <- v.rmap(_ * 2)\n y <- x.rmap(_ / 2)\n } yield y\n }\n}\n\nproc[Int] { i =>\n for {\n x <- i.rmap(_ * 2)\n y <- mulDiv(x)\n } yield y\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[672],{3905:(e,n,a)=>{a.d(n,{Zo:()=>c,kt:()=>u});var t=a(7294);function r(e,n,a){return n in e?Object.defineProperty(e,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[n]=a,e}function i(e,n){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(e);n&&(t=t.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),a.push.apply(a,t)}return a}function l(e){for(var n=1;n=0||(r[a]=e[a]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var s=t.createContext({}),p=function(e){var n=t.useContext(s),a=n;return e&&(a="function"==typeof e?e(n):l(l({},n),e)),a},c=function(e){var n=p(e.components);return t.createElement(s.Provider,{value:n},e.children)},d={inlineCode:"code",wrapper:function(e){var n=e.children;return t.createElement(t.Fragment,{},n)}},m=t.forwardRef((function(e,n){var a=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),m=p(a),u=r,v=m["".concat(s,".").concat(u)]||m[u]||d[u]||i;return a?t.createElement(v,l(l({ref:n},c),{},{components:a})):t.createElement(v,l({ref:n},c))}));function u(e,n){var a=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=a.length,l=new Array(i);l[0]=m;var o={};for(var s in n)hasOwnProperty.call(n,s)&&(o[s]=n[s]);o.originalType=e,o.mdxType="string"==typeof e?e:r,l[1]=o;for(var p=2;p{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var t=a(7462),r=(a(7294),a(3905));const i={title:"Monadic Resolver DSL"},l=void 0,o={unversionedId:"server/schema/arrow_dsl",id:"server/schema/arrow_dsl",title:"Monadic Resolver DSL",description:"Modelling complex evaluation with Resolvers can be tricky.",source:"@site/docs/server/schema/arrow_dsl.md",sourceDirName:"server/schema",slug:"/server/schema/arrow_dsl",permalink:"/gql/docs/server/schema/arrow_dsl",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/schema/arrow_dsl.md",tags:[],version:"current",frontMatter:{title:"Monadic Resolver DSL"},sidebar:"docs",previous:{title:"The DSL",permalink:"/gql/docs/server/schema/dsl"},next:{title:"Resolvers",permalink:"/gql/docs/server/schema/resolvers"}},s={},p=[{value:"Technical details",id:"technical-details",level:3},{value:"Builder extensions",id:"builder-extensions",level:3},{value:"Composition",id:"composition",level:3},{value:"Toplevel expressions",id:"toplevel-expressions",level:4},{value:"Lifting arguments",id:"lifting-arguments",level:2},{value:"Choice",id:"choice",level:2},{value:"Batching example",id:"batching-example",level:2},{value:"Arrowless final?",id:"arrowless-final",level:2}],c={toc:p};function d(e){let{components:n,...a}=e;return(0,r.kt)("wrapper",(0,t.Z)({},c,a,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Modelling complex evaluation with ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver"),"s can be tricky.\nIt often involves using ",(0,r.kt)("inlineCode",{parentName:"p"},"first")," to pair up an arrow's result with it's input and proceeding with ",(0,r.kt)("inlineCode",{parentName:"p"},"map")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"contramap"),"."),(0,r.kt)("p",null,"Gql introduces a in-language monadic arrow dsl that re-writes a monadic arrow expression into a series of ",(0,r.kt)("inlineCode",{parentName:"p"},"map"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"contramap")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"first")," invocations."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"This feature is akin to the ",(0,r.kt)("inlineCode",{parentName:"p"},"proc")," ",(0,r.kt)("a",{parentName:"p",href:"https://en.wikibooks.org/wiki/Haskell/Arrow_tutorial#Arrow_proc_notation"},"notation in Haskell"),".")),(0,r.kt)("p",null,"Using the notation is straightforward, the same (covariant) combinators for ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver")," exist in the arrow dsl."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"import gql.resolver._\nimport cats.implicits._\nimport cats.effect._\nimport gql.arrow._\n\n// Bind the effect type (IO) to aid with compiler errors and inference\nval d = dsl[IO]\nimport d._\nval r: Resolver[IO, Int, String] = \n proc[Int] { i: Var[Int] =>\n for {\n a <- i.evalMap(x => IO(x + 2))\n b <- a.evalMap(x => IO(x * 3))\n c <- (a, b).tupled.evalMap{ case (aa, bb) => IO(aa + bb) }\n } yield c.map(_.toString)\n }\n")),(0,r.kt)("details",null,(0,r.kt)("summary",null,"Most syntatic extensions don't make much sense unless the arrow type (Resolver) is bound which requires knowing the effect type. The full monadic arrows language is available as toplevel functions also."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"import gql.arrow.{Language => L}\nL.proc[Resolver[IO, *, *], Int, String] { i =>\n for {\n x <- L.declare[Resolver[IO, *, *], Int, Int](i)(Resolver.lift[IO, Int](z => z * 2))\n y <- L.declare[Resolver[IO, *, *], (Int, Int), String]((x, x).tupled)(Resolver.lift[IO, (Int, Int)]{ case (a, b) => (a + b).toString() })\n } yield y\n}\n// res0: Resolver[IO, Int, String] = gql.resolver.Resolver@32ac8243\n"))),(0,r.kt)("p",null,"The underlying arrow is also available for composition via ",(0,r.kt)("inlineCode",{parentName:"p"},"apply"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"proc[Int] { i =>\n for {\n x <- i(_.evalMap(z => IO(z + 1)))\n out <- x.apply(_.map(_.toString))\n } yield out\n}\n")),(0,r.kt)("h3",{id:"technical-details"},"Technical details"),(0,r.kt)("p",null,"The dsl introduces two datatypes, ",(0,r.kt)("inlineCode",{parentName:"p"},"Var")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"Decl"),"."),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"Var")," is a reference to a set of variables that occur in the arrow. ",(0,r.kt)("inlineCode",{parentName:"li"},"Var")," forms an ",(0,r.kt)("inlineCode",{parentName:"li"},"Applicative"),"."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"Decl")," is used to re-write the monadic (",(0,r.kt)("inlineCode",{parentName:"li"},"flatMap"),") structure into an arrow. ",(0,r.kt)("inlineCode",{parentName:"li"},"Decl")," forms a ",(0,r.kt)("inlineCode",{parentName:"li"},"Monad"),".")),(0,r.kt)("p",null,"The primary use of ",(0,r.kt)("inlineCode",{parentName:"p"},"Decl")," is to bind variables.\nEvery transformation on a ",(0,r.kt)("inlineCode",{parentName:"p"},"Var"),"iable introduces a new ",(0,r.kt)("inlineCode",{parentName:"p"},"Var"),"iable which is stored in the ",(0,r.kt)("inlineCode",{parentName:"p"},"Decl")," structure."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Since ",(0,r.kt)("inlineCode",{parentName:"p"},"Var")," forms an ",(0,r.kt)("inlineCode",{parentName:"p"},"Applicative")," that implies that ",(0,r.kt)("inlineCode",{parentName:"p"},"map")," is available for ",(0,r.kt)("inlineCode",{parentName:"p"},"Var"),".\n",(0,r.kt)("inlineCode",{parentName:"p"},"map")," for ",(0,r.kt)("inlineCode",{parentName:"p"},"Var")," is not memoized since it does not lift ",(0,r.kt)("inlineCode",{parentName:"p"},"Var")," into ",(0,r.kt)("inlineCode",{parentName:"p"},"Decl"),".\n",(0,r.kt)("inlineCode",{parentName:"p"},"Var")," has an extension ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/typelevel/cats/blob/c8aabcacd6045b9aed5c8626c4bf5308dd3f4912/core/src/main/scala/cats/arrow/Profunctor.scala#L59"},(0,r.kt)("inlineCode",{parentName:"a"},"rmap"))," which introduces a new ",(0,r.kt)("inlineCode",{parentName:"p"},"Var"),"iable that memoizes the result.\nThat is, the following equivalences holds:"),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"declare((v: Var[A]).map(f))(Resolver.id[F, A]) <-> \n (v: Var[A]).rmap(f) <->\n (v: Var[A]).apply(_.map(f))\n"))),(0,r.kt)("p",null,"Closures are illegal in the dsl, as they are refer to variables that are not guaranteed to be available, so prefer invoking ",(0,r.kt)("inlineCode",{parentName:"p"},"proc")," once per ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"println {\n scala.util.Try {\n proc[Int] { i =>\n for {\n x <- i.evalMap(x => IO(x + 2))\n o <- x.andThen(proc[Int]{ _ =>\n x.rmap(y => y + 2)\n })\n } yield o\n }\n }.toEither.leftMap(_.getMessage)\n}\n// Left(Variable closure error.\n// Variable declared at arrow_dsl.md:70.\n// Compilation initiated at arrow_dsl.md:68.\n// Variables that were not declared in this scope may not be referenced.\n// Example:\n// ```\n// proc[Int]{ i =>\n// for {\n// x <- i.apply(_.map(_ + 1))\n// y <- i.apply(_.andThen(proc[Int]{ _ =>\n// // referencing 'x' here is an error\n// x.apply(_.map(_ + 1))\n// }))\n// } yield y\n// }\n// ```)\n")),(0,r.kt)("h3",{id:"builder-extensions"},"Builder extensions"),(0,r.kt)("p",null,"The dsl includes an extension method to ",(0,r.kt)("inlineCode",{parentName:"p"},"FieldBuilder")," that eases construction of ",(0,r.kt)("inlineCode",{parentName:"p"},"Field"),"s.\nThe dsl also enhances any resolver with a ",(0,r.kt)("inlineCode",{parentName:"p"},"proc")," extension method."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.ast._\n\nval gqlDsl = gql.dsl.GqlDsl[IO]\nimport gqlDsl._\n\nbuilder[Unit]{ b =>\n b.tpe(\n "MyType",\n "field" -> b.proc{ i =>\n for {\n x <- i.evalMap(_ => IO(1 + 2))\n y <- x.rmap(_ + 3)\n } yield y\n },\n "otherField" -> b(_.proc{ i =>\n i.evalMap(_ => IO(1 + 2))\n })\n )\n}\n')),(0,r.kt)("h3",{id:"composition"},"Composition"),(0,r.kt)("p",null,"Sharing common sub-arrows is a desirable property.\nThis can is expressed naturally with the dsl."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"def mulDiv(i: Var[Int]): Decl[Var[Int]] = for {\n x <- i.rmap(_ * 2)\n y <- x.rmap(_ / 2)\n} yield y\n\nproc[Int](mulDiv(_) >>= mulDiv)\n// res4: Resolver[IO, Int, Int] = gql.resolver.Resolver@33e1504\n\nproc[Int](mulDiv(_) >>= mulDiv >>= mulDiv)\n// res5: Resolver[IO, Int, Int] = gql.resolver.Resolver@4558c855\n")),(0,r.kt)("h4",{id:"toplevel-expressions"},"Toplevel expressions"),(0,r.kt)("p",null,"It is recommended to always work in a scope with your effect type (",(0,r.kt)("inlineCode",{parentName:"p"},"F"),") bound, to ease inference and type signatures.\nThere is however support for toplevel proc resolver expressions."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"def toplevelMulDiv[F[_]](i: Var[Int]): ResolverDecl[F, Var[Int]] = {\n val d = dsl[F]\n import d._\n for {\n x <- i.rmap(_ * 2)\n y <- x.rmap(_ / 2)\n } yield y\n}\n")),(0,r.kt)("p",null,"Passing the dsl as an implicit parameter is also an option."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"def toplevelMulDiv[F[_]](i: Var[Int])(implicit d: ResolverArrowDsl[F]): ResolverDecl[F, Var[Int]] = {\n import d._\n for {\n x <- i.rmap(_ * 2)\n y <- x.rmap(_ / 2)\n } yield y\n}\n")),(0,r.kt)("h2",{id:"lifting-arguments"},"Lifting arguments"),(0,r.kt)("p",null,"Request arguments is made easier by the arrow dsl."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'proc[Int] { i =>\n for {\n x <- i.evalMap(x => IO(x + 2))\n y <- argument(arg[Int]("age"))\n z <- (x, y).tupled.evalMap { case (a, b) => IO(a + b) }\n } yield z\n}\n')),(0,r.kt)("h2",{id:"choice"},"Choice"),(0,r.kt)("p",null,"The dsl also covers ",(0,r.kt)("inlineCode",{parentName:"p"},"ArrowChoice"),"'s ",(0,r.kt)("inlineCode",{parentName:"p"},"choice")," combinator."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'proc[Int] { i =>\n for {\n x <- i.rmap(v => if (v > 5) Left(v) else Right(v))\n y <- x.choice(\n l => l.rmap(_ * 2),\n r => for {\n a <- argument(arg[Int]("age"))\n out <- (a, r, i).tupled.rmap{ case (a, b, c) => a + b + c }\n } yield out\n )\n } yield y\n}\n')),(0,r.kt)("h2",{id:"batching-example"},"Batching example"),(0,r.kt)("p",null,"Some steps commonly occur when writing batched resolvers:"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Pulling an id out of the parent datatype."),(0,r.kt)("li",{parentName:"ol"},"Passing the id to a batching resolver."),(0,r.kt)("li",{parentName:"ol"},"Pairing the batched output with the parent datatype.")),(0,r.kt)("p",null,"This pairing requires some clever use of ",(0,r.kt)("inlineCode",{parentName:"p"},"first")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"contramap/lmap"),".\nThis behaviour is much easier to express monadically since we have access to closures."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'def getAddresses(ids: Set[Int]): IO[Map[Int, String]] =\n IO(ids.toList.map(id => id -> s"Address $id").toMap)\n\ncase class DataType(id: Int, name: String)\nproc[DataType] { i =>\n for {\n id <- i.rmap(_.id)\n r = Resolver.inlineBatch[IO, Int, String](getAddresses).opt\n (addr: Var[Option[String]]) <- id.andThen(r)\n p = (i, addr).tupled\n out <- p.rmap{ case (dt, a) => s"${dt.name} @ ${a.getOrElse("")}" }\n } yield out\n}\n')),(0,r.kt)("h2",{id:"arrowless-final"},"Arrowless final?"),(0,r.kt)("p",null,"Expressions can be declared for any arrow, not just ",(0,r.kt)("inlineCode",{parentName:"p"},"Resolver"),".\nThe usefullness of this property is not significant, but an interesting property nonetheless."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"import cats.free._\nimport cats.arrow._\ndef mulDiv[F2[_, _]](v: Var[Int]): Free[DeclAlg[F2, *], Var[Int]] = {\n val d = new Language[F2] {}\n import d._\n // We can ask for the arrow evidence that must occur when some proc compiles us\n askArrow.flatMap{ implicit arrow: Arrow[F2] =>\n for {\n x <- v.rmap(_ * 2)\n y <- x.rmap(_ / 2)\n } yield y\n }\n}\n\nproc[Int] { i =>\n for {\n x <- i.rmap(_ * 2)\n y <- mulDiv(x)\n } yield y\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8588ea58.4a381287.js b/assets/js/8588ea58.0a065fe3.js similarity index 73% rename from assets/js/8588ea58.4a381287.js rename to assets/js/8588ea58.0a065fe3.js index ac94dcf7c..f414e8ac3 100644 --- a/assets/js/8588ea58.4a381287.js +++ b/assets/js/8588ea58.0a065fe3.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[776],{3905:(e,n,a)=>{a.d(n,{Zo:()=>c,kt:()=>d});var t=a(7294);function r(e,n,a){return n in e?Object.defineProperty(e,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[n]=a,e}function i(e,n){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(e);n&&(t=t.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),a.push.apply(a,t)}return a}function l(e){for(var n=1;n=0||(r[a]=e[a]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var s=t.createContext({}),p=function(e){var n=t.useContext(s),a=n;return e&&(a="function"==typeof e?e(n):l(l({},n),e)),a},c=function(e){var n=p(e.components);return t.createElement(s.Provider,{value:n},e.children)},u={inlineCode:"code",wrapper:function(e){var n=e.children;return t.createElement(t.Fragment,{},n)}},m=t.forwardRef((function(e,n){var a=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),m=p(a),d=r,g=m["".concat(s,".").concat(d)]||m[d]||u[d]||i;return a?t.createElement(g,l(l({ref:n},c),{},{components:a})):t.createElement(g,l({ref:n},c))}));function d(e,n){var a=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=a.length,l=new Array(i);l[0]=m;var o={};for(var s in n)hasOwnProperty.call(n,s)&&(o[s]=n[s]);o.originalType=e,o.mdxType="string"==typeof e?e:r,l[1]=o;for(var p=2;p{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var t=a(7462),r=(a(7294),a(3905));const i={title:"Query DSL"},l=void 0,o={unversionedId:"client/dsl",id:"client/dsl",title:"Query DSL",description:"gql provides a dsl for building graphql queries and response parsers.",source:"@site/docs/client/dsl.md",sourceDirName:"client",slug:"/client/dsl",permalink:"/gql/docs/client/dsl",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/client/dsl.md",tags:[],version:"current",frontMatter:{title:"Query DSL"},sidebar:"docs",previous:{title:"Relational",permalink:"/gql/docs/server/integrations/relational"},next:{title:"Code generation",permalink:"/gql/docs/client/code-generation"}},s={},p=[{value:"Selections",id:"selections",level:2},{value:"Fragments",id:"fragments",level:2},{value:"Variables",id:"variables",level:2},{value:"Execution",id:"execution",level:2}],c={toc:p};function u(e){let{components:n,...a}=e;return(0,r.kt)("wrapper",(0,t.Z)({},c,a,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"gql provides a dsl for building graphql queries and response parsers.\nWhen you compose your query with the dsl, you automatically compose both a query and a json decoder for the query response."),(0,r.kt)("h2",{id:"selections"},"Selections"),(0,r.kt)("p",null,"The simplest combinator is ",(0,r.kt)("inlineCode",{parentName:"p"},"sel")," which declares a field selection:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.client._\nimport gql.client.dsl._\nimport cats.implicits._\n\nsel[Option[String]]("name")\n// res0: SelectionSet[Option[String]] = SelectionSet(\n// impl = Fmap(\n// fa = Lift(\n// fa = Field(\n// fieldName = "name",\n// alias0 = None,\n// args0 = List(),\n// subQuery = OptionModifier(\n// subQuery = Terminal(decoder = io.circe.Decoder$$anon$26@7ad1f762)\n// ),\n// directives0 = List()\n// )\n// ),\n// f = gql.client.SelectionSet$$$Lambda$12550/0x0000000803266840@7b78470\n// )\n// )\n')),(0,r.kt)("p",null,"Most combinators in the dsl have multiple overloads to provide various features."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'sel.build[Option[String]]("name", _.alias("n"))\n\nsel.build[Option[String]]("name", _.args(arg("id", 42)))\n')),(0,r.kt)("p",null,"Every selection related structure forms an ",(0,r.kt)("inlineCode",{parentName:"p"},"Applicative")," such that you can compose multiple selections together:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'val s1 = sel[Option[String]]("name")\n\nval s2 = sel[Option[Int]]("age")\n\nval s3: SelectionSet[(Option[String], Option[Int])] = (s1, s2).tupled\n\nfinal case class PersonQuery(name: Option[String], age: Option[Int])\n\nval pq: SelectionSet[PersonQuery] = (s1, s2).mapN(PersonQuery.apply)\n')),(0,r.kt)("p",null,"Queries can also act as sub-selections (",(0,r.kt)("inlineCode",{parentName:"p"},"SubQuery")," in gql):"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'sel[PersonQuery]("person") {\n pq\n}\n')),(0,r.kt)("p",null,"In the first examples the sub-query is captured implicitly.\nWe can also do this for custom types:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'implicit val pq2: SelectionSet[PersonQuery] = pq\n\nsel[PersonQuery]("person")\n')),(0,r.kt)("h2",{id:"fragments"},"Fragments"),(0,r.kt)("p",null,"Like in graphql we can define fragments to reuse selections:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'val frag = fragment[String]("MyFragment", on="Person") {\n sel[String]("name")\n}\n\nval fragmentSpreads = sel[(Option[String], Option[Int])]("person") {\n (\n fragment.spread(frag),\n inlineFrag[Int]("Person") {\n sel[Int]("age")\n }\n ).tupled\n}\n')),(0,r.kt)("p",null,"Notice that both ",(0,r.kt)("inlineCode",{parentName:"p"},"fragment")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"inlineFrag")," return an optional result.\nThis is because the spread may not match on the type (if the spread condition is a sub-type of the spread-on type).\nThis is not always the desired behavior, and as such, fragments can be required:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"fragment.spread(frag).required: SelectionSet[String]\n")),(0,r.kt)("p",null,"You can provide additional information, should the fragment turn out to actually be missing:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'fragment.spread(frag).requiredFragment("MyFragment", on="Person")\n')),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Fragments should be preferred over re-using selections directly to reduce the rendered query size.")),(0,r.kt)("h2",{id:"variables"},"Variables"),(0,r.kt)("p",null,"Variables are accumulated into a sort of writer monad, such that they can be declared ad-hoc:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'variable[String]("name")\n// res7: Var[String, VariableName[String]] = Var(\n// impl = WriterT(\n// run = (\n// Singleton(\n// a = One(\n// name = VariableName(name = "name"),\n// tpe = "String!",\n// default = None\n// )\n// ),\n// io.circe.Encoder$AsObject$$anon$68@4aaa166d\n// )\n// ),\n// variableNames = VariableName(name = "name")\n// )\n')),(0,r.kt)("p",null,"Variables can be combined with the ",(0,r.kt)("inlineCode",{parentName:"p"},"~")," operator:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'variable[String]("name") ~ variable[Int]("age")\n// res8: Var[(String, Int), (VariableName[String], VariableName[Int])] = Var(\n// impl = WriterT(\n// run = (\n// Append(\n// leftNE = Singleton(\n// a = One(\n// name = VariableName(name = "name"),\n// tpe = "String!",\n// default = None\n// )\n// ),\n// rightNE = Singleton(\n// a = One(\n// name = VariableName(name = "age"),\n// tpe = "Int!",\n// default = None\n// )\n// )\n// ),\n// io.circe.Encoder$AsObject$$anon$68@49486fcd\n// )\n// ),\n// variableNames = (VariableName(name = "name"), VariableName(name = "age"))\n// )\n')),(0,r.kt)("p",null,"Variables can also be declared as omittable, optionally with a default value:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'omittableVariable[String]("name", value("John")) ~\n omittableVariable[Int]("age")\n// res9: Var[(Option[String], Option[Int]), (VariableName[String], VariableName[Int])] = Var(\n// impl = WriterT(\n// run = (\n// Append(\n// leftNE = Singleton(\n// a = One(\n// name = VariableName(name = "name"),\n// tpe = "String!",\n// default = Some(value = StringValue(v = "John", c = ()))\n// )\n// ),\n// rightNE = Singleton(\n// a = One(\n// name = VariableName(name = "age"),\n// tpe = "Int!",\n// default = None\n// )\n// )\n// ),\n// io.circe.Encoder$AsObject$$anon$68@86db9a4\n// )\n// ),\n// variableNames = (VariableName(name = "name"), VariableName(name = "age"))\n// )\n')),(0,r.kt)("p",null,'Variables can be "materialized" into a ',(0,r.kt)("inlineCode",{parentName:"p"},"VariableClosure")," by introducing them to a query:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'// Given a variable of type String, we can construct a query that returns an Int\nval queryWithVariable: VariableClosure[String, Int] = \n variable[String]("name").introduce{ name: VariableName[String] =>\n sel.build[Int]("id", _.args(arg("name", name)))\n }\n')),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"VariableClosure")," can be combined via ",(0,r.kt)("inlineCode",{parentName:"p"},"~")," and have their selections modified via ",(0,r.kt)("inlineCode",{parentName:"p"},"modify"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'def subQuery1: VariableClosure[String, Int] = queryWithVariable\n\ndef subQuery2: VariableClosure[String, Int] = \n variable[String]("name2").introduce{ name: VariableName[String] =>\n sel.build[Int]("id2", _.args(arg("name", name)))\n }\n\ndef combined: VariableClosure[(String, String), Int] = \n (subQuery1 ~ subQuery2).modify(_.map{ case (v1, v2) => v1 + v2 })\n\n// VariableClosure also forms a profunctor so we can also use rmap\n(subQuery1 ~ subQuery2).rmap{ case (v1, v2) => v1 + v2 }\n')),(0,r.kt)("h2",{id:"execution"},"Execution"),(0,r.kt)("p",null,"Once a query has been constructed, there are three ways to wrap it together.\n",(0,r.kt)("inlineCode",{parentName:"p"},"simple")," if the query is parameter-less and name-less, ",(0,r.kt)("inlineCode",{parentName:"p"},"named")," if your query is named and ",(0,r.kt)("inlineCode",{parentName:"p"},"parameterized")," if it is both named and parameterized:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.parser.QueryAst.OperationType\ndef simpleQuery = Query.simple(\n OperationType.Query,\n sel[Unit]("person") {\n (\n sel[Int]("id"),\n sel.build[Int]("age", _.args(arg("numbers", List(42))))\n ).tupled.void\n }\n)\n\nsimpleQuery.compile.query\n// res11: String = "query { person { age( numbers: [42] ), id } }"\n\nQuery.named(\n OperationType.Mutation,\n "MyMutation",\n sel[String]("name")\n).compile.query\n// res12: String = "mutation MyMutation { name }"\n\ndef paramQuery = Query.parameterized(\n OperationType.Subscription,\n "MySubscription",\n combined\n)\n\ndef compiledParamQuery = paramQuery.compile(("first", "second"))\ncompiledParamQuery.query\n// res13: String = """subscription MySubscription( $name : String!, $name2 : String! ) {\n// id2( name: $name2 ),\n// id( name: $name )\n// }"""\n\ncompiledParamQuery.variables\n// res14: Option[io.circe.JsonObject] = Some(\n// value = object[name -> "first",name2 -> "second"]\n// )\n')))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[776],{3905:(e,n,a)=>{a.d(n,{Zo:()=>c,kt:()=>d});var t=a(7294);function r(e,n,a){return n in e?Object.defineProperty(e,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[n]=a,e}function i(e,n){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(e);n&&(t=t.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),a.push.apply(a,t)}return a}function l(e){for(var n=1;n=0||(r[a]=e[a]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var s=t.createContext({}),p=function(e){var n=t.useContext(s),a=n;return e&&(a="function"==typeof e?e(n):l(l({},n),e)),a},c=function(e){var n=p(e.components);return t.createElement(s.Provider,{value:n},e.children)},u={inlineCode:"code",wrapper:function(e){var n=e.children;return t.createElement(t.Fragment,{},n)}},m=t.forwardRef((function(e,n){var a=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),m=p(a),d=r,g=m["".concat(s,".").concat(d)]||m[d]||u[d]||i;return a?t.createElement(g,l(l({ref:n},c),{},{components:a})):t.createElement(g,l({ref:n},c))}));function d(e,n){var a=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=a.length,l=new Array(i);l[0]=m;var o={};for(var s in n)hasOwnProperty.call(n,s)&&(o[s]=n[s]);o.originalType=e,o.mdxType="string"==typeof e?e:r,l[1]=o;for(var p=2;p{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var t=a(7462),r=(a(7294),a(3905));const i={title:"Query DSL"},l=void 0,o={unversionedId:"client/dsl",id:"client/dsl",title:"Query DSL",description:"gql provides a dsl for building graphql queries and response parsers.",source:"@site/docs/client/dsl.md",sourceDirName:"client",slug:"/client/dsl",permalink:"/gql/docs/client/dsl",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/client/dsl.md",tags:[],version:"current",frontMatter:{title:"Query DSL"},sidebar:"docs",previous:{title:"Relational",permalink:"/gql/docs/server/integrations/relational"},next:{title:"Code generation",permalink:"/gql/docs/client/code-generation"}},s={},p=[{value:"Selections",id:"selections",level:2},{value:"Fragments",id:"fragments",level:2},{value:"Variables",id:"variables",level:2},{value:"Execution",id:"execution",level:2}],c={toc:p};function u(e){let{components:n,...a}=e;return(0,r.kt)("wrapper",(0,t.Z)({},c,a,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"gql provides a dsl for building graphql queries and response parsers.\nWhen you compose your query with the dsl, you automatically compose both a query and a json decoder for the query response."),(0,r.kt)("h2",{id:"selections"},"Selections"),(0,r.kt)("p",null,"The simplest combinator is ",(0,r.kt)("inlineCode",{parentName:"p"},"sel")," which declares a field selection:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.client._\nimport gql.client.dsl._\nimport cats.implicits._\n\nsel[Option[String]]("name")\n// res0: SelectionSet[Option[String]] = SelectionSet(\n// impl = Fmap(\n// fa = Lift(\n// fa = Field(\n// fieldName = "name",\n// alias0 = None,\n// args0 = List(),\n// subQuery = OptionModifier(\n// subQuery = Terminal(decoder = io.circe.Decoder$$anon$26@2742580c)\n// ),\n// directives0 = List()\n// )\n// ),\n// f = gql.client.SelectionSet$$$Lambda$12491/0x0000000803266840@b3d51a1\n// )\n// )\n')),(0,r.kt)("p",null,"Most combinators in the dsl have multiple overloads to provide various features."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'sel.build[Option[String]]("name", _.alias("n"))\n\nsel.build[Option[String]]("name", _.args(arg("id", 42)))\n')),(0,r.kt)("p",null,"Every selection related structure forms an ",(0,r.kt)("inlineCode",{parentName:"p"},"Applicative")," such that you can compose multiple selections together:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'val s1 = sel[Option[String]]("name")\n\nval s2 = sel[Option[Int]]("age")\n\nval s3: SelectionSet[(Option[String], Option[Int])] = (s1, s2).tupled\n\nfinal case class PersonQuery(name: Option[String], age: Option[Int])\n\nval pq: SelectionSet[PersonQuery] = (s1, s2).mapN(PersonQuery.apply)\n')),(0,r.kt)("p",null,"Queries can also act as sub-selections (",(0,r.kt)("inlineCode",{parentName:"p"},"SubQuery")," in gql):"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'sel[PersonQuery]("person") {\n pq\n}\n')),(0,r.kt)("p",null,"In the first examples the sub-query is captured implicitly.\nWe can also do this for custom types:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'implicit val pq2: SelectionSet[PersonQuery] = pq\n\nsel[PersonQuery]("person")\n')),(0,r.kt)("h2",{id:"fragments"},"Fragments"),(0,r.kt)("p",null,"Like in graphql we can define fragments to reuse selections:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'val frag = fragment[String]("MyFragment", on="Person") {\n sel[String]("name")\n}\n\nval fragmentSpreads = sel[(Option[String], Option[Int])]("person") {\n (\n fragment.spread(frag),\n inlineFrag[Int]("Person") {\n sel[Int]("age")\n }\n ).tupled\n}\n')),(0,r.kt)("p",null,"Notice that both ",(0,r.kt)("inlineCode",{parentName:"p"},"fragment")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"inlineFrag")," return an optional result.\nThis is because the spread may not match on the type (if the spread condition is a sub-type of the spread-on type).\nThis is not always the desired behavior, and as such, fragments can be required:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"fragment.spread(frag).required: SelectionSet[String]\n")),(0,r.kt)("p",null,"You can provide additional information, should the fragment turn out to actually be missing:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'fragment.spread(frag).requiredFragment("MyFragment", on="Person")\n')),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Fragments should be preferred over re-using selections directly to reduce the rendered query size.")),(0,r.kt)("h2",{id:"variables"},"Variables"),(0,r.kt)("p",null,"Variables are accumulated into a sort of writer monad, such that they can be declared ad-hoc:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'variable[String]("name")\n// res7: Var[String, VariableName[String]] = Var(\n// impl = WriterT(\n// run = (\n// Singleton(\n// a = One(\n// name = VariableName(name = "name"),\n// tpe = "String!",\n// default = None\n// )\n// ),\n// io.circe.Encoder$AsObject$$anon$68@647df9d3\n// )\n// ),\n// variableNames = VariableName(name = "name")\n// )\n')),(0,r.kt)("p",null,"Variables can be combined with the ",(0,r.kt)("inlineCode",{parentName:"p"},"~")," operator:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'variable[String]("name") ~ variable[Int]("age")\n// res8: Var[(String, Int), (VariableName[String], VariableName[Int])] = Var(\n// impl = WriterT(\n// run = (\n// Append(\n// leftNE = Singleton(\n// a = One(\n// name = VariableName(name = "name"),\n// tpe = "String!",\n// default = None\n// )\n// ),\n// rightNE = Singleton(\n// a = One(\n// name = VariableName(name = "age"),\n// tpe = "Int!",\n// default = None\n// )\n// )\n// ),\n// io.circe.Encoder$AsObject$$anon$68@19f500f8\n// )\n// ),\n// variableNames = (VariableName(name = "name"), VariableName(name = "age"))\n// )\n')),(0,r.kt)("p",null,"Variables can also be declared as omittable, optionally with a default value:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'omittableVariable[String]("name", value("John")) ~\n omittableVariable[Int]("age")\n// res9: Var[(Option[String], Option[Int]), (VariableName[String], VariableName[Int])] = Var(\n// impl = WriterT(\n// run = (\n// Append(\n// leftNE = Singleton(\n// a = One(\n// name = VariableName(name = "name"),\n// tpe = "String!",\n// default = Some(value = StringValue(v = "John", c = ()))\n// )\n// ),\n// rightNE = Singleton(\n// a = One(\n// name = VariableName(name = "age"),\n// tpe = "Int!",\n// default = None\n// )\n// )\n// ),\n// io.circe.Encoder$AsObject$$anon$68@6c6b3c48\n// )\n// ),\n// variableNames = (VariableName(name = "name"), VariableName(name = "age"))\n// )\n')),(0,r.kt)("p",null,'Variables can be "materialized" into a ',(0,r.kt)("inlineCode",{parentName:"p"},"VariableClosure")," by introducing them to a query:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'// Given a variable of type String, we can construct a query that returns an Int\nval queryWithVariable: VariableClosure[String, Int] = \n variable[String]("name").introduce{ name: VariableName[String] =>\n sel.build[Int]("id", _.args(arg("name", name)))\n }\n')),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"VariableClosure")," can be combined via ",(0,r.kt)("inlineCode",{parentName:"p"},"~")," and have their selections modified via ",(0,r.kt)("inlineCode",{parentName:"p"},"modify"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'def subQuery1: VariableClosure[String, Int] = queryWithVariable\n\ndef subQuery2: VariableClosure[String, Int] = \n variable[String]("name2").introduce{ name: VariableName[String] =>\n sel.build[Int]("id2", _.args(arg("name", name)))\n }\n\ndef combined: VariableClosure[(String, String), Int] = \n (subQuery1 ~ subQuery2).modify(_.map{ case (v1, v2) => v1 + v2 })\n\n// VariableClosure also forms a profunctor so we can also use rmap\n(subQuery1 ~ subQuery2).rmap{ case (v1, v2) => v1 + v2 }\n')),(0,r.kt)("h2",{id:"execution"},"Execution"),(0,r.kt)("p",null,"Once a query has been constructed, there are three ways to wrap it together.\n",(0,r.kt)("inlineCode",{parentName:"p"},"simple")," if the query is parameter-less and name-less, ",(0,r.kt)("inlineCode",{parentName:"p"},"named")," if your query is named and ",(0,r.kt)("inlineCode",{parentName:"p"},"parameterized")," if it is both named and parameterized:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.parser.QueryAst.OperationType\ndef simpleQuery = Query.simple(\n OperationType.Query,\n sel[Unit]("person") {\n (\n sel[Int]("id"),\n sel.build[Int]("age", _.args(arg("numbers", List(42))))\n ).tupled.void\n }\n)\n\nsimpleQuery.compile.query\n// res11: String = "query { person { age( numbers: [42] ), id } }"\n\nQuery.named(\n OperationType.Mutation,\n "MyMutation",\n sel[String]("name")\n).compile.query\n// res12: String = "mutation MyMutation { name }"\n\ndef paramQuery = Query.parameterized(\n OperationType.Subscription,\n "MySubscription",\n combined\n)\n\ndef compiledParamQuery = paramQuery.compile(("first", "second"))\ncompiledParamQuery.query\n// res13: String = """subscription MySubscription( $name : String!, $name2 : String! ) {\n// id2( name: $name2 ),\n// id( name: $name )\n// }"""\n\ncompiledParamQuery.variables\n// res14: Option[io.circe.JsonObject] = Some(\n// value = object[name -> "first",name2 -> "second"]\n// )\n')))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/92cae478.d4fc4188.js b/assets/js/92cae478.934d8266.js similarity index 99% rename from assets/js/92cae478.d4fc4188.js rename to assets/js/92cae478.934d8266.js index cbf06fd3b..07ed3dfa7 100644 --- a/assets/js/92cae478.d4fc4188.js +++ b/assets/js/92cae478.934d8266.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[633],{3905:(e,n,r)=>{r.d(n,{Zo:()=>u,kt:()=>d});var t=r(7294);function a(e,n,r){return n in e?Object.defineProperty(e,n,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[n]=r,e}function i(e,n){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(e);n&&(t=t.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),r.push.apply(r,t)}return r}function l(e){for(var n=1;n=0||(a[r]=e[r]);return a}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var s=t.createContext({}),c=function(e){var n=t.useContext(s),r=n;return e&&(r="function"==typeof e?e(n):l(l({},n),e)),r},u=function(e){var n=c(e.components);return t.createElement(s.Provider,{value:n},e.children)},p={inlineCode:"code",wrapper:function(e){var n=e.children;return t.createElement(t.Fragment,{},n)}},m=t.forwardRef((function(e,n){var r=e.components,a=e.mdxType,i=e.originalType,s=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),m=c(r),d=a,f=m["".concat(s,".").concat(d)]||m[d]||p[d]||i;return r?t.createElement(f,l(l({ref:n},u),{},{components:r})):t.createElement(f,l({ref:n},u))}));function d(e,n){var r=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var i=r.length,l=new Array(i);l[0]=m;var o={};for(var s in n)hasOwnProperty.call(n,s)&&(o[s]=n[s]);o.originalType=e,o.mdxType="string"==typeof e?e:a,l[1]=o;for(var c=2;c{r.r(n),r.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>p,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var t=r(7462),a=(r(7294),r(3905));const i={title:"Error handling"},l=void 0,o={unversionedId:"server/schema/error_handling",id:"server/schema/error_handling",title:"Error handling",description:"There are different types of errors in gql.",source:"@site/docs/server/schema/error_handling.md",sourceDirName:"server/schema",slug:"/server/schema/error_handling",permalink:"/gql/docs/server/schema/error_handling",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/schema/error_handling.md",tags:[],version:"current",frontMatter:{title:"Error handling"},sidebar:"docs",previous:{title:"Context",permalink:"/gql/docs/server/schema/context"},next:{title:"Compiler",permalink:"/gql/docs/server/schema/compiler"}},s={},c=[{value:"Execution",id:"execution",level:2},{value:"Examples",id:"examples",level:2},{value:"Exception trick",id:"exception-trick",level:3}],u={toc:c};function p(e){let{components:n,...i}=e;return(0,a.kt)("wrapper",(0,t.Z)({},u,i,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"There are different types of errors in gql."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Schema validation errors, which should be caught in development.\nThese are for instance caused by duplicate field names or invalid typenames."),(0,a.kt)("li",{parentName:"ul"},"Query preparation errors, which are errors caused by invalid queries."),(0,a.kt)("li",{parentName:"ul"},"Execuion errors. These are errors that occur during query evaluation, caused by resolvers that fail.")),(0,a.kt)("h2",{id:"execution"},"Execution"),(0,a.kt)("p",null,"Error handling in gql can be performed in two ways, it can be returned explicitly or raised in ",(0,a.kt)("inlineCode",{parentName:"p"},"F"),"."),(0,a.kt)("h2",{id:"examples"},"Examples"),(0,a.kt)("p",null,"Let's setup the scene:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.ast._\nimport gql.dsl.all._\nimport gql.dsl.all.value._\nimport gql._\nimport cats.implicits._\nimport cats.data._\nimport cats.effect._\nimport cats.effect.unsafe.implicits.global\nimport io.circe.syntax._\n \ndef multifailSchema = \n tpe[IO, Unit](\n "Query", \n "field" -> build.from(arged(arg[Int]("i", scalar(10))).evalMap{ \n case 0 => IO.pure(Ior.left("fail gracefully"))\n case 1 => IO.raiseError(new Exception("fail hard"))\n case i => IO.pure(Ior.right(i))\n }.rethrow)\n )\n\ndef go(query: String, tpe: Type[IO, Unit] = multifailSchema) = \n Schema.query(tpe).flatMap { sch =>\n Compiler[IO].compile(sch, query) match {\n case Left(err) => \n println(err)\n IO.pure(err.asJson)\n case Right(Application.Query(fa)) => \n fa.map{x => println(x.errors);x.asJson }\n }\n }.unsafeRunSync()\n \ngo("query { field }")\n// Chain()\n// res0: io.circe.Json = JObject(\n// value = object[data -> {\n// "field" : 10\n// }]\n// )\n')),(0,a.kt)("p",null,"A query can fail gracefully by returning ",(0,a.kt)("inlineCode",{parentName:"p"},"Ior.left"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'go("query { field(i: 0) }")\n// Chain(Error(Right(fail gracefully),Chain("field")))\n// res1: io.circe.Json = JObject(\n// value = object[data -> {\n// "field" : null\n// },errors -> [\n// {\n// "message" : "fail gracefully",\n// "path" : [\n// "field"\n// ]\n// }\n// ]]\n// )\n')),(0,a.kt)("p",null,"A query can fail hard by raising an exception:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'go("query { field(i: 1) }")\n// Chain(Error(Left(java.lang.Exception: fail hard),Chain("field")))\n// res2: io.circe.Json = JObject(\n// value = object[data -> {\n// "field" : null\n// },errors -> [\n// {\n// "message" : "internal error",\n// "path" : [\n// "field"\n// ]\n// }\n// ]]\n// )\n')),(0,a.kt)("p",null,"A query can also fail before even evaluating the query:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'go("query { nonExisting }")\n// Preparation(Chain(PositionalError(Cursor(Chain()),List(Caret(0,8,8)),Field \'nonExisting\' is not a member of `Query`.)))\n// res3: io.circe.Json = JObject(\n// value = object[errors -> [\n// {\n// "message" : "Field \'nonExisting\' is not a member of `Query`.",\n// "locations" : [\n// {\n// "line" : 0,\n// "column" : 8\n// }\n// ]\n// }\n// ]]\n// )\n')),(0,a.kt)("p",null,"And finally, it can fail if it isn't parsable:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'def largerQuery = """\n query {\n field1\n field2(test: 42)\n }\n \n fragment test on Test {\n -value1\n value2 \n }\n"""\n\ngo(largerQuery)\n// Parse(ParseError(Caret(8,4,80),cats.Always@730a9839))\n// res4: io.circe.Json = JObject(\n// value = object[errors -> [\n// {\n// "message" : "could not parse query",\n// "locations" : [\n// {\n// "line" : 8,\n// "column" : 4\n// }\n// ],\n// "error" : "\\u001b[34mfailed at offset 80 on line 7 with code 45\\none of \\"...\\"\\nin char in range A to Z (code 65 to 90)\\nin char in range _ to _ (code 95 to 95)\\nin char in range a to z (code 97 to 122)\\nfor document:\\n\\u001b[0m\\u001b[32m| \\u001b[0m\\u001b[32m\\n| query {\\n| field1\\n| field2(test: 42)\\n| }\\n| \\n| fragment test on Test {\\n| \\u001b[41m\\u001b[30m-\\u001b[0m\\u001b[32mvalue1\\n| \\u001b[31m>^^^^^^^ line:7, column:4, offset:80, character code code:45\\u001b[0m\\u001b[32m\\n| value2 \\n| }\\n| \\u001b[0m\\u001b[0m"\n// }\n// ]]\n// )\n')),(0,a.kt)("p",null,"Parser errors also look nice in ANSI terminals:"),(0,a.kt)("p",null,(0,a.kt)("img",{alt:"Terminal output",src:r(4543).Z,width:"350",height:"329"})),(0,a.kt)("h3",{id:"exception-trick"},"Exception trick"),(0,a.kt)("p",null,"If for whatever reason you wish to pass information through exceptions, that is also possible:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'final case class MyException(msg: String, data: Int) extends Exception(msg)\n\nval res = \n Schema.query(\n tpe[IO, Unit](\n "Query",\n "field" -> eff(_ => IO.raiseError[String](MyException("fail hard", 42)))\n )\n ).flatMap { sch =>\n Compiler[IO].compile(sch, "query { field } ") match {\n case Right(Application.Query(run)) => run\n }\n }.unsafeRunSync()\n// res: QueryResult = QueryResult(\n// data = object[field -> null],\n// errors = Singleton(\n// a = Error(\n// error = Left(value = MyException(msg = "fail hard", data = 42)),\n// path = Singleton(a = JString(value = "field"))\n// )\n// )\n// )\n \nres.errors.headOption.flatMap(_.error.left.toOption) match {\n case Some(MyException(_, data)) => println(s"Got data: $data")\n case _ => println("No data")\n}\n// Got data: 42\n')))}p.isMDXComponent=!0},4543:(e,n,r)=>{r.d(n,{Z:()=>t});const t=r.p+"assets/images/error_image-7805f49e8b21d536040a6e281835df41.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[633],{3905:(e,n,r)=>{r.d(n,{Zo:()=>u,kt:()=>d});var t=r(7294);function a(e,n,r){return n in e?Object.defineProperty(e,n,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[n]=r,e}function i(e,n){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(e);n&&(t=t.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),r.push.apply(r,t)}return r}function l(e){for(var n=1;n=0||(a[r]=e[r]);return a}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var s=t.createContext({}),c=function(e){var n=t.useContext(s),r=n;return e&&(r="function"==typeof e?e(n):l(l({},n),e)),r},u=function(e){var n=c(e.components);return t.createElement(s.Provider,{value:n},e.children)},p={inlineCode:"code",wrapper:function(e){var n=e.children;return t.createElement(t.Fragment,{},n)}},m=t.forwardRef((function(e,n){var r=e.components,a=e.mdxType,i=e.originalType,s=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),m=c(r),d=a,f=m["".concat(s,".").concat(d)]||m[d]||p[d]||i;return r?t.createElement(f,l(l({ref:n},u),{},{components:r})):t.createElement(f,l({ref:n},u))}));function d(e,n){var r=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var i=r.length,l=new Array(i);l[0]=m;var o={};for(var s in n)hasOwnProperty.call(n,s)&&(o[s]=n[s]);o.originalType=e,o.mdxType="string"==typeof e?e:a,l[1]=o;for(var c=2;c{r.r(n),r.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>p,frontMatter:()=>i,metadata:()=>o,toc:()=>c});var t=r(7462),a=(r(7294),r(3905));const i={title:"Error handling"},l=void 0,o={unversionedId:"server/schema/error_handling",id:"server/schema/error_handling",title:"Error handling",description:"There are different types of errors in gql.",source:"@site/docs/server/schema/error_handling.md",sourceDirName:"server/schema",slug:"/server/schema/error_handling",permalink:"/gql/docs/server/schema/error_handling",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/schema/error_handling.md",tags:[],version:"current",frontMatter:{title:"Error handling"},sidebar:"docs",previous:{title:"Context",permalink:"/gql/docs/server/schema/context"},next:{title:"Compiler",permalink:"/gql/docs/server/schema/compiler"}},s={},c=[{value:"Execution",id:"execution",level:2},{value:"Examples",id:"examples",level:2},{value:"Exception trick",id:"exception-trick",level:3}],u={toc:c};function p(e){let{components:n,...i}=e;return(0,a.kt)("wrapper",(0,t.Z)({},u,i,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"There are different types of errors in gql."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Schema validation errors, which should be caught in development.\nThese are for instance caused by duplicate field names or invalid typenames."),(0,a.kt)("li",{parentName:"ul"},"Query preparation errors, which are errors caused by invalid queries."),(0,a.kt)("li",{parentName:"ul"},"Execuion errors. These are errors that occur during query evaluation, caused by resolvers that fail.")),(0,a.kt)("h2",{id:"execution"},"Execution"),(0,a.kt)("p",null,"Error handling in gql can be performed in two ways, it can be returned explicitly or raised in ",(0,a.kt)("inlineCode",{parentName:"p"},"F"),"."),(0,a.kt)("h2",{id:"examples"},"Examples"),(0,a.kt)("p",null,"Let's setup the scene:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.ast._\nimport gql.dsl.all._\nimport gql.dsl.all.value._\nimport gql._\nimport cats.implicits._\nimport cats.data._\nimport cats.effect._\nimport cats.effect.unsafe.implicits.global\nimport io.circe.syntax._\n \ndef multifailSchema = \n tpe[IO, Unit](\n "Query", \n "field" -> build.from(arged(arg[Int]("i", scalar(10))).evalMap{ \n case 0 => IO.pure(Ior.left("fail gracefully"))\n case 1 => IO.raiseError(new Exception("fail hard"))\n case i => IO.pure(Ior.right(i))\n }.rethrow)\n )\n\ndef go(query: String, tpe: Type[IO, Unit] = multifailSchema) = \n Schema.query(tpe).flatMap { sch =>\n Compiler[IO].compile(sch, query) match {\n case Left(err) => \n println(err)\n IO.pure(err.asJson)\n case Right(Application.Query(fa)) => \n fa.map{x => println(x.errors);x.asJson }\n }\n }.unsafeRunSync()\n \ngo("query { field }")\n// Chain()\n// res0: io.circe.Json = JObject(\n// value = object[data -> {\n// "field" : 10\n// }]\n// )\n')),(0,a.kt)("p",null,"A query can fail gracefully by returning ",(0,a.kt)("inlineCode",{parentName:"p"},"Ior.left"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'go("query { field(i: 0) }")\n// Chain(Error(Right(fail gracefully),Chain("field")))\n// res1: io.circe.Json = JObject(\n// value = object[data -> {\n// "field" : null\n// },errors -> [\n// {\n// "message" : "fail gracefully",\n// "path" : [\n// "field"\n// ]\n// }\n// ]]\n// )\n')),(0,a.kt)("p",null,"A query can fail hard by raising an exception:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'go("query { field(i: 1) }")\n// Chain(Error(Left(java.lang.Exception: fail hard),Chain("field")))\n// res2: io.circe.Json = JObject(\n// value = object[data -> {\n// "field" : null\n// },errors -> [\n// {\n// "message" : "internal error",\n// "path" : [\n// "field"\n// ]\n// }\n// ]]\n// )\n')),(0,a.kt)("p",null,"A query can also fail before even evaluating the query:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'go("query { nonExisting }")\n// Preparation(Chain(PositionalError(Cursor(Chain()),List(Caret(0,8,8)),Field \'nonExisting\' is not a member of `Query`.)))\n// res3: io.circe.Json = JObject(\n// value = object[errors -> [\n// {\n// "message" : "Field \'nonExisting\' is not a member of `Query`.",\n// "locations" : [\n// {\n// "line" : 0,\n// "column" : 8\n// }\n// ]\n// }\n// ]]\n// )\n')),(0,a.kt)("p",null,"And finally, it can fail if it isn't parsable:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'def largerQuery = """\n query {\n field1\n field2(test: 42)\n }\n \n fragment test on Test {\n -value1\n value2 \n }\n"""\n\ngo(largerQuery)\n// Parse(ParseError(Caret(8,4,80),cats.Always@294f3a08))\n// res4: io.circe.Json = JObject(\n// value = object[errors -> [\n// {\n// "message" : "could not parse query",\n// "locations" : [\n// {\n// "line" : 8,\n// "column" : 4\n// }\n// ],\n// "error" : "\\u001b[34mfailed at offset 80 on line 7 with code 45\\none of \\"...\\"\\nin char in range A to Z (code 65 to 90)\\nin char in range _ to _ (code 95 to 95)\\nin char in range a to z (code 97 to 122)\\nfor document:\\n\\u001b[0m\\u001b[32m| \\u001b[0m\\u001b[32m\\n| query {\\n| field1\\n| field2(test: 42)\\n| }\\n| \\n| fragment test on Test {\\n| \\u001b[41m\\u001b[30m-\\u001b[0m\\u001b[32mvalue1\\n| \\u001b[31m>^^^^^^^ line:7, column:4, offset:80, character code code:45\\u001b[0m\\u001b[32m\\n| value2 \\n| }\\n| \\u001b[0m\\u001b[0m"\n// }\n// ]]\n// )\n')),(0,a.kt)("p",null,"Parser errors also look nice in ANSI terminals:"),(0,a.kt)("p",null,(0,a.kt)("img",{alt:"Terminal output",src:r(4543).Z,width:"350",height:"329"})),(0,a.kt)("h3",{id:"exception-trick"},"Exception trick"),(0,a.kt)("p",null,"If for whatever reason you wish to pass information through exceptions, that is also possible:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'final case class MyException(msg: String, data: Int) extends Exception(msg)\n\nval res = \n Schema.query(\n tpe[IO, Unit](\n "Query",\n "field" -> eff(_ => IO.raiseError[String](MyException("fail hard", 42)))\n )\n ).flatMap { sch =>\n Compiler[IO].compile(sch, "query { field } ") match {\n case Right(Application.Query(run)) => run\n }\n }.unsafeRunSync()\n// res: QueryResult = QueryResult(\n// data = object[field -> null],\n// errors = Singleton(\n// a = Error(\n// error = Left(value = MyException(msg = "fail hard", data = 42)),\n// path = Singleton(a = JString(value = "field"))\n// )\n// )\n// )\n \nres.errors.headOption.flatMap(_.error.left.toOption) match {\n case Some(MyException(_, data)) => println(s"Got data: $data")\n case _ => println("No data")\n}\n// Got data: 42\n')))}p.isMDXComponent=!0},4543:(e,n,r)=>{r.d(n,{Z:()=>t});const t=r.p+"assets/images/error_image-7805f49e8b21d536040a6e281835df41.png"}}]); \ No newline at end of file diff --git a/assets/js/ceb10064.4b885e55.js b/assets/js/ceb10064.94908964.js similarity index 95% rename from assets/js/ceb10064.4b885e55.js rename to assets/js/ceb10064.94908964.js index 1284451c2..e13e9bce9 100644 --- a/assets/js/ceb10064.4b885e55.js +++ b/assets/js/ceb10064.94908964.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[143],{3905:(e,a,t)=>{t.d(a,{Zo:()=>o,kt:()=>d});var n=t(7294);function s(e,a,t){return a in e?Object.defineProperty(e,a,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[a]=t,e}function r(e,a){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);a&&(n=n.filter((function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),t.push.apply(t,n)}return t}function l(e){for(var a=1;a=0||(s[t]=e[t]);return s}(e,a);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(s[t]=e[t])}return s}var p=n.createContext({}),i=function(e){var a=n.useContext(p),t=a;return e&&(t="function"==typeof e?e(a):l(l({},a),e)),t},o=function(e){var a=i(e.components);return n.createElement(p.Provider,{value:a},e.children)},c={inlineCode:"code",wrapper:function(e){var a=e.children;return n.createElement(n.Fragment,{},a)}},h=n.forwardRef((function(e,a){var t=e.components,s=e.mdxType,r=e.originalType,p=e.parentName,o=m(e,["components","mdxType","originalType","parentName"]),h=i(t),d=s,u=h["".concat(p,".").concat(d)]||h[d]||c[d]||r;return t?n.createElement(u,l(l({ref:a},o),{},{components:t})):n.createElement(u,l({ref:a},o))}));function d(e,a){var t=arguments,s=a&&a.mdxType;if("string"==typeof e||s){var r=t.length,l=new Array(r);l[0]=h;var m={};for(var p in a)hasOwnProperty.call(a,p)&&(m[p]=a[p]);m.originalType=e,m.mdxType="string"==typeof e?e:s,l[1]=m;for(var i=2;i{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>l,default:()=>c,frontMatter:()=>r,metadata:()=>m,toc:()=>i});var n=t(7462),s=(t(7294),t(3905));const r={title:"Planning"},l=void 0,m={unversionedId:"server/execution/planning",id:"server/execution/planning",title:"Planning",description:"Planner algorithm",source:"@site/docs/server/execution/planning.md",sourceDirName:"server/execution",slug:"/server/execution/planning",permalink:"/gql/docs/server/execution/planning",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/execution/planning.md",tags:[],version:"current",frontMatter:{title:"Planning"},sidebar:"docs",previous:{title:"Structuring large applications",permalink:"/gql/docs/server/schema/structuring_apps"},next:{title:"Statistics",permalink:"/gql/docs/server/execution/statistics"}},p={},i=[{value:"Planner algorithm",id:"planner-algorithm",level:2},{value:"The high-level idea",id:"the-high-level-idea",level:3},{value:"Default planner intuition",id:"default-planner-intuition",level:3},{value:"Converting a query to a problem",id:"converting-a-query-to-a-problem",level:3}],o={toc:i};function c(e){let{components:a,...r}=e;return(0,s.kt)("wrapper",(0,n.Z)({},o,r,{components:a,mdxType:"MDXLayout"}),(0,s.kt)("h2",{id:"planner-algorithm"},"Planner algorithm"),(0,s.kt)("h3",{id:"the-high-level-idea"},"The high-level idea"),(0,s.kt)("p",null,"When planning for a query the planner assigns weights to every edge/field, optionally labels them with their batch names (if a batch resolver was used) and finally converts the problem to a simpler DAG (directed asyclic graph) form."),(0,s.kt)("admonition",{type:"tip"},(0,s.kt)("p",{parentName:"admonition"},"For information on how the planner assigns weights, check out the ",(0,s.kt)("a",{parentName:"p",href:"/gql/docs/server/execution/statistics"},"statistics"),".")),(0,s.kt)("p",null,"The goal now is to form batches by contracting nodes that are batchable (jobs of the same family in scheduling/OR jargon)."),(0,s.kt)("p",null,"For instance, assume the following DAG is in question:"),(0,s.kt)("mermaid",{value:"flowchart LR\n Query((Query)) ---\x3e a(a
batch: z
cost: 2)\n a --\x3e A((A))\n\n Query --\x3e b(b
cost: 1)\n b --\x3e B((B))\n \n B ---\x3e c(c
batch: z
cost: 2)\n c --\x3e C((C))"}),(0,s.kt)("p",null,"Now consider the following plan, where a possible contraction is colored red:"),(0,s.kt)("mermaid",{value:"flowchart LR\n Query((Query)) -----\x3e a(a
batch: z
cost: 2)\n a --\x3e A((A))\n\n Query --\x3e b(b
cost: 1)\n b --\x3e B((B))\n \n B ---\x3e c(c
batch: z
cost: 2)\n c --\x3e C((C))\n\nstyle a stroke:#f66,stroke-dasharray: 5 5\nstyle c stroke:#f66,stroke-dasharray: 5 5"}),(0,s.kt)("p",null,"And contracted it becomes:"),(0,s.kt)("mermaid",{value:'flowchart LR\n Query((Query)) --\x3e b(b
cost: 1)\n b --\x3e B((B))\n \n B ---\x3e ac("{a,c}"
batch: z
cost: 2)\n ac --\x3e A((A))\n ac --\x3e C((C))\n\nstyle ac stroke:#f66,stroke-dasharray: 5 5'}),(0,s.kt)("h3",{id:"default-planner-intuition"},"Default planner intuition"),(0,s.kt)("p",null,"The default planner heuristic in gql lazily enumerates all plans, imposing a locally greedy order to the enumerated plans.\nThe default planner also employs some simple but powerful pruning rules to eliminate trivially uninteresting plan variantions."),(0,s.kt)("p",null,'The planner works through the problem from the root(s) and down through the DAG.\nThe algorithm keeps some state regarding what batches have been visited and what nodes are scheduled in the "current plan".\nIn a round of planning the algorithm will figure out what nodes are schedulable by looking at it\'s state.'),(0,s.kt)("p",null,"The planner will lazily generate all combinations of possible batches of schedulable nodes."),(0,s.kt)("admonition",{type:"note"},(0,s.kt)("p",{parentName:"admonition"},"One can easily cause a combinatorial explosion by generation of combinations.\nFortunately we don't consider every plan (and in fact, the default algorithm only pulls ",(0,s.kt)("span",{parentName:"p",className:"math math-inline"},(0,s.kt)("span",{parentName:"span",className:"katex"},(0,s.kt)("span",{parentName:"span",className:"katex-mathml"},(0,s.kt)("math",{parentName:"span",xmlns:"http://www.w3.org/1998/Math/MathML"},(0,s.kt)("semantics",{parentName:"math"},(0,s.kt)("mrow",{parentName:"semantics"},(0,s.kt)("mi",{parentName:"mrow"},"O"),(0,s.kt)("mo",{parentName:"mrow",stretchy:"false"},"("),(0,s.kt)("mi",{parentName:"mrow",mathvariant:"normal"},"\u2223"),(0,s.kt)("mi",{parentName:"mrow"},"V"),(0,s.kt)("mi",{parentName:"mrow",mathvariant:"normal"},"\u2223"),(0,s.kt)("mo",{parentName:"mrow",stretchy:"false"},")")),(0,s.kt)("annotation",{parentName:"semantics",encoding:"application/x-tex"},"O(|V|)")))),(0,s.kt)("span",{parentName:"span",className:"katex-html","aria-hidden":"true"},(0,s.kt)("span",{parentName:"span",className:"base"},(0,s.kt)("span",{parentName:"span",className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,s.kt)("span",{parentName:"span",className:"mord mathnormal",style:{marginRight:"0.02778em"}},"O"),(0,s.kt)("span",{parentName:"span",className:"mopen"},"("),(0,s.kt)("span",{parentName:"span",className:"mord"},"\u2223"),(0,s.kt)("span",{parentName:"span",className:"mord mathnormal",style:{marginRight:"0.22222em"}},"V"),(0,s.kt)("span",{parentName:"span",className:"mord"},"\u2223"),(0,s.kt)("span",{parentName:"span",className:"mclose"},")")))))," plans).\nFurthermore, most problems will have less than n plans.")),(0,s.kt)("p",null,'The planner will always generate the largest batches first, hence the "locally greedy" ordering.'),(0,s.kt)("p",null,"Trivially schedulable nodes are always scheduled first if possible; a pruning rules makes sure of this.\nFor a given scheduleable node, if no other un-scheduled node exists of the same family (excluding it's own descendants), then that node's only and optimal batch is the singleton batch containing only that node."),(0,s.kt)("p",null,"There are other pruning rules that have been considered, but don't seem necessary for practical problems since most problems produce very few plans."),(0,s.kt)("p",null,'One such pruning rule consideres "optimal" generated batch combinations.\nIf the largest batch that the planner can generate ',(0,s.kt)("span",{parentName:"p",className:"math math-inline"},(0,s.kt)("span",{parentName:"span",className:"katex"},(0,s.kt)("span",{parentName:"span",className:"katex-mathml"},(0,s.kt)("math",{parentName:"span",xmlns:"http://www.w3.org/1998/Math/MathML"},(0,s.kt)("semantics",{parentName:"math"},(0,s.kt)("mrow",{parentName:"semantics"},(0,s.kt)("mo",{parentName:"mrow",fence:"true"},"("),(0,s.kt)("mfrac",{parentName:"mrow",linethickness:"0px"},(0,s.kt)("mi",{parentName:"mfrac"},"n"),(0,s.kt)("mi",{parentName:"mfrac"},"n")),(0,s.kt)("mo",{parentName:"mrow",fence:"true"},")")),(0,s.kt)("annotation",{parentName:"semantics",encoding:"application/x-tex"},"n \\choose n")))),(0,s.kt)("span",{parentName:"span",className:"katex-html","aria-hidden":"true"},(0,s.kt)("span",{parentName:"span",className:"base"},(0,s.kt)("span",{parentName:"span",className:"strut",style:{height:"1.2em",verticalAlign:"-0.35em"}}),(0,s.kt)("span",{parentName:"span",className:"mord"},(0,s.kt)("span",{parentName:"span",className:"mopen delimcenter",style:{top:"0em"}},(0,s.kt)("span",{parentName:"span",className:"delimsizing size1"},"(")),(0,s.kt)("span",{parentName:"span",className:"mfrac"},(0,s.kt)("span",{parentName:"span",className:"vlist-t vlist-t2"},(0,s.kt)("span",{parentName:"span",className:"vlist-r"},(0,s.kt)("span",{parentName:"span",className:"vlist",style:{height:"0.7454em"}},(0,s.kt)("span",{parentName:"span",style:{top:"-2.355em"}},(0,s.kt)("span",{parentName:"span",className:"pstrut",style:{height:"2.7em"}}),(0,s.kt)("span",{parentName:"span",className:"sizing reset-size6 size3 mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mathnormal mtight"},"n")))),(0,s.kt)("span",{parentName:"span",style:{top:"-3.144em"}},(0,s.kt)("span",{parentName:"span",className:"pstrut",style:{height:"2.7em"}}),(0,s.kt)("span",{parentName:"span",className:"sizing reset-size6 size3 mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mathnormal mtight"},"n"))))),(0,s.kt)("span",{parentName:"span",className:"vlist-s"},"\u200b")),(0,s.kt)("span",{parentName:"span",className:"vlist-r"},(0,s.kt)("span",{parentName:"span",className:"vlist",style:{height:"0.345em"}},(0,s.kt)("span",{parentName:"span"}))))),(0,s.kt)("span",{parentName:"span",className:"mclose delimcenter",style:{top:"0em"}},(0,s.kt)("span",{parentName:"span",className:"delimsizing size1"},")"))))))),' contains nodes that all have the same "latest ending parent", then all other combinations ',(0,s.kt)("span",{parentName:"p",className:"math math-inline"},(0,s.kt)("span",{parentName:"span",className:"katex"},(0,s.kt)("span",{parentName:"span",className:"katex-mathml"},(0,s.kt)("math",{parentName:"span",xmlns:"http://www.w3.org/1998/Math/MathML"},(0,s.kt)("semantics",{parentName:"math"},(0,s.kt)("mrow",{parentName:"semantics"},(0,s.kt)("mrow",{parentName:"mrow"},(0,s.kt)("mo",{parentName:"mrow",fence:"true"},"("),(0,s.kt)("mfrac",{parentName:"mrow",linethickness:"0px"},(0,s.kt)("mi",{parentName:"mfrac"},"n"),(0,s.kt)("mi",{parentName:"mfrac"},"k")),(0,s.kt)("mo",{parentName:"mrow",fence:"true"},")")),(0,s.kt)("mtext",{parentName:"mrow"},"\xa0where\xa0"),(0,s.kt)("mi",{parentName:"mrow"},"k"),(0,s.kt)("mo",{parentName:"mrow"},"<"),(0,s.kt)("mi",{parentName:"mrow"},"n")),(0,s.kt)("annotation",{parentName:"semantics",encoding:"application/x-tex"},"{n \\choose k} \\text{ where } k < n")))),(0,s.kt)("span",{parentName:"span",className:"katex-html","aria-hidden":"true"},(0,s.kt)("span",{parentName:"span",className:"base"},(0,s.kt)("span",{parentName:"span",className:"strut",style:{height:"1.2em",verticalAlign:"-0.35em"}}),(0,s.kt)("span",{parentName:"span",className:"mord"},(0,s.kt)("span",{parentName:"span",className:"mord"},(0,s.kt)("span",{parentName:"span",className:"mopen delimcenter",style:{top:"0em"}},(0,s.kt)("span",{parentName:"span",className:"delimsizing size1"},"(")),(0,s.kt)("span",{parentName:"span",className:"mfrac"},(0,s.kt)("span",{parentName:"span",className:"vlist-t vlist-t2"},(0,s.kt)("span",{parentName:"span",className:"vlist-r"},(0,s.kt)("span",{parentName:"span",className:"vlist",style:{height:"0.7454em"}},(0,s.kt)("span",{parentName:"span",style:{top:"-2.355em"}},(0,s.kt)("span",{parentName:"span",className:"pstrut",style:{height:"2.7em"}}),(0,s.kt)("span",{parentName:"span",className:"sizing reset-size6 size3 mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mathnormal mtight",style:{marginRight:"0.03148em"}},"k")))),(0,s.kt)("span",{parentName:"span",style:{top:"-3.144em"}},(0,s.kt)("span",{parentName:"span",className:"pstrut",style:{height:"2.7em"}}),(0,s.kt)("span",{parentName:"span",className:"sizing reset-size6 size3 mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mathnormal mtight"},"n"))))),(0,s.kt)("span",{parentName:"span",className:"vlist-s"},"\u200b")),(0,s.kt)("span",{parentName:"span",className:"vlist-r"},(0,s.kt)("span",{parentName:"span",className:"vlist",style:{height:"0.345em"}},(0,s.kt)("span",{parentName:"span"}))))),(0,s.kt)("span",{parentName:"span",className:"mclose delimcenter",style:{top:"0em"}},(0,s.kt)("span",{parentName:"span",className:"delimsizing size1"},")")))),(0,s.kt)("span",{parentName:"span",className:"mord text"},(0,s.kt)("span",{parentName:"span",className:"mord"},"\xa0where\xa0")),(0,s.kt)("span",{parentName:"span",className:"mord mathnormal",style:{marginRight:"0.03148em"}},"k"),(0,s.kt)("span",{parentName:"span",className:"mspace",style:{marginRight:"0.2778em"}}),(0,s.kt)("span",{parentName:"span",className:"mrel"},"<"),(0,s.kt)("span",{parentName:"span",className:"mspace",style:{marginRight:"0.2778em"}})),(0,s.kt)("span",{parentName:"span",className:"base"},(0,s.kt)("span",{parentName:"span",className:"strut",style:{height:"0.4306em"}}),(0,s.kt)("span",{parentName:"span",className:"mord mathnormal"},"n")))))," are trivially fruitless."),(0,s.kt)("p",null,"Once the planner has constructed a lazy list of batches, it then consideres every plan that ",(0,s.kt)("em",{parentName:"p"},"could")," exist for every batch, hence a computational difficulty of finding the ",(0,s.kt)("strong",{parentName:"p"},"best")," plan."),(0,s.kt)("admonition",{type:"info"},(0,s.kt)("p",{parentName:"admonition"},"If you want to understand the algorithm better, consider taking a look at the source code.")),(0,s.kt)("h3",{id:"converting-a-query-to-a-problem"},"Converting a query to a problem"),(0,s.kt)("p",null,"gql considers only resolvers when running query planning.\nEvery field that is traversed in a query is expanded to all the resolvers it consists such that it becomes a digraph."),(0,s.kt)("p",null,"As an example, consider the following instance:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-scala"},'import gql._\nimport gql.dsl.all._\nimport gql.ast._\nimport gql.server.planner._\nimport gql.resolver._\nimport scala.concurrent.duration._\nimport cats.implicits._\nimport cats.effect._\nimport cats.effect.unsafe.implicits.global\n\ncase object Child\n\ndef wait[I](ms: Int) = Resolver.effect[IO, I](_ => IO.sleep(50.millis))\n\nval schem = Schema.stateful{\n Resolver.batch[IO, Unit, Int](_ => IO.sleep(10.millis) as Map(() -> 42)).flatMap{ b1 =>\n Resolver.batch[IO, Unit, String](_ => IO.sleep(15.millis) as Map(() -> "42")).map{ b2 =>\n implicit lazy val child: Type[IO, Child.type] = builder[IO, Child.type]{ b =>\n b.tpe(\n "Child",\n "b1" -> b.from(wait(50) andThen b1.opt map (_.get)),\n "b2" -> b.from(wait(100) andThen b2.opt map (_.get)),\n )\n }\n\n SchemaShape.unit[IO](\n builder[IO, Unit]{ b =>\n b.fields(\n "child" -> b.from(wait(42) as Child),\n "b2" -> b.from(wait(25) andThen b2.opt map (_.get))\n )\n }\n )\n }\n }\n}.unsafeRunSync()\n')),(0,s.kt)("p",null,"Now let's define our query and modify our schema so the planner logs:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-scala"},'val qry = """\n query {\n child {\n b1\n b2\n }\n b2\n }\n"""\n\nval withLoggedPlanner = schem.copy(planner = new Planner[IO] {\n def plan(naive: NodeTree): IO[OptimizedDAG] =\n schem.planner.plan(naive).map { output =>\n println(output.show(ansiColors = false))\n println(s"naive: ${output.totalCost}")\n println(s"optimized: ${output.optimizedCost}")\n output\n }\n})\n')),(0,s.kt)("p",null,"And we plan for it inspect the result:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-scala"},"def runQry() = {\n Compiler[IO]\n .compile(withLoggedPlanner, qry)\n .traverse_{ case Application.Query(fa) => fa }\n .unsafeRunSync()\n}\n\nrunQry()\n// name: Query_child.compose-left.compose-right.compose-right, cost: 100.00, end: 100.00, batch: 5\n// name: Child_b2.compose-left.compose-left.compose-right, cost: 100.00, end: 200.00, batch: 2\n// name: batch_1, cost: 100.00, end: 300.00, batch: 4\n// name: Child_b1.compose-left.compose-left.compose-right, cost: 100.00, end: 200.00, batch: 3\n// name: batch_0, cost: 100.00, end: 300.00, batch: 1\n// name: Query_b2.compose-left.compose-left.compose-right, cost: 100.00, end: 100.00, batch: 0\n// name: batch_1, cost: 100.00, end: 200.00, batch: 4\n// >>>>>>>>>>>>>name: batch_1, cost: 100.00, end: 300.00, batch: 4\n// \n// naive: 700.0\n// optimized: 600.0\n")),(0,s.kt)("p",null,"We can warm up the weights (statistics) a bit by running the query a few times:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-scala"},"(0 to 10).toList.foreach(_ => runQry())\n")),(0,s.kt)("p",null,"Now we can see how the weights are assigned:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-scala"},"runQry()\n// name: Query_child.compose-left.compose-right.compose-right, cost: 50195.19, end: 50195.19, batch: 0\n// name: Child_b2.compose-left.compose-left.compose-right, cost: 50161.73, end: 100356.91, batch: 5\n// name: batch_1, cost: 15149.46, end: 115506.37, batch: 1\n// name: Child_b1.compose-left.compose-left.compose-right, cost: 50163.37, end: 100358.55, batch: 2\n// name: batch_0, cost: 10142.82, end: 110501.37, batch: 4\n// name: Query_b2.compose-left.compose-left.compose-right, cost: 50198.64, end: 50198.64, batch: 3\n// name: batch_1, cost: 15149.46, end: 65348.10, batch: 1\n// >>>>>>>>>>>>>>>>>name: batch_1, cost: 15149.46, end: 115506.37, batch: 1\n// \n// naive: 241160.6363636364\n// optimized: 226011.18181818182\n")),(0,s.kt)("p",null,"Plans can also be shown nicely in a terminal with ANSI colors:\n",(0,s.kt)("img",{alt:"Terminal output",src:t(1745).Z,width:"1144",height:"333"})))}c.isMDXComponent=!0},1745:(e,a,t)=>{t.d(a,{Z:()=>n});const n=t.p+"assets/images/plan_image-bacfe186ade480842758a5d754111dfd.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[143],{3905:(e,a,t)=>{t.d(a,{Zo:()=>o,kt:()=>d});var n=t(7294);function s(e,a,t){return a in e?Object.defineProperty(e,a,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[a]=t,e}function r(e,a){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);a&&(n=n.filter((function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),t.push.apply(t,n)}return t}function l(e){for(var a=1;a=0||(s[t]=e[t]);return s}(e,a);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(s[t]=e[t])}return s}var p=n.createContext({}),i=function(e){var a=n.useContext(p),t=a;return e&&(t="function"==typeof e?e(a):l(l({},a),e)),t},o=function(e){var a=i(e.components);return n.createElement(p.Provider,{value:a},e.children)},c={inlineCode:"code",wrapper:function(e){var a=e.children;return n.createElement(n.Fragment,{},a)}},h=n.forwardRef((function(e,a){var t=e.components,s=e.mdxType,r=e.originalType,p=e.parentName,o=m(e,["components","mdxType","originalType","parentName"]),h=i(t),d=s,u=h["".concat(p,".").concat(d)]||h[d]||c[d]||r;return t?n.createElement(u,l(l({ref:a},o),{},{components:t})):n.createElement(u,l({ref:a},o))}));function d(e,a){var t=arguments,s=a&&a.mdxType;if("string"==typeof e||s){var r=t.length,l=new Array(r);l[0]=h;var m={};for(var p in a)hasOwnProperty.call(a,p)&&(m[p]=a[p]);m.originalType=e,m.mdxType="string"==typeof e?e:s,l[1]=m;for(var i=2;i{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>l,default:()=>c,frontMatter:()=>r,metadata:()=>m,toc:()=>i});var n=t(7462),s=(t(7294),t(3905));const r={title:"Planning"},l=void 0,m={unversionedId:"server/execution/planning",id:"server/execution/planning",title:"Planning",description:"Planner algorithm",source:"@site/docs/server/execution/planning.md",sourceDirName:"server/execution",slug:"/server/execution/planning",permalink:"/gql/docs/server/execution/planning",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/server/execution/planning.md",tags:[],version:"current",frontMatter:{title:"Planning"},sidebar:"docs",previous:{title:"Structuring large applications",permalink:"/gql/docs/server/schema/structuring_apps"},next:{title:"Statistics",permalink:"/gql/docs/server/execution/statistics"}},p={},i=[{value:"Planner algorithm",id:"planner-algorithm",level:2},{value:"The high-level idea",id:"the-high-level-idea",level:3},{value:"Default planner intuition",id:"default-planner-intuition",level:3},{value:"Converting a query to a problem",id:"converting-a-query-to-a-problem",level:3}],o={toc:i};function c(e){let{components:a,...r}=e;return(0,s.kt)("wrapper",(0,n.Z)({},o,r,{components:a,mdxType:"MDXLayout"}),(0,s.kt)("h2",{id:"planner-algorithm"},"Planner algorithm"),(0,s.kt)("h3",{id:"the-high-level-idea"},"The high-level idea"),(0,s.kt)("p",null,"When planning for a query the planner assigns weights to every edge/field, optionally labels them with their batch names (if a batch resolver was used) and finally converts the problem to a simpler DAG (directed asyclic graph) form."),(0,s.kt)("admonition",{type:"tip"},(0,s.kt)("p",{parentName:"admonition"},"For information on how the planner assigns weights, check out the ",(0,s.kt)("a",{parentName:"p",href:"/gql/docs/server/execution/statistics"},"statistics"),".")),(0,s.kt)("p",null,"The goal now is to form batches by contracting nodes that are batchable (jobs of the same family in scheduling/OR jargon)."),(0,s.kt)("p",null,"For instance, assume the following DAG is in question:"),(0,s.kt)("mermaid",{value:"flowchart LR\n Query((Query)) ---\x3e a(a
batch: z
cost: 2)\n a --\x3e A((A))\n\n Query --\x3e b(b
cost: 1)\n b --\x3e B((B))\n \n B ---\x3e c(c
batch: z
cost: 2)\n c --\x3e C((C))"}),(0,s.kt)("p",null,"Now consider the following plan, where a possible contraction is colored red:"),(0,s.kt)("mermaid",{value:"flowchart LR\n Query((Query)) -----\x3e a(a
batch: z
cost: 2)\n a --\x3e A((A))\n\n Query --\x3e b(b
cost: 1)\n b --\x3e B((B))\n \n B ---\x3e c(c
batch: z
cost: 2)\n c --\x3e C((C))\n\nstyle a stroke:#f66,stroke-dasharray: 5 5\nstyle c stroke:#f66,stroke-dasharray: 5 5"}),(0,s.kt)("p",null,"And contracted it becomes:"),(0,s.kt)("mermaid",{value:'flowchart LR\n Query((Query)) --\x3e b(b
cost: 1)\n b --\x3e B((B))\n \n B ---\x3e ac("{a,c}"
batch: z
cost: 2)\n ac --\x3e A((A))\n ac --\x3e C((C))\n\nstyle ac stroke:#f66,stroke-dasharray: 5 5'}),(0,s.kt)("h3",{id:"default-planner-intuition"},"Default planner intuition"),(0,s.kt)("p",null,"The default planner heuristic in gql lazily enumerates all plans, imposing a locally greedy order to the enumerated plans.\nThe default planner also employs some simple but powerful pruning rules to eliminate trivially uninteresting plan variantions."),(0,s.kt)("p",null,'The planner works through the problem from the root(s) and down through the DAG.\nThe algorithm keeps some state regarding what batches have been visited and what nodes are scheduled in the "current plan".\nIn a round of planning the algorithm will figure out what nodes are schedulable by looking at it\'s state.'),(0,s.kt)("p",null,"The planner will lazily generate all combinations of possible batches of schedulable nodes."),(0,s.kt)("admonition",{type:"note"},(0,s.kt)("p",{parentName:"admonition"},"One can easily cause a combinatorial explosion by generation of combinations.\nFortunately we don't consider every plan (and in fact, the default algorithm only pulls ",(0,s.kt)("span",{parentName:"p",className:"math math-inline"},(0,s.kt)("span",{parentName:"span",className:"katex"},(0,s.kt)("span",{parentName:"span",className:"katex-mathml"},(0,s.kt)("math",{parentName:"span",xmlns:"http://www.w3.org/1998/Math/MathML"},(0,s.kt)("semantics",{parentName:"math"},(0,s.kt)("mrow",{parentName:"semantics"},(0,s.kt)("mi",{parentName:"mrow"},"O"),(0,s.kt)("mo",{parentName:"mrow",stretchy:"false"},"("),(0,s.kt)("mi",{parentName:"mrow",mathvariant:"normal"},"\u2223"),(0,s.kt)("mi",{parentName:"mrow"},"V"),(0,s.kt)("mi",{parentName:"mrow",mathvariant:"normal"},"\u2223"),(0,s.kt)("mo",{parentName:"mrow",stretchy:"false"},")")),(0,s.kt)("annotation",{parentName:"semantics",encoding:"application/x-tex"},"O(|V|)")))),(0,s.kt)("span",{parentName:"span",className:"katex-html","aria-hidden":"true"},(0,s.kt)("span",{parentName:"span",className:"base"},(0,s.kt)("span",{parentName:"span",className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,s.kt)("span",{parentName:"span",className:"mord mathnormal",style:{marginRight:"0.02778em"}},"O"),(0,s.kt)("span",{parentName:"span",className:"mopen"},"("),(0,s.kt)("span",{parentName:"span",className:"mord"},"\u2223"),(0,s.kt)("span",{parentName:"span",className:"mord mathnormal",style:{marginRight:"0.22222em"}},"V"),(0,s.kt)("span",{parentName:"span",className:"mord"},"\u2223"),(0,s.kt)("span",{parentName:"span",className:"mclose"},")")))))," plans).\nFurthermore, most problems will have less than n plans.")),(0,s.kt)("p",null,'The planner will always generate the largest batches first, hence the "locally greedy" ordering.'),(0,s.kt)("p",null,"Trivially schedulable nodes are always scheduled first if possible; a pruning rules makes sure of this.\nFor a given scheduleable node, if no other un-scheduled node exists of the same family (excluding it's own descendants), then that node's only and optimal batch is the singleton batch containing only that node."),(0,s.kt)("p",null,"There are other pruning rules that have been considered, but don't seem necessary for practical problems since most problems produce very few plans."),(0,s.kt)("p",null,'One such pruning rule consideres "optimal" generated batch combinations.\nIf the largest batch that the planner can generate ',(0,s.kt)("span",{parentName:"p",className:"math math-inline"},(0,s.kt)("span",{parentName:"span",className:"katex"},(0,s.kt)("span",{parentName:"span",className:"katex-mathml"},(0,s.kt)("math",{parentName:"span",xmlns:"http://www.w3.org/1998/Math/MathML"},(0,s.kt)("semantics",{parentName:"math"},(0,s.kt)("mrow",{parentName:"semantics"},(0,s.kt)("mo",{parentName:"mrow",fence:"true"},"("),(0,s.kt)("mfrac",{parentName:"mrow",linethickness:"0px"},(0,s.kt)("mi",{parentName:"mfrac"},"n"),(0,s.kt)("mi",{parentName:"mfrac"},"n")),(0,s.kt)("mo",{parentName:"mrow",fence:"true"},")")),(0,s.kt)("annotation",{parentName:"semantics",encoding:"application/x-tex"},"n \\choose n")))),(0,s.kt)("span",{parentName:"span",className:"katex-html","aria-hidden":"true"},(0,s.kt)("span",{parentName:"span",className:"base"},(0,s.kt)("span",{parentName:"span",className:"strut",style:{height:"1.2em",verticalAlign:"-0.35em"}}),(0,s.kt)("span",{parentName:"span",className:"mord"},(0,s.kt)("span",{parentName:"span",className:"mopen delimcenter",style:{top:"0em"}},(0,s.kt)("span",{parentName:"span",className:"delimsizing size1"},"(")),(0,s.kt)("span",{parentName:"span",className:"mfrac"},(0,s.kt)("span",{parentName:"span",className:"vlist-t vlist-t2"},(0,s.kt)("span",{parentName:"span",className:"vlist-r"},(0,s.kt)("span",{parentName:"span",className:"vlist",style:{height:"0.7454em"}},(0,s.kt)("span",{parentName:"span",style:{top:"-2.355em"}},(0,s.kt)("span",{parentName:"span",className:"pstrut",style:{height:"2.7em"}}),(0,s.kt)("span",{parentName:"span",className:"sizing reset-size6 size3 mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mathnormal mtight"},"n")))),(0,s.kt)("span",{parentName:"span",style:{top:"-3.144em"}},(0,s.kt)("span",{parentName:"span",className:"pstrut",style:{height:"2.7em"}}),(0,s.kt)("span",{parentName:"span",className:"sizing reset-size6 size3 mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mathnormal mtight"},"n"))))),(0,s.kt)("span",{parentName:"span",className:"vlist-s"},"\u200b")),(0,s.kt)("span",{parentName:"span",className:"vlist-r"},(0,s.kt)("span",{parentName:"span",className:"vlist",style:{height:"0.345em"}},(0,s.kt)("span",{parentName:"span"}))))),(0,s.kt)("span",{parentName:"span",className:"mclose delimcenter",style:{top:"0em"}},(0,s.kt)("span",{parentName:"span",className:"delimsizing size1"},")"))))))),' contains nodes that all have the same "latest ending parent", then all other combinations ',(0,s.kt)("span",{parentName:"p",className:"math math-inline"},(0,s.kt)("span",{parentName:"span",className:"katex"},(0,s.kt)("span",{parentName:"span",className:"katex-mathml"},(0,s.kt)("math",{parentName:"span",xmlns:"http://www.w3.org/1998/Math/MathML"},(0,s.kt)("semantics",{parentName:"math"},(0,s.kt)("mrow",{parentName:"semantics"},(0,s.kt)("mrow",{parentName:"mrow"},(0,s.kt)("mo",{parentName:"mrow",fence:"true"},"("),(0,s.kt)("mfrac",{parentName:"mrow",linethickness:"0px"},(0,s.kt)("mi",{parentName:"mfrac"},"n"),(0,s.kt)("mi",{parentName:"mfrac"},"k")),(0,s.kt)("mo",{parentName:"mrow",fence:"true"},")")),(0,s.kt)("mtext",{parentName:"mrow"},"\xa0where\xa0"),(0,s.kt)("mi",{parentName:"mrow"},"k"),(0,s.kt)("mo",{parentName:"mrow"},"<"),(0,s.kt)("mi",{parentName:"mrow"},"n")),(0,s.kt)("annotation",{parentName:"semantics",encoding:"application/x-tex"},"{n \\choose k} \\text{ where } k < n")))),(0,s.kt)("span",{parentName:"span",className:"katex-html","aria-hidden":"true"},(0,s.kt)("span",{parentName:"span",className:"base"},(0,s.kt)("span",{parentName:"span",className:"strut",style:{height:"1.2em",verticalAlign:"-0.35em"}}),(0,s.kt)("span",{parentName:"span",className:"mord"},(0,s.kt)("span",{parentName:"span",className:"mord"},(0,s.kt)("span",{parentName:"span",className:"mopen delimcenter",style:{top:"0em"}},(0,s.kt)("span",{parentName:"span",className:"delimsizing size1"},"(")),(0,s.kt)("span",{parentName:"span",className:"mfrac"},(0,s.kt)("span",{parentName:"span",className:"vlist-t vlist-t2"},(0,s.kt)("span",{parentName:"span",className:"vlist-r"},(0,s.kt)("span",{parentName:"span",className:"vlist",style:{height:"0.7454em"}},(0,s.kt)("span",{parentName:"span",style:{top:"-2.355em"}},(0,s.kt)("span",{parentName:"span",className:"pstrut",style:{height:"2.7em"}}),(0,s.kt)("span",{parentName:"span",className:"sizing reset-size6 size3 mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mathnormal mtight",style:{marginRight:"0.03148em"}},"k")))),(0,s.kt)("span",{parentName:"span",style:{top:"-3.144em"}},(0,s.kt)("span",{parentName:"span",className:"pstrut",style:{height:"2.7em"}}),(0,s.kt)("span",{parentName:"span",className:"sizing reset-size6 size3 mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mtight"},(0,s.kt)("span",{parentName:"span",className:"mord mathnormal mtight"},"n"))))),(0,s.kt)("span",{parentName:"span",className:"vlist-s"},"\u200b")),(0,s.kt)("span",{parentName:"span",className:"vlist-r"},(0,s.kt)("span",{parentName:"span",className:"vlist",style:{height:"0.345em"}},(0,s.kt)("span",{parentName:"span"}))))),(0,s.kt)("span",{parentName:"span",className:"mclose delimcenter",style:{top:"0em"}},(0,s.kt)("span",{parentName:"span",className:"delimsizing size1"},")")))),(0,s.kt)("span",{parentName:"span",className:"mord text"},(0,s.kt)("span",{parentName:"span",className:"mord"},"\xa0where\xa0")),(0,s.kt)("span",{parentName:"span",className:"mord mathnormal",style:{marginRight:"0.03148em"}},"k"),(0,s.kt)("span",{parentName:"span",className:"mspace",style:{marginRight:"0.2778em"}}),(0,s.kt)("span",{parentName:"span",className:"mrel"},"<"),(0,s.kt)("span",{parentName:"span",className:"mspace",style:{marginRight:"0.2778em"}})),(0,s.kt)("span",{parentName:"span",className:"base"},(0,s.kt)("span",{parentName:"span",className:"strut",style:{height:"0.4306em"}}),(0,s.kt)("span",{parentName:"span",className:"mord mathnormal"},"n")))))," are trivially fruitless."),(0,s.kt)("p",null,"Once the planner has constructed a lazy list of batches, it then consideres every plan that ",(0,s.kt)("em",{parentName:"p"},"could")," exist for every batch, hence a computational difficulty of finding the ",(0,s.kt)("strong",{parentName:"p"},"best")," plan."),(0,s.kt)("admonition",{type:"info"},(0,s.kt)("p",{parentName:"admonition"},"If you want to understand the algorithm better, consider taking a look at the source code.")),(0,s.kt)("h3",{id:"converting-a-query-to-a-problem"},"Converting a query to a problem"),(0,s.kt)("p",null,"gql considers only resolvers when running query planning.\nEvery field that is traversed in a query is expanded to all the resolvers it consists such that it becomes a digraph."),(0,s.kt)("p",null,"As an example, consider the following instance:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-scala"},'import gql._\nimport gql.dsl.all._\nimport gql.ast._\nimport gql.server.planner._\nimport gql.resolver._\nimport scala.concurrent.duration._\nimport cats.implicits._\nimport cats.effect._\nimport cats.effect.unsafe.implicits.global\n\ncase object Child\n\ndef wait[I](ms: Int) = Resolver.effect[IO, I](_ => IO.sleep(50.millis))\n\nval schem = Schema.stateful{\n Resolver.batch[IO, Unit, Int](_ => IO.sleep(10.millis) as Map(() -> 42)).flatMap{ b1 =>\n Resolver.batch[IO, Unit, String](_ => IO.sleep(15.millis) as Map(() -> "42")).map{ b2 =>\n implicit lazy val child: Type[IO, Child.type] = builder[IO, Child.type]{ b =>\n b.tpe(\n "Child",\n "b1" -> b.from(wait(50) andThen b1.opt map (_.get)),\n "b2" -> b.from(wait(100) andThen b2.opt map (_.get)),\n )\n }\n\n SchemaShape.unit[IO](\n builder[IO, Unit]{ b =>\n b.fields(\n "child" -> b.from(wait(42) as Child),\n "b2" -> b.from(wait(25) andThen b2.opt map (_.get))\n )\n }\n )\n }\n }\n}.unsafeRunSync()\n')),(0,s.kt)("p",null,"Now let's define our query and modify our schema so the planner logs:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-scala"},'val qry = """\n query {\n child {\n b1\n b2\n }\n b2\n }\n"""\n\nval withLoggedPlanner = schem.copy(planner = new Planner[IO] {\n def plan(naive: NodeTree): IO[OptimizedDAG] =\n schem.planner.plan(naive).map { output =>\n println(output.show(ansiColors = false))\n println(s"naive: ${output.totalCost}")\n println(s"optimized: ${output.optimizedCost}")\n output\n }\n})\n')),(0,s.kt)("p",null,"And we plan for it inspect the result:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-scala"},"def runQry() = {\n Compiler[IO]\n .compile(withLoggedPlanner, qry)\n .traverse_{ case Application.Query(fa) => fa }\n .unsafeRunSync()\n}\n\nrunQry()\n// name: Query_child.compose-left.compose-right.compose-right, cost: 100.00, end: 100.00, batch: 5\n// name: Child_b2.compose-left.compose-left.compose-right, cost: 100.00, end: 200.00, batch: 2\n// name: batch_1, cost: 100.00, end: 300.00, batch: 4\n// name: Child_b1.compose-left.compose-left.compose-right, cost: 100.00, end: 200.00, batch: 3\n// name: batch_0, cost: 100.00, end: 300.00, batch: 1\n// name: Query_b2.compose-left.compose-left.compose-right, cost: 100.00, end: 100.00, batch: 0\n// name: batch_1, cost: 100.00, end: 200.00, batch: 4\n// >>>>>>>>>>>>>name: batch_1, cost: 100.00, end: 300.00, batch: 4\n// \n// naive: 700.0\n// optimized: 600.0\n")),(0,s.kt)("p",null,"We can warm up the weights (statistics) a bit by running the query a few times:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-scala"},"(0 to 10).toList.foreach(_ => runQry())\n")),(0,s.kt)("p",null,"Now we can see how the weights are assigned:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-scala"},"runQry()\n// name: Query_child.compose-left.compose-right.compose-right, cost: 50270.73, end: 50270.73, batch: 0\n// name: Child_b2.compose-left.compose-left.compose-right, cost: 50231.00, end: 100501.73, batch: 3\n// name: batch_1, cost: 15191.64, end: 115693.37, batch: 4\n// name: Child_b1.compose-left.compose-left.compose-right, cost: 50239.82, end: 100510.55, batch: 1\n// name: batch_0, cost: 10223.82, end: 110734.37, batch: 5\n// name: Query_b2.compose-left.compose-left.compose-right, cost: 50280.91, end: 50280.91, batch: 2\n// name: batch_1, cost: 15191.64, end: 65472.55, batch: 4\n// >>>>>>>>>>>>>>>>>name: batch_1, cost: 15191.64, end: 115693.37, batch: 4\n// \n// naive: 241629.54545454547\n// optimized: 226437.9090909091\n")),(0,s.kt)("p",null,"Plans can also be shown nicely in a terminal with ANSI colors:\n",(0,s.kt)("img",{alt:"Terminal output",src:t(1745).Z,width:"1144",height:"333"})))}c.isMDXComponent=!0},1745:(e,a,t)=>{t.d(a,{Z:()=>n});const n=t.p+"assets/images/plan_image-bacfe186ade480842758a5d754111dfd.png"}}]); \ No newline at end of file diff --git a/assets/js/ffc79f40.7760afdc.js b/assets/js/ffc79f40.2e9e3fa6.js similarity index 97% rename from assets/js/ffc79f40.7760afdc.js rename to assets/js/ffc79f40.2e9e3fa6.js index 32f825fef..5907aae4e 100644 --- a/assets/js/ffc79f40.7760afdc.js +++ b/assets/js/ffc79f40.2e9e3fa6.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[960],{3905:(e,n,t)=>{t.d(n,{Zo:()=>u,kt:()=>g});var r=t(7294);function l(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=0||(l[t]=e[t]);return l}(e,n);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(l[t]=e[t])}return l}var s=r.createContext({}),c=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=c(e.components);return r.createElement(s.Provider,{value:n},e.children)},p={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,l=e.mdxType,a=e.originalType,s=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),m=c(t),g=l,d=m["".concat(s,".").concat(g)]||m[g]||p[g]||a;return t?r.createElement(d,i(i({ref:n},u),{},{components:t})):r.createElement(d,i({ref:n},u))}));function g(e,n){var t=arguments,l=n&&n.mdxType;if("string"==typeof e||l){var a=t.length,i=new Array(a);i[0]=m;var o={};for(var s in n)hasOwnProperty.call(n,s)&&(o[s]=n[s]);o.originalType=e,o.mdxType="string"==typeof e?e:l,i[1]=o;for(var c=2;c{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>p,frontMatter:()=>a,metadata:()=>o,toc:()=>c});var r=t(7462),l=(t(7294),t(3905));const a={title:"Code generation"},i=void 0,o={unversionedId:"client/code-generation",id:"client/code-generation",title:"Code generation",description:"Writing queries in scala using the dsl is more concise and type-safe than writing out the types and codecs by hand, but still requires a lot of code for non-trivial queries.",source:"@site/docs/client/code-generation.md",sourceDirName:"client",slug:"/client/code-generation",permalink:"/gql/docs/client/code-generation",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/client/code-generation.md",tags:[],version:"current",frontMatter:{title:"Code generation"},sidebar:"docs",previous:{title:"Query DSL",permalink:"/gql/docs/client/dsl"},next:{title:"Http4s",permalink:"/gql/docs/client/integrations/http4s"}},s={},c=[{value:"Setting up",id:"setting-up",level:2},{value:"Sbt integration",id:"sbt-integration",level:3},{value:"Usage",id:"usage",level:2}],u={toc:c};function p(e){let{components:n,...t}=e;return(0,l.kt)("wrapper",(0,r.Z)({},u,t,{components:n,mdxType:"MDXLayout"}),(0,l.kt)("p",null,"Writing queries in scala using the dsl is more concise and type-safe than writing out the types and codecs by hand, but still requires a lot of code for non-trivial queries."),(0,l.kt)("p",null,"gql also features a code generator that transforms a graphql schema file and a set of queries (or fragments) into dsl code."),(0,l.kt)("h2",{id:"setting-up"},"Setting up"),(0,l.kt)("p",null,"The code generator comes as a stand-alone cli at the maven coordinates:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'// build.sbt\n"io.github.valdemargr" %% "gql-client-codegen-cli" % "0.3.5"\n')),(0,l.kt)("p",null,"The code generator can also be integrated into sbt for a smoother development experience:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'// project/plugins.sbt\naddSbtPlugin("io.github.valdemargr" % "gql-client-codegen-sbt" % "0.3.5")\n')),(0,l.kt)("h3",{id:"sbt-integration"},"Sbt integration"),(0,l.kt)("p",null,"By default the sbt integration will look for a schema file in the resources directory at ",(0,l.kt)("inlineCode",{parentName:"p"},".../resources/schema.graphql")," and queries in the resources directory at ",(0,l.kt)("inlineCode",{parentName:"p"},".../resources/queries"),"."),(0,l.kt)("p",null,"You can, however, override or add more sources at custom locations:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'lazy val myBuild = \n ...\n .settings(\n resourceGroups += Gql.resourceGroup(\n name="other_resources",\n schemaFile= file("path/to/schema.graphql"),\n file("path/to/query1.graphql"),\n file("path/to/query2.graphql")\n )\n )\n')),(0,l.kt)("h2",{id:"usage"},"Usage"),(0,l.kt)("p",null,"When the code-generator is invoked it will use the queries and fragments in combination with the schema to generate a set of scala files containing the equivalent query in scala code."),(0,l.kt)("p",null,"For this demonstration, the code generator will be invoked manually:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.client.codegen.{ GeneratorCli => Gen }\nimport fs2.io.file.Files\nimport cats.effect._\nimport cats.implicits._\nimport cats.effect.unsafe.implicits.global\n\ndef runQuery(queryDef: String) =\n Files[IO].tempDirectory.use{ tmp => \n val schemaFile = tmp / "schema.graphql"\n val queryFile = tmp / "query.graphql"\n val sharedOutFile = tmp / "shared.scala"\n val queryOutFile = tmp / "query.scala"\n\n val schemaDef = """\n enum HelloEnum {\n HELLO,\n WORLD\n }\n\n type A {\n a: String\n }\n\n type B {\n b: String\n }\n\n union HelloUnion = A | B\n\n type Query {\n helloEnum(name: String): HelloEnum,\n helloUnion(name2: String): HelloUnion\n }\n """\n\n val writeSchemaF = fs2.Stream(schemaDef)\n .through(fs2.text.utf8.encode)\n .through(Files[IO].writeAll(schemaFile))\n .compile\n .drain\n\n val writeQueryF = fs2.Stream(queryDef)\n .through(fs2.text.utf8.encode)\n .through(Files[IO].writeAll(queryFile))\n .compile\n .drain\n\n import io.circe._\n import io.circe.syntax._\n val jo = Json.obj(\n "schema" -> Json.fromString(schemaFile.toString),\n "shared" -> Json.fromString(sharedOutFile.toString),\n "queries" -> Json.arr(\n Json.obj(\n "query" -> Json.fromString(queryFile.toString),\n "output" -> Json.fromString(queryOutFile.toString)\n )\n )\n )\n\n writeSchemaF >>\n writeQueryF >>\n Gen.run(List("--validate", "--input",jo.spaces2)) >>\n Files[IO].readAll(queryOutFile)\n .through(fs2.text.utf8.decode)\n .compile\n .string\n .map(println)\n }.unsafeRunSync()\n\nrunQuery(\n """\n fragment HelloFragment on Query {\n helloEnum(name: $name)\n }\n\n query HelloQuery($name: String) {\n ...HelloFragment\n helloUnion(name2: "hey") {\n ... on A {\n a\n }\n ... on B {\n b\n }\n }\n }\n """\n)\n// package gql.client.generated\n// \n// import _root_.gql.client._\n// import _root_.gql.client.dsl._\n// import _root_.gql.parser.{Value => V, AnyValue, Const}\n// import cats.implicits._\n// \n// final case class HelloFragment(\n// helloEnum: Option[HelloEnum]\n// )\n// \n// object HelloFragment {\n// implicit val selectionSet: SelectionSet[HelloFragment] = (\n// sel.build[Option[HelloEnum]]("helloEnum", x => x.args(arg("name", V.VariableValue("name"))))\n// ).map(apply)\n// \n// implicit val fragdef: Fragment[HelloFragment] = fragment[HelloFragment]("HelloFragment", "Query")\n// }\n// \n// final case class HelloQuery(\n// helloFragment: gql.client.generated.HelloFragment,\n// helloUnion: Option[HelloQuery.HelloUnion]\n// )\n// \n// object HelloQuery {\n// final case class HelloUnion(\n// a: Option[HelloUnion.InlineA],\n// b: Option[HelloUnion.InlineB]\n// ) {\n// lazy val variant: Option[HelloUnion.Variant] = \n// (a).map(HelloUnion.Variant.OnA.apply) orElse\n// (b).map(HelloUnion.Variant.OnB.apply)\n// }\n// \n// object HelloUnion {\n// sealed trait Variant extends Product with Serializable\n// object Variant {\n// final case class OnA(\n// a: HelloUnion.InlineA\n// ) extends Variant\n// \n// final case class OnB(\n// b: HelloUnion.InlineB\n// ) extends Variant\n// }\n// \n// final case class InlineA(\n// a: Option[String]\n// )\n// \n// object InlineA {\n// implicit val selectionSet: SelectionSet[InlineA] = (\n// sel.build[Option[String]]("a", x => x)\n// ).map(apply)\n// }\n// \n// final case class InlineB(\n// b: Option[String]\n// )\n// \n// object InlineB {\n// implicit val selectionSet: SelectionSet[InlineB] = (\n// sel.build[Option[String]]("b", x => x)\n// ).map(apply)\n// }\n// \n// implicit val selectionSet: SelectionSet[HelloUnion] = (\n// inlineFrag.build[HelloUnion.InlineA]("A", x => x),\n// inlineFrag.build[HelloUnion.InlineB]("B", x => x)\n// ).mapN(apply)\n// }\n// \n// implicit val selectionSet: SelectionSet[HelloQuery] = (\n// fragment.spread.build[gql.client.generated.HelloFragment](x => x).requiredFragment("HelloFragment", "Query"),\n// sel.build[Option[HelloQuery.HelloUnion]]("helloUnion", x => x.args(arg("name2", V.StringValue("hey"))))\n// ).mapN(apply)\n// \n// final case class Variables(\n// name: Option[Option[String]] = None\n// ) {\n// def setName(value: Option[String]): Variables = copy(name = Some(value))\n// }\n// \n// val queryExpr = (\n// omittableVariable[Option[String]]("name")\n// ).introduce{ _ =>\n// selectionSet\n// }\n// \n// val query = _root_.gql.client.Query.parameterized(_root_.gql.parser.QueryAst.OperationType.Query, "HelloQuery", queryExpr)\n// }\n')),(0,l.kt)("p",null,"When supplying the ",(0,l.kt)("inlineCode",{parentName:"p"},"--validate")," flag, gql will generate a stub implementation of the schema and run the same code as if running a gql server."),(0,l.kt)("p",null,"Lets construct a helper to show this:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'import scala.util.{Try,Failure}\n// We will also remove the ansii color codes from the output, since they don\'t render well in the docs\ndef runFail(q: String) = \n Try {\n runQuery(q)\n } match {\n case Failure(ex) => println(ex.getMessage().replaceAll("\\u001B\\\\[[;\\\\d]*m", ""))\n }\n')),(0,l.kt)("p",null,"Now with a parsing error:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'runFail(\n """\n query MyQuery {\n test.,test\n }\n """\n)\n// Failed to generate code with error: failed at offset 41 on line 2 with code 46\n// char in range } to } (code 125 to 125)\n// for document:\n// | \n// | query MyQuery {\n// | test.,test\n// | >>>>>>>>>>>>>^^^^^^^ line:2, column:16, offset:41, character code code:46\n// | }\n// |\n')),(0,l.kt)("p",null,"And also with a query validation error:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'runFail(\n """\n query MyQuery {\n helloEnum(name: 1)\n }\n """\n)\n// Failed to generate code with error: decoding failure for type `String` with message Got value \'1\' with wrong type, expecting string at root.helloEnum.name.String\n// in file /tmp/17471336844117445542/query.graphql\n// | \n// | query MyQuery {\n// | helloEnum(name: 1)\n// | >>>>>>>>>>>>>>>>>>>>>>>>>^^^^^^^ line:2, column:28, offset:53, character code code:49\n// | }\n// |\n')))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[960],{3905:(e,n,t)=>{t.d(n,{Zo:()=>u,kt:()=>g});var r=t(7294);function l(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=0||(l[t]=e[t]);return l}(e,n);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(l[t]=e[t])}return l}var s=r.createContext({}),c=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=c(e.components);return r.createElement(s.Provider,{value:n},e.children)},p={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,l=e.mdxType,a=e.originalType,s=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),m=c(t),g=l,d=m["".concat(s,".").concat(g)]||m[g]||p[g]||a;return t?r.createElement(d,i(i({ref:n},u),{},{components:t})):r.createElement(d,i({ref:n},u))}));function g(e,n){var t=arguments,l=n&&n.mdxType;if("string"==typeof e||l){var a=t.length,i=new Array(a);i[0]=m;var o={};for(var s in n)hasOwnProperty.call(n,s)&&(o[s]=n[s]);o.originalType=e,o.mdxType="string"==typeof e?e:l,i[1]=o;for(var c=2;c{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>p,frontMatter:()=>a,metadata:()=>o,toc:()=>c});var r=t(7462),l=(t(7294),t(3905));const a={title:"Code generation"},i=void 0,o={unversionedId:"client/code-generation",id:"client/code-generation",title:"Code generation",description:"Writing queries in scala using the dsl is more concise and type-safe than writing out the types and codecs by hand, but still requires a lot of code for non-trivial queries.",source:"@site/docs/client/code-generation.md",sourceDirName:"client",slug:"/client/code-generation",permalink:"/gql/docs/client/code-generation",draft:!1,editUrl:"https://github.com/valdemargr/gql/tree/main/docs/client/code-generation.md",tags:[],version:"current",frontMatter:{title:"Code generation"},sidebar:"docs",previous:{title:"Query DSL",permalink:"/gql/docs/client/dsl"},next:{title:"Http4s",permalink:"/gql/docs/client/integrations/http4s"}},s={},c=[{value:"Setting up",id:"setting-up",level:2},{value:"Sbt integration",id:"sbt-integration",level:3},{value:"Usage",id:"usage",level:2}],u={toc:c};function p(e){let{components:n,...t}=e;return(0,l.kt)("wrapper",(0,r.Z)({},u,t,{components:n,mdxType:"MDXLayout"}),(0,l.kt)("p",null,"Writing queries in scala using the dsl is more concise and type-safe than writing out the types and codecs by hand, but still requires a lot of code for non-trivial queries."),(0,l.kt)("p",null,"gql also features a code generator that transforms a graphql schema file and a set of queries (or fragments) into dsl code."),(0,l.kt)("h2",{id:"setting-up"},"Setting up"),(0,l.kt)("p",null,"The code generator comes as a stand-alone cli at the maven coordinates:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'// build.sbt\n"io.github.valdemargr" %% "gql-client-codegen-cli" % "0.3.5"\n')),(0,l.kt)("p",null,"The code generator can also be integrated into sbt for a smoother development experience:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'// project/plugins.sbt\naddSbtPlugin("io.github.valdemargr" % "gql-client-codegen-sbt" % "0.3.5")\n')),(0,l.kt)("h3",{id:"sbt-integration"},"Sbt integration"),(0,l.kt)("p",null,"By default the sbt integration will look for a schema file in the resources directory at ",(0,l.kt)("inlineCode",{parentName:"p"},".../resources/schema.graphql")," and queries in the resources directory at ",(0,l.kt)("inlineCode",{parentName:"p"},".../resources/queries"),"."),(0,l.kt)("p",null,"You can, however, override or add more sources at custom locations:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'lazy val myBuild = \n ...\n .settings(\n resourceGroups += Gql.resourceGroup(\n name="other_resources",\n schemaFile= file("path/to/schema.graphql"),\n file("path/to/query1.graphql"),\n file("path/to/query2.graphql")\n )\n )\n')),(0,l.kt)("h2",{id:"usage"},"Usage"),(0,l.kt)("p",null,"When the code-generator is invoked it will use the queries and fragments in combination with the schema to generate a set of scala files containing the equivalent query in scala code."),(0,l.kt)("p",null,"For this demonstration, the code generator will be invoked manually:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'import gql.client.codegen.{ GeneratorCli => Gen }\nimport fs2.io.file.Files\nimport cats.effect._\nimport cats.implicits._\nimport cats.effect.unsafe.implicits.global\n\ndef runQuery(queryDef: String) =\n Files[IO].tempDirectory.use{ tmp => \n val schemaFile = tmp / "schema.graphql"\n val queryFile = tmp / "query.graphql"\n val sharedOutFile = tmp / "shared.scala"\n val queryOutFile = tmp / "query.scala"\n\n val schemaDef = """\n enum HelloEnum {\n HELLO,\n WORLD\n }\n\n type A {\n a: String\n }\n\n type B {\n b: String\n }\n\n union HelloUnion = A | B\n\n type Query {\n helloEnum(name: String): HelloEnum,\n helloUnion(name2: String): HelloUnion\n }\n """\n\n val writeSchemaF = fs2.Stream(schemaDef)\n .through(fs2.text.utf8.encode)\n .through(Files[IO].writeAll(schemaFile))\n .compile\n .drain\n\n val writeQueryF = fs2.Stream(queryDef)\n .through(fs2.text.utf8.encode)\n .through(Files[IO].writeAll(queryFile))\n .compile\n .drain\n\n import io.circe._\n import io.circe.syntax._\n val jo = Json.obj(\n "schema" -> Json.fromString(schemaFile.toString),\n "shared" -> Json.fromString(sharedOutFile.toString),\n "queries" -> Json.arr(\n Json.obj(\n "query" -> Json.fromString(queryFile.toString),\n "output" -> Json.fromString(queryOutFile.toString)\n )\n )\n )\n\n writeSchemaF >>\n writeQueryF >>\n Gen.run(List("--validate", "--input",jo.spaces2)) >>\n Files[IO].readAll(queryOutFile)\n .through(fs2.text.utf8.decode)\n .compile\n .string\n .map(println)\n }.unsafeRunSync()\n\nrunQuery(\n """\n fragment HelloFragment on Query {\n helloEnum(name: $name)\n }\n\n query HelloQuery($name: String) {\n ...HelloFragment\n helloUnion(name2: "hey") {\n ... on A {\n a\n }\n ... on B {\n b\n }\n }\n }\n """\n)\n// package gql.client.generated\n// \n// import _root_.gql.client._\n// import _root_.gql.client.dsl._\n// import _root_.gql.parser.{Value => V, AnyValue, Const}\n// import cats.implicits._\n// \n// final case class HelloFragment(\n// helloEnum: Option[HelloEnum]\n// )\n// \n// object HelloFragment {\n// implicit val selectionSet: SelectionSet[HelloFragment] = (\n// sel.build[Option[HelloEnum]]("helloEnum", x => x.args(arg("name", V.VariableValue("name"))))\n// ).map(apply)\n// \n// implicit val fragdef: Fragment[HelloFragment] = fragment[HelloFragment]("HelloFragment", "Query")\n// }\n// \n// final case class HelloQuery(\n// helloFragment: gql.client.generated.HelloFragment,\n// helloUnion: Option[HelloQuery.HelloUnion]\n// )\n// \n// object HelloQuery {\n// final case class HelloUnion(\n// a: Option[HelloUnion.InlineA],\n// b: Option[HelloUnion.InlineB]\n// ) {\n// lazy val variant: Option[HelloUnion.Variant] = \n// (a).map(HelloUnion.Variant.OnA.apply) orElse\n// (b).map(HelloUnion.Variant.OnB.apply)\n// }\n// \n// object HelloUnion {\n// sealed trait Variant extends Product with Serializable\n// object Variant {\n// final case class OnA(\n// a: HelloUnion.InlineA\n// ) extends Variant\n// \n// final case class OnB(\n// b: HelloUnion.InlineB\n// ) extends Variant\n// }\n// \n// final case class InlineA(\n// a: Option[String]\n// )\n// \n// object InlineA {\n// implicit val selectionSet: SelectionSet[InlineA] = (\n// sel.build[Option[String]]("a", x => x)\n// ).map(apply)\n// }\n// \n// final case class InlineB(\n// b: Option[String]\n// )\n// \n// object InlineB {\n// implicit val selectionSet: SelectionSet[InlineB] = (\n// sel.build[Option[String]]("b", x => x)\n// ).map(apply)\n// }\n// \n// implicit val selectionSet: SelectionSet[HelloUnion] = (\n// inlineFrag.build[HelloUnion.InlineA]("A", x => x),\n// inlineFrag.build[HelloUnion.InlineB]("B", x => x)\n// ).mapN(apply)\n// }\n// \n// implicit val selectionSet: SelectionSet[HelloQuery] = (\n// fragment.spread.build[gql.client.generated.HelloFragment](x => x).requiredFragment("HelloFragment", "Query"),\n// sel.build[Option[HelloQuery.HelloUnion]]("helloUnion", x => x.args(arg("name2", V.StringValue("hey"))))\n// ).mapN(apply)\n// \n// final case class Variables(\n// name: Option[Option[String]] = None\n// ) {\n// def setName(value: Option[String]): Variables = copy(name = Some(value))\n// }\n// \n// val queryExpr = (\n// omittableVariable[Option[String]]("name")\n// ).introduce{ _ =>\n// selectionSet\n// }\n// \n// val query = _root_.gql.client.Query.parameterized(_root_.gql.parser.QueryAst.OperationType.Query, "HelloQuery", queryExpr)\n// }\n')),(0,l.kt)("p",null,"When supplying the ",(0,l.kt)("inlineCode",{parentName:"p"},"--validate")," flag, gql will generate a stub implementation of the schema and run the same code as if running a gql server."),(0,l.kt)("p",null,"Lets construct a helper to show this:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'import scala.util.{Try,Failure}\n// We will also remove the ansii color codes from the output, since they don\'t render well in the docs\ndef runFail(q: String) = \n Try {\n runQuery(q)\n } match {\n case Failure(ex) => println(ex.getMessage().replaceAll("\\u001B\\\\[[;\\\\d]*m", ""))\n }\n')),(0,l.kt)("p",null,"Now with a parsing error:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'runFail(\n """\n query MyQuery {\n test.,test\n }\n """\n)\n// Failed to generate code with error: failed at offset 41 on line 2 with code 46\n// char in range } to } (code 125 to 125)\n// for document:\n// | \n// | query MyQuery {\n// | test.,test\n// | >>>>>>>>>>>>>^^^^^^^ line:2, column:16, offset:41, character code code:46\n// | }\n// |\n')),(0,l.kt)("p",null,"And also with a query validation error:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-scala"},'runFail(\n """\n query MyQuery {\n helloEnum(name: 1)\n }\n """\n)\n// Failed to generate code with error: decoding failure for type `String` with message Got value \'1\' with wrong type, expecting string at root.helloEnum.name.String\n// in file /tmp/3746945946060040401/query.graphql\n// | \n// | query MyQuery {\n// | helloEnum(name: 1)\n// | >>>>>>>>>>>>>>>>>>>>>>>>>^^^^^^^ line:2, column:28, offset:53, character code code:49\n// | }\n// |\n')))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/main.92eaa1e9.js b/assets/js/main.aa6edb2e.js similarity index 99% rename from assets/js/main.92eaa1e9.js rename to assets/js/main.aa6edb2e.js index d7bf34610..daa7a0c1b 100644 --- a/assets/js/main.92eaa1e9.js +++ b/assets/js/main.aa6edb2e.js @@ -1,2 +1,2 @@ -/*! For license information please see main.92eaa1e9.js.LICENSE.txt */ -(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[179],{723:(e,t,n)=>{"use strict";n.d(t,{Z:()=>p});var r=n(7294),a=n(7462),o=n(8356),i=n.n(o),l=n(6887);const s={"018b0880":[()=>n.e(227).then(n.bind(n,6967)),"@site/docs/server/schema/structuring_apps.md",6967],"078b5d8a":[()=>n.e(970).then(n.t.bind(n,3769,19)),"/home/runner/work/gql/gql/website/.docusaurus/docusaurus-plugin-content-docs/default/plugin-route-context-module-100.json",3769],"09d7b412":[()=>n.e(268).then(n.bind(n,802)),"@site/docs/server/schema/schema.md",802],"0a44bcdb":[()=>n.e(436).then(n.bind(n,2801)),"@site/docs/server/schema/dsl.md",2801],17896441:[()=>Promise.all([n.e(532),n.e(814),n.e(218),n.e(918)]).then(n.bind(n,903)),"@theme/DocItem",903],"1be78505":[()=>Promise.all([n.e(532),n.e(514)]).then(n.bind(n,9963)),"@theme/DocPage",9963],"1db64337":[()=>n.e(372).then(n.bind(n,6777)),"@site/docs/overview.md",6777],"1df93b7f":[()=>Promise.all([n.e(532),n.e(814),n.e(237)]).then(n.bind(n,193)),"@site/src/pages/index.tsx",193],"1f391b9e":[()=>Promise.all([n.e(532),n.e(814),n.e(218),n.e(85)]).then(n.bind(n,4247)),"@theme/MDXPage",4247],"208ff3a3":[()=>n.e(413).then(n.bind(n,6663)),"@site/docs/server/schema/compiler.md",6663],"2e533e94":[()=>n.e(338).then(n.bind(n,6433)),"@site/docs/server/integrations/http4s.md",6433],"393be207":[()=>n.e(414).then(n.bind(n,3123)),"@site/src/pages/markdown-page.md",3123],"4f169309":[()=>n.e(381).then(n.bind(n,3674)),"@site/docs/server/schema/resolvers.md",3674],"53a3a604":[()=>n.e(287).then(n.bind(n,3481)),"@site/docs/server/integrations/natchez.md",3481],"5e9f5e1a":[()=>Promise.resolve().then(n.bind(n,6809)),"@generated/docusaurus.config",6809],"60875e34":[()=>n.e(554).then(n.bind(n,6351)),"@site/docs/tutorial.md",6351],"61de56f5":[()=>n.e(562).then(n.bind(n,6398)),"@site/docs/overview/modules.md",6398],"62af8b26":[()=>n.e(508).then(n.bind(n,8812)),"@site/docs/server/integrations/relational.md",8812],"677daa8b":[()=>n.e(672).then(n.bind(n,3983)),"@site/docs/server/schema/arrow_dsl.md",3983],"77f00812":[()=>n.e(782).then(n.bind(n,7801)),"@site/docs/server/integrations/goi.md",7801],"7c053662":[()=>n.e(80).then(n.bind(n,288)),"@site/docs/server/schema/output_types.md",288],"8588ea58":[()=>n.e(776).then(n.bind(n,4823)),"@site/docs/client/dsl.md",4823],"92cae478":[()=>n.e(633).then(n.bind(n,5001)),"@site/docs/server/schema/error_handling.md",5001],"935f2afb":[()=>n.e(53).then(n.t.bind(n,1109,19)),"~docs/default/version-current-metadata-prop-751.json",1109],"98b0d92d":[()=>n.e(899).then(n.bind(n,3296)),"@site/docs/server/execution/statistics.md",3296],"9bf1d1b7":[()=>n.e(947).then(n.bind(n,8828)),"@site/docs/server/schema/extending.md",8828],a72b1aff:[()=>n.e(184).then(n.t.bind(n,5745,19)),"/home/runner/work/gql/gql/website/.docusaurus/docusaurus-plugin-content-pages/default/plugin-route-context-module-100.json",5745],cd780aef:[()=>n.e(931).then(n.bind(n,4518)),"@site/docs/server/schema/context.md",4518],ceb10064:[()=>n.e(143).then(n.bind(n,2598)),"@site/docs/server/execution/planning.md",2598],d81c13dc:[()=>n.e(483).then(n.bind(n,8392)),"@site/docs/server/schema/input_types.md",8392],de3af6ca:[()=>n.e(645).then(n.bind(n,4844)),"@site/docs/client/integrations/http4s.md",4844],f790aebb:[()=>n.e(357).then(n.bind(n,399)),"@site/docs/server/integrations/graphqlws.md",399],ffc79f40:[()=>n.e(960).then(n.bind(n,8949)),"@site/docs/client/code-generation.md",8949]};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(9670),d=n(226);function f(e,t){if("*"===e)return i()({loading:u,loader:()=>n.e(972).then(n.bind(n,4972)),modules:["@theme/NotFound"],webpack:()=>[4972],render(e,t){const n=e.default;return r.createElement(d.z,{value:{plugin:{name:"native",id:"default"}}},r.createElement(n,t))}});const o=l[`${e}-${t}`],f={},p=[],m=[],g=(0,c.Z)(o);return Object.entries(g).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.z,{value:s},r.createElement(l,(0,a.Z)({},i,n)))}})}const p=[{path:"/gql/markdown-page",component:f("/gql/markdown-page","130"),exact:!0},{path:"/gql/docs",component:f("/gql/docs","2e0"),routes:[{path:"/gql/docs/client/code-generation",component:f("/gql/docs/client/code-generation","e18"),exact:!0,sidebar:"docs"},{path:"/gql/docs/client/dsl",component:f("/gql/docs/client/dsl","466"),exact:!0,sidebar:"docs"},{path:"/gql/docs/client/integrations/http4s",component:f("/gql/docs/client/integrations/http4s","70a"),exact:!0,sidebar:"docs"},{path:"/gql/docs/overview",component:f("/gql/docs/overview","d3e"),exact:!0,sidebar:"docs"},{path:"/gql/docs/overview/modules",component:f("/gql/docs/overview/modules","5ee"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/execution/planning",component:f("/gql/docs/server/execution/planning","ef4"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/execution/statistics",component:f("/gql/docs/server/execution/statistics","db0"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/integrations/goi",component:f("/gql/docs/server/integrations/goi","8ea"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/integrations/graphqlws",component:f("/gql/docs/server/integrations/graphqlws","bf2"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/integrations/http4s",component:f("/gql/docs/server/integrations/http4s","fa8"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/integrations/natchez",component:f("/gql/docs/server/integrations/natchez","90a"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/integrations/relational",component:f("/gql/docs/server/integrations/relational","920"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/schema/",component:f("/gql/docs/server/schema/","48c"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/schema/arrow_dsl",component:f("/gql/docs/server/schema/arrow_dsl","23e"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/schema/compiler",component:f("/gql/docs/server/schema/compiler","85c"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/schema/context",component:f("/gql/docs/server/schema/context","0b0"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/schema/dsl",component:f("/gql/docs/server/schema/dsl","16a"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/schema/error_handling",component:f("/gql/docs/server/schema/error_handling","90c"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/schema/extending",component:f("/gql/docs/server/schema/extending","74f"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/schema/input_types",component:f("/gql/docs/server/schema/input_types","0c8"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/schema/output_types",component:f("/gql/docs/server/schema/output_types","5a9"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/schema/resolvers",component:f("/gql/docs/server/schema/resolvers","ca5"),exact:!0,sidebar:"docs"},{path:"/gql/docs/server/schema/structuring_apps",component:f("/gql/docs/server/schema/structuring_apps","734"),exact:!0,sidebar:"docs"},{path:"/gql/docs/tutorial",component:f("/gql/docs/tutorial","e83"),exact:!0,sidebar:"docs"}]},{path:"/gql/",component:f("/gql/","07d"),exact:!0},{path:"*",component:f("*")}]},8934:(e,t,n)=>{"use strict";n.d(t,{_:()=>a,t:()=>o});var r=n(7294);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)}},9383:(e,t,n)=>{"use strict";var r=n(7294),a=n(3935),o=n(3727),i=n(405),l=n(412);const s=[n(2497),n(3310),n(8320),n(2295)];var u=n(723),c=n(6775),d=n(8790);function f(e){let{children:t}=e;return r.createElement(r.Fragment,null,t)}var p=n(7462),m=n(5742),g=n(2263),h=n(4996),v=n(6668),b=n(1944),y=n(4711),w=n(9727),E=n(3320),k=n(197);function S(){const{i18n:{defaultLocale:e,localeConfigs:t}}=(0,g.Z)(),n=(0,y.l)();return r.createElement(m.Z,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 _(e){let{permalink:t}=e;const{siteConfig:{url:n}}=(0,g.Z)(),a=function(){const{siteConfig:{url:e}}=(0,g.Z)(),{pathname:t}=(0,c.TH)();return e+(0,h.Z)(t)}(),o=t?`${n}${t}`:a;return r.createElement(m.Z,null,r.createElement("meta",{property:"og:url",content:o}),r.createElement("link",{rel:"canonical",href:o}))}function x(){const{i18n:{currentLocale:e}}=(0,g.Z)(),{metadata:t,image:n}=(0,v.L)();return r.createElement(r.Fragment,null,r.createElement(m.Z,null,r.createElement("meta",{name:"twitter:card",content:"summary_large_image"}),r.createElement("body",{className:w.h})),n&&r.createElement(b.d,{image:n}),r.createElement(_,null),r.createElement(S,null),r.createElement(k.Z,{tag:E.HX,locale:e}),r.createElement(m.Z,null,t.map(((e,t)=>r.createElement("meta",(0,p.Z)({key:t},e))))))}const C=new Map;function T(e){if(C.has(e.pathname))return{...e,pathname:C.get(e.pathname)};if((0,d.f)(u.Z,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 A=n(8934),L=n(8940);function N(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r{var r;const a=(null==(r=t.default)?void 0:r[e])??t[e];return null==a?void 0:a(...n)}));return()=>a.forEach((e=>null==e?void 0:e()))}const O=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)),t=document.getElementById(e);null==t||t.scrollIntoView()}else window.scrollTo(0,0)}({location:n,previousLocation:a}),N("onRouteDidUpdate",{previousLocation:a,location:n}))}),[a,n]),t};function P(e){const t=Array.from(new Set([e,decodeURI(e)])).map((e=>(0,d.f)(u.Z,e))).flat();return Promise.all(t.map((e=>null==e.route.component.preload?void 0:e.route.component.preload())))}class I extends r.Component{constructor(e){super(e),this.previousLocation=void 0,this.routeUpdateCleanupCb=void 0,this.previousLocation=null,this.routeUpdateCleanupCb=l.Z.canUseDOM?N("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=N("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(O,{previousLocation:this.previousLocation,location:t},r.createElement(c.AW,{location:t,render:()=>e}))}}const R=I,D="docusaurus-base-url-issue-banner-container",M="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('${D}');\n if (!bannerContainer) {\n return;\n }\n var bannerHtml = ${JSON.stringify(function(e){return`\n
\n

Your Docusaurus site did not load properly.

\n

A very common reason is a wrong site baseUrl configuration.

\n

Current configured baseUrl = ${e} ${"/"===e?" (default value)":""}

\n

We suggest trying baseUrl =

\n
\n`}(e)).replace(/{window[F]=!1}),[]),r.createElement(r.Fragment,null,!l.Z.canUseDOM&&r.createElement(m.Z,null,r.createElement("script",null,B(e))),r.createElement("div",{id:D}))}function z(){const{siteConfig:{baseUrl:e,baseUrlIssueBanner:t}}=(0,g.Z)(),{pathname:n}=(0,c.TH)();return t&&n===e?r.createElement($,null):null}function U(){const{siteConfig:{favicon:e,title:t,noIndex:n},i18n:{currentLocale:a,localeConfigs:o}}=(0,g.Z)(),i=(0,h.Z)(e),{htmlLang:l,direction:s}=o[a];return r.createElement(m.Z,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(4763);function q(){const e=(0,d.H)(u.Z),t=(0,c.TH)();return r.createElement(j.Z,null,r.createElement(L.M,null,r.createElement(A.t,null,r.createElement(f,null,r.createElement(U,null),r.createElement(x,null),r.createElement(z,null),r.createElement(R,{location:T(t)},e)))))}var Z=n(6887);const H=function(e){try{return document.createElement("link").relList.supports(e)}catch{return!1}}("prefetch")?function(e){return new Promise(((t,n)=>{var r;if("undefined"==typeof document)return void n();const a=document.createElement("link");a.setAttribute("rel","prefetch"),a.setAttribute("href",e),a.onload=()=>t(),a.onerror=()=>n();const o=document.getElementsByTagName("head")[0]??(null==(r=document.getElementsByName("script")[0])?void 0:r.parentNode);null==o||o.appendChild(a)}))}: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 V=n(9670);const G=new Set,W=new Set,Y=()=>{var e,t;return(null==(e=navigator.connection)?void 0:e.effectiveType.includes("2g"))||(null==(t=navigator.connection)?void 0:t.saveData)},K={prefetch(e){if(!(e=>!Y()&&!W.has(e)&&!G.has(e))(e))return!1;G.add(e);const t=(0,d.f)(u.Z,e).flatMap((e=>{return t=e.route.path,Object.entries(Z).filter((e=>{let[n]=e;return n.replace(/-[^-]+$/,"")===t})).flatMap((e=>{let[,t]=e;return Object.values((0,V.Z)(t))}));var t}));return Promise.all(t.map((e=>{const t=n.gca(e);return t&&!t.includes("undefined")?H(t).catch((()=>{})):Promise.resolve()})))},preload:e=>!!(e=>!Y()&&!W.has(e))(e)&&(W.add(e),P(e))},X=Object.freeze(K);if(l.Z.canUseDOM){window.docusaurus=X;const e=a.hydrate;P(window.location.pathname).then((()=>{e(r.createElement(i.B6,null,r.createElement(o.VK,null,r.createElement(q,null))),document.getElementById("__docusaurus"))}))}},8940:(e,t,n)=>{"use strict";n.d(t,{_:()=>c,M:()=>d});var r=n(7294),a=n(6809);const o=JSON.parse('{"docusaurus-plugin-content-docs":{"default":{"path":"/gql/docs","versions":[{"name":"current","label":"Next","isLast":true,"path":"/gql/docs","mainDocId":"overview","docs":[{"id":"client/code-generation","path":"/gql/docs/client/code-generation","sidebar":"docs"},{"id":"client/dsl","path":"/gql/docs/client/dsl","sidebar":"docs"},{"id":"client/integrations/http4s","path":"/gql/docs/client/integrations/http4s","sidebar":"docs"},{"id":"overview","path":"/gql/docs/overview","sidebar":"docs"},{"id":"overview/modules","path":"/gql/docs/overview/modules","sidebar":"docs"},{"id":"server/execution/planning","path":"/gql/docs/server/execution/planning","sidebar":"docs"},{"id":"server/execution/statistics","path":"/gql/docs/server/execution/statistics","sidebar":"docs"},{"id":"server/integrations/goi","path":"/gql/docs/server/integrations/goi","sidebar":"docs"},{"id":"server/integrations/graphqlws","path":"/gql/docs/server/integrations/graphqlws","sidebar":"docs"},{"id":"server/integrations/http4s","path":"/gql/docs/server/integrations/http4s","sidebar":"docs"},{"id":"server/integrations/natchez","path":"/gql/docs/server/integrations/natchez","sidebar":"docs"},{"id":"server/integrations/relational","path":"/gql/docs/server/integrations/relational","sidebar":"docs"},{"id":"server/schema/arrow_dsl","path":"/gql/docs/server/schema/arrow_dsl","sidebar":"docs"},{"id":"server/schema/compiler","path":"/gql/docs/server/schema/compiler","sidebar":"docs"},{"id":"server/schema/context","path":"/gql/docs/server/schema/context","sidebar":"docs"},{"id":"server/schema/dsl","path":"/gql/docs/server/schema/dsl","sidebar":"docs"},{"id":"server/schema/error_handling","path":"/gql/docs/server/schema/error_handling","sidebar":"docs"},{"id":"server/schema/extending","path":"/gql/docs/server/schema/extending","sidebar":"docs"},{"id":"server/schema/input_types","path":"/gql/docs/server/schema/input_types","sidebar":"docs"},{"id":"server/schema/output_types","path":"/gql/docs/server/schema/output_types","sidebar":"docs"},{"id":"server/schema/resolvers","path":"/gql/docs/server/schema/resolvers","sidebar":"docs"},{"id":"server/schema/schema","path":"/gql/docs/server/schema/","sidebar":"docs"},{"id":"server/schema/structuring_apps","path":"/gql/docs/server/schema/structuring_apps","sidebar":"docs"},{"id":"tutorial","path":"/gql/docs/tutorial","sidebar":"docs"}],"draftIds":[],"sidebars":{"docs":{"link":{"path":"/gql/docs/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(7529);const s=JSON.parse('{"docusaurusVersion":"2.3.1","siteVersion":"0.0.0","pluginVersions":{"docusaurus-plugin-content-docs":{"type":"package","name":"@docusaurus/plugin-content-docs","version":"2.3.1"},"docusaurus-plugin-content-blog":{"type":"package","name":"@docusaurus/plugin-content-blog","version":"2.3.1"},"docusaurus-plugin-content-pages":{"type":"package","name":"@docusaurus/plugin-content-pages","version":"2.3.1"},"docusaurus-plugin-sitemap":{"type":"package","name":"@docusaurus/plugin-sitemap","version":"2.3.1"},"docusaurus-theme-classic":{"type":"package","name":"@docusaurus/theme-classic","version":"2.3.1"},"docusaurus-theme-mermaid":{"type":"package","name":"@docusaurus/theme-mermaid","version":"2.3.1"}}}'),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)}},4763:(e,t,n)=>{"use strict";n.d(t,{Z:()=>c});var r=n(7294),a=n(412),o=n(5742),i=n(6963);function l(e){let{error:t,tryAgain:n}=e;return r.createElement("div",{style:{display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"center",height:"50vh",width:"100%",fontSize:"20px"}},r.createElement("h1",null,"This page crashed."),r.createElement("p",null,t.message),r.createElement("button",{type:"button",onClick:n},"Try again"))}function s(e){let{error:t,tryAgain:n}=e;return r.createElement(c,{fallback:()=>r.createElement(l,{error:t,tryAgain:n})},r.createElement(o.Z,null,r.createElement("title",null,"Page Error")),r.createElement(i.Z,null,r.createElement(l,{error:t,tryAgain:n})))}const u=e=>r.createElement(s,e);class c extends r.Component{constructor(e){super(e),this.state={error:null}}componentDidCatch(e){a.Z.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??u)(e)}return e??null}}},412:(e,t,n)=>{"use strict";n.d(t,{Z:()=>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}},5742:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});var r=n(7294),a=n(405);function o(e){return r.createElement(a.ql,e)}},9960:(e,t,n)=>{"use strict";n.d(t,{Z:()=>p});var r=n(7462),a=n(7294),o=n(3727),i=n(8780),l=n(2263),s=n(3919),u=n(412);const c=a.createContext({collectLink:()=>{}});var d=n(4996);function f(e,t){var n;let{isNavLink:f,to:p,href:m,activeClassName:g,isActive:h,"data-noBrokenLinkCheck":v,autoAddBaseUrl:b=!0,...y}=e;const{siteConfig:{trailingSlash:w,baseUrl:E}}=(0,l.Z)(),{withBaseUrl:k}=(0,d.C)(),S=(0,a.useContext)(c),_=(0,a.useRef)(null);(0,a.useImperativeHandle)(t,(()=>_.current));const x=p||m;const C=(0,s.Z)(x),T=null==x?void 0:x.replace("pathname://","");let A=void 0!==T?(L=T,b&&(e=>e.startsWith("/"))(L)?k(L):L):void 0;var L;A&&C&&(A=(0,i.applyTrailingSlash)(A,{trailingSlash:w,baseUrl:E}));const N=(0,a.useRef)(!1),O=f?o.OL:o.rU,P=u.Z.canUseIntersectionObserver,I=(0,a.useRef)(),R=()=>{N.current||null==A||(window.docusaurus.preload(A),N.current=!0)};(0,a.useEffect)((()=>(!P&&C&&null!=A&&window.docusaurus.prefetch(A),()=>{P&&I.current&&I.current.disconnect()})),[I,A,P,C]);const D=(null==(n=A)?void 0:n.startsWith("#"))??!1,M=!A||!C||D;return M||v||S.collectLink(A),M?a.createElement("a",(0,r.Z)({ref:_,href:A},x&&!C&&{target:"_blank",rel:"noopener noreferrer"},y)):a.createElement(O,(0,r.Z)({},y,{onMouseEnter:R,onTouchStart:R,innerRef:e=>{_.current=e,P&&e&&C&&(I.current=new window.IntersectionObserver((t=>{t.forEach((t=>{e===t.target&&(t.isIntersecting||t.intersectionRatio>0)&&(I.current.unobserve(e),I.current.disconnect(),null!=A&&window.docusaurus.prefetch(A))}))})),I.current.observe(e))},to:A},f&&{isActive:h,activeClassName:g}))}const p=a.forwardRef(f)},5999:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s,I:()=>l});var r=n(7294);function a(e,t){const n=e.split(/(\{\w+\})/).map(((e,n)=>{if(n%2==1){const n=null==t?void 0: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(7529);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 children",t),new Error("The Docusaurus component only accept simple string values");const l=i({message:t,id:n});return r.createElement(r.Fragment,null,a(l,o))}},9935:(e,t,n)=>{"use strict";n.d(t,{m:()=>r});const r="default"},3919:(e,t,n)=>{"use strict";function r(e){return/^(?:\w*:|\/\/)/.test(e)}function a(e){return void 0!==e&&!r(e)}n.d(t,{Z:()=>a,b:()=>r})},4996:(e,t,n)=>{"use strict";n.d(t,{C:()=>i,Z:()=>l});var r=n(7294),a=n(2263),o=n(3919);function i(){const{siteConfig:{baseUrl:e,url:t}}=(0,a.Z)(),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.b)(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)}},2263:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});var r=n(7294),a=n(8940);function o(){return(0,r.useContext)(a._)}},2389:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});var r=n(7294),a=n(8934);function o(){return(0,r.useContext)(a._)}},9670:(e,t,n)=>{"use strict";n.d(t,{Z:()=>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}},226:(e,t,n)=>{"use strict";n.d(t,{_:()=>a,z:()=>o});var r=n(7294);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,...null==n?void 0:n.data};return{plugin:t.plugin,data:r}}({parent:o,value:n})),[o,n]);return r.createElement(a.Provider,{value:i},t)}},143:(e,t,n)=>{"use strict";n.d(t,{Iw:()=>g,gA:()=>f,_r:()=>c,Jo:()=>h,zh:()=>d,yW:()=>m,gB:()=>p});var r=n(6775),a=n(2263),o=n(9935);function i(e,t){void 0===t&&(t={});const n=function(){const{globalData:e}=(0,a.Z)();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.LX)(t,{path:e.path,exact:!1,strict:!1})))}(e,t),a=null==n?void 0:n.docs.find((e=>!!(0,r.LX)(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.m),void 0===n&&(n={});const r=i(e),a=null==r?void 0:r[t];if(!a&&n.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin with id "${t}".`);return a}("docusaurus-plugin-content-docs",e,{failfast:!0});function f(e){void 0===e&&(e={});const t=c(),{pathname:n}=(0,r.TH)();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.LX)(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 g(e){const t=d(e),{pathname:n}=(0,r.TH)();return s(t,n)}function h(e){const t=d(e),{pathname:n}=(0,r.TH)();return function(e,t){const n=l(e);return{latestDocSuggestion:s(e,t).alternateDocVersions[n.name],latestVersionSuggestion:n}}(t,n)}},8320:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(4865),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()}}},3310:(e,t,n)=>{"use strict";n.r(t);var r=n(7410),a=n(6809);!function(e){const{themeConfig:{prism:t}}=a.default,{additionalLanguages:r}=t;globalThis.Prism=e,r.forEach((e=>{n(7783)(`./prism-${e}`)})),delete globalThis.Prism}(r.Z)},9471:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});var r=n(7294);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"}))}},6963:(e,t,n)=>{"use strict";n.d(t,{Z:()=>st});var r=n(7294),a=n(6010),o=n(4763),i=n(1944),l=n(7462),s=n(6775),u=n(5999),c=n(5936);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.k6)(),n=(0,r.useCallback)((e=>{e.preventDefault();const t=document.querySelector("main:first-of-type")??document.getElementById(d);t&&f(t)}),[]);return(0,c.S)((n=>{let{location:r}=n;e.current&&!r.hash&&"PUSH"===t&&f(e.current)})),{containerRef:e,onClick:n}}const m=(0,u.I)({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 g(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.Z)({},e,{href:`#${d}`,onClick:a}),t))}var h=n(5281),v=n(9727);const b="skipToContent_fXgn";function y(){return r.createElement(g,{className:b})}var w=n(6668),E=n(9689);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.Z)({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 _(e){return r.createElement("button",(0,l.Z)({type:"button","aria-label":(0,u.I)({id:"theme.AnnouncementBar.closeButtonAriaLabel",message:"Close",description:"The ARIA label for close button of announcement bar"})},e,{className:(0,a.Z)("clean-btn close",S,e.className)}),r.createElement(k,{width:14,height:14,strokeWidth:3.1}))}const x="content_knG7";function C(e){const{announcementBar:t}=(0,w.L)(),{content:n}=t;return r.createElement("div",(0,l.Z)({},e,{className:(0,a.Z)(x,e.className),dangerouslySetInnerHTML:{__html:n}}))}const T="announcementBar_mb4j",A="announcementBarPlaceholder_vyr4",L="announcementBarClose_gvF7",N="announcementBarContent_xLdY";function O(){const{announcementBar:e}=(0,w.L)(),{isActive:t,close:n}=(0,E.nT)();if(!t)return null;const{backgroundColor:a,textColor:o,isCloseable:i}=e;return r.createElement("div",{className:T,style:{backgroundColor:a,color:o},role:"banner"},i&&r.createElement("div",{className:A}),r.createElement(C,{className:N}),i&&r.createElement(_,{onClick:n,className:L}))}var P=n(2961),I=n(2466);var R=n(902),D=n(3102);const M=r.createContext(null);function F(e){let{children:t}=e;const n=function(){const e=(0,P.e)(),t=(0,D.HY)(),[n,a]=(0,r.useState)(!1),o=null!==t.component,i=(0,R.D9)(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(M.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)(M);if(!e)throw new R.i6("NavbarSecondaryMenuDisplayProvider");const[t,n]=e,a=(0,r.useCallback)((()=>n(!1)),[n]),o=(0,D.HY)();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.Z)("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(2949),j=n(2389);function q(e){return r.createElement("svg",(0,l.Z)({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 Z(e){return r.createElement("svg",(0,l.Z)({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 H={toggle:"toggle_vylO",toggleButton:"toggleButton_gllP",darkToggleIcon:"darkToggleIcon_wfgR",lightToggleIcon:"lightToggleIcon_pyhR",toggleButtonDisabled:"toggleButtonDisabled_aARS"};function V(e){let{className:t,value:n,onChange:o}=e;const i=(0,j.Z)(),l=(0,u.I)({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"===n?(0,u.I)({message:"dark mode",id:"theme.colorToggle.ariaLabel.mode.dark",description:"The name for the dark color mode"}):(0,u.I)({message:"light mode",id:"theme.colorToggle.ariaLabel.mode.light",description:"The name for the light color mode"})});return r.createElement("div",{className:(0,a.Z)(H.toggle,t)},r.createElement("button",{className:(0,a.Z)("clean-btn",H.toggleButton,!i&&H.toggleButtonDisabled),type:"button",onClick:()=>o("dark"===n?"light":"dark"),disabled:!i,title:l,"aria-label":l,"aria-live":"polite"},r.createElement(q,{className:(0,a.Z)(H.toggleIcon,H.lightToggleIcon)}),r.createElement(Z,{className:(0,a.Z)(H.toggleIcon,H.darkToggleIcon)})))}const G=r.memo(V);function W(e){let{className:t}=e;const n=(0,w.L)().colorMode.disableSwitch,{colorMode:a,setColorMode:o}=(0,U.I)();return n?null:r.createElement(G,{className:t,value:a,onChange:o})}var Y=n(1327);function K(){return r.createElement(Y.Z,{className:"navbar__brand",imageClassName:"navbar__logo",titleClassName:"navbar__title text--truncate"})}function X(){const e=(0,P.e)();return r.createElement("button",{type:"button","aria-label":(0,u.I)({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 Q(){return r.createElement("div",{className:"navbar-sidebar__brand"},r.createElement(K,null),r.createElement(W,{className:"margin-right--md"}),r.createElement(X,null))}var J=n(9960),ee=n(4996),te=n(3919);function ne(e,t){return void 0!==e&&void 0!==t&&new RegExp(e,"gi").test(t)}var re=n(9471);function ae(e){let{activeBasePath:t,activeBaseRegex:n,to:a,href:o,label:i,html:s,isDropdownLink:u,prependBaseUrlToHref:c,...d}=e;const f=(0,ee.Z)(a),p=(0,ee.Z)(t),m=(0,ee.Z)(o,{forcePrependBaseUrl:!0}),g=i&&o&&!(0,te.Z)(o),h=s?{dangerouslySetInnerHTML:{__html:s}}:{children:r.createElement(r.Fragment,null,i,g&&r.createElement(re.Z,u&&{width:12,height:12}))};return o?r.createElement(J.Z,(0,l.Z)({href:c?m:o},d,h)):r.createElement(J.Z,(0,l.Z)({to:f,isNavLink:!0},(t||n)&&{isActive:(e,t)=>n?ne(n,t.pathname):t.pathname.startsWith(p)},d,h))}function oe(e){let{className:t,isDropdownItem:n=!1,...o}=e;const i=r.createElement(ae,(0,l.Z)({className:(0,a.Z)(n?"dropdown__link":"navbar__item navbar__link",t),isDropdownLink:n},o));return n?r.createElement("li",null,i):i}function ie(e){let{className:t,isDropdownItem:n,...o}=e;return r.createElement("li",{className:"menu__list-item"},r.createElement(ae,(0,l.Z)({className:(0,a.Z)("menu__link",t)},o)))}function le(e){let{mobile:t=!1,position:n,...a}=e;const o=t?ie:oe;return r.createElement(o,(0,l.Z)({},a,{activeClassName:a.activeClassName??(t?"menu__link--active":"navbar__link--active")}))}var se=n(6043),ue=n(8596),ce=n(2263);function de(e,t){return e.some((e=>function(e,t){return!!(0,ue.Mg)(e.to,t)||!!ne(e.activeBaseRegex,t)||!(!e.activeBasePath||!t.startsWith(e.activeBasePath))}(e,t)))}function fe(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.removeEventListener("mousedown",e),document.removeEventListener("touchstart",e)}}),[u]),r.createElement("div",{ref:u,className:(0,a.Z)("navbar__item","dropdown","dropdown--hoverable",{"dropdown--right":"right"===n,"dropdown--show":c})},r.createElement(ae,(0,l.Z)({"aria-haspopup":"true","aria-expanded":c,role:"button",href:s.to?void 0:"#",className:(0,a.Z)("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,n)=>r.createElement(Ce,(0,l.Z)({isDropdownItem:!0,onKeyDown:e=>{if(n===t.length-1&&"Tab"===e.key){e.preventDefault(),d(!1);const t=u.current.nextElementSibling;if(t){(t instanceof HTMLAnchorElement?t:t.querySelector("a")).focus()}}},activeClassName:"dropdown__link--active"},e,{key:n}))))))}function pe(e){let{items:t,className:n,position:o,onClick:i,...u}=e;const c=function(){const{siteConfig:{baseUrl:e}}=(0,ce.Z)(),{pathname:t}=(0,s.TH)();return t.replace(e,"/")}(),d=de(t,c),{collapsed:f,toggleCollapsed:p,setCollapsed:m}=(0,se.u)({initialState:()=>!d});return(0,r.useEffect)((()=>{d&&m(!d)}),[c,d,m]),r.createElement("li",{className:(0,a.Z)("menu__list-item",{"menu__list-item--collapsed":f})},r.createElement(ae,(0,l.Z)({role:"button",className:(0,a.Z)("menu__link menu__link--sublist menu__link--sublist-caret",n)},u,{onClick:e=>{e.preventDefault(),p()}}),u.children??u.label),r.createElement(se.z,{lazy:!0,as:"ul",className:"menu__list",collapsed:f},t.map(((e,t)=>r.createElement(Ce,(0,l.Z)({mobile:!0,isDropdownItem:!0,onClick:i,activeClassName:"menu__link--active"},e,{key:t}))))))}function me(e){let{mobile:t=!1,...n}=e;const a=t?pe:fe;return r.createElement(a,n)}var ge=n(4711);function he(e){let{width:t=20,height:n=20,...a}=e;return r.createElement("svg",(0,l.Z)({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";const be=()=>null,ye="searchBox_ZlJk";function we(e){let{children:t,className:n}=e;return r.createElement("div",{className:(0,a.Z)(n,ye)},t)}var Ee=n(143),ke=n(2802);var Se=n(373);const _e=e=>e.docs.find((t=>t.id===e.mainDocId));const xe={default:le,localeDropdown:function(e){let{mobile:t,dropdownItemsBefore:n,dropdownItemsAfter:a,...o}=e;const{i18n:{currentLocale:i,locales:c,localeConfigs:d}}=(0,ce.Z)(),f=(0,ge.l)(),{search:p,hash:m}=(0,s.TH)(),g=[...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],h=t?(0,u.I)({message:"Languages",id:"theme.navbar.mobileLanguageDropdown.label",description:"The label for the mobile language switcher dropdown"}):d[i].label;return r.createElement(me,(0,l.Z)({},o,{mobile:t,label:r.createElement(r.Fragment,null,r.createElement(he,{className:ve}),h),items:g}))},search:function(e){let{mobile:t,className:n}=e;return t?null:r.createElement(we,{className:n},r.createElement(be,null))},dropdown:me,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.Z)({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,Ee.Iw)(a),s=(0,ke.vY)(t,a);return null===s?null:r.createElement(le,(0,l.Z)({exact:!0},o,{isActive:()=>(null==i?void 0:i.path)===s.path||!(null==i||!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,Ee.Iw)(a),s=(0,ke.oz)(t,a).link;if(!s)throw new Error(`DocSidebarNavbarItem: Sidebar with ID "${t}" doesn't have anything to be linked to.`);return r.createElement(le,(0,l.Z)({exact:!0},o,{isActive:()=>(null==i?void 0: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,ke.lO)(a)[0],s=t??i.label,u=n??(e=>e.docs.find((t=>t.id===e.mainDocId)))(i).path;return r.createElement(le,(0,l.Z)({},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.TH)(),p=(0,Ee.Iw)(n),m=(0,Ee.gB)(n),{savePreferredVersionName:g}=(0,Se.J)(n),h=[...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:()=>g(e.name)}})),...i],v=(0,ke.lO)(n)[0],b=t&&h.length>1?(0,u.I)({id:"theme.navbar.mobileVersionsDropdown.label",message:"Versions",description:"The label for the navbar versions dropdown on mobile view"}):v.label,y=t&&h.length>1?void 0:_e(v).path;return h.length<=1?r.createElement(le,(0,l.Z)({},c,{mobile:t,label:b,to:y,isActive:a?()=>!1:void 0})):r.createElement(me,(0,l.Z)({},c,{mobile:t,label:b,to:y,items:h,isActive:a?()=>!1:void 0}))}};function Ce(e){let{type:t,...n}=e;const a=function(e,t){return e&&"default"!==e?e:"items"in t?"dropdown":"default"}(t,n),o=xe[a];if(!o)throw new Error(`No NavbarItem component found for type "${t}".`);return r.createElement(o,n)}function Te(){const e=(0,P.e)(),t=(0,w.L)().navbar.items;return r.createElement("ul",{className:"menu__list"},t.map(((t,n)=>r.createElement(Ce,(0,l.Z)({mobile:!0},t,{onClick:()=>e.toggle(),key:n})))))}function Ae(e){return r.createElement("button",(0,l.Z)({},e,{type:"button",className:"clean-btn navbar-sidebar__back"}),r.createElement(u.Z,{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 Le(){const e=0===(0,w.L)().navbar.items.length,t=$();return r.createElement(r.Fragment,null,!e&&r.createElement(Ae,{onClick:()=>t.hide()}),t.content)}function Ne(){const e=(0,P.e)();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(Q,null),primaryMenu:r.createElement(Te,null),secondaryMenu:r.createElement(Le,null)}):null}const Oe="navbarHideable_m1mJ",Pe="navbarHidden_jGov";function Ie(e){return r.createElement("div",(0,l.Z)({role:"presentation"},e,{className:(0,a.Z)("navbar-sidebar__backdrop",e.className)}))}function Re(e){let{children:t}=e;const{navbar:{hideOnScroll:n,style:o}}=(0,w.L)(),i=(0,P.e)(),{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,I.RF)(((t,r)=>{let{scrollY:i}=t;if(!e)return;if(i=l?n(!1):i+u{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.I)({id:"theme.NavBar.navAriaLabel",message:"Main",description:"The ARIA label for the main navigation"}),className:(0,a.Z)("navbar","navbar--fixed-top",n&&[Oe,!s&&Pe],{"navbar--dark":"dark"===o,"navbar--primary":"primary"===o,"navbar-sidebar--show":i.shown})},t,r.createElement(Ie,{onClick:i.toggle}),r.createElement(Ne,null))}function De(e){let{width:t=30,height:n=30,className:a,...o}=e;return r.createElement("svg",(0,l.Z)({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 Me(){const{toggle:e,shown:t}=(0,P.e)();return r.createElement("button",{onClick:e,"aria-label":(0,u.I)({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(De,null))}const Fe="colorModeToggle_DEke";function Be(e){let{items:t}=e;return r.createElement(r.Fragment,null,t.map(((e,t)=>r.createElement(Ce,(0,l.Z)({},e,{key:t})))))}function $e(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 ze(){const e=(0,P.e)(),t=(0,w.L)().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($e,{left:r.createElement(r.Fragment,null,!e.disabled&&r.createElement(Me,null),r.createElement(K,null),r.createElement(Be,{items:n})),right:r.createElement(r.Fragment,null,r.createElement(Be,{items:a}),r.createElement(W,{className:Fe}),!o&&r.createElement(we,null,r.createElement(be,null)))})}function Ue(){return r.createElement(Re,null,r.createElement(ze,null))}function je(e){let{item:t}=e;const{to:n,href:a,label:o,prependBaseUrlToHref:i,...s}=t,u=(0,ee.Z)(n),c=(0,ee.Z)(a,{forcePrependBaseUrl:!0});return r.createElement(J.Z,(0,l.Z)({className:"footer__link-item"},a?{href:i?c:a}:{to:u},s),o,a&&!(0,te.Z)(a)&&r.createElement(re.Z,null))}function qe(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(je,{item:t}))}function Ze(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(qe,{key:t,item:e})))))}function He(e){let{columns:t}=e;return r.createElement("div",{className:"row footer__links"},t.map(((e,t)=>r.createElement(Ze,{key:t,column:e}))))}function Ve(){return r.createElement("span",{className:"footer__link-separator"},"\xb7")}function Ge(e){let{item:t}=e;return t.html?r.createElement("span",{className:"footer__link-item",dangerouslySetInnerHTML:{__html:t.html}}):r.createElement(je,{item:t})}function We(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(Ge,{item:e}),t.length!==n+1&&r.createElement(Ve,null))))))}function Ye(e){let{links:t}=e;return function(e){return"title"in e[0]}(t)?r.createElement(He,{columns:t}):r.createElement(We,{links:t})}var Ke=n(941);const Xe="footerLogoLink_BH7S";function Qe(e){let{logo:t}=e;const{withBaseUrl:n}=(0,ee.C)(),o={light:n(t.src),dark:n(t.srcDark??t.src)};return r.createElement(Ke.Z,{className:(0,a.Z)("footer__logo",t.className),alt:t.alt,sources:o,width:t.width,height:t.height,style:t.style})}function Je(e){let{logo:t}=e;return t.href?r.createElement(J.Z,{href:t.href,className:Xe,target:t.target},r.createElement(Qe,{logo:t})):r.createElement(Qe,{logo:t})}function et(e){let{copyright:t}=e;return r.createElement("div",{className:"footer__copyright",dangerouslySetInnerHTML:{__html:t}})}function tt(e){let{style:t,links:n,logo:o,copyright:i}=e;return r.createElement("footer",{className:(0,a.Z)("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 nt(){const{footer:e}=(0,w.L)();if(!e)return null;const{copyright:t,links:n,logo:a,style:o}=e;return r.createElement(tt,{style:o,links:n&&n.length>0&&r.createElement(Ye,{links:n}),logo:a&&r.createElement(Je,{logo:a}),copyright:t&&r.createElement(et,{copyright:t})})}const rt=r.memo(nt),at=(0,R.Qc)([U.S,E.pl,I.OC,Se.L5,i.VC,function(e){let{children:t}=e;return r.createElement(D.n2,null,r.createElement(P.M,null,r.createElement(F,null,t)))}]);function ot(e){let{children:t}=e;return r.createElement(at,null,t)}function it(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.Z,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed"},"This page crashed.")),r.createElement("p",null,t.message),r.createElement("div",null,r.createElement("button",{type:"button",onClick:n},r.createElement(u.Z,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again when the page crashed"},"Try again"))))))}const lt="mainWrapper_z2l0";function st(e){const{children:t,noFooter:n,wrapperClassName:l,title:s,description:u}=e;return(0,v.t)(),r.createElement(ot,null,r.createElement(i.d,{title:s,description:u}),r.createElement(y,null),r.createElement(O,null),r.createElement(Ue,null),r.createElement("div",{id:d,className:(0,a.Z)(h.k.wrapper.main,lt,l)},r.createElement(o.Z,{fallback:e=>r.createElement(it,e)},t)),!n&&r.createElement(rt,null))}},1327:(e,t,n)=>{"use strict";n.d(t,{Z:()=>d});var r=n(7462),a=n(7294),o=n(9960),i=n(4996),l=n(2263),s=n(6668),u=n(941);function c(e){let{logo:t,alt:n,imageClassName:r}=e;const o={light:(0,i.Z)(t.src),dark:(0,i.Z)(t.srcDark||t.src)},l=a.createElement(u.Z,{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.Z)(),{navbar:{title:n,logo:u}}=(0,s.L)(),{imageClassName:d,titleClassName:f,...p}=e,m=(0,i.Z)((null==u?void 0:u.href)||"/"),g=n?"":t,h=(null==u?void 0:u.alt)??g;return a.createElement(o.Z,(0,r.Z)({to:m},p,(null==u?void 0:u.target)&&{target:u.target}),u&&a.createElement(c,{logo:u,alt:h,imageClassName:d}),null!=n&&a.createElement("b",{className:f},n))}},197:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});var r=n(7294),a=n(5742);function o(e){let{locale:t,version:n,tag:o}=e;const i=t;return r.createElement(a.Z,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}))}},941:(e,t,n)=>{"use strict";n.d(t,{Z:()=>u});var r=n(7462),a=n(7294),o=n(6010),i=n(2389),l=n(2949);const s={themedImage:"themedImage_ToTc","themedImage--light":"themedImage--light_HNdA","themedImage--dark":"themedImage--dark_i4oU"};function u(e){const t=(0,i.Z)(),{colorMode:n}=(0,l.I)(),{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.Z)({key:e,src:u[e],alt:d,className:(0,o.Z)(s.themedImage,s[`themedImage--${e}`],c)},f)))))}},6043:(e,t,n)=>{"use strict";n.d(t,{u:()=>i,z:()=>m});var r=n(7462),a=n(7294),o=n(412);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){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=e.scrollHeight,n=(null==r?void 0:r.duration)??function(e){const t=e/36;return Math.round(10*(4+15*t**.25+t/5))}(t);return{transition:`height ${n}ms ${(null==r?void 0:r.easing)??"ease-in-out"}`,height:`${t}px`}}function i(){const t=a();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?(i(),requestAnimationFrame((()=>{e.style.height=l.height,e.style.overflow=l.overflow}))):(e.style.display="block",requestAnimationFrame((()=>{i()})))}));return()=>cancelAnimationFrame(t)}()}),[t,n,r])}function d(e){if(!o.Z.canUseDOM)return e?l:s}function f(e){let{as:t="div",collapsed:n,children:r,animation:o,onCollapseTransitionEnd:i,className:l,disableSSRStyle:s}=e;const f=(0,a.useRef)(null);return c({collapsibleRef:f,collapsed:n,animation:o}),a.createElement(t,{ref:f,style:s?void 0:d(n),onTransitionEnd:e=>{"height"===e.propertyName&&(u(f.current,n),null==i||i(n))},className:l},r)}function p(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(f,(0,r.Z)({},n,{collapsed:l})):null}function m(e){let{lazy:t,...n}=e;const r=t?p:f;return a.createElement(r,n)}},9689:(e,t,n)=>{"use strict";n.d(t,{nT:()=>m,pl:()=>p});var r=n(7294),a=n(2389),o=n(12),i=n(902),l=n(6668);const s=(0,o.WA)("docusaurus.announcement.dismiss"),u=(0,o.WA)("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.L)(),t=(0,a.Z)(),[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.i6("AnnouncementBarProvider");return e}},2949:(e,t,n)=>{"use strict";n.d(t,{I:()=>h,S:()=>g});var r=n(7294),a=n(412),o=n(902),i=n(12),l=n(6668);const s=r.createContext(void 0),u="theme",c=(0,i.WA)(u),d="light",f="dark",p=e=>e===f?f:d;function m(){const{colorMode:{defaultMode:e,disableSwitch:t,respectPrefersColorScheme:n}}=(0,l.L)(),[o,i]=(0,r.useState)((e=>a.Z.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 g(e){let{children:t}=e;const n=m();return r.createElement(s.Provider,{value:n},t)}function h(){const e=(0,r.useContext)(s);if(null==e)throw new o.i6("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},373:(e,t,n)=>{"use strict";n.d(t,{J:()=>y,L5:()=>v});var r=n(7294),a=n(143),o=n(9935),i=n(6668),l=n(2802),s=n(902),u=n(12);const c=e=>`docs-preferred-version-${e}`,d=(e,t,n)=>{(0,u.WA)(c(e),{persistence:t}).set(n)},f=(e,t)=>(0,u.WA)(c(e),{persistence:t}).get(),p=(e,t)=>{(0,u.WA)(c(e),{persistence:t}).del()};const m=r.createContext(null);function g(){const e=(0,a._r)(),t=(0,i.L)().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 h(e){let{children:t}=e;const n=g();return r.createElement(m.Provider,{value:n},t)}function v(e){let{children:t}=e;return l.cE?r.createElement(h,null,t):r.createElement(r.Fragment,null,t)}function b(){const e=(0,r.useContext)(m);if(!e)throw new s.i6("DocsPreferredVersionContextProvider");return e}function y(e){void 0===e&&(e=o.m);const t=(0,a.zh)(e),[n,i]=b(),{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])}}},1116:(e,t,n)=>{"use strict";n.d(t,{V:()=>s,b:()=>l});var r=n(7294),a=n(902);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.i6("DocsSidebarProvider");return e}},2961:(e,t,n)=>{"use strict";n.d(t,{M:()=>f,e:()=>p});var r=n(7294),a=n(3102),o=n(7524),i=n(6775),l=(n(1688),n(902));function s(e){!function(e){const t=(0,i.k6)(),n=(0,l.zX)(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(6668);const c=r.createContext(void 0);function d(){const e=function(){const e=(0,a.HY)(),{items:t}=(0,u.L)().navbar;return 0===t.length&&!e.component}(),t=(0,o.i)(),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.i6("NavbarMobileSidebarProvider");return e}},3102:(e,t,n)=>{"use strict";n.d(t,{HY:()=>l,Zo:()=>s,n2:()=>i});var r=n(7294),a=n(902);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.i6("NavbarSecondaryMenuContentProvider");return e[0]}function s(e){let{component:t,props:n}=e;const i=(0,r.useContext)(o);if(!i)throw new a.i6("NavbarSecondaryMenuContentProvider");const[,l]=i,s=(0,a.Ql)(n);return(0,r.useEffect)((()=>{l({component:t,props:s})}),[l,t,s]),(0,r.useEffect)((()=>()=>l({component:null,props:null})),[l]),null}},9727:(e,t,n)=>{"use strict";n.d(t,{h:()=>a,t:()=>o});var r=n(7294);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)}}),[])}},7524:(e,t,n)=>{"use strict";n.d(t,{i:()=>u});var r=n(7294),a=n(412);const o="desktop",i="mobile",l="ssr";function s(){return a.Z.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}},5281:(e,t,n)=>{"use strict";n.d(t,{k:()=>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:{}}},2802:(e,t,n)=>{"use strict";n.d(t,{Wl:()=>f,_F:()=>m,cE:()=>d,hI:()=>w,lO:()=>v,vY:()=>y,oz:()=>b,s1:()=>h});var r=n(7294),a=n(6775),o=n(8790),i=n(143),l=n(373),s=n(1116);function u(e){return Array.from(new Set(e))}var c=n(8596);const d=!!i._r;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.Mg)(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 g(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.Mg)(o.href,n)||e(o.items))||"link"===o.type&&(0,c.Mg)(o.href,n)){return r&&"category"!==o.type||a.unshift(o),!0}return!1}(t),a}function h(){var e;const t=(0,s.V)(),{pathname:n}=(0,a.TH)();return!1!==(null==(e=(0,i.gA)())?void 0:e.pluginData.breadcrumbs)&&t?g({sidebarItems:t.items,pathname:n}):null}function v(e){const{activeVersion:t}=(0,i.Iw)(e),{preferredVersion:n}=(0,l.J)(e),a=(0,i.yW)(e);return(0,r.useMemo)((()=>u([t,n,a].filter(Boolean))),[t,n,a])}function b(e,t){const n=v(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(", ")}".\n Available sidebar ids are:\n - ${Object.keys(t).join("\n- ")}`);return r[1]}),[e,n])}function y(e,t){const n=v(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(`DocNavbarItem: 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.TH)(),i=t.routes,l=i.find((e=>(0,a.LX)(r.pathname,e)));if(!l)return null;const s=l.sidebar,u=s?n.docsSidebars[s]:void 0;return{docElement:(0,o.H)(i),sidebarName:s,sidebarItems:u}}},1944:(e,t,n)=>{"use strict";n.d(t,{FG:()=>f,d:()=>c,VC:()=>p});var r=n(7294),a=n(6010),o=n(5742),i=n(226);function l(){const e=r.useContext(i._);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}var s=n(4996),u=n(2263);function c(e){let{title:t,description:n,keywords:a,image:i,children:l}=e;const c=function(e){const{siteConfig:t}=(0,u.Z)(),{title:n,titleDelimiter:r}=t;return null!=e&&e.trim().length?`${e.trim()} ${r} ${n}`:n}(t),{withBaseUrl:d}=(0,s.C)(),f=i?d(i,{absolute:!0}):void 0;return r.createElement(o.Z,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.Z)(i,t);return r.createElement(d.Provider,{value:l},r.createElement(o.Z,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.Z)(o,i)},t)}},902:(e,t,n)=>{"use strict";n.d(t,{D9:()=>i,Qc:()=>u,Ql:()=>s,i6:()=>l,zX:()=>o});var r=n(7294);const a=n(412).Z.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){var n,r,a;super(),this.name="ReactContextError",this.message=`Hook ${(null==(n=this.stack)||null==(r=n.split("\n")[1])||null==(a=r.match(/at (?:\w+\.)?(?\w+)/))?void 0:a.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))}}},8596:(e,t,n)=>{"use strict";n.d(t,{Mg:()=>i,Ns:()=>l});var r=n(7294),a=n(723),o=n(2263);function i(e,t){const n=e=>{var t;return null==(t=!e||e.endsWith("/")?e:`${e}/`)?void 0:t.toLowerCase()};return n(e)===n(t)}function l(){const{baseUrl:e}=(0,o.Z)().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.Z,baseUrl:e})),[e])}},2466:(e,t,n)=>{"use strict";n.d(t,{Ct:()=>f,OC:()=>s,RF:()=>d});var r=n(7294),a=n(412),o=n(2389),i=n(902);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.i6("ScrollControllerProvider");return e}const c=()=>a.Z.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.zX)(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.Z)()&&"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&&at&&cancelAnimationFrame(t)}(n)},cancelScroll:()=>null==e.current?void 0:e.current()}}},3320:(e,t,n)=>{"use strict";n.d(t,{HX:()=>r,os:()=>a});n(2263);const r="default";function a(e,t){return`docs-${e}-${t}`}},12:(e,t,n)=>{"use strict";n.d(t,{WA:()=>s});n(7294),n(1688);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(null==t?void 0: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),()=>{}}}}}},4711:(e,t,n)=>{"use strict";n.d(t,{l:()=>o});var r=n(2263),a=n(6775);function o(){const{siteConfig:{baseUrl:e,url:t},i18n:{defaultLocale:n,currentLocale:o}}=(0,r.Z)(),{pathname:i}=(0,a.TH)(),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}`}}}},5936:(e,t,n)=>{"use strict";n.d(t,{S:()=>i});var r=n(7294),a=n(6775),o=n(902);function i(e){const t=(0,a.TH)(),n=(0,o.D9)(t),i=(0,o.zX)(e);(0,r.useEffect)((()=>{n&&t!==n&&i({location:t,previousLocation:n})}),[i,t,n])}},6668:(e,t,n)=>{"use strict";n.d(t,{L:()=>a});var r=n(2263);function a(){return(0,r.Z)().siteConfig.themeConfig}},8802:(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)}},8780: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.applyTrailingSlash=t.blogPostContainerID=void 0,t.blogPostContainerID="post-content";var a=n(8802);Object.defineProperty(t,"applyTrailingSlash",{enumerable:!0,get:function(){return r(a).default}})},6010:(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;ta});const a=function(){for(var e,t,n=0,a="";n{"use strict";n.d(t,{lX:()=>w,q_:()=>C,ob:()=>p,PP:()=>A,Ep:()=>f});var r=n(7462);function a(e){return"/"===e.charAt(0)}function o(e,t){for(var n=t,r=n+1,a=e.length;r=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(2177);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.Z)({},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;rt?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,g(),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{"use strict";var r=n(9864),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),g=s(n),h=0;h{"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}}},5826:e=>{e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},2497:(e,t,n)=>{"use strict";n.r(t)},2295:(e,t,n)=>{"use strict";n.r(t)},4865: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:'
'};function a(e,t,n){return en?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)},7418: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{"use strict";n.d(t,{Z:()=>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(/=d.reach);S+=k.value.length,k=k.next){var _=k.value;if(t.length>e.length)return;if(!(_ instanceof a)){var x,C=1;if(b){if(!(x=o(E,S,e,v))||x.index>=e.length)break;var T=x.index,A=x.index+x[0].length,L=S;for(L+=k.value.length;T>=L;)L+=(k=k.next).value.length;if(S=L-=k.value.length,k.value instanceof a)continue;for(var N=k;N!==t.tail&&(Ld.reach&&(d.reach=R);var D=k.prev;if(P&&(D=s(t,D,P),S+=P.length),u(t,D,C),k=s(t,D,new a(f,h?r.tokenize(O,h):O,y,O)),I&&s(t,k,I),C>1){var M={cause:f+","+m,reach:R};i(e,t,n,k.prev,S,M),d&&M.reach>d.reach&&(d.reach=M.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"+o.content+""},r}(),a=r;r.default=r,a.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\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://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:/(^$)/i,lookbehind:!0,inside:a.languages[t]},n.cdata=/^$/i;var r={"included-cdata":{pattern://i,inside:n}};r["language-"+t]={pattern:/[\s\S]+/,inside:a.languages[t]};var o={};o[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.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]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,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(?!)\w+(?:\s*\.\s*\w+)*\b/.source.replace(//g,(function(){return t.source}));e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!)\w+/.source.replace(//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+"|"+/(?:\s*:\s*)?|:\s*/.source.replace(//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]|[?:-])(?:[ \t]*(?:(?![#:])|:))*/.source.replace(//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<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<>/g,(function(){return r})).replace(/<>/g,(function(){return e}));return RegExp(n,t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<>/g,(function(){return r}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\s*:\s)/.source.replace(/<>/g,(function(){return r})).replace(/<>/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(//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__(?:(?!_)|_(?:(?!_))+_)+__\b|\*\*(?:(?!\*)|\*(?:(?!\*))+\*)+\*\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n(/\b_(?:(?!_)|__(?:(?!_))+__)+_\b|\*(?:(?!\*)|\*\*(?:(?!\*))+\*\*)+\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n(/(~~?)(?:(?!~))+\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(/!?\[(?:(?!\]))+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\]))+\])/.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",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;n0)){var l=f(/^\{$/,/^\}$/);if(-1===l)continue;for(var s=n;s=0&&p(u,"variable-input")}}}}function c(e){return t[n+e]}function d(e,t){t=t||0;for(var n=0;n?|<|>)?|>[>=]?|\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=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 g=[p];e(g),m.push.apply(m,g)}"string"==typeof r?(t.splice.apply(t,[n,1].concat(m)),n+=m.length-1):r.content=m}}else{var h=r.content;Array.isArray(h)?e(h):e([h])}}}(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]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,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(//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*)(?:(?:\s*,\s*(?:\*\s*as\s+|\{[^{}]*\}))?|\*\s*as\s+|\{[^{}]*\})(?=\s*\bfrom\b)/.source),lookbehind:!0,inside:e.languages.javascript},exports:{pattern:t(/(\bexport\b\s*)(?:\*(?:\s*as\s+)?(?=\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*)#?/.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*\.{3}(?:[^{}]|)*\})/.source;function o(e,t){return e=e.replace(//g,(function(){return n})).replace(//g,(function(){return r})).replace(//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.:-]+(?:+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\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(//.source),inside:e.languages.jsx}},e.languages.jsx.tag),e.languages.insertBefore("inside","special-attr",{script:{pattern:o(/=/.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;r0&&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);r0&&("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=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 g=f.substring(0,m),h=new e.Token(r,e.tokenize(d,n.grammar),"language-"+r,d),v=f.substring(m+p.length),b=[];g&&b.push.apply(b,i([g])),b.push(h),v&&b.push.apply(b,i([v])),"string"==typeof u?l.splice.apply(l,[s,1].concat(b)):u.content=b}}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},2503:()=>{!function(e){var t=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,n=/(?:[a-z]\w*\s*\.\s*)*(?:[A-Z]\w*\s*\.\s*)*/.source,r={pattern:RegExp(/(^|[^\w.])/.source+n+/[A-Z](?:[\d_A-Z]*[a-z]\w*)?\b/.source),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}};e.languages.java=e.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"/,lookbehind:!0,greedy:!0},"class-name":[r,{pattern:RegExp(/(^|[^\w.])/.source+n+/[A-Z]\w*(?=\s+\w+\s*[;,=()]|\s*(?:\[[\s,]*\]\s*)?::\s*new\b)/.source),lookbehind:!0,inside:r.inside},{pattern:RegExp(/(\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\s+)/.source+n+/[A-Z]\w*\b/.source),lookbehind:!0,inside:r.inside}],keyword:t,function:[e.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0},constant:/\b[A-Z][A-Z_\d]+\b/}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"},char:{pattern:/'(?:\\.|[^'\\\r\n]){1,6}'/,greedy:!0}}),e.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":r,keyword:t,punctuation:/[<>(),.:]/,operator:/[?&|]/}},import:[{pattern:RegExp(/(\bimport\s+)/.source+n+/(?:[A-Z]\w*|\*)(?=\s*;)/.source),lookbehind:!0,inside:{namespace:r.inside.namespace,punctuation:/\./,operator:/\*/,"class-name":/\w+/}},{pattern:RegExp(/(\bimport\s+static\s+)/.source+n+/(?:\w+|\*)(?=\s*;)/.source),lookbehind:!0,alias:"static",inside:{namespace:r.inside.namespace,static:/\b\w+$/,punctuation:/\./,operator:/\*/,"class-name":/\w+/}}],namespace:{pattern:RegExp(/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)(?!)[a-z]\w*(?:\.[a-z]\w*)*\.?/.source.replace(//g,(function(){return t.source}))),lookbehind:!0,inside:{punctuation:/\./}}})}(Prism)},2886:()=>{Prism.languages.scala=Prism.languages.extend("java",{"triple-quoted-string":{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},keyword:/<-|=>|\b(?:abstract|case|catch|class|def|derives|do|else|enum|extends|extension|final|finally|for|forSome|given|if|implicit|import|infix|inline|lazy|match|new|null|object|opaque|open|override|package|private|protected|return|sealed|self|super|this|throw|trait|transparent|try|type|using|val|var|while|with|yield)\b/,number:/\b0x(?:[\da-f]*\.)?[\da-f]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e\d+)?[dfl]?/i,builtin:/\b(?:Any|AnyRef|AnyVal|Boolean|Byte|Char|Double|Float|Int|Long|Nothing|Short|String|Unit)\b/,symbol:/'[^\d\s\\]\w*/}),Prism.languages.insertBefore("scala","triple-quoted-string",{"string-interpolation":{pattern:/\b[a-z]\w*(?:"""(?:[^$]|\$(?:[^{]|\{(?:[^{}]|\{[^{}]*\})*\}))*?"""|"(?:[^$"\r\n]|\$(?:[^{]|\{(?:[^{}]|\{[^{}]*\})*\}))*")/i,greedy:!0,inside:{id:{pattern:/^\w+/,greedy:!0,alias:"function"},escape:{pattern:/\\\$"|\$[$"]/,greedy:!0,alias:"symbol"},interpolation:{pattern:/\$(?:\w+|\{(?:[^{}]|\{[^{}]*\})*\})/,greedy:!0,inside:{punctuation:/^\$\{?|\}$/,expression:{pattern:/[\s\S]+/,inside:Prism.languages.scala}}},string:/[\s\S]+/}}}),delete Prism.languages.scala["class-name"],delete Prism.languages.scala.function,delete Prism.languages.scala.constant},7783:(e,t,n)=>{var r={"./prism-java":2503,"./prism-scala":2886};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=7783},2703:(e,t,n)=>{"use strict";var r=n(414);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}},5697:(e,t,n)=>{e.exports=n(2703)()},414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},4448:(e,t,n)=>{"use strict";var r=n(7294),a=n(7418),o=n(3840);function i(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n