diff --git a/gradle.properties b/gradle.properties index 33f326d7f4..f5051d1c8a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group = com.expediagroup -version = 8.0.0-SNAPSHOT +version = 9.0.0-SNAPSHOT # build config org.gradle.caching=true diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index d947e98dd0..19a5afe011 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -21,14 +21,14 @@ module.exports = { { docs: { editUrl: "https://github.com/ExpediaGroup/graphql-kotlin/tree/master/website", - lastVersion: '7.x.x', + lastVersion: '8.x.x', showLastUpdateAuthor: true, showLastUpdateTime: true, sidebarPath: require.resolve('./sidebars.js'), versions: { current: { label: 'pre-release', - path: '8.x.x' + path: '9.x.x' } } } diff --git a/website/package-lock.json b/website/package-lock.json index 24e324d003..a2a945a6c2 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -7,9 +7,9 @@ "name": "graphql-kotlin-docs", "license": "Apache-2.0", "dependencies": { - "@docusaurus/core": "^3.4.0", - "@docusaurus/preset-classic": "^3.4.0", - "@docusaurus/theme-mermaid": "^3.4.0" + "@docusaurus/core": "^3.5.2", + "@docusaurus/preset-classic": "^3.5.2", + "@docusaurus/theme-mermaid": "^3.5.2" }, "engines": { "node": ">=20", @@ -20,6 +20,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "license": "MIT", "dependencies": { "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", "@algolia/autocomplete-shared": "1.9.3" @@ -29,6 +30,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "license": "MIT", "dependencies": { "@algolia/autocomplete-shared": "1.9.3" }, @@ -40,6 +42,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "license": "MIT", "dependencies": { "@algolia/autocomplete-shared": "1.9.3" }, @@ -52,147 +55,281 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.3.tgz", - "integrity": "sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz", + "integrity": "sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==", + "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.23.3" + "@algolia/cache-common": "4.24.0" } }, "node_modules/@algolia/cache-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.3.tgz", - "integrity": "sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.24.0.tgz", + "integrity": "sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==", + "license": "MIT" }, "node_modules/@algolia/cache-in-memory": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz", - "integrity": "sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz", + "integrity": "sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==", + "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.23.3" + "@algolia/cache-common": "4.24.0" } }, "node_modules/@algolia/client-account": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.3.tgz", - "integrity": "sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", + "integrity": "sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/@algolia/client-analytics": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.3.tgz", - "integrity": "sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", + "integrity": "sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, - "node_modules/@algolia/client-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.3.tgz", - "integrity": "sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==", + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.7.0.tgz", + "integrity": "sha512-hrYlN9yNQukmNj8bBlw9PCXi9jmRQqNUXaG6MXH1aDabjO6YD1WPVqTvaELbIBgTbDJzCn0R2owms0uaxQkjUg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.3.tgz", - "integrity": "sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", + "integrity": "sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/@algolia/client-search": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz", - "integrity": "sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.7.0.tgz", + "integrity": "sha512-0Frfjt4oxvVP2qsTQAjwdaG5SvJ3TbHBkBrS6M7cG5RDrgHqOrhBnBGCFT+YO3CeNK54r+d57oB1VcD2F1lHuQ==", + "license": "MIT", + "peer": true, "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "5.7.0", + "@algolia/requester-browser-xhr": "5.7.0", + "@algolia/requester-fetch": "5.7.0", + "@algolia/requester-node-http": "5.7.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/events": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", - "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", + "license": "MIT" }, "node_modules/@algolia/logger-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.3.tgz", - "integrity": "sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", + "integrity": "sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==", + "license": "MIT" }, "node_modules/@algolia/logger-console": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.3.tgz", - "integrity": "sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.24.0.tgz", + "integrity": "sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==", + "license": "MIT", "dependencies": { - "@algolia/logger-common": "4.23.3" + "@algolia/logger-common": "4.24.0" } }, "node_modules/@algolia/recommend": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.3.tgz", - "integrity": "sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.3", - "@algolia/cache-common": "4.23.3", - "@algolia/cache-in-memory": "4.23.3", - "@algolia/client-common": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/logger-common": "4.23.3", - "@algolia/logger-console": "4.23.3", - "@algolia/requester-browser-xhr": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/requester-node-http": "4.23.3", - "@algolia/transporter": "4.23.3" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", + "integrity": "sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==", + "license": "MIT", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.3.tgz", - "integrity": "sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.7.0.tgz", + "integrity": "sha512-ohtIp+lyTGM3agrHyedC3w7ijfdUvSN6wmGuKqUezrNzd0nCkFoLW0OINlyv1ODrTEVnL8PAM/Zqubjafxd/Ww==", + "license": "MIT", + "peer": true, "dependencies": { - "@algolia/requester-common": "4.23.3" + "@algolia/client-common": "5.7.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.3.tgz", - "integrity": "sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", + "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==", + "license": "MIT" + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.7.0.tgz", + "integrity": "sha512-Eg8cBhNg2QNnDDldyK77aXvg3wIc5qnpCDCAJXQ2oaqZwwvvYaTgnP1ofznNG6+klri4Fk1YAaC9wyDBhByWIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@algolia/client-common": "5.7.0" + }, + "engines": { + "node": ">= 14.0.0" + } }, "node_modules/@algolia/requester-node-http": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz", - "integrity": "sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.7.0.tgz", + "integrity": "sha512-8BDssYEkcp1co06KtHO9b37H+5zVM/h+5kyesJb2C2EHFO3kgzLHWl/JyXOVtYlKQBkmdObYOI0s6JaXRy2yQA==", + "license": "MIT", + "peer": true, "dependencies": { - "@algolia/requester-common": "4.23.3" + "@algolia/client-common": "5.7.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/transporter": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.3.tgz", - "integrity": "sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.24.0.tgz", + "integrity": "sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==", + "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.23.3", - "@algolia/logger-common": "4.23.3", - "@algolia/requester-common": "4.23.3" + "@algolia/cache-common": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/requester-common": "4.24.0" } }, "node_modules/@ampproject/remapping": { @@ -539,9 +676,10 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", - "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -1616,11 +1754,12 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.7.tgz", - "integrity": "sha512-7LidzZfUXyfZ8/buRW6qIIHBY8wAZ1OrY9c/wTr8YhZ6vMPo+Uc/CVFLYY1spZrEQlD4w5u8wjqk5NQ3OVqQKA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.7.tgz", + "integrity": "sha512-/qXt69Em8HgsjCLu7G3zdIQn7A2QwmYND7Wa0LTp09Na+Zn8L5d0A7wSXrKi18TJRc/Q5S1i1De/SU1LzVkSvA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2141,18 +2280,20 @@ } }, "node_modules/@docsearch/css": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", - "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==" + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.2.tgz", + "integrity": "sha512-vKNZepO2j7MrYBTZIGXvlUOIR+v9KRf70FApRgovWrj3GTs1EITz/Xb0AOlm1xsQBp16clVZj1SY/qaOJbQtZw==", + "license": "MIT" }, "node_modules/@docsearch/react": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", - "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.2.tgz", + "integrity": "sha512-rtZce46OOkVflCQH71IdbXSFK+S8iJZlUF56XBW5rIgx/eG5qoomC7Ag3anZson1bBac/JFQn7XOBfved/IMRA==", + "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "1.9.3", "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.6.0", + "@docsearch/css": "3.6.2", "algoliasearch": "^4.19.1" }, "peerDependencies": { @@ -2177,9 +2318,10 @@ } }, "node_modules/@docusaurus/core": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.4.0.tgz", - "integrity": "sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.5.2.tgz", + "integrity": "sha512-4Z1WkhCSkX4KO0Fw5m/Vuc7Q3NxBG53NE5u59Rs96fWkMPZVSrzEPP16/Nk6cWb/shK7xXPndTmalJtw7twL/w==", + "license": "MIT", "dependencies": { "@babel/core": "^7.23.3", "@babel/generator": "^7.23.3", @@ -2191,12 +2333,12 @@ "@babel/runtime": "^7.22.6", "@babel/runtime-corejs3": "^7.22.6", "@babel/traverse": "^7.22.8", - "@docusaurus/cssnano-preset": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/cssnano-preset": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "autoprefixer": "^10.4.14", "babel-loader": "^9.1.3", "babel-plugin-dynamic-import-node": "^2.3.3", @@ -2257,14 +2399,16 @@ "node": ">=18.0" }, "peerDependencies": { + "@mdx-js/react": "^3.0.0", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/cssnano-preset": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.4.0.tgz", - "integrity": "sha512-qwLFSz6v/pZHy/UP32IrprmH5ORce86BGtN0eBtG75PpzQJAzp9gefspox+s8IEOr0oZKuQ/nhzZ3xwyc3jYJQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.5.2.tgz", + "integrity": "sha512-D3KiQXOMA8+O0tqORBrTOEQyQxNIfPm9jEaJoALjjSjc2M/ZAWcUfPQEnwr2JB2TadHw2gqWgpZckQmrVWkytA==", + "license": "MIT", "dependencies": { "cssnano-preset-advanced": "^6.1.2", "postcss": "^8.4.38", @@ -2276,9 +2420,10 @@ } }, "node_modules/@docusaurus/logger": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.4.0.tgz", - "integrity": "sha512-bZwkX+9SJ8lB9kVRkXw+xvHYSMGG4bpYHKGXeXFvyVc79NMeeBSGgzd4TQLHH+DYeOJoCdl8flrFJVxlZ0wo/Q==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.5.2.tgz", + "integrity": "sha512-LHC540SGkeLfyT3RHK3gAMK6aS5TRqOD4R72BEU/DE2M/TY8WwEUAMY576UUc/oNJXv8pGhBmQB6N9p3pt8LQw==", + "license": "MIT", "dependencies": { "chalk": "^4.1.2", "tslib": "^2.6.0" @@ -2288,13 +2433,14 @@ } }, "node_modules/@docusaurus/mdx-loader": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.4.0.tgz", - "integrity": "sha512-kSSbrrk4nTjf4d+wtBA9H+FGauf2gCax89kV8SUSJu3qaTdSIKdWERlngsiHaCFgZ7laTJ8a67UFf+xlFPtuTw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.5.2.tgz", + "integrity": "sha512-ku3xO9vZdwpiMIVd8BzWV0DCqGEbCP5zs1iHfKX50vw6jX8vQo0ylYo1YJMZyz6e+JFJ17HYHT5FzVidz2IflA==", + "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/logger": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@mdx-js/mdx": "^3.0.0", "@slorber/remark-comment": "^1.0.0", "escape-html": "^1.0.3", @@ -2326,11 +2472,12 @@ } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz", - "integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.5.2.tgz", + "integrity": "sha512-Z+Xu3+2rvKef/YKTMxZHsEXp1y92ac0ngjDiExRdqGTmEKtCUpkbNYH8v5eXo5Ls+dnW88n6WTa+Q54kLOkwPg==", + "license": "MIT", "dependencies": { - "@docusaurus/types": "3.4.0", + "@docusaurus/types": "3.5.2", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2344,18 +2491,20 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.4.0.tgz", - "integrity": "sha512-vv6ZAj78ibR5Jh7XBUT4ndIjmlAxkijM3Sx5MAAzC1gyv0vupDQNhzuFg1USQmQVj3P5I6bquk12etPV3LJ+Xw==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", - "cheerio": "^1.0.0-rc.12", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.5.2.tgz", + "integrity": "sha512-R7ghWnMvjSf+aeNDH0K4fjyQnt5L0KzUEnUhmf1e3jZrv3wogeytZNN6n7X8yHcMsuZHPOrctQhXWnmxu+IRRg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", + "cheerio": "1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^11.1.1", "lodash": "^4.17.21", @@ -2370,23 +2519,26 @@ "node": ">=18.0" }, "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.4.0.tgz", - "integrity": "sha512-HkUCZffhBo7ocYheD9oZvMcDloRnGhBMOZRyVcAQRFmZPmNqSyISlXA1tQCIxW+r478fty97XXAGjNYzBjpCsg==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.5.2.tgz", + "integrity": "sha512-Bt+OXn/CPtVqM3Di44vHjE7rPCEsRCB/DMo2qoOuozB9f7+lsdrHvD0QCHdBs0uhz6deYJDppAr2VgqybKPlVQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.1.1", @@ -2405,15 +2557,16 @@ } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz", - "integrity": "sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.5.2.tgz", + "integrity": "sha512-WzhHjNpoQAUz/ueO10cnundRz+VUtkjFhhaQ9jApyv1a46FPURO4cef89pyNIOMny1fjDz/NUN2z6Yi+5WUrCw==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "fs-extra": "^11.1.1", "tslib": "^2.6.0", "webpack": "^5.88.1" @@ -2427,13 +2580,14 @@ } }, "node_modules/@docusaurus/plugin-debug": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.4.0.tgz", - "integrity": "sha512-uV7FDUNXGyDSD3PwUaf5YijX91T5/H9SX4ErEcshzwgzWwBtK37nUWPU3ZLJfeTavX3fycTOqk9TglpOLaWkCg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.5.2.tgz", + "integrity": "sha512-kBK6GlN0itCkrmHuCS6aX1wmoWc5wpd5KJlqQ1FyrF0cLDnvsYSnh7+ftdwzt7G6lGBho8lrVwkkL9/iQvaSOA==", + "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", "fs-extra": "^11.1.1", "react-json-view-lite": "^1.2.0", "tslib": "^2.6.0" @@ -2447,13 +2601,14 @@ } }, "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.4.0.tgz", - "integrity": "sha512-mCArluxEGi3cmYHqsgpGGt3IyLCrFBxPsxNZ56Mpur0xSlInnIHoeLDH7FvVVcPJRPSQ9/MfRqLsainRw+BojA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.5.2.tgz", + "integrity": "sha512-rjEkJH/tJ8OXRE9bwhV2mb/WP93V441rD6XnM6MIluu7rk8qg38iSxS43ga2V2Q/2ib53PcqbDEJDG/yWQRJhQ==", + "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "tslib": "^2.6.0" }, "engines": { @@ -2465,13 +2620,14 @@ } }, "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.4.0.tgz", - "integrity": "sha512-Dsgg6PLAqzZw5wZ4QjUYc8Z2KqJqXxHxq3vIoyoBWiLEEfigIs7wHR+oiWUQy3Zk9MIk6JTYj7tMoQU0Jm3nqA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.5.2.tgz", + "integrity": "sha512-lm8XL3xLkTPHFKKjLjEEAHUrW0SZBSHBE1I+i/tmYMBsjCcUB5UJ52geS5PSiOCFVR74tbPGcPHEV/gaaxFeSA==", + "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@types/gtag.js": "^0.0.12", "tslib": "^2.6.0" }, @@ -2484,13 +2640,14 @@ } }, "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.4.0.tgz", - "integrity": "sha512-O9tX1BTwxIhgXpOLpFDueYA9DWk69WCbDRrjYoMQtFHSkTyE7RhNgyjSPREUWJb9i+YUg3OrsvrBYRl64FCPCQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.5.2.tgz", + "integrity": "sha512-QkpX68PMOMu10Mvgvr5CfZAzZQFx8WLlOiUQ/Qmmcl6mjGK6H21WLT5x7xDmcpCoKA/3CegsqIqBR+nA137lQg==", + "license": "MIT", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "tslib": "^2.6.0" }, "engines": { @@ -2502,16 +2659,17 @@ } }, "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.4.0.tgz", - "integrity": "sha512-+0VDvx9SmNrFNgwPoeoCha+tRoAjopwT0+pYO1xAbyLcewXSemq+eLxEa46Q1/aoOaJQ0qqHELuQM7iS2gp33Q==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.5.2.tgz", + "integrity": "sha512-DnlqYyRAdQ4NHY28TfHuVk414ft2uruP4QWCH//jzpHjqvKyXjj2fmDtI8RPUBh9K8iZKFMHRnLtzJKySPWvFA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "fs-extra": "^11.1.1", "sitemap": "^7.1.1", "tslib": "^2.6.0" @@ -2525,23 +2683,24 @@ } }, "node_modules/@docusaurus/preset-classic": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.4.0.tgz", - "integrity": "sha512-Ohj6KB7siKqZaQhNJVMBBUzT3Nnp6eTKqO+FXO3qu/n1hJl3YLwVKTWBg28LF7MWrKu46UuYavwMRxud0VyqHg==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/plugin-content-blog": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/plugin-content-pages": "3.4.0", - "@docusaurus/plugin-debug": "3.4.0", - "@docusaurus/plugin-google-analytics": "3.4.0", - "@docusaurus/plugin-google-gtag": "3.4.0", - "@docusaurus/plugin-google-tag-manager": "3.4.0", - "@docusaurus/plugin-sitemap": "3.4.0", - "@docusaurus/theme-classic": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/theme-search-algolia": "3.4.0", - "@docusaurus/types": "3.4.0" + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.5.2.tgz", + "integrity": "sha512-3ihfXQ95aOHiLB5uCu+9PRy2gZCeSZoDcqpnDvf3B+sTrMvMTr8qRUzBvWkoIqc82yG5prCboRjk1SVILKx6sg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/plugin-content-blog": "3.5.2", + "@docusaurus/plugin-content-docs": "3.5.2", + "@docusaurus/plugin-content-pages": "3.5.2", + "@docusaurus/plugin-debug": "3.5.2", + "@docusaurus/plugin-google-analytics": "3.5.2", + "@docusaurus/plugin-google-gtag": "3.5.2", + "@docusaurus/plugin-google-tag-manager": "3.5.2", + "@docusaurus/plugin-sitemap": "3.5.2", + "@docusaurus/theme-classic": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/theme-search-algolia": "3.5.2", + "@docusaurus/types": "3.5.2" }, "engines": { "node": ">=18.0" @@ -2552,26 +2711,27 @@ } }, "node_modules/@docusaurus/theme-classic": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz", - "integrity": "sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/plugin-content-blog": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/plugin-content-pages": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/theme-translations": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.5.2.tgz", + "integrity": "sha512-XRpinSix3NBv95Rk7xeMF9k4safMkwnpSgThn0UNQNumKvmcIYjfkwfh2BhwYh/BxMXQHJ/PdmNh22TQFpIaYg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/plugin-content-blog": "3.5.2", + "@docusaurus/plugin-content-docs": "3.5.2", + "@docusaurus/plugin-content-pages": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/theme-translations": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "copy-text-to-clipboard": "^3.2.0", - "infima": "0.2.0-alpha.43", + "infima": "0.2.0-alpha.44", "lodash": "^4.17.21", "nprogress": "^0.2.0", "postcss": "^8.4.26", @@ -2591,17 +2751,15 @@ } }, "node_modules/@docusaurus/theme-common": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.4.0.tgz", - "integrity": "sha512-0A27alXuv7ZdCg28oPE8nH/Iz73/IUejVaCazqu9elS4ypjiLhK3KfzdSQBnL/g7YfHSlymZKdiOHEo8fJ0qMA==", - "dependencies": { - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/plugin-content-blog": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/plugin-content-pages": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.5.2.tgz", + "integrity": "sha512-QXqlm9S6x9Ibwjs7I2yEDgsCocp708DrCrgHgKwg2n2AY0YQ6IjU0gAK35lHRLOvAoJUfCKpQAwUykB0R7+Eew==", + "license": "MIT", + "dependencies": { + "@docusaurus/mdx-loader": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2615,20 +2773,22 @@ "node": ">=18.0" }, "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/theme-mermaid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.4.0.tgz", - "integrity": "sha512-3w5QW0HEZ2O6x2w6lU3ZvOe1gNXP2HIoKDMJBil1VmLBc9PmpAG17VmfhI/p3L2etNmOiVs5GgniUqvn8AFEGQ==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.5.2.tgz", + "integrity": "sha512-7vWCnIe/KoyTN1Dc55FIyqO5hJ3YaV08Mr63Zej0L0mX1iGzt+qKSmeVUAJ9/aOalUhF0typV0RmNUSy5FAmCg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.5.2", + "@docusaurus/module-type-aliases": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/types": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "mermaid": "^10.4.0", "tslib": "^2.6.0" }, @@ -2641,18 +2801,19 @@ } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.4.0.tgz", - "integrity": "sha512-aiHFx7OCw4Wck1z6IoShVdUWIjntC8FHCw9c5dR8r3q4Ynh+zkS8y2eFFunN/DL6RXPzpnvKCg3vhLQYJDmT9Q==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.5.2.tgz", + "integrity": "sha512-qW53kp3VzMnEqZGjakaV90sst3iN1o32PH+nawv1uepROO8aEGxptcq2R5rsv7aBShSRbZwIobdvSYKsZ5pqvA==", + "license": "MIT", "dependencies": { "@docsearch/react": "^3.5.2", - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/theme-translations": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.2", + "@docusaurus/logger": "3.5.2", + "@docusaurus/plugin-content-docs": "3.5.2", + "@docusaurus/theme-common": "3.5.2", + "@docusaurus/theme-translations": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-validation": "3.5.2", "algoliasearch": "^4.18.0", "algoliasearch-helper": "^3.13.3", "clsx": "^2.0.0", @@ -2671,9 +2832,10 @@ } }, "node_modules/@docusaurus/theme-translations": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.4.0.tgz", - "integrity": "sha512-zSxCSpmQCCdQU5Q4CnX/ID8CSUUI3fvmq4hU/GNP/XoAWtXo9SAVnM3TzpU8Gb//H3WCsT8mJcTfyOk3d9ftNg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.5.2.tgz", + "integrity": "sha512-GPZLcu4aT1EmqSTmbdpVrDENGR2yObFEX8ssEFYTCiAIVc0EihNSdOIBTazUvgNqwvnoU1A8vIs1xyzc3LITTw==", + "license": "MIT", "dependencies": { "fs-extra": "^11.1.1", "tslib": "^2.6.0" @@ -2683,9 +2845,10 @@ } }, "node_modules/@docusaurus/types": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", - "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.2.tgz", + "integrity": "sha512-N6GntLXoLVUwkZw7zCxwy9QiuEXIcTVzA9AkmNw16oc0AP3SXLrMmDMMBIfgqwuKWa6Ox6epHol9kMtJqekACw==", + "license": "MIT", "dependencies": { "@mdx-js/mdx": "^3.0.0", "@types/history": "^4.7.11", @@ -2703,12 +2866,13 @@ } }, "node_modules/@docusaurus/utils": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.4.0.tgz", - "integrity": "sha512-fRwnu3L3nnWaXOgs88BVBmG1yGjcQqZNHG+vInhEa2Sz2oQB+ZjbEMO5Rh9ePFpZ0YDiDUhpaVjwmS+AU2F14g==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.5.2.tgz", + "integrity": "sha512-33QvcNFh+Gv+C2dP9Y9xWEzMgf3JzrpL2nW9PopidiohS1nDcyknKRx2DWaFvyVTTYIkkABVSr073VTj/NITNA==", + "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.4.0", - "@docusaurus/utils-common": "3.4.0", + "@docusaurus/logger": "3.5.2", + "@docusaurus/utils-common": "3.5.2", "@svgr/webpack": "^8.1.0", "escape-string-regexp": "^4.0.0", "file-loader": "^6.2.0", @@ -2741,9 +2905,10 @@ } }, "node_modules/@docusaurus/utils-common": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.4.0.tgz", - "integrity": "sha512-NVx54Wr4rCEKsjOH5QEVvxIqVvm+9kh7q8aYTU5WzUU9/Hctd6aTrcZ3G0Id4zYJ+AeaG5K5qHA4CY5Kcm2iyQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.5.2.tgz", + "integrity": "sha512-i0AZjHiRgJU6d7faQngIhuHKNrszpL/SHQPgF1zH4H+Ij6E9NBYGy6pkcGWToIv7IVPbs+pQLh1P3whn0gWXVg==", + "license": "MIT", "dependencies": { "tslib": "^2.6.0" }, @@ -2760,13 +2925,14 @@ } }, "node_modules/@docusaurus/utils-validation": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.4.0.tgz", - "integrity": "sha512-hYQ9fM+AXYVTWxJOT1EuNaRnrR2WGpRdLDQG07O8UOpsvCPWUVOeo26Rbm0JWY2sGLfzAb+tvJ62yF+8F+TV0g==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.5.2.tgz", + "integrity": "sha512-m+Foq7augzXqB6HufdS139PFxDC5d5q2QKZy8q0qYYvGdI6nnlNsGH4cIGsgBnV7smz+mopl3g4asbSDvMV0jA==", + "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", + "@docusaurus/logger": "3.5.2", + "@docusaurus/utils": "3.5.2", + "@docusaurus/utils-common": "3.5.2", "fs-extra": "^11.2.0", "joi": "^17.9.2", "js-yaml": "^4.1.0", @@ -2780,12 +2946,14 @@ "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" }, "node_modules/@hapi/topo": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.0.0" } @@ -2878,6 +3046,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.1.tgz", "integrity": "sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==", + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", @@ -3002,6 +3171,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.0.0" } @@ -3009,12 +3179,14 @@ "node_modules/@sideway/formula": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" }, "node_modules/@sideway/pinpoint": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" }, "node_modules/@sinclair/typebox": { "version": "0.27.8", @@ -3025,6 +3197,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -3036,6 +3209,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", + "license": "MIT", "dependencies": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.1.0", @@ -3046,6 +3220,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3061,6 +3236,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3076,6 +3252,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3091,6 +3268,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3106,6 +3284,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3121,6 +3300,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3136,6 +3316,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3151,6 +3332,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -3166,6 +3348,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "license": "MIT", "dependencies": { "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", @@ -3191,6 +3374,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "license": "MIT", "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -3210,6 +3394,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "license": "MIT", "dependencies": { "@babel/types": "^7.21.3", "entities": "^4.4.0" @@ -3226,6 +3411,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "license": "MIT", "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -3247,6 +3433,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "license": "MIT", "dependencies": { "cosmiconfig": "^8.1.3", "deepmerge": "^4.3.1", @@ -3267,6 +3454,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", + "license": "MIT", "dependencies": { "@babel/core": "^7.21.3", "@babel/plugin-transform-react-constant-elements": "^7.21.3", @@ -3308,6 +3496,7 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==", + "license": "MIT", "dependencies": { "@types/estree": "*" } @@ -3396,9 +3585,10 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "node_modules/@types/estree-jsx": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.4.tgz", - "integrity": "sha512-5idy3hvI9lAMqsyilBM+N+boaCf1MgoefbDxN6KEO5aK17TOHwFAYT9sjxzeKAiIWRUBgLxmZ9mPcnzZXtTcRQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", "dependencies": { "@types/estree": "*" } @@ -3428,12 +3618,14 @@ "node_modules/@types/gtag.js": { "version": "0.0.12", "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.12.tgz", - "integrity": "sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==" + "integrity": "sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==", + "license": "MIT" }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", "dependencies": { "@types/unist": "*" } @@ -3441,7 +3633,8 @@ "node_modules/@types/history": { "version": "4.7.11", "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "license": "MIT" }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", @@ -3493,9 +3686,10 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/mdast": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", - "integrity": "sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", "dependencies": { "@types/unist": "*" } @@ -3537,9 +3731,10 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prismjs": { - "version": "1.26.3", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.3.tgz", - "integrity": "sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==" + "version": "1.26.4", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.4.tgz", + "integrity": "sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==", + "license": "MIT" }, "node_modules/@types/prop-types": { "version": "15.7.11", @@ -3570,6 +3765,7 @@ "version": "5.1.20", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "license": "MIT", "dependencies": { "@types/history": "^4.7.11", "@types/react": "*" @@ -3579,6 +3775,7 @@ "version": "5.0.11", "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz", "integrity": "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==", + "license": "MIT", "dependencies": { "@types/history": "^4.7.11", "@types/react": "*", @@ -3589,6 +3786,7 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "license": "MIT", "dependencies": { "@types/history": "^4.7.11", "@types/react": "*", @@ -3604,6 +3802,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -3649,9 +3848,10 @@ } }, "node_modules/@types/unist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", - "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" }, "node_modules/@types/ws": { "version": "8.5.10", @@ -3677,7 +3877,8 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "license": "ISC" }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", @@ -3874,6 +4075,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -3949,31 +4151,33 @@ } }, "node_modules/algoliasearch": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz", - "integrity": "sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.3", - "@algolia/cache-common": "4.23.3", - "@algolia/cache-in-memory": "4.23.3", - "@algolia/client-account": "4.23.3", - "@algolia/client-analytics": "4.23.3", - "@algolia/client-common": "4.23.3", - "@algolia/client-personalization": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/logger-common": "4.23.3", - "@algolia/logger-console": "4.23.3", - "@algolia/recommend": "4.23.3", - "@algolia/requester-browser-xhr": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/requester-node-http": "4.23.3", - "@algolia/transporter": "4.23.3" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", + "integrity": "sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==", + "license": "MIT", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-account": "4.24.0", + "@algolia/client-analytics": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-personalization": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/recommend": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.1.tgz", - "integrity": "sha512-fSxJ4YreH4kOME9CnKazbAn2tK/rvBoV37ETd6nTt4j7QfkcnW+c+F22WfuE9Q/sRpvOMnUwU/BXAVEiwW7p/w==", + "version": "3.22.5", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.5.tgz", + "integrity": "sha512-lWvhdnc+aKOKx8jyA3bsdEgHzm/sglC4cYdMG4xSQyRiPLJVJtH/IVYZG3Hp6PkTEhQqhyVYkeP9z2IlcHJsWw==", + "license": "MIT", "dependencies": { "@algolia/events": "^4.0.1" }, @@ -3981,6 +4185,45 @@ "algoliasearch": ">= 3.1 < 6" } }, + "node_modules/algoliasearch/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -4055,7 +4298,8 @@ "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", @@ -4076,9 +4320,10 @@ } }, "node_modules/astring": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz", - "integrity": "sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", "bin": { "astring": "bin/astring" } @@ -4092,9 +4337,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "funding": [ { "type": "opencollective", @@ -4109,12 +4354,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -4199,6 +4445,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4341,9 +4588,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", "funding": [ { "type": "opencollective", @@ -4358,11 +4605,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -4478,9 +4726,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001636", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", - "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", + "version": "1.0.30001666", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz", + "integrity": "sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==", "funding": [ { "type": "opencollective", @@ -4494,12 +4742,14 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4524,6 +4774,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "license": "MIT", "engines": { "node": ">=10" } @@ -4541,6 +4792,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4550,6 +4802,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4559,6 +4812,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4568,6 +4822,7 @@ "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", @@ -4588,6 +4843,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", @@ -4740,9 +4996,10 @@ } }, "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -4751,6 +5008,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4794,6 +5052,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4949,6 +5208,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz", "integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -5287,6 +5547,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", + "license": "MIT", "dependencies": { "autoprefixer": "^10.4.19", "browserslist": "^4.23.0", @@ -6080,6 +6341,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", "dependencies": { "dequal": "^2.0.0" }, @@ -6229,9 +6491,10 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.673", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.673.tgz", - "integrity": "sha512-zjqzx4N7xGdl5468G+vcgzDhaHkaYgVcf9MqgexcTqsl2UHSCmOj/Bi3HAprg4BZCpC7HyD8a6nZl6QAZf72gw==" + "version": "1.5.31", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.31.tgz", + "integrity": "sha512-QcDoBbQeYt0+3CWcK/rEbuHvwpbT/8SV9T3OSgs6cX1FlcUAkgrkqbg9zLnDrMM/rLamzQwal4LYFCiWk861Tg==", + "license": "ISC" }, "node_modules/elkjs": { "version": "0.9.1", @@ -6246,7 +6509,8 @@ "node_modules/emojilib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==" + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "license": "MIT" }, "node_modules/emojis-list": { "version": "3.0.0", @@ -6257,9 +6521,10 @@ } }, "node_modules/emoticon": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.0.1.tgz", - "integrity": "sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", + "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -6329,9 +6594,10 @@ "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -6379,6 +6645,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -6418,6 +6685,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" }, @@ -6430,6 +6698,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", @@ -6445,6 +6714,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" @@ -6454,6 +6724,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "astring": "^1.8.0", @@ -6465,15 +6736,12 @@ } }, "node_modules/estree-util-value-to-estree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.0.1.tgz", - "integrity": "sha512-b2tdzTurEIbwRh+mKrEcaWfu1wgb8J1hVsgREg7FFiecWwK/PhO8X0kyc+0bIcKNtD4sqxIdNoRy6/p/TvECEA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.1.2.tgz", + "integrity": "sha512-S0gW2+XZkmsx00tU2uJ4L9hUT7IFabbml9pHh2WQqFmAbxit++YGZne0sKJbNwkj9Wvg9E4uqWl4nCIFQMmfag==", + "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "is-plain-obj": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" + "@types/estree": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/remcohaszing" @@ -6483,6 +6751,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/unist": "^3.0.0" @@ -6496,6 +6765,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -6655,12 +6925,14 @@ "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" }, "node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", "dependencies": { "is-extendable": "^0.1.0" }, @@ -6713,6 +6985,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "license": "MIT", "dependencies": { "format": "^0.2.0" }, @@ -6736,6 +7009,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", + "license": "MIT", "dependencies": { "xml-js": "^1.6.11" }, @@ -7061,6 +7335,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "license": "MIT", "engines": { "node": "*" }, @@ -7150,7 +7425,8 @@ "node_modules/get-own-enumerable-property-symbols": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "license": "ISC" }, "node_modules/get-stream": { "version": "6.0.1", @@ -7166,7 +7442,8 @@ "node_modules/github-slugger": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", - "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==" + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", + "license": "ISC" }, "node_modules/glob": { "version": "7.2.3", @@ -7342,6 +7619,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "license": "MIT", "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", @@ -7356,6 +7634,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -7364,6 +7643,7 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -7458,6 +7738,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -7477,6 +7758,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, @@ -7486,9 +7768,10 @@ } }, "node_modules/hast-util-raw": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.2.tgz", - "integrity": "sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", + "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -7513,6 +7796,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.0.tgz", "integrity": "sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==", + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", @@ -7540,6 +7824,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==", + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", @@ -7563,22 +7848,25 @@ } }, "node_modules/hast-util-to-jsx-runtime/node_modules/inline-style-parser": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.2.tgz", - "integrity": "sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ==" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" }, "node_modules/hast-util-to-jsx-runtime/node_modules/style-to-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.5.tgz", - "integrity": "sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", "dependencies": { - "inline-style-parser": "0.2.2" + "inline-style-parser": "0.2.4" } }, "node_modules/hast-util-to-parse5": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", @@ -7597,6 +7885,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, @@ -7609,6 +7898,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", @@ -7761,6 +8051,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -7836,6 +8127,7 @@ "url": "https://github.com/sponsors/fb55" } ], + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", @@ -7974,6 +8266,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", + "license": "MIT", "dependencies": { "queue": "6.0.2" }, @@ -8033,9 +8326,10 @@ } }, "node_modules/infima": { - "version": "0.2.0-alpha.43", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.43.tgz", - "integrity": "sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==", + "version": "0.2.0-alpha.44", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.44.tgz", + "integrity": "sha512-tuRkUSO/lB3rEhLJk25atwAjgLuzq070+pOW8XcvpHky/YbENnRRdPd85IBkyeTgttmOy5ah+yHYsK1HhUd4lQ==", + "license": "MIT", "engines": { "node": ">=12" } @@ -8062,7 +8356,8 @@ "node_modules/inline-style-parser": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", - "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", + "license": "MIT" }, "node_modules/internmap": { "version": "2.0.3", @@ -8100,6 +8395,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -8109,6 +8405,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" @@ -8160,6 +8457,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -8183,6 +8481,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8218,6 +8517,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -8261,6 +8561,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8285,6 +8586,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -8304,6 +8606,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "license": "MIT", "dependencies": { "@types/estree": "*" } @@ -8312,6 +8615,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8430,9 +8734,10 @@ } }, "node_modules/joi": { - "version": "17.12.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz", - "integrity": "sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==", + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", @@ -8650,6 +8955,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -8697,6 +9003,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", "engines": { "node": ">=16" }, @@ -8708,6 +9015,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -8717,6 +9025,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz", "integrity": "sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", @@ -8736,6 +9045,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", @@ -8751,6 +9061,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -8759,9 +9070,10 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz", - "integrity": "sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz", + "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", @@ -8794,12 +9106,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/mdast-util-frontmatter": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", @@ -8817,6 +9131,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -8828,6 +9143,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "license": "MIT", "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", @@ -8843,9 +9159,10 @@ } }, "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", - "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", @@ -8872,6 +9189,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -8890,12 +9208,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/mdast-util-gfm-footnote": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", @@ -8912,6 +9232,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", @@ -8926,6 +9247,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", @@ -8942,6 +9264,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", @@ -8957,6 +9280,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-mdx-expression": "^2.0.0", @@ -8970,9 +9294,10 @@ } }, "node_modules/mdast-util-mdx-expression": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", - "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", @@ -8987,9 +9312,10 @@ } }, "node_modules/mdast-util-mdx-jsx": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.0.tgz", - "integrity": "sha512-A8AJHlR7/wPQ3+Jre1+1rq040fX9A4Q1jG8JxmSNp/PLPHg80A6475wxTp3KzHpApFH6yWxFotHrJQA3dXP6/w==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", + "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==", + "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", @@ -9001,7 +9327,6 @@ "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", - "unist-util-remove-position": "^5.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" }, @@ -9014,6 +9339,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", @@ -9031,6 +9357,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" @@ -9041,9 +9368,10 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.1.0.tgz", - "integrity": "sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", @@ -9064,6 +9392,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", @@ -9083,6 +9412,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0" }, @@ -9609,6 +9939,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", @@ -9630,9 +9961,9 @@ } }, "node_modules/micromark-core-commonmark": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz", - "integrity": "sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz", + "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==", "funding": [ { "type": "GitHub Sponsors", @@ -9643,6 +9974,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", @@ -9676,6 +10008,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -9695,6 +10028,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -9713,12 +10047,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-directive": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.0.tgz", - "integrity": "sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -9747,6 +10083,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -9766,6 +10103,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -9784,12 +10122,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-frontmatter": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "license": "MIT", "dependencies": { "fault": "^2.0.0", "micromark-util-character": "^2.0.0", @@ -9815,6 +10155,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -9833,12 +10174,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-gfm": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", @@ -9855,9 +10198,10 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz", - "integrity": "sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", @@ -9883,6 +10227,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -9901,12 +10246,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-gfm-footnote": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz", - "integrity": "sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", @@ -9936,6 +10283,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -9955,6 +10303,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -9973,12 +10322,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", @@ -10005,12 +10356,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz", - "integrity": "sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", + "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -10037,6 +10390,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10056,6 +10410,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10074,12 +10429,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-gfm-tagfilter": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" }, @@ -10089,9 +10446,10 @@ } }, "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz", - "integrity": "sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -10118,6 +10476,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10137,6 +10496,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10155,7 +10515,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-mdx-expression": { "version": "3.0.0", @@ -10171,6 +10532,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", @@ -10196,6 +10558,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10215,6 +10578,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10233,12 +10597,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.0.tgz", - "integrity": "sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz", + "integrity": "sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==", + "license": "MIT", "dependencies": { "@types/acorn": "^4.0.0", "@types/estree": "^1.0.0", @@ -10247,6 +10613,7 @@ "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" @@ -10270,6 +10637,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10289,6 +10657,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10307,12 +10676,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-mdx-md": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" }, @@ -10325,6 +10696,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", "dependencies": { "acorn": "^8.0.0", "acorn-jsx": "^5.0.0", @@ -10344,6 +10716,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", @@ -10374,6 +10747,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10392,7 +10766,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-factory-destination": { "version": "2.0.0", @@ -10408,6 +10783,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", @@ -10428,6 +10804,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10446,7 +10823,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-factory-label": { "version": "2.0.0", @@ -10462,6 +10840,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", @@ -10483,6 +10862,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10501,12 +10881,13 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.1.tgz", - "integrity": "sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz", + "integrity": "sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw==", "funding": [ { "type": "GitHub Sponsors", @@ -10517,9 +10898,11 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", @@ -10528,6 +10911,26 @@ "vfile-message": "^4.0.0" } }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", @@ -10542,6 +10945,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10560,7 +10964,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-factory-space": { "version": "1.1.0", @@ -10610,6 +11015,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", @@ -10631,6 +11037,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10650,6 +11057,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10668,7 +11076,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-factory-whitespace": { "version": "2.0.0", @@ -10684,6 +11093,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", @@ -10705,6 +11115,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10724,6 +11135,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10742,7 +11154,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-character": { "version": "1.2.0", @@ -10792,6 +11205,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0" } @@ -10809,7 +11223,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-classify-character": { "version": "2.0.0", @@ -10825,6 +11240,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", @@ -10845,6 +11261,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10863,7 +11280,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-combine-extensions": { "version": "2.0.0", @@ -10879,6 +11297,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10898,6 +11317,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0" } @@ -10915,7 +11335,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-decode-string": { "version": "2.0.0", @@ -10931,6 +11352,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", @@ -10952,6 +11374,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -10970,7 +11393,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-encode": { "version": "2.0.0", @@ -10985,7 +11409,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-events-to-acorn": { "version": "2.0.2", @@ -11001,6 +11426,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "@types/acorn": "^4.0.0", "@types/estree": "^1.0.0", @@ -11025,7 +11451,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.0", @@ -11040,7 +11467,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-normalize-identifier": { "version": "2.0.0", @@ -11056,6 +11484,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0" } @@ -11073,7 +11502,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-resolve-all": { "version": "2.0.0", @@ -11089,6 +11519,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" } @@ -11107,6 +11538,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", @@ -11127,6 +11559,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -11145,12 +11578,13 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-subtokenize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz", - "integrity": "sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz", + "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==", "funding": [ { "type": "GitHub Sponsors", @@ -11161,6 +11595,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", @@ -11181,7 +11616,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-symbol": { "version": "1.1.0", @@ -11211,7 +11647,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark/node_modules/micromark-factory-space": { "version": "2.0.0", @@ -11227,6 +11664,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -11246,6 +11684,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -11264,7 +11703,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromatch": { "version": "4.0.5", @@ -11446,6 +11886,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", + "license": "MIT", "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", @@ -11465,9 +11906,10 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "license": "MIT" }, "node_modules/non-layered-tidy-tree-layout": { "version": "2.0.2", @@ -11486,6 +11928,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11504,7 +11947,8 @@ "node_modules/nprogress": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", - "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", + "license": "MIT" }, "node_modules/nth-check": { "version": "2.1.1", @@ -11742,6 +12186,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "license": "MIT", "dependencies": { "@types/unist": "^2.0.0", "character-entities": "^2.0.0", @@ -11758,9 +12203,10 @@ } }, "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", - "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" }, "node_modules/parse-json": { "version": "5.2.0", @@ -11782,12 +12228,14 @@ "node_modules/parse-numeric-range": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", - "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", + "license": "ISC" }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "license": "MIT", "dependencies": { "entities": "^4.4.0" }, @@ -11799,6 +12247,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "license": "MIT", "dependencies": { "domhandler": "^5.0.2", "parse5": "^7.0.0" @@ -11878,6 +12327,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^3.0.0", @@ -11885,9 +12335,10 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -12103,6 +12554,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" }, @@ -12138,6 +12590,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", + "license": "MIT", "dependencies": { "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" @@ -12439,6 +12892,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12494,6 +12948,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", + "license": "MIT", "dependencies": { "sort-css-media-queries": "2.2.0" }, @@ -12542,6 +12997,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -12567,9 +13023,10 @@ } }, "node_modules/prism-react-renderer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz", - "integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz", + "integrity": "sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==", + "license": "MIT", "dependencies": { "@types/prismjs": "^1.26.0", "clsx": "^2.0.0" @@ -12582,6 +13039,7 @@ "version": "1.29.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "license": "MIT", "engines": { "node": ">=6" } @@ -12614,9 +13072,10 @@ } }, "node_modules/property-information": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.4.1.tgz", - "integrity": "sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -12684,6 +13143,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", "dependencies": { "inherits": "~2.0.3" } @@ -12964,9 +13424,10 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-json-view-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.4.0.tgz", - "integrity": "sha512-wh6F6uJyYAmQ4fK0e8dSQMEWuvTs2Wr3el3sLD9bambX1+pSWUVXIz1RFaoy3TI1mZ0FqdpKq9YgbgTTgyrmXA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.5.0.tgz", + "integrity": "sha512-nWqA1E4jKPklL2jvHWs6s+7Na0qNgw9HCP6xehdQJeg6nPBTFZgGwyko9Q0oj+jQWKTTVRS30u0toM5wiuL3iw==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -13076,7 +13537,8 @@ "node_modules/reading-time": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", - "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==" + "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==", + "license": "MIT" }, "node_modules/rechoir": { "version": "0.6.2", @@ -13193,6 +13655,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", @@ -13215,6 +13678,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.0.tgz", "integrity": "sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-directive": "^3.0.0", @@ -13230,6 +13694,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.2", "emoticon": "^4.0.1", @@ -13245,6 +13710,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-frontmatter": "^2.0.0", @@ -13260,6 +13726,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", @@ -13277,6 +13744,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.1.tgz", "integrity": "sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==", + "license": "MIT", "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" @@ -13290,6 +13758,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", @@ -13302,9 +13771,10 @@ } }, "node_modules/remark-rehype": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz", - "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", + "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", @@ -13321,6 +13791,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", @@ -13535,9 +14006,10 @@ "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" }, "node_modules/rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", + "license": "MIT", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", @@ -13614,9 +14086,10 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" }, "node_modules/scheduler": { "version": "0.23.0", @@ -13646,15 +14119,17 @@ } }, "node_modules/search-insights": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.14.0.tgz", - "integrity": "sha512-OLN6MsPMCghDOqlCtsIsYgtsC0pnwVTyT9Mu6A3ewOj1DxvzZF6COrn2g86E/c05xbktB0XN04m/t1Z+n+fTGw==", + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.2.tgz", + "integrity": "sha512-zFNpOpUO+tY2D85KrxJ+aqwnIfdEGi06UH2+xEb+Bp9Mwznmauqc9djbnBibJO5mpfUPPa8st6Sx65+vbeO45g==", + "license": "MIT", "peer": true }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" @@ -14009,6 +14484,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz", "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==", + "license": "MIT", "dependencies": { "@types/node": "^17.0.5", "@types/sax": "^1.2.1", @@ -14026,12 +14502,14 @@ "node_modules/sitemap/node_modules/@types/node": { "version": "17.0.45", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" }, "node_modules/skin-tone": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "license": "MIT", "dependencies": { "unicode-emoji-modifier-base": "^1.0.0" }, @@ -14051,6 +14529,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -14078,6 +14557,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", + "license": "MIT", "engines": { "node": ">= 6.3.0" } @@ -14086,6 +14566,7 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", "engines": { "node": ">= 8" } @@ -14119,6 +14600,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -14155,12 +14637,14 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" }, "node_modules/srcset": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -14231,9 +14715,10 @@ } }, "node_modules/stringify-entities": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", - "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" @@ -14247,6 +14732,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "license": "BSD-2-Clause", "dependencies": { "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", @@ -14271,6 +14757,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -14287,6 +14774,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -14298,6 +14786,7 @@ "version": "0.4.4", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "license": "MIT", "dependencies": { "inline-style-parser": "0.1.1" } @@ -14347,7 +14836,8 @@ "node_modules/svg-parser": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "license": "MIT" }, "node_modules/svgo": { "version": "3.3.2", @@ -14575,6 +15065,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -14584,6 +15075,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -14682,6 +15174,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "license": "MIT", "engines": { "node": ">=4" } @@ -14715,9 +15208,10 @@ } }, "node_modules/unified": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.4.tgz", - "integrity": "sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", @@ -14750,6 +15244,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, @@ -14762,6 +15257,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, @@ -14774,6 +15270,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, @@ -14782,23 +15279,11 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-remove-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", - "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, @@ -14811,6 +15296,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", @@ -14825,6 +15311,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" @@ -14851,9 +15338,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "funding": [ { "type": "opencollective", @@ -14868,9 +15355,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -15069,6 +15557,7 @@ "version": "3.11.0", "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -15132,12 +15621,12 @@ } }, "node_modules/vfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", - "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" }, "funding": { @@ -15146,9 +15635,10 @@ } }, "node_modules/vfile-location": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", - "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" @@ -15162,6 +15652,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" @@ -15195,6 +15686,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -15671,6 +16163,7 @@ "version": "1.6.11", "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "license": "MIT", "dependencies": { "sax": "^1.2.4" }, @@ -15706,6 +16199,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" diff --git a/website/package.json b/website/package.json index 147d2c4519..210e7cf27a 100644 --- a/website/package.json +++ b/website/package.json @@ -16,8 +16,8 @@ "swizzle": "docusaurus swizzle" }, "dependencies": { - "@docusaurus/core": "^3.4.0", - "@docusaurus/preset-classic": "^3.4.0", - "@docusaurus/theme-mermaid": "^3.4.0" + "@docusaurus/core": "^3.5.2", + "@docusaurus/preset-classic": "^3.5.2", + "@docusaurus/theme-mermaid": "^3.5.2" } } diff --git a/website/versioned_docs/version-8.x.x/assets/data-loader-level-sync-execution-exhausted-instrumentation.png b/website/versioned_docs/version-8.x.x/assets/data-loader-level-sync-execution-exhausted-instrumentation.png new file mode 100644 index 0000000000..1a49d17f68 Binary files /dev/null and b/website/versioned_docs/version-8.x.x/assets/data-loader-level-sync-execution-exhausted-instrumentation.png differ diff --git a/website/versioned_docs/version-8.x.x/assets/ktor-initializer.png b/website/versioned_docs/version-8.x.x/assets/ktor-initializer.png new file mode 100644 index 0000000000..8062374e79 Binary files /dev/null and b/website/versioned_docs/version-8.x.x/assets/ktor-initializer.png differ diff --git a/website/versioned_docs/version-8.x.x/assets/spring-initializer.png b/website/versioned_docs/version-8.x.x/assets/spring-initializer.png new file mode 100644 index 0000000000..bfd4a6301d Binary files /dev/null and b/website/versioned_docs/version-8.x.x/assets/spring-initializer.png differ diff --git a/website/versioned_docs/version-8.x.x/blogs-and-videos.md b/website/versioned_docs/version-8.x.x/blogs-and-videos.md new file mode 100644 index 0000000000..1a4360c015 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/blogs-and-videos.md @@ -0,0 +1,34 @@ +--- +id: blogs-and-videos +title: Blogs & Videos +--- +Here are some links to other blog posts and videos which may provide further examples and reading. + +## graphql-kotlin + +Articles and videos specifically about `graphql-kotlin` + +- 📺  [Supercharge your GraphQL with Ktor and GraalVM (Dariusz Kuc)(KotlinConf 2023)](https://www.youtube.com/watch?v=gqQwTFeHOUU&list=PLlFc5cFwUnmwcJ7ZXyMmS70A9QFyUu1HI) (en) +- 📝  [GraphQL Kotlin 6.0.0 Release](https://medium.com/expedia-group-tech/graphql-kotlin-6-0-0-release-2227fd066dcd) +- 📝  [GraphQL Kotlin 4.0.0 Release](https://medium.com/expedia-group-tech/graphql-kotlin-4-0-0-release-eb87e150a192) +- 📺  [Bootiful GraphQL with Kotlin (Dariusz Kuc, Guillaume Scheibel)(Spring One 2020)](https://www.youtube.com/watch?v=t9He4vHZC24) (en) +- 📺  [Bootiful GraphQL with Kotlin (Dariusz Kuc)(GOTO Chicago 2020)](https://www.youtube.com/watch?v=1siPT1pTXFU) (en) +- 📝  [Introducing GraphQL Kotlin Client](https://medium.com/expedia-group-tech/introducing-graphql-kotlin-client-b32dc3061a6f) +- 📝  [Announcing graphql-kotlin 2.0!](https://medium.com/expedia-group-tech/graphql-kotlin-2-0-4006ea41f774) +- 📺  [Bootiful GraphQL with Kotlin (Dariusz Kuc, Guillaume Scheibel)(KotlinConf 2019)](https://www.youtube.com/watch?v=7YJyPXjLdug&index=25) (en) +- 📝  [Creating a Reactive GraphQL Server with Spring Boot and Kotlin](https://medium.com/expedia-group-tech/creating-a-reactive-graphql-server-with-spring-boot-and-kotlin-54aca7316470) +- 📝  [Apollo Federation in a GraphQL Kotlin Server](https://medium.com/expedia-group-tech/apollo-federation-in-a-graphql-kotlin-server-115cea51607a) +- 📝  [Creating GraphQL Schemas in Kotlin](https://medium.com/expedia-group-tech/creating-graphql-schemas-in-kotlin-aaaac0ab0672) +- 📝  [Release of graphql-kotlin 1.0.0!](https://medium.com/expedia-group-tech/release-of-graphql-kotlin-1-0-0-791ad85d3116) +- 📝  [graphql-kotlin: Generate a GraphQL schema from Kotlin code](https://medium.com/expedia-group-tech/graphql-kotlin-generate-a-graphql-schema-from-kotlin-code-21d1dc2f6e27) + +## GraphQL + +Articles and videos about how Expedia Group is using GraphQL + +- 📝  [Expedia Group Case Study: Bootiful APIs With GraphQL and Kotlin](https://kotlinlang.org/lp/server-side/case-studies/expedia) +- 📝  [Expedia Group Transforms Product Development with Apollo](https://www.apollographql.com/customers/expediagroup/) +- 📺  [Creating a federated schema for a global company (Shane Myrick)](https://youtu.be/MuD3TAP0D9Y) (en) +- 📺  [Migrer ses APIs vers GraphQL: pourquoi? comment! (Guillaume Scheibel)](https://youtu.be/IRIkpvJo95s) (fr) +- 📺  [Migrate your APIs to GraphQL: how? and why! (Guillaume Scheibel)](https://youtu.be/IkPMpzQ-TRI) (en) +- 📺  [Transforming customer experiences and your organization with GraphQL (Jim Gust, Dan Boerner)](https://youtu.be/Jt-ZD4zj4Ow) (en) diff --git a/website/versioned_docs/version-8.x.x/client/client-customization.mdx b/website/versioned_docs/version-8.x.x/client/client-customization.mdx new file mode 100644 index 0000000000..072892f956 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/client/client-customization.mdx @@ -0,0 +1,126 @@ +--- +id: client-customization +title: Client Customization +--- + + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## Ktor HTTP Client Customization + +`GraphQLKtorClient` is a thin wrapper on top of [Ktor HTTP Client](https://ktor.io/docs/client.html) and supports fully +asynchronous non-blocking communication. It is highly customizable and can be configured with any supported Ktor HTTP +[engine](https://ktor.io/clients/http-client/engines.html) and [features](https://ktor.io/clients/http-client/features.html). + +See [Ktor HTTP Client documentation](https://ktor.io/clients/index.html) for additional details. + +### Global Client Customization + +A single instance of `GraphQLKtorClient` can be used to handle many GraphQL operations. You can specify a custom instance +of Ktor `HttpClient` and a target `GraphQLClientSerializer`. + +The below example configures a new `GraphQLKtorClient` to use the `OkHttp` engine with custom timeouts, adds a default `X-MY-API-KEY` +header to all requests, and enables basic logging of the requests. + +```kotlin +val okHttpClient = HttpClient(engineFactory = OkHttp) { + engine { + config { + connectTimeout(10, TimeUnit.SECONDS) + readTimeout(60, TimeUnit.SECONDS) + writeTimeout(60, TimeUnit.SECONDS) + } + } + defaultRequest { + header("X-MY-API-KEY", "someSecretApiKey") + } + install(Logging) { + logger = Logger.DEFAULT + level = LogLevel.INFO + } +} +val client = GraphQLKtorClient( + url = URL("http://localhost:8080/graphql"), + httpClient = okHttpClient +) +``` + +### Per Request Customization + +Individual GraphQL requests can be customized through [HttpRequestBuilder](https://ktor.io/docs/request.html#customizing-requests). +You can use this mechanism to specify custom headers, update target url to include custom query parameters, configure +attributes that can be accessed from the pipeline features as well specify timeouts per request. + +```kotlin +val helloWorldQuery = HelloWorldQuery(variables = HelloWorldQuery.Variables(name = "John Doe")) +val result = client.execute(helloWorldQuery) { + header("X-B3-TraceId", "0123456789abcdef") +} +``` + +## Spring WebClient Customization + +`GraphQLWebClient` is a thin wrapper on top of [Spring WebClient](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html) +that relies on Reactor Netty for fully asynchronous non-blocking communications. If you want to use Jetty instead you will +need to exclude provided `io.projectreactor.netty:reactor-netty` dependency and instead add `org.eclipse.jetty:jetty-reactive-httpclient` +dependency. + +### Global Client Customization + +A single instance of `GraphQLWebClient` can be used to handle many GraphQL operations and you can customize it by providing +a custom instance of `WebClient.Builder`. See [Spring documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-webclient-customization) +for additional details. + +Example below configures `GraphQLWebClient` with custom timeouts and adds a default `X-MY-API-KEY` header to all requests. + +```kotlin +val httpClient: HttpClient = HttpClient.create() + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000) + .responseTimeout(Duration.ofMillis(10_000)) +val connector: ClientHttpConnector = ReactorClientHttpConnector(httpClient.wiretap(true)) +val webClientBuilder = WebClient.builder() + .clientConnector(connector) + .defaultHeader("X-MY-API-KEY", "someSecretApiKey") + +val client = GraphQLWebClient( + url = "http://localhost:8080/graphql", + builder = webClientBuilder +) +``` + +### Per Request Customization + +Individual GraphQL requests can be customized by providing `WebClient.RequestBodyUriSpec` lambda. You can use this mechanism +to specify custom headers or include custom attributes or query parameters. + +```kotlin +val helloWorldQuery = HelloWorldQuery(variables = HelloWorldQuery.Variables(name = "John Doe")) +val result = client.execute(helloWorldQuery) { + header("X-B3-TraceId", "0123456789abcdef") +} +``` + +## Custom GraphQL Client + +GraphQL Kotlin libraries provide generic a `GraphQLClient` interface as well as Ktor HTTP Client and Spring WebClient based +reference implementations. Both `GraphQLKtorClient` and `GraphQLWebClient` are open classes which means you can also +extend them to provide some custom `execute` logic. + +```kotlin +class CustomGraphQLClient(url: URL) : GraphQLKtorClient(url = url) { + + override suspend fun execute(request: GraphQLClientRequest, requestCustomizer: HttpRequestBuilder.() -> Unit): GraphQLClientResponse { + // custom init logic + val result = super.execute(request, requestCustomizer) + // custom finalize logic + return result + } +} +``` + +## Deprecated Field Usage + +Build plugins will automatically fail generation of a client if any of the specified query files are referencing +deprecated fields. This ensures that your clients have to explicitly opt-in into deprecated usage by specifying +`allowDeprecatedFields` configuration option. diff --git a/website/versioned_docs/version-8.x.x/client/client-features.mdx b/website/versioned_docs/version-8.x.x/client/client-features.mdx new file mode 100644 index 0000000000..4bfd3b8e42 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/client/client-features.mdx @@ -0,0 +1,504 @@ +--- +id: client-features +title: Client Features +--- + + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## Jackson and Kotlinx Serialization Support + +GraphQL Kotlin supports generation of client data models that are compatible with both `Jackson` (default) and `kotlinx.serialization` +formats. Build plugins and `graphql-kotlin-spring-client` default to use `Jackson` whereas `graphql-kotlin-ktor-client` +defaults to `kotlinx.serialization`. + +See [client serialization documentation](./client-serialization.mdx) for additional details. + +## Polymorphic Types Support + +GraphQL supports polymorphic types through unions and interfaces which can be represented in Kotlin as marker and +regular interfaces. In order to ensure generated objects are not empty, GraphQL queries referencing polymorphic types +will automatically generate fallback implementations that will be used if there is no match. Polymorphic queries have to +explicitly request `__typename` field as it is used by serializers to correctly distinguish between different implementations. + +:::caution +`kotlinx-serialization` currently does not provide mechanism to automatically register polymorphic fallbacks. Fallbacks +have to be explicitly configured when creating your `GraphQLClientKotlinxSerializer`. + +```kotlin +val serializerWithFallback = GraphQLClientKotlinxSerializer(jsonBuilder = { + serializersModule = SerializersModule { + polymorphic(BasicInterface::class) { + defaultDeserializer { DefaultBasicInterfaceImplementation.serializer() } + } + } +}) +val client = GraphQLKtorClient(url = URL("http://localhost:8080/graphql"), serializer = serializerWithFallback) +``` + +See https://github.com/Kotlin/kotlinx.serialization/issues/1575 for details. +::: + +Given example schema + +```graphql +type Query { + interfaceQuery: BasicInterface! +} + +interface BasicInterface { + id: Int! + name: String! +} + +type FirstInterfaceImplementation implements BasicInterface { + id: Int! + intValue: Int! + name: String! +} + +type SecondInterfaceImplementation implements BasicInterface { + floatValue: Float! + id: Int! + name: String! +} +``` + +We can query interface field as + +```graphql +query PolymorphicQuery { + interfaceQuery { + __typename + id + name + ... on FirstInterfaceImplementation { + intValue + } + ... on SecondInterfaceImplementation { + floatValue + } + } +} +``` + +Which will generate following data models + + + + + +```kotlin +@Generated +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "__typename", + defaultImpl = DefaultBasicInterfaceImplementation::class +) +@JsonSubTypes(value = [com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = + FirstInterfaceImplementation::class, + name="FirstInterfaceImplementation"),com.fasterxml.jackson.annotation.JsonSubTypes.Type(value + = SecondInterfaceImplementation::class, name="SecondInterfaceImplementation")]) +interface BasicInterface { + abstract val id: Int + abstract val name: String +} + +@Generated +data class FirstInterfaceImplementation( + override val id: Int, + override val name: String, + val intValue: Int +) : BasicInterface + +@Generated +data class SecondInterfaceImplementation( + override val id: Int, + override val name: String, + val floatValue: Float +) : BasicInterface + +@Generated +data class DefaultBasicInterfaceImplementation( + override val id: Int, + override val name: String +) : BasicInterface +``` + + + + +```kotlin +@Generated +@Serializable +sealed class BasicInterface { + abstract val id: Int + abstract val name: String +} + +@Generated +@Serializable +@SerialName(value = "FirstInterfaceImplementation") +data class FirstInterfaceImplementation( + override val id: Int, + override val name: String, + val intValue: Int +) : BasicInterface() + +@Generated +@Serializable +@SerialName(value = "SecondInterfaceImplementation") +data class SecondInterfaceImplementation( + override val id: Int, + override val name: String, + val floatValue: Float +) : BasicInterface() + +@Generated +@Serializable +data class DefaultBasicInterfaceImplementation( + override val id: String + override val name: String +) : BasicInterface() +``` + + + + +## Custom Scalar Support + +By default, custom GraphQL scalars are serialized and [type-aliased](https://kotlinlang.org/docs/reference/type-aliases.html) +to a String. GraphQL Kotlin plugins also support custom serialization based on provided configuration. + +In order to automatically convert between custom GraphQL `UUID` scalar type and `java.util.UUID`, we first need to create +our custom `ScalarConverter`. + +```kotlin +package com.example.client + +import com.expediagroup.graphql.client.converter.ScalarConverter +import java.util.UUID + +class UUIDScalarConverter : ScalarConverter { + override fun toScalar(rawValue: Any): UUID = UUID.fromString(rawValue.toString()) + override fun toJson(value: UUID): Any = value.toString() +} +``` + +And then configure build plugin by specifying + +- Custom GraphQL scalar name +- Target JVM class name +- Converter that provides logic to map between GraphQL and Kotlin type + +```kotlin +graphql { + packageName = "com.example.generated" + endpoint = "http://localhost:8080/graphql" + customScalars = listOf(GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")) +} +``` + +Custom scalar fields will then be automatically converted to a `java.util.UUID` type using appropriate converter/serializer. + + + + + +Following converters will be generated under `com.example.generated.scalars` package. + +```kotlin +@Generated +public class AnyToUUIDConverter : StdConverter() { + private val converter: UUIDScalarConverter = UUIDScalarConverter() + + public override fun convert(`value`: Any): UUID = converter.toScalar(value) +} + +@Generated +public class UUIDToAnyConverter : StdConverter() { + private val converter: UUIDScalarConverter = UUIDScalarConverter() + + public override fun convert(`value`: UUID): Any = converter.toJson(value) +} +``` + +Custom scalars fields will then be annotated with Jackson annotations referencing the above converters. + +```kotlin +@Generated +public data class Result( + @JsonSerialize(converter = UUIDToAnyConverter::class) + @JsonDeserialize(converter = AnyToUUIDConverter::class) + public val custom: UUID, + @JsonSerialize(contentConverter = UUIDToAnyConverter::class) + @JsonDeserialize(contentConverter = AnyToUUIDConverter::class) + public val customList: List +) +``` + + + + +Following serializer will be generated under `com.example.generated.scalars` package. + +```kotlin +@Generated +public object UUIDSerializer : KSerializer { + private val converter: UUIDScalarConverter = UUIDScalarConverter() + + public override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", STRING) + + public override fun serialize(encoder: Encoder, `value`: UUID): Unit { + val encoded = converter.toJson(value) + encoder.encodeString(encoded.toString()) + } + + public override fun deserialize(decoder: Decoder): UUID { + val jsonDecoder = decoder as JsonDecoder + val element = jsonDecoder.decodeJsonElement() + val rawContent = element.jsonPrimitive.content + return converter.toScalar(rawContent) + } +} +``` + +Custom scalars fields will then be annotated with `@Serializable` annotation referencing the above serializer. + +```kotlin +@Generated +@Serializable +public data class Result( + @Serializable(with = UUIDSerializer::class) + public val custom: UUID, + public val customList: List<@Serializable(with = UUIDSerializer::class) UUID> +) +``` + + + + +See [Gradle](../plugins/gradle-plugin-tasks.mdx) and [Maven](../plugins/maven-plugin-goals.md) plugin documentation for additional details. + +:::info +While custom scalars are most commonly represented using some primitive values (e.g. serializing UUID as String), it is +possible to use arbitrary objects representation as custom scalar. For example Apollo Federation relies on `_Any` scalar +to accept federated entity representations which is a JSON map containing `__typename` information and a number of additional +fields used to uniquely identify the target object. + + + + + +Jackson uses reflection to automatically serialize the objects. In order to rely on this behavior for custom scalars, +we simply need to implement a pass-through converter. + +```kotlin +class AnyScalarConverter : ScalarConverter { + override fun toScalar(rawValue: Any): Any = rawValue + override fun toJson(value: Any): Any = value +} +``` + +This will allow us to pass arbitrary objects as custom scalar inputs. Given following Federation type and `_entities` query + +``` +type Product @key(fields : "id") { + id: String! + name: String! +} + +query EntitiesQuery($representations: [_Any!]!) { + _entities(representations: $representations) { + __typename + ...on Product { name } + } + } +} +``` + +We can create corresponding `ProductEntityRepresentation` data class and use it in our generated query. + +```kotlin +data class ProductEntityRepresentation(val id: String) { + val __typename: String = "Product" +} + +val entityData = client.execute(EntitiesQuery(variables = EntitiesQuery.Variables(representations = listOf(ProductEntityRepresentation(id = "apollo-federation"))))) +``` + + + + +Using kotlinx-serialization we can use `JsonObject` to represent arbitrary objects as custom scalars. + +```kotlin +class AnyScalarConverter : ScalarConverter { + override fun toScalar(rawValue: Any): JsonObject = Json.parseToJsonElement(rawValue.toString()).jsonObject + override fun toJson(value: JsonObject): Any = value +} +``` + +This will allow us to pass arbitrary objects as custom scalar inputs. Given following Federation type and `_entities` query + +``` +type Product @key(fields : "id") { + id: String! + name: String! +} + +query EntitiesQuery($representations: [_Any!]!) { + _entities(representations: $representations) { + __typename + ...on Product { name } + } + } +} +``` + +We can then represent product entity representation as `JsonObject` and use it in our generated query. + +```kotlin +val entity = Json.decodeFromString( + """ + |{ + | "__typename": "Product", + | "id": "apollo-federation" + |} + """.trimMargin() +) + +val entityData = client.execute(EntitiesQuery(variables = EntitiesQuery.Variables(representations = listOf(entity)))) +``` + + + +::: + +## Default Enum Values + +Enums represent predefined set of values. Adding additional enum values could be a potentially breaking change as your +clients may not be able to process it. GraphQL Kotlin Client automatically adds default `__UNKNOWN_VALUE` to all generated +enums as a catch all safeguard for handling new enum values. + +## Auto Generated Documentation + +GraphQL Kotlin build plugins automatically pull in GraphQL descriptions of the queried fields from the target schema and +add it as KDoc to corresponding data models. + +Given simple GraphQL object definition + +```graphql +"Some basic description" +type BasicObject { + "Unique identifier" + id: Int! + "Object name" + name: String! +} +``` + +Will result in a corresponding auto generated data class + +```kotlin +/** + * Some basic description + */ + @Generated +data class BasicObject( + /** + * Unique identifier + */ + val id: Int, + /** + * Object name + */ + val name: String +) +``` + +## Native Support for Coroutines + +GraphQL Kotlin Client is a generic interface that exposes `execute` methods that will suspend your GraphQL operation until +it gets a response back without blocking the underlying thread. Reference Ktor and Spring WebClient based implementations +offer fully asynchronous communication through Kotlin coroutines. In order to fetch data asynchronously you should wrap +your client execution in `launch` or `async` coroutine builder and explicitly `await` for their results. + +See [Kotlin coroutines documentation](https://kotlinlang.org/docs/reference/coroutines-overview.html) for additional details. + +## Batch Operation Support + +GraphQL Kotlin Clients provide out of the box support for batching multiple client operations into a single GraphQL request. +Batch requests are sent as an array of individual GraphQL requests and clients expect the server to respond with a corresponding +array of GraphQL responses. Each response is then deserialized to a corresponding result type. + +```kotlin +val client = GraphQLKtorClient(url = URL("http://localhost:8080/graphql")) +val firstQuery = FirstQuery(variables = FirstQuery.Variables(foo = "bar")) +val secondQuery = SecondQuery(variables = SecondQuery.Variables(foo = "baz")) + +val results: List> = client.execute(listOf(firstQuery, secondQuery)) +``` + +## Optional Input Support + +In the GraphQL world, input types can be optional which means that the client can specify a value, specify a `null` value +OR don't specify any value. This is in contrast with the JVM world where objects can either have some specific value or +don't have any value (i.e. are `null`). By default, GraphQL Kotlin Client treats `null` Kotlin values as unspecified, which +means they will skip all `null` values when serializing the request, e.g. given following query + +```graphql +query OptionalInputQuery($optionalValue: String) { + optional(value: $optionalValue) +``` + +GraphQL Kotlin plugins will generate corresponding POJO that defines `Variables` as + +```kotlin +public data class Variables( + public val optionalValue: String? = null +) +``` + +Regardless whether we specify `optionalValue` as `null` or rely on the default value, this field won't be serialized, +i.e. variables will be serialized as an empty JSON object `{}`. + +By specifying `useOptionalInputWrapper = true` plugin configuration option, we can opt-in to a behavior that supports +three states - defined, `null` or undefined. Generated code will then use `OptionalInput` wrapper to represent those states. +See [Gradle](../plugins/gradle-plugin-tasks) and [Maven](../plugins/maven-plugin-goals) plugin for configuration details. + +```kotlin +public data class Variables( + public val optionalValue: OptionalInput = OptionalInput.Undefined +) + +// usage +// - same behavior as default null, serialized as {} +val undefinedVariables = Variables(optionalValue = OptionalInput.Undefined) + +// - serialized as {"optionalValue": null} +val nullVariables = Variables(optionalValue = OptionalInput.Defined(null)) + +// - serialized as {"optionalValue": "foo"} +val definedVariables = Variables(optionalValue = OptionalInput.Defined("foo") +``` diff --git a/website/versioned_docs/version-8.x.x/client/client-overview.mdx b/website/versioned_docs/version-8.x.x/client/client-overview.mdx new file mode 100644 index 0000000000..ab0c7bc097 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/client/client-overview.mdx @@ -0,0 +1,222 @@ +--- +id: client-overview +title: Client Overview +--- + + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +GraphQL Kotlin provides a set of lightweight type-safe GraphQL HTTP clients. The library provides [Ktor HTTP client](https://ktor.io/clients/index.html) +and [Spring WebClient](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-webclient) +based reference implementations as well as allows for custom implementations using other engines, see [client customization](client-customization.mdx) +documentation for additional details. Type-safe data models are generated at build time by the GraphQL Kotlin [Gradle](../plugins/gradle-plugin-tasks.mdx) +and [Maven](../plugins/maven-plugin-goals.md) plugins. + +Client Features: + +- Supports query and mutation operations +- Supports batch operations +- Automatic generation of type-safe Kotlin models supporting `kotlinx.serialization` and `Jackson` formats +- Custom scalar support - defaults to String but can be configured to deserialize to specific types +- Supports default enum values to gracefully handle new/unknown server values +- Native support for coroutines +- Easily configurable Ktor and Spring WebClient based HTTP Clients +- Documentation generated from the underlying GraphQL schema + +## Project Configuration + +GraphQL Kotlin provides both Gradle and Maven plugins to automatically generate your client code at build time. In order +to auto-generate the client code, plugins require target GraphQL schema and a list of query files to process. + +GraphQL schema can be provided as + +- result of introspection query (default) +- downloaded from an SDL endpoint +- local file + +See [Gradle](https://expediagroup.github.io/graphql-kotlin/docs/plugins/gradle-plugin) and [Maven](https://expediagroup.github.io/graphql-kotlin/docs/plugins/maven-plugin) +plugin documentation for additional details. + +GraphQL Kotlin plugins generated classes are simple POJOs that implement `GraphQLClientRequest` and optionally accept variables +(only if underlying operation uses variables) as a constructor parameter. Generated classes can then be passed directly +to a GraphQL client to execute either a single or a batch request. + +Example below configures the project to use introspection query to obtain the schema and uses Spring WebClient based HTTP client. + +### Build Configuration + + + + + +Basic `build.gradle.kts` Gradle configuration that executes introspection query against specified endpoint to obtain target +schema and then generate the clients under `com.example.generated` package name: + +```kotlin +import com.expediagroup.graphql.plugin.gradle.graphql + +plugins { + id("com.expediagroup.graphql") version $latestGraphQLKotlinVersion +} + +dependencies { + implementation("com.expediagroup:graphql-kotlin-spring-client:$latestGraphQLKotlinVersion") +} + +graphql { + client { + endpoint = "http://localhost:8080/graphql" + packageName = "com.example.generated" + } +} +``` + + + + +Basic Maven `pom.xml` configuration that executes introspection query against specified endpoint to obtain target +schema and then generate the clients under `com.example.generated` package name: + +```xml + + + 4.0.0 + + com.example + graphql-kotlin-maven-client-example + 1.0.0-SNAPSHOT + + + $latestGraphQLKotlinVersion + + + + + com.expediagroup + graphql-kotlin-spring-client + ${graphql-kotlin.version} + + + + + + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + generate-graphql-client + + introspect-schema + generate-client + + + http://localhost:8080/graphql + com.example.generated + ${project.build.directory}/schema.graphql + + + + + + + +``` + + + + +See [graphql-kotlin-client-example](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/client) project for complete +working examples of Gradle and Maven based projects. + +### Generating GraphQL Operations + +By default, GraphQL Kotlin build plugins will attempt to generate GraphQL operations from all `*.graphql` files located under +`src/main/resources`. Operations are validated against the target GraphQL schema, which can be manually provided, retrieved by +the plugins through introspection (as configured in examples above) or downloaded directly from a custom SDL endpoint. +See our documentation for more details on supported [Gradle tasks](../plugins/gradle-plugin-tasks.mdx) +and [Maven Mojos](../plugins/maven-plugin-goals.md). + +When creating your GraphQL operations make sure to always specify an operation name and name the files accordingly. Each +one of your GraphQL operation files will generate a corresponding Kotlin file with a class matching your operation +name. Input objects, enums and custom scalars definitions will be shared across different operations. All other objects +will be generated operation specific package name. For example, given `HelloWorldQuery.graphql` with `HelloWorldQuery` as +the operation name, GraphQL Kotlin plugins will generate a corresponding `HelloWorldQuery.kt` file with a `HelloWorldQuery` +class under the configured package. + +For example, given a simple schema + +```graphql +type Query { + helloWorld: String +} +``` + +And a corresponding `HelloWorldQuery.graphql` query + +```graphql +query HelloWorldQuery { + helloWorld +} +``` + +Plugins will generate following client code + +```kotlin +package com.example.generated + +import com.expediagroup.graphql.client.Generated +import com.expediagroup.graphql.client.types.GraphQLClientRequest +import kotlin.String +import kotlin.reflect.KClass + +const val HELLO_WORLD_QUERY: String = "query HelloWorldQuery {\n helloWorld\n}" + +@Generated +class HelloWorldQuery: GraphQLClientRequest { + override val query: String = HELLO_WORLD_QUERY + + override val operationName: String = "HelloWorldQuery" + + override fun responseType(): KClass = HelloWorldQuery.Result::class + + @Generated + data class Result( + val helloWorld: String + } +} +``` + +Generated classes are simple POJOs that implement `GraphQLClientRequest` interface and represent a GraphQL request. + +### Executing Operations + +`GraphQLWebClient` uses the Spring WebClient to execute the underlying operations and allows you to customize it by providing +an instance of `WebClient.Builder`. `GraphQLWebClient` requires target URL to be specified and defaults to use `Jackson` +based GraphQL serializer. Please refer to [Spring documentation](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client) +for additional details. + +```kotlin +package com.example.client + +import com.expediagroup.graphql.client.spring.GraphQLWebClient +import com.expediagroup.graphql.generated.HelloWorldQuery +import kotlinx.coroutines.runBlocking + +fun main() { + val client = GraphQLWebClient(url = "http://localhost:8080/graphql") + runBlocking { + val helloWorldQuery = HelloWorldQuery() + val result = client.execute(helloWorldQuery) + println("hello world query result: ${result.data?.helloWorld}") + } +} +``` diff --git a/website/versioned_docs/version-8.x.x/client/client-serialization.mdx b/website/versioned_docs/version-8.x.x/client/client-serialization.mdx new file mode 100644 index 0000000000..cfb226e743 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/client/client-serialization.mdx @@ -0,0 +1,473 @@ +--- +id: client-serialization +title: Client Serialization +--- + + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +GraphQL Kotlin build plugins can generate GraphQL client data classes that are compatible with [`Jackson`](https://github.com/FasterXML/jackson) +(default) or [`kotlinx.serialization`](https://github.com/Kotlin/kotlinx.serialization) data models. By default, GraphQL +clients will attempt to pick up the appropriate serializer from a classpath - `graphql-kotlin-spring-client` defines implicit +dependency on `Jackson` based serializer and `graphql-kotlin-ktor-client` define a dependency on a `kotlinx.serialization`. + +`GraphQLClientSerializer` is a service provider interface that expose generic serialize/deserialize methods that are used +by the GraphQL clients to serialize requests to String and deserialize responses from String. By utilizing Java [ServiceLoader](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html) +mechanism we can dynamically load appropriate serializer from the classpath. If there are multiple providers on the classpath, +we default to select the first one available one on the classpath. + +## GraphQL Kotlin Spring Client + +### Using Jackson + +`Jackson` is the default serializer used by the build plugins and by GraphQL Kotlin Spring Client. + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.graphql + +dependencies { + implementation("com.expediagroup", "graphql-kotlin-spring-client", $graphQLKotlinVersion) { +} + +graphql { + client { + endpoint = "http://localhost:8080/graphql" + packageName = "com.example.generated" + } +} +``` + + + + +```xml + + + + + com.expediagroup + graphql-kotlin-spring-client + ${graphql-kotlin.version} + + + + + + + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + introspect-schema + generate-client + + + http://localhost:8080/graphql + com.example.generated + + + + + + + +``` + + + + +By default, `ServiceLoader` mechanism will load the first available GraphQL client serializer from the classpath. + +```kotlin +val client = GraphQLWebClient( + url = "http://localhost:8080/graphql" + serializer = GraphQLClientJacksonSerializer() +) +``` + +### Using Kotlinx Serialization + +In order to use `kotlinx.serialization` we need to + +* add dependency on `graphql-kotlin-client-serialization` +* configure GraphQL plugin to generate `kotlinx.serialization` compatible data models +* configure corresponding compiler plugin +* explicitly specify the target serializer during client construction OR exclude `graphql-kotlin-client-jackson` dependency + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer +import com.expediagroup.graphql.plugin.gradle.graphql + +plugins { + kotlin("plugin.serialization") version $kotlinVersion +} + +dependencies { + implementation("com.expediagroup", "graphql-kotlin-spring-client", $graphQLKotlinVersion) { + exclude("com.expediagroup", "graphql-kotlin-client-jackson") + } + implementation("com.expediagroup", "graphql-kotlin-client-serialization", $graphQLKotlinVersion) +} + +graphql { + client { + endpoint = "http://localhost:8080/graphql" + packageName = "com.example.generated" + serializer = GraphQLSerializer.KOTLINX + } +} +``` + + + + +```xml + + + + + com.expediagroup + graphql-kotlin-spring-client + ${graphql-kotlin.version} + + + com.expediagroup + graphql-kotlin-client-jackson + + + + + com.expediagroup + graphql-kotlin-client-serialization + ${graphql-kotlin.version} + + + org.jetbrains.kotlinx + kotlinx-serialization-json + ${kotlinx-serialization.version} + + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + 1.8 + + kotlinx-serialization + + + + + compile + + compile + + + + test-compile + + test-compile + + + + + + org.jetbrains.kotlin + kotlin-maven-serialization + ${kotlin.version} + + + + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + introspect-schema + generate-client + + + http://localhost:8080/graphql + com.example.generated + KOTLINX + + + + + + + +``` + + + + +By default, `ServiceLoader` mechanism will load the first available GraphQL client serializer from the classpath. We can +also explicitly specify serializer during client construction + +```kotlin +val client = GraphQLWebClient( + url = "http://localhost:8080/graphql" + serializer = GraphQLClientKotlinxSerializer() +) +``` + +## GraphQL Kotlin Ktor Client + +### Using Kotlinx Serialization + +`kotlinx.serialization` is the default serializer used by the GraphQL Kotlin Ktor Client. Build plugins default to use +`Jackson` so we have to explicitly configure the tasks/mojos to use appropriate serializer. + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer +import com.expediagroup.graphql.plugin.gradle.graphql + +plugins { + kotlin("plugin.serialization") version $kotlinVersion +} + +dependencies { + implementation("com.expediagroup", "graphql-kotlin-ktor-client", $graphQLKotlinVersion) { +} + +graphql { + client { + endpoint = "http://localhost:8080/graphql" + packageName = "com.example.generated" + serializer = GraphQLSerializer.KOTLINX + } +} +``` + + + + +```xml + + + + + com.expediagroup + graphql-kotlin-ktor-client + ${graphql-kotlin.version} + + + org.jetbrains.kotlinx + kotlinx-serialization-json + ${kotlinx-serialization.version} + + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + 1.8 + + kotlinx-serialization + + + + + compile + + compile + + + + test-compile + + test-compile + + + + + + org.jetbrains.kotlin + kotlin-maven-serialization + ${kotlin.version} + + + + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + introspect-schema + generate-client + + + http://localhost:8080/graphql + com.example.generated + KOTLINX + + + + + + + +``` + + + + +By default, `ServiceLoader` mechanism will load the first available GraphQL client serializer from the classpath. + +```kotlin +val client = GraphQLKtorClient( + url = URL("http://localhost:8080/graphql") + serializer = GraphQLClientKotlinxSerializer() +) +``` + +### Using Jackson + +In order to use `Jackson` we need to + +* add dependency on `graphql-kotlin-client-jackson` +* explicitly specify the target serializer during client construction OR exclude `graphql-kotlin-client-serialization` dependency + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.graphql + +dependencies { + implementation("com.expediagroup", "graphql-kotlin-ktor-client", $graphQLKotlinVersion) { + exclude("com.expediagroup", "graphql-kotlin-client-serialization") + } + implementation("com.expediagroup", "graphql-kotlin-client-jackson", $graphQLKotlinVersion) +} + +graphql { + client { + endpoint = "http://localhost:8080/graphql" + packageName = "com.example.generated" + } +} +``` + + + + +```xml + + + + + com.expediagroup + graphql-kotlin-ktor-client + ${graphql-kotlin.version} + + + com.expediagroup + graphql-kotlin-client-serialization + + + + + com.expediagroup + graphql-kotlin-client-jackson + ${graphql-kotlin.version} + + + + + + + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + introspect-schema + generate-client + + + http://localhost:8080/graphql + com.example.generated + + + + + + + +``` + + + + +By default, `ServiceLoader` mechanism will load the first available GraphQL client serializer from the classpath. We can +also explicitly specify serializer during client construction + +```kotlin +val client = GraphQLKtorClient( + url = URL("http://localhost:8080/graphql") + serializer = GraphQLClientJacksonSerializer() +) +``` diff --git a/website/versioned_docs/version-8.x.x/examples.md b/website/versioned_docs/version-8.x.x/examples.md new file mode 100644 index 0000000000..d6d2c55fce --- /dev/null +++ b/website/versioned_docs/version-8.x.x/examples.md @@ -0,0 +1,52 @@ +--- +id: examples +title: Examples +--- +A collection of example apps that use graphql-kotlin libraries to test and demonstrate usages can be found in the [examples module](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples). + +## Client Example + +A `graphql-kotlin-client` can be generated by using the provided Maven or Gradle. Example integration using Maven and +Gradle plugins can be found under the [examples/client](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/client) +folder. + +## Federation Example + +There is also an example of [Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction/) +with two Spring Boot apps using `graphql-kotlin-federation` and an Apollo Gateway app in Nodejs that exposes a single +federated schema in [examples/federation](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/federation) +project. Please refer to the README files for details on how to run each application. + +## Server Examples + +Example integrations of `graphql-kotlin-schema-generator` with number of popular application frameworks can be found under +[examples/server](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/server) folder. + +These examples also demonstrates how to include [`DataLoaders`](https://github.com/graphql-java/java-dataloader) in your query execution. + +### Ktor Server Example + +[Ktor](http://ktor.io/) is an asynchronous framework for creating microservices, web applications, and more. Example +integration can be found at [examples/server/ktor-server](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/server/ktor-server) + +### Spring Server Example + +One way to run a GraphQL server is with [Spring Boot](https://github.com/spring-projects/spring-boot). A sample Spring +Boot app that uses [Spring +Webflux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) together with +`graphql-kotlin-schema-generator` and [graphiql](https://github.com/graphql/graphiql) is provided as +a [examples/server/spring-server](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/server/spring-server). +All the examples used in this documentation should be available in this sample app. + +In order to run it you can run +[Application.kt](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/examples/spring/src/main/kotlin/com/expediagroup/graphql/examples/Application.kt) +directly from your IDE. Alternatively you can also use the Spring Boot plugin from the command line. + +```shell script + +./gradlew bootRun + +``` + +Once the app has started you can explore the example schema by opening Playground endpoint at +http:. diff --git a/website/versioned_docs/version-8.x.x/framework-comparison.md b/website/versioned_docs/version-8.x.x/framework-comparison.md new file mode 100644 index 0000000000..11a42bd21a --- /dev/null +++ b/website/versioned_docs/version-8.x.x/framework-comparison.md @@ -0,0 +1,168 @@ +--- +id: framework-comparison +title: GraphQL Frameworks Comparison +--- + +## GraphQL Java +[graphql-java](https://graphql-java.com/) is one of the most popular JVM based GraphQL implemenations. GraphQL Kotlin is +built on top of `grahpql-java` as it can be easily extended with additional functionality and this implementation +has been used and tested by many users. + +### GraphQL Java Schema + +The most common way to create the schema in `graphql-java` is to first manually write the SDL file: + +```graphql +schema { + query: Query +} + +type Query { + bookById(id: ID): Book +} + +type Book { + id: ID! + name: String! + pageCount: Int! + author: Author +} + +type Author { + id: ID! + firstName: String! + lastName: String! +} +``` + +Then write the runtime code that matches this schema to build the `GraphQLSchema` object. + +```kotlin +// Internal DB class, not schema class +class Book( + val id: ID, + val name: String, + val totalPages: Int, // This needs to be renamed to pageCount + val authorId: ID // This is not in the schema +) + +// Internal DB class, not schema class +class Author( + val id: ID, + val firstName: String, + val lastName: String +) + +class GraphQLDataFetchers { + private val books: List = booksFromDB() + private val authors: List = authorsFromDB() + + fun getBookByIdDataFetcher() = DataFetcher { dataFetchingEnvironment -> + val bookId: String = dataFetchingEnvironment.getArgument("id") + return books.firstOrNull { it.id == bookId } + } + + fun getAuthorDataFetcher() = DataFetcher { dataFetchingEnvironment -> + val book: Book = dataFetchingEnvironment.getSource() as Book + return authors.firstOrNull { it.id == book.authorId } + } + + fun getPageCountDataFetcher() = DataFetcher { dataFetchingEnvironment -> + val book: Book = dataFetchingEnvironment.getSource() as Book + return book.totalPages + } +} + +val schemaParser = SchemaParser() +val schemaGenerator = SchemaGenerator() +val schemaFile = loadSchema("schema.graphqls") +val typeRegistry = schemaParser.parse(schemaFile) +val graphQLDataFetchers = GraphQLDataFetchers() + +val runtimeWiring = RuntimeWiring.newRuntimeWiring() + .type( + newTypeWiring("Query") + .dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()) + ) + .type( + newTypeWiring("Book") + .dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()) + .dataFetcher("pageCount", graphQLDataFetchers.getPageCountDataFetcher()) + ) + .build() + +// Combine the types and runtime code together to make a schema +val graphQLSchema: GraphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring) +``` + +This means that there are two sources of truth for your schema and changes in either have to be reflected in both locations. +As your schema scales to hundreds of types and many different resolvers, it can get more difficult to track what code needs to be changed if you want to add a new field, +deprecate or delete an existing one, or fix a bug in the resolver code. + +These errors will hopefully be caught by your build or automated tests, but it is another layer your have to be worried about when creating your API. + +### GraphQL Kotlin Schema + +`graphql-kotlin-schema-generator` aims to simplify this process by using Kotlin reflection to generate the schema for you. +All you need to do is write your schema code in a Kotlin class with public functions or properties. + +```kotlin +private val books: List = booksFromDB() +private val authors: List = authorsFromDB() + +class Query { + fun bookById(id: ID): Book? = books.find { it.id == id } +} + +class Book( + val id: ID, + val name: String, + private val totalPages: Int, + private val authorId: ID +) { + fun author(): Author? = authors.find { it.id == authorId } + fun pageCount(): Int = totalPages +} + +class Author( + val id: ID, + val firstName: String, + val lastName: String +) + +val config = SchemaGeneratorConfig(supportedPackages = "com.example") +val queries = listOf(TopLevelObject(Query())) +val schema: GraphQLSchema = toSchema(config, queries) +``` + +This makes changes in code directly reflect to your schema and you can still produce the `GraphQLSchema` to print and export an SDL file. + + +## DGS +[DGS](https://netflix.github.io/dgs/) is a GraphQL server framework for Spring Boot. It works with both Java and Kotlin. +DGS is also built on top of `graphql-java` and implements many similar features to `graphql-kotlin` and [graphql-java-kickstart/graphql-spring-boot](https://github.com/graphql-java-kickstart/graphql-spring-boot). + +* Auto-configuration of server routes and request handling +* Auto-wiring of data fetchers (resolvers) to the `GraphQLSchema` +* Apollo Federation support +* Subscriptions support +* Client schema-code generation + +While both libraries do very similar things, there are some minor differences which may serve different usecases better. +As with open source library, you can compare and use the right tool for the job. + +### Extra Features of DGS + +* Support for a SDL-First (Schema-First) approach +* Ability to autogenerate code stubs from the schema +* Includes [JsonPath](https://github.com/json-path/JsonPath) testing library +* Build on top of Spring MVC + +### Extra Features of graphql-kotlin + +* Supports code-first approach (generates schema from source code - does not require duplicate implementation of data fetchers, schema classes, and SDL files) +* Abstract server logic can be used in any framework, e.g. Ktor +* Reference server implementation build on top of [Spring Webflux](https://spring.io/reactive) for a reactive server stack +* Simple nesting of data fetchers +* Client code generation for Ktor and Spring +* Client plugin support for both Maven and Gradle diff --git a/website/versioned_docs/version-8.x.x/getting-started.mdx b/website/versioned_docs/version-8.x.x/getting-started.mdx new file mode 100644 index 0000000000..9a5643db03 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/getting-started.mdx @@ -0,0 +1,82 @@ +--- +id: getting-started +title: Getting Started +slug: / +--- + + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +GraphQL Kotlin is a collection of libraries, built on top of [graphql-java](https://www.graphql-java.com/), that simplify running GraphQL clients and servers in Kotlin. + +## Installation + +Using a JVM dependency manager, link any `graphql-kotlin-*` library to your project. + + + + + +```kotlin +implementation("com.expediagroup", "graphql-kotlin-spring-server", latestVersion) +``` + + + + +```xml + + com.expediagroup + graphql-kotlin-spring-server + ${latestVersion} + +``` + + + + +## Generating a Schema + +You can use `graphql-kotlin-schema-generator` to generate a schema from Kotlin code and expose it with any server library. + +See the docs in [Schema Generator Getting Started](./schema-generator/schema-generator-getting-started.mdx). + +### Apollo Federation + +Using `graphql-kotlin-federation`, you can generate an [Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/federation-spec/) compliant schema. + +See the docs in [Apollo Federation](./schema-generator/federation/apollo-federation.mdx). + +## Running a Server + +`graphql-kotlin-server` is a combination of the schema generator, federation, and server libraries. If you are looking to run a GraphQL server, this is the place to start. + +See the docs in [GraphQL Kotlin Server](./server/graphql-server.md). + +## Creating a Client + +`graphql-kotlin-plugins` can be used to generate a `graphql-kotlin-client` from an existing schema that is easy to use and type-safe. + +See the docs in [Client Overview](./client/client-overview.mdx). + +## Examples + +The `examples` module is a collection of working code and examples on how to use all of the `graphql-kotlin` modules. + +See the [example docs](./examples.md) for more info. + +## Blogs and Videos + +You can find more posts and recorded conference talks on GraphQL and `graphql-kotlin` on our [Blogs and Videos](./blogs-and-videos.md) page. + +## Additional resources + +- [GraphQL](https://graphql.org/) +- [graphql-java](https://www.graphql-java.com/) diff --git a/website/versioned_docs/version-8.x.x/plugins/gradle-plugin-tasks.mdx b/website/versioned_docs/version-8.x.x/plugins/gradle-plugin-tasks.mdx new file mode 100644 index 0000000000..a92e14cee2 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/plugins/gradle-plugin-tasks.mdx @@ -0,0 +1,450 @@ +--- +id: gradle-plugin-tasks +title: Gradle Plugin Tasks +sidebar_label: Tasks Overiew +--- + + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +GraphQL Kotlin Gradle Plugin provides functionality to generate a lightweight GraphQL HTTP client and generate GraphQL +schema directly from your source code. + +:::info +This plugin is dependent on Kotlin compiler plugin as it generates Kotlin source code that needs to be compiled. +::: + +## Usage + +`graphql-kotlin-gradle-plugin` is published on Gradle [Plugin Portal](https://plugins.gradle.org/plugin/com.expediagroup.graphql). +In order to execute any of the provided tasks you need to first apply the plugin on your project. + + + + + +Using plugins DSL syntax + +```kotlin +plugins { + id("com.expediagroup.graphql") version $graphQLKotlinVersion +} +``` + +Or by using legacy plugin application + +```kotlin +buildscript { + repositories { + maven { + url = uri("https://plugins.gradle.org/m2/") + } + } + dependencies { + classpath("com.expediagroup:graphql-kotlin-gradle-plugin:$graphQLKotlinVersion") + } +} + +apply(plugin = "com.expediagroup.graphql") +``` + + + + +Using plugins DSL syntax + +```groovy +// build.gradle +plugins { + id 'com.expediagroup.graphql' version $graphQLKotlinVersion +} +``` + +Or by using legacy plugin application + +```groovy +// build.gradle +buildscript { + repositories { + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath "com.expediagroup:graphql-kotlin-gradle-plugin:$graphQLKotlinVersion" + } +} + +apply plugin: "com.expediagroup.graphql" +``` + + + + +## Extension + +GraphQL Kotlin Gradle Plugin uses an extension on the project named `graphql` of type +[GraphQLPluginExtension](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/plugins/graphql-kotlin-gradle-plugin/src/main/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLPluginExtension.kt). +This extension can be used to configure global options instead of explicitly configuring individual tasks. Once extension +is configured, it will automatically download SDL/run introspection to generate GraphQL schema and subsequently generate +all GraphQL clients. GraphQL Extension should be used by default, except for cases where you need to only run individual +tasks. + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.config.GraphQLScalar +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer +import com.expediagroup.graphql.plugin.gradle.graphql + +graphql { + client { + // Boolean flag indicating whether or not selection of deprecated fields is allowed. + allowDeprecatedFields = false + // List of custom GraphQL scalar to converter mapping containing information about corresponding Java type and converter that should be used to serialize/deserialize values. + customScalars = listOf(GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")) + // GraphQL server endpoint that will be used to for running introspection queries. Alternatively you can download schema directly from `sdlEndpoint` or specify local `schemaFile`. + endpoint = "http://localhost:8080/graphql" + // Optional HTTP headers to be specified on an introspection query or SDL request. + headers = mapOf("X-Custom-Header" to "Custom-Header-Value") + // Target package name to be used for generated classes. + packageName = "com.example.generated" + // Custom directory containing query files, defaults to src/main/resources + queryFileDirectory = "${project.projectDir}/src/main/resources/queries" + // Optional list of query files to be processed, takes precedence over queryFileDirectory + queryFiles = listOf(file("${project.projectDir}/src/main/resources/queries/MyQuery.graphql")) + // GraphQL schema file. Can be used instead of `endpoint` or `sdlEndpoint`. + schemaFile = file("${project.projectDir}/src/main/resources/myLocalSchema.graphql") + // GraphQL server SDL endpoint that will be used to download schema. Alternatively you can run introspection query against `endpoint` or specify local `schemaFile`. + sdlEndpoint = "http://localhost:8080/sdl" + // JSON serializer that will be used to generate the data classes. + serializer = GraphQLSerializer.JACKSON + // Timeout configuration for introspection query/downloading SDL + timeout { + // Connect timeout in milliseconds + connect = 5_000 + // Read timeout in milliseconds + read = 15_000 + } + // Override options that GraphQL Java uses when parsing GraphQL queries and schema definition language documents. + parserOptions { + // Override the maximum number of tokens read to prevent processing extremely large queries. + maxTokens = 15000 + // Override the maximum number of whitespace tokens read to prevent processing extremely large queries. + maxWhitespaceTokens = 200000 + // Modify the maximum number of characters in a document to prevent malicious documents consuming CPU + maxCharacters = 1048576 + // Modify the maximum grammar rule depth to negate malicious documents that can cause stack overflows + maxRuleDepth = 500 + // Single-line comments do not have any semantic meaning in GraphQL source documents and can be ignored + captureLineComments = false + // Memory usage is significantly reduced by not capturing ignored characters, especially in SDL parsing. + captureIgnoredChars = false + // Memory usage is reduced by not setting SourceLocations on AST nodes, especially in SDL parsing. + captureSourceLocation = true + } + // Opt-in flag to wrap nullable arguments in OptionalInput that distinguish between null and undefined value. + useOptionalInputWrapper = false + } + graalVm { + // List of supported packages that can contain GraphQL schema type definitions + packages = listOf("com.example") + // Application main class name + mainClassName = "com.example.ApplicationKt" + } + schema { + // List of supported packages that can contain GraphQL schema type definitions + packages = listOf("com.example") + } +} +``` + + + + +```groovy +// build.gradle +import com.expediagroup.graphql.plugin.gradle.config.GraphQLScalar +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer + +graphql { + client { + // Boolean flag indicating whether or not selection of deprecated fields is allowed. + allowDeprecatedFields = false + // List of custom GraphQL scalar to converter mapping containing information about corresponding Java type and converter that should be used to serialize/deserialize values. + customScalars = [new GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")] + // GraphQL server endpoint that will be used to for running introspection queries. Alternatively you can download schema directly from `sdlEndpoint` or specify local `schemaFile`. + endpoint = "http://localhost:8080/graphql" + // Optional HTTP headers to be specified on an introspection query or SDL request. + headers = ["X-Custom-Header" : "My-Custom-Header-Value"] + // Target package name to be used for generated classes. + packageName = "com.example.generated" + // Custom directory containing query files, defaults to src/main/resources + queryFileDirectory = "${project.projectDir}/src/main/resources/queries" + // Optional list of query files to be processed, takes precedence over queryFileDirectory + queryFiles = [file("${project.projectDir}/src/main/resources/queries/MyQuery.graphql")] + // GraphQL schema file. Can be used instead of `endpoint` or `sdlEndpoint`. + schemaFile = file("${project.projectDir}/src/main/resources/myLocalSchema.graphql") + // GraphQL server SDL endpoint that will be used to download schema. Alternatively you can run introspection query against `endpoint` or specify local `schemaFile`. + sdlEndpoint = "http://localhost:8080/sdl" + // JSON serializer that will be used to generate the data classes. + serializer = GraphQLSerializer.JACKSON + // Timeout configuration for introspection query/downloading SDL + timeout { t -> + // Connect timeout in milliseconds + t.connect = 5000 + t.read = 15000 + } + // Override options that GraphQL Java uses when parsing GraphQL queries and schema definition language documents. + parserOptions { options -> + // Override the maximum number of tokens read to prevent processing extremely large queries. + options.maxTokens = 15000 + // Override the maximum number of whitespace tokens read to prevent processing extremely large queries. + options.maxWhitespaceTokens = 200000 + // Modify the maximum number of characters in a document to prevent malicious documents consuming CPU + options.maxCharacters = 1048576 + // Modify the maximum grammar rule depth to negate malicious documents that can cause stack overflows + options.maxRuleDepth = 500 + // Memory usage is significantly reduced by not capturing ignored characters, especially in SDL parsing. + options.captureIgnoredChars = false + // Memory usage is reduced by not setting SourceLocations on AST nodes, especially in SDL parsing. + options.captureSourceLocation = true + // Single-line comments do not have any semantic meaning in GraphQL source documents and can be ignored + options.captureLineComments = true + } + // Opt-in flag to wrap nullable arguments in OptionalInput that distinguish between null and undefined value. + useOptionalInputWrapper = false + } + graalVm { + // List of supported packages that can contain GraphQL schema type definitions + packages = ["com.example"] + // Application main class name + mainClassName = "com.example.ApplicationKt" + } + schema { + packages = ["com.example"] + } +} +``` + + + + +## Tasks + +By default, `graphql-kotlin-gradle-plugin` lazily registers all its tasks which means that while they are known to the build, +they are not created nor executed until something in the build needs the instantiated object (e.g. adding explicit dependency +on those tasks or explicitly creating them). **Configuring plugin through the `graphql` extension will automatically create +all the corresponding tasks**. + +All `graphql-kotlin-gradle-plugin` tasks are grouped together under `GraphQL` task group and their names are prefixed with +`graphql`. You can find detailed information about GraphQL kotlin tasks by running `gradle help --task `. + +```shell +$ gradle tasks --group graphql + +GraphQL tasks +------------- +graphqlDownloadSDL - Download schema in SDL format from target endpoint. +graphqlGenerateClient - Generate HTTP client from the specified GraphQL queries. +graphqlGenerateSDL - Generate GraphQL schema in SDL format. +graphqlGenerateTestClient - Generate HTTP test client from the specified GraphQL queries. +graphqLGraalVmMetadata - Generate GraalVM reflect metadata for GraphQL Kotlin servers. +graphqlIntrospectSchema - Run introspection query against target GraphQL endpoint and save schema locally.--> +``` + +### graphqlDownloadSDL + +Task that attempts to download GraphQL schema in SDL format from the specified `endpoint` and saves the underlying +schema file as `schema.graphql` under build directory. In general, this task provides limited functionality by itself +and could be used as an alternative to `graphqlIntrospectSchema` to generate input for the subsequent +`graphqlGenerateClient` task. + +:::info +This task will always be `UP-TO-DATE` and in order to trigger the re-download of an SDL you need to force rerun it by +specifying `--rerun-tasks` option or by executing `clean` task. +::: + +**Properties** + +| Property | Type | Required | Description | +| ---------------------- | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `endpoint` | String | yes | Target GraphQL server SDL endpoint that will be used to download schema.
**Command line property is**: `endpoint`. | +| `headers` | `Map` | | Optional HTTP headers to be specified on a SDL request. | +| `outputFile` | File | | Target GraphQL schema file to be generated.
**Default value is:** `${project.buildDir}/schema.graphql` | +| `timeoutConfig` | TimeoutConfig | | Timeout configuration (in milliseconds) to download schema from SDL endpoint before we cancel the request.
**Default value are:**
connect timeout = 5_000
read timeout = 15_000.
| + +### graphqlGenerateClient + +Task that generates GraphQL Kotlin client and corresponding data classes based on the provided GraphQL queries that are +evaluated against target Graphql schema. Individual clients with their specific data models are generated for each query +file and are placed under specified `packageName`. When this task is added to the project, either through explicit configuration +or through the `graphql` extension, it will automatically configure itself as a dependency of a `compileKotlin` task and +resulting generated code will be automatically added to the project main source set. + +**Properties** + +| Property | Type | Required | Description | +|---------------------------|-----------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `allowDeprecatedFields` | Boolean | | Boolean flag indicating whether selection of deprecated fields is allowed or not.
**Default value is:** `false`.
**Command line property is**: `allowDeprecatedFields`. | +| `customScalars` | `List` | | List of custom GraphQL scalar to converter mappings containing information about corresponding Java type and converter that should be used to serialize/deserialize values. | +| `packageName` | String | yes | Target package name for generated code.
**Command line property is**: `packageName`. | +| `parserOptions` | GraphQLParserOptions | | Configures the settings used when parsing GraphQL queries and schema definition language documents. | +| `queryFiles` | FileCollection | | List of query files to be processed. Instead of a list of files to be processed you can specify `queryFileDirectory` directory instead. If this property is specified it will take precedence over the corresponding directory property. | +| `queryFileDirectory` | Directory | | Directory file containing GraphQL queries. Instead of specifying a directory you can also specify list of query file by using `queryFiles` property instead.
**Default value is:** `src/main/resources`. | +| `schemaFile` | File | yes | GraphQL schema file that will be used to generate client code. | +| `serializer` | GraphQLSerializer | | JSON serializer that will be used to generate the data classes.
**Default value is:** `GraphQLSerializer.JACKSON`. | +| `useOptionalInputWrapper` | Boolean | | Boolean opt-in flag to wrap nullable arguments in `OptionalInput` that distinguish between `null` and undefined/omitted value.
**Default value is:** `false`.
**Command line property is**: `useOptionalInputWrapper` | + +By default, this task will generate Jackson compatible data models. See [client serialization documentation](../client/client-serialization.mdx) +for details on how to update this process to use `kotlinx.serialization` instead. + +**Parameter Details** + + * *parserOptions* - Configure options for parsing GraphQL queries and schema definition language documents. Settings here override the defaults set by GraphQL Java. + + ```kotlin + // Override options that GraphQL Java uses when parsing GraphQL queries and schema definition language documents. + parserOptions { + // Override the maximum number of tokens read to prevent processing extremely large queries. + maxTokens = 15000 + // Override the maximum number of whitespace tokens read to prevent processing extremely large queries. + maxWhitespaceTokens = 200000 + // Modify the maximum number of characters in a document to prevent malicious documents consuming CPU + maxCharacters = 1048576 + // Modify the maximum grammar rule depth to negate malicious documents that can cause stack overflows + maxRuleDepth = 500 + // Memory usage is significantly reduced by not capturing ignored characters, especially in SDL parsing. + captureIgnoredChars = false + // Memory usage is reduced by not setting SourceLocations on AST nodes, especially in SDL parsing. + captureSourceLocation = true + // Single-line comments do not have any semantic meaning in GraphQL source documents and can be ignored + captureLineComments = true + } + ``` + +### graphqlGenerateSDL + +Task that generates GraphQL schema in SDL format from your source code using reflections. Utilizes `graphql-kotlin-schema-generator` +to generate the schema from classes implementing `graphql-kotlin-server` marker `Query`, `Mutation` and `Subscription` interfaces. +In order to limit the amount of packages to scan, this task requires users to provide a list of `packages` that can contain +GraphQL types. + +**Properties** + +| Property | Type | Required | Description | +| -------- | ---- | -------- | ----------- | +| `packages` | `List` | yes | List of supported packages that can be scanned to generate SDL. | +| `schemaFile` | File | | Target GraphQL schema file to be generated.
**Default value is:** `${project.buildDir}/schema.graphql` | + +By default, this task will attempt to generate the schema using `NoopSchemaGeneratorHooks`. If you need to customize your +schema generation process, you will need to provide your custom instance of `SchemaGeneratorHooksProvider` service provider. +Service provider can be provided as part of your project, included in one of your project dependencies or through explicitly +provided artifact to the `graphqlSDL` configuration. + +```kotlin +dependencies { + graphqlSDL("com.expediagroup:graphql-kotlin-federated-hooks-provider:$graphQLKotlinVersion") +} +``` + +### graphqlGenerateTestClient + +Task that generates GraphQL Kotlin test client and corresponding data classes based on the provided GraphQL queries that are +evaluated against target Graphql schema. Individual test clients with their specific data models are generated for each query +file and are placed under specified `packageName`. When this task is added to the project it will automatically configure +itself as a dependency of a `compileTestKotlin` task and resulting generated code will be automatically added to the project +test source set. + +**Properties** + +| Property | Type | Required | Description | +|---------------------------|-----------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `allowDeprecatedFields` | Boolean | | Boolean flag indicating whether selection of deprecated fields is allowed or not.
**Default value is:** `false`.
**Command line property is**: `allowDeprecatedFields`. | +| `customScalars` | `List` | | List of custom GraphQL scalar to converter mappings containing information about corresponding Java type and converter that should be used to serialize/deserialize values. | +| `packageName` | String | yes | Target package name for generated code.
**Command line property is**: `packageName`. | +| `parserOptions` | GraphQLParserOptions | | Configures the settings used when parsing GraphQL queries and schema definition language documents. | +| `queryFiles` | FileCollection | | List of query files to be processed. Instead of a list of files to be processed you can specify `queryFileDirectory` directory instead. If this property is specified it will take precedence over the corresponding directory property. | +| `queryFileDirectory` | Directory | | Directory file containing GraphQL queries. Instead of specifying a directory you can also specify list of query file by using `queryFiles` property instead.
**Default value is:** `src/test/resources`. | +| `schemaFile` | File | yes | GraphQL schema file that will be used to generate client code. | +| `serializer` | GraphQLSerializer | | JSON serializer that will be used to generate the data classes.
**Default value is:** `GraphQLSerializer.JACKSON`. | +| `useOptionalInputWrapper` | Boolean | | Boolean opt-in flag to wrap nullable arguments in `OptionalInput` that distinguish between `null` and undefined/omitted value.
**Default value is:** `false`.
**Command line property is**: `useOptionalInputWrapper` | + +By default, this task will generate Jackson compatible data models. See [client serialization documentation](../client/client-serialization.mdx) +for details on how to update this process to use `kotlinx.serialization` instead. + +**Parameter Details** + + * *parserOptions* - Configure options for parsing GraphQL queries and schema definition language documents. Settings here override the defaults set by GraphQL Java. + + ```kotlin + // Override options that GraphQL Java uses when parsing GraphQL queries and schema definition language documents. + parserOptions { + // Override the maximum number of tokens read to prevent processing extremely large queries. + maxTokens = 15000 + // Override the maximum number of whitespace tokens read to prevent processing extremely large queries. + maxWhitespaceTokens = 200000 + // Modify the maximum number of characters in a document to prevent malicious documents consuming CPU + maxCharacters = 1048576 + // Modify the maximum grammar rule depth to negate malicious documents that can cause stack overflows + maxRuleDepth = 500 + // Memory usage is significantly reduced by not capturing ignored characters, especially in SDL parsing. + captureIgnoredChars = false + // Memory usage is reduced by not setting SourceLocations on AST nodes, especially in SDL parsing. + captureSourceLocation = true + // Single-line comments do not have any semantic meaning in GraphQL source documents and can be ignored + captureLineComments = true + } + ``` + +### graphqLGraalVmMetadata + +Task that generates [GraalVM Reachability Metadata](https://www.graalvm.org/latest/reference-manual/native-image/metadata/) +for `graphql-kotlin` servers. Based on the GraphQL schema it will generate `native-image.properties`, `reflect-config.json` +and `resource-config.json` metadata files under `build/generated/graphqlGraalVmResources/META-INF/native-image///graphql` + +Task will be automatically applied if the project applies [GraalVM Native Plugin](https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html). + +**Properties** + +| Property | Type | Required | Description | +| -------- |------| -------- |-------------| +| `packages` | `List` | yes | List of supported packages that can be can contain GraphQL schema. | +| `mainClassName` | String | | Application main class name. | + +### graphqlIntrospectSchema + +Task that executes GraphQL introspection query against specified `endpoint` and saves the underlying schema file as +`schema.graphql` under build directory. In general, this task provides limited functionality by itself and instead +should be used to generate input for the subsequent `graphqlGenerateClient` task. + +:::info +This task will always be `UP-TO-DATE` and in order to trigger the re-running of the introspection query you need to force +rerun it by specifying `--rerun-tasks` option or by executing `clean` task. +::: + +**Properties** + +| Property | Type | Required | Description | +| -------- | ---- | -------- | ----------- | +| `endpoint` | String | yes | Target GraphQL server endpoint that will be used to execute introspection queries.
**Command line property is**: `endpoint`. | +| `headers` | `Map` | | Optional HTTP headers to be specified on an introspection query. | +| `outputFile` | File | | Target GraphQL schema file to be generated.
**Default value is:** `${project.buildDir}/schema.graphql` | +| `streamResponse` | Boolean | | Boolean property to indicate whether to use streamed (chunked) responses.
**Default value is:** true. | +| `timeoutConfig` | TimeoutConfig | | Timeout configuration (in milliseconds) to download schema from SDL endpoint before we cancel the request.
**Default value are:**
connect timeout = 5_000
read timeout = 15_000.
| diff --git a/website/versioned_docs/version-8.x.x/plugins/gradle-plugin-usage-client.mdx b/website/versioned_docs/version-8.x.x/plugins/gradle-plugin-usage-client.mdx new file mode 100644 index 0000000000..8e33db432e --- /dev/null +++ b/website/versioned_docs/version-8.x.x/plugins/gradle-plugin-usage-client.mdx @@ -0,0 +1,654 @@ +--- +id: gradle-plugin-usage-client +title: Gradle Plugin Client Usage +sidebar_label: Generating Client +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +GraphQL Kotlin plugins can be used to generate a lightweight type-safe GraphQL HTTP clients. See examples below for more +information about the client generating tasks. + +## Downloading Schema SDL + +GraphQL endpoints are often public and as such many servers might disable introspection queries in production environment. +Since GraphQL schema is needed to generate type safe clients, as alternative GraphQL servers might expose private +endpoints (e.g. accessible only from within network, etc) that could be used to download schema in Schema Definition +Language (SDL) directly. `graphqlDownloadSDL` task requires target GraphQL server `endpoint` to be specified and can +be executed directly from the command line by explicitly passing `endpoint` parameter + +```shell script +$ gradle graphqlDownloadSDL --endpoint="http://localhost:8080/sdl" +``` + +Task can also be explicitly configured in your Gradle build file + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLDownloadSDLTask + +val graphqlDownloadSDL by tasks.getting(GraphQLDownloadSDLTask::class) { + endpoint.set("http://localhost:8080/sdl") +} +``` + + + + +```groovy +//build.gradle +graphqlDownloadSDL { + endpoint = "http://localhost:8080/sdl" +} +``` + + + + +:::info +This task does not automatically configure itself to be part of your build lifecycle. You will need to explicitly +invoke it OR configure it as a dependency of some other task. +::: + +## Introspecting Schema + +Introspection task requires target GraphQL server `endpoint` to be specified. Task can be executed directly from the +command line by explicitly passing endpoint parameter + +```shell script +$ gradle graphqlIntrospectSchema --endpoint="http://localhost:8080/graphql" +``` + +Task can also be explicitly configured in your Gradle build file + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask + +val graphqlIntrospectSchema by tasks.getting(GraphQLIntrospectSchemaTask::class) { + endpoint.set("http://localhost:8080/graphql") +} +``` + + + + +```groovy +//build.gradle +graphqlIntrospectSchema { + endpoint = "http://localhost:8080/graphql" +} +``` + + + + +:::info +This task does not automatically configure itself to be part of your build lifecycle. You will need to explicitly +invoke it OR configure it as a dependency of some other task. +::: + +## Generating Client + +GraphQL Kotlin client code is generated based on the provided queries that will be executed against target GraphQL `schemaFile`. +Separate class is generated for each provided GraphQL query and are saved under specified `packageName`. + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask + +val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) { + packageName.set("com.example.generated") + schemaFile.set(file("${project.projectDir}/mySchema.graphql")) +} +``` + + + + +```groovy +//build.gradle +graphqlGenerateClient { + packageName = "com.example.generated" + schemaFile = file("${project.projectDir}/mySchema.graphql") +} +``` + + + + +This will process all GraphQL files located under `src/main/resources` and generate corresponding GraphQL Kotlin client +data models. Generated classes will be automatically added to the project compile sources. + +## Generating Client with Custom Scalars + +By default, all custom GraphQL scalars will be serialized as Strings. You can override this default behavior by specifying +custom [scalar converter](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/clients/graphql-kotlin-client/src/main/kotlin/com/expediagroup/graphql/client/converter/ScalarConverter.kt). + +For example given following custom scalar in our GraphQL schema + +```graphql +scalar UUID +``` + +We can create a custom converter to automatically convert this custom scalar to `java.util.UUID` + +```kotlin +package com.example + +import com.expediagroup.graphql.client.converter.ScalarConverter +import java.util.UUID + +class UUIDScalarConverter : ScalarConverter { + override fun toScalar(rawValue: Any): UUID = UUID.fromString(rawValue.toString() + override fun toJson(value: UUID): String = value.toString() +} +``` + +Afterwards we need to configure our plugin to use this custom converter + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.config.GraphQLScalar +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask + +val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) { + packageName.set("com.example.generated") + schemaFile.set(file("${project.projectDir}/mySchema.graphql")) + customScalars.add(GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")) +} +``` + + + + +```groovy +//build.gradle +import com.expediagroup.graphql.plugin.gradle.config.GraphQLScalar + +graphqlGenerateClient { + packageName = "com.example.generated" + schemaFile = file("${project.projectDir}/mySchema.graphql") + customScalars.add(new GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")) +} +``` + + + + +## Generating Client using Kotlinx Serialization + +GraphQL Kotlin plugins default to generate client data models that are compatible with [Jackson](https://github.com/FasterXML/jackson). +We can change this default behavior by explicitly specifying serializer type (in the extension or explicitly in the generate +client task) and configuring `kotlinx.serialization` compiler plugin. See [kotlinx.serialization documentation](https://github.com/Kotlin/kotlinx.serialization) +for details. + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.config.GraphQLScalar +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer + +plugins { + kotlin("plugin.serialization") version $kotlinVersion +} + +val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) { + packageName.set("com.example.generated") + schemaFile.set(file("${project.projectDir}/mySchema.graphql")) + serializer.set(GraphQLSerializer.KOTLINX) +} +``` + + + + +```groovy +//build.gradle +import com.expediagroup.graphql.plugin.gradle.config.GraphQLScalar +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer + +plugins { + id 'org.jetbrains.kotlin.plugin.serialization' version '$kotlinVersion' +} + +graphqlGenerateClient { + packageName = "com.example.generated" + schemaFile = file("${project.projectDir}/mySchema.graphql") + serializer = GraphQLSerializer.KOTLINX +} +``` + + + + +## Generating Client using Classpath Schema + +Client generation tasks require `schemaFile` to be provided. Using Gradle we can configure tasks to use local schema file, +load it from an URI or consume it directly from a classpath. See [Gradle TextResourceFactory](https://docs.gradle.org/current/dsl/org.gradle.api.resources.TextResourceFactory.html) +for additional details. + +If `schema.graphql` file is provided in a `my-lib` JAR we can configure our generate client task as follows + + + + + +```kotlin +// build.gradle.kts +val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) { + packageName.set("com.example.generated") + val archive = configurations["compileClasspath"].filter { + // filter on the jar name. + it.name.startsWith("my-lib") + } + schemaFile.set(resources.text.fromArchive(archive, "schema.graphql").asFile()) +} +``` + + + + +```groovy +//build.gradle +graphqlGenerateClient { + packageName = "com.example.generated" + val archive = configurations["compileClasspath"].filter { + // filter on the jar name. + it.name.startsWith("my-lib") + } + schemaFile = resources.text.fromArchive(archive, "schema.graphql").asFile() +} +``` + + + + +## Generating Test Client + +GraphQL Kotlin test client code is generated based on the provided queries that will be executed against target GraphQL `schemaFile`. +Separate class is generated for each provided GraphQL query and are saved under specified `packageName`. + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateTestClientTask + +val graphqlGenerateTestClient by tasks.getting(GraphQLGenerateTestClientTask::class) { + packageName.set("com.example.generated") + schemaFile.set(file("${project.projectDir}/mySchema.graphql")) +} +``` + + + + +```groovy +//build.gradle +graphqlGenerateTestClient { + packageName = "com.example.generated" + schemaFile = file("${project.projectDir}/mySchema.graphql") +} +``` + + + + +This will process all GraphQL queries located under `src/test/resources` and generate corresponding GraphQL Kotlin clients. +Generated classes will be automatically added to the project test compile sources. + +:::info +`graphqlGenerateTestClient` cannot be configured through the `graphql` extension and has to be explicitly configured. +::: + +## Minimal Client Configuration Example + +Following is the minimal configuration that runs introspection query against a target GraphQL server and generates a +corresponding schema. This generated schema is subsequently used to generate GraphQL client code based on the queries +provided under `src/main/resources` directory. + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.graphql + +graphql { + client { + endpoint = "http://localhost:8080/graphql" + packageName = "com.example.generated" + } +} +``` + +Above configuration is equivalent to the following + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLIntrospectSchemaTask + +val graphqlIntrospectSchema by tasks.getting(GraphQLIntrospectSchemaTask::class) { + endpoint.set("http://localhost:8080/graphql") +} +val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) { + packageName.set("com.example.generated") + schemaFile.set(graphqlIntrospectSchema.outputFile) + dependsOn("graphqlIntrospectSchema") +} +``` + + + + +```groovy +graphql { + client { + endpoint = "http://localhost:8080/graphql" + packageName = "com.example.generated" + } +} +``` + +Above configuration is equivalent to the following + +```groovy +// build.gradle +graphqlIntrospectSchema { + endpoint = "http://localhost:8080/graphql" +} +graphqlGenerateClient { + packageName = "com.example.generated" + schemaFile = graphqlIntrospectSchema.outputFile + dependsOn graphqlIntrospectSchema +} +``` + + + + +## Complete Client Configuration Example + +Following is a configuration example that downloads schema SDL from a target GraphQL server that is then used to generate +the GraphQL client data models using `kotlinx.serialization` that are based on the provided query. + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.config.GraphQLScalar +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer +import com.expediagroup.graphql.plugin.gradle.graphql + +plugins { + kotlin("plugin.serialization") version $kotlinVersion +} + +graphql { + client { + sdlEndpoint = "http://localhost:8080/sdl" + packageName = "com.example.generated" + // optional configuration + allowDeprecatedFields = true + customScalars = listOf(GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")) + headers = mapOf("X-Custom-Header" to "My-Custom-Header") + queryFiles = listOf(file("${project.projectDir}/src/main/resources/queries/MyQuery.graphql")) + serializer = GraphQLSerializer.KOTLINX + timeout { + connect = 10_000 + read = 30_000 + } + } +} +``` + +Above configuration is equivalent to the following + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.config.GraphQLScalar +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer +import com.expediagroup.graphql.plugin.gradle.config.TimeoutConfiguration +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLDownloadSDLTask +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask + +plugins { + kotlin("plugin.serialization") version $kotlinVersion +} + +val graphqlDownloadSDL by tasks.getting(GraphQLDownloadSDLTask::class) { + endpoint.set("http://localhost:8080/sdl") + headers.put("X-Custom-Header", "My-Custom-Header") + timeoutConfig.set(TimeoutConfiguration(connect = 10_000, read = 30_000)) +} +val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) { + packageName.set("com.example.generated") + schemaFile.set(graphqlDownloadSDL.outputFile) + // optional + allowDeprecatedFields.set(true) + customScalars.add(GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")) + queryFiles.from("${project.projectDir}/src/main/resources/queries/MyQuery.graphql") + serializer.set(GraphQLSerializer.KOTLINX) + + dependsOn("graphqlDownloadSDL") +} +``` + + + + +```groovy +// build.gradle +import com.expediagroup.graphql.plugin.gradle.config.GraphQLScalar +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer + +plugins { + id 'org.jetbrains.kotlin.plugin.serialization' version '$kotlinVersion' +} + +graphql { + client { + sdlEndpoint = "http://localhost:8080/sdl" + packageName = "com.example.generated" + // optional configuration + allowDeprecatedFields = true + customScalars = [new GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")] + headers = ["X-Custom-Header" : "My-Custom-Header"] + queryFiles = [file("${project.projectDir}/src/main/resources/queries/MyQuery.graphql")] + serializer = GraphQLSerializer.KOTLINX + timeout { t -> + t.connect = 10000 + t.read = 30000 + } + } +} +``` + +Above configuration is equivalent to the following + +```groovy +//build.gradle +import com.expediagroup.graphql.plugin.gradle.config.GraphQLScalar +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer +import com.expediagroup.graphql.plugin.gradle.config.TimeoutConfiguration + +plugins { + id 'org.jetbrains.kotlin.plugin.serialization' version '$kotlinVersion' +} + +graphqlDownloadSDL { + endpoint = "http://localhost:8080/sdl" + headers["X-Custom-Header"] = "My-Custom-Header" + timeoutConfig = new TimeoutConfiguration(10000, 30000) +} +graphqlGenerateClient { + packageName = "com.example.generated" + schemaFile = graphqlDownloadSDL.outputFile + // optional + allowDeprecatedFields = true + customScalars.add(new GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")) + queryFiles.from("${project.projectDir}/src/main/resources/queries/MyQuery.graphql") + serializer = GraphQLSerializer.KOTLINX + + dependsOn graphqlDownloadSDL +} +``` + + + + +## Generating Multiple Clients + +GraphQL Kotlin Gradle Plugin registers tasks for generation of a client queries targeting single GraphQL endpoint. You +can generate queries targeting additional endpoints by explicitly creating and configuring additional tasks. + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLDownloadSDLTask +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask + +val graphqlDownloadSDL by tasks.getting(GraphQLDownloadSDLTask::class) { + endpoint.set("http://localhost:8080/sdl") +} +val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) { + packageName.set("com.example.generated.first") + schemaFile.set(graphqlDownloadSDL.outputFile) + queryFiles.from("${project.projectDir}/src/main/resources/queries/MyFirstQuery.graphql") + dependsOn("graphqlDownloadSDL") +} + +val graphqlDownloadOtherSDL by tasks.creating(GraphQLDownloadSDLTask::class) { + endpoint.set("http://localhost:8081/sdl") +} +val graphqlGenerateOtherClient by tasks.creating(GraphQLGenerateClientTask::class) { + packageName.set("com.example.generated.second") + schemaFile.set(graphqlDownloadOtherSDL.outputFile) + queryFiles.from("${project.projectDir}/src/main/resources/queries/MySecondQuery.graphql") + dependsOn("graphqlDownloadOtherSDL") +} +``` + + + + +```groovy +//build.gradle +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLDownloadSDLTask +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask + +graphqlDownloadSDL { + endpoint = "http://localhost:8080/sdl" +} +graphqlGenerateClient { + packageName = "com.example.generated.first" + schemaFile = graphqlDownloadSDL.outputFile + queryFiles.from("${project.projectDir}/src/main/resources/queries/MyFirstQuery.graphql") + + dependsOn graphqlDownloadSDL +} + +task graphqlDownloadOtherSDL(type: GraphQLDownloadSDLTask) { + endpoint = "http://localhost:8081/sdl" +} +task graphqlGenerateOtherClient(type: GraphQLGenerateClientTask) { + packageName = "com.other.generated.second" + schemaFile = graphqlDownloadOtherSDL.outputFile + queryFiles.from("${project.projectDir}/src/main/resources/queries/MySecondQuery.graphql") + + dependsOn graphqlDownloadOtherSDL +} +``` + + + diff --git a/website/versioned_docs/version-8.x.x/plugins/gradle-plugin-usage-graalvm.mdx b/website/versioned_docs/version-8.x.x/plugins/gradle-plugin-usage-graalvm.mdx new file mode 100644 index 0000000000..b5d835dd8f --- /dev/null +++ b/website/versioned_docs/version-8.x.x/plugins/gradle-plugin-usage-graalvm.mdx @@ -0,0 +1,270 @@ +--- +id: gradle-plugin-usage-graalvm +title: Gradle Plugin GraalVM Usage +sidebar_label: GraalVM Native Image +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +[GraalVm](https://www.graalvm.org/) is a high performance runtime from Oracle that supports Ahead-of-Time (AOT) compilation +that allows you to build native images. By shifting compilation to the build time, we can create binaries that are +**already optimized so they start almost instantaneously with immediate peak performance**. Compiled code is also much +more memory efficient as we no longer need the big memory overhead of running the JVM. + +In order to generate GraalVM Native image we need to provide the information about all the dynamic JVM features that our +application relies on. Since `graphql-kotlin` generates schema directly from your source code using reflections, we need +to capture this information to make it available at build time. By default, `graphql-kotlin` also relies on classpath scanning +to look up all polymorphic types implementations as well as to locate all the (Apollo) Federated entity types. + +## Ktor GraalVM Native Image + +Given following schema + +```kotlin +class NativeExampleQuery : Query { + fun helloWorld() = "Hello World" +} +``` + +We first need to configure our server to avoid class scanning. Even though our example schema does not contain any +polymorphic types, **we still need to explicitly opt-out of class scanning by providing type hierarchy**. + +```kotlin +fun Application.graphQLModule() { + install(GraphQL) { + schema { + packages = listOf("com.example") + queries = listOf( + HelloWorldQuery() + ) + // mapping between interfaces/union KClass and their implementation KClasses + typeHierarchy = mapOf() + } + } + install(Routing) { + graphQLPostRoute() + graphiQLRoute() + } +} +``` + +We then need to update our build with native configuration + + + + + +```kotlin +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "1.7.21" + application +} + +dependencies { + implementation("com.expediagroup", "graphql-kotlin-ktor-server", $latestGraphQLKotlinVersion) + implementation("ch.qos.logback", "logback-classic", "1.4.7") + implementation("io.ktor", "ktor-client-cio", "2.2.4") +} + +tasks.withType { + kotlinOptions.jvmTarget = "17" +} + +application { + mainClass.set("com.example.ApplicationKt") +} +``` + + + + +```kotlin +import com.expediagroup.graphql.plugin.gradle.graphql +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "1.7.21" + application + id("org.graalvm.buildtools.native") version "0.9.21" // (1) + id("com.expediagroup.graphql") version $latestGraphQLKotlinVersion // (2) +} + +dependencies { + implementation("com.expediagroup", "graphql-kotlin-ktor-server", $latestGraphQLKotlinVersion) + implementation("ch.qos.logback", "logback-classic", "1.4.7") + implementation("io.ktor", "ktor-client-cio", "2.2.4") +} + +tasks.withType { + kotlinOptions.jvmTarget = "17" +} + +application { + mainClass.set("com.example.ApplicationKt") +} + +graalvmNative { // (3) + toolchainDetection.set(false) + binaries { + named("main") { + verbose.set(true) + buildArgs.add("--initialize-at-build-time=io.ktor,kotlin,ch.qos.logback,org.slf4j") + buildArgs.add("-H:+ReportExceptionStackTraces") + } + // enable using reachability metadata repository + metadataRepository { + enabled.set(true) + } + } +} + +graphql { // (4) + graalVm { + packages = listOf("com.example") + } +} +``` + +We need to make couple changes to our build file to be able to generate GraalVM native image: + +1. Apply [GraalVM Native Gradle plugin](https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html) +2. Apply GraphQL Kotlin Gradle plugin +3. Configure GraalVM native image +4. Configure GraphQL Kotlin GraalVM extension + + + + +Once the build is configured we can then generate our native image by running `nativeCompile` task. + +```shell +> ./gradlew nativeCompile +``` + +Native executable image will then be generated under `build/native/nativeCompile` directory. + +## Spring GraalVM Native Image + +Given following schema + +```kotlin +@Component +class NativeExampleQuery : Query { + fun helloWorld() = "Hello World" +} +``` + +We first need to configure our server to avoid class scanning. Even though our example schema does not contain any +polymorphic types, **we still need to explicitly opt-out of class scanning by providing type hierarchy**. + +```kotlin +@SpringBootApplication +class Application { + @Bean + fun typeResolver(): GraphQLTypeResolver = SimpleTypeResolver(mapOf()) +} + +fun main(args: Array) { + runApplication(*args) +} +``` + +We then need to update our build with native configuration + + + + + +```kotlin +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "1.7.21" + kotlin("plugin.spring") version "1.7.21" + id("org.springframework.boot") version "3.0.5" +} + +dependencies { + implementation("com.expediagroup", "graphql-kotlin-spring-server", $latestGraphQLKotlinVersion) +} + +tasks.withType { + kotlinOptions.jvmTarget = "17" +} +``` + + + + +```kotlin +import com.expediagroup.graphql.plugin.gradle.graphql +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "1.7.21" + kotlin("plugin.spring") version "1.7.21" + id("org.springframework.boot") version "3.0.6" + id("org.graalvm.buildtools.native") version "0.9.21" // (1) + id("com.expediagroup.graphql") version $latestGraphQLKotlinVersion // (2) +} + +dependencies { + implementation("com.expediagroup", "graphql-kotlin-spring-server", $latestGraphQLKotlinVersion) +} + +tasks.withType { + kotlinOptions.jvmTarget = "17" +} + +graalvmNative { // (3) + toolchainDetection.set(false) + binaries { + named("main") { + verbose.set(true) + } + // enable using reachability metadata repository + metadataRepository { + enabled.set(true) + } + } +} + +graphql { // (4) + graalVm { + packages = listOf("com.example") + } +} +``` + +We need to make couple changes to our build file to be able to generate GraalVM native image: + +1. Apply [GraalVM Native Gradle plugin](https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html) +2. Apply GraphQL Kotlin Gradle plugin +3. Configure GraalVM native image +4. Configure GraphQL Kotlin GraalVM extension + + + + +Once the build is configured we can then generate our native image by running `nativeCompile` task. + +```shell +> ./gradlew nativeCompile +``` + +Native executable image will then be generated under `build/native/nativeCompile` directory. diff --git a/website/versioned_docs/version-8.x.x/plugins/gradle-plugin-usage-sdl.mdx b/website/versioned_docs/version-8.x.x/plugins/gradle-plugin-usage-sdl.mdx new file mode 100644 index 0000000000..55b99ca166 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/plugins/gradle-plugin-usage-sdl.mdx @@ -0,0 +1,171 @@ +--- +id: gradle-plugin-usage-sdl +title: Gradle Plugin SDL Usage +sidebar_label: Generating SDL +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +GraphQL Kotlin follows a code-first approach where schema is auto generated from your source code at runtime. GraphQL Kotlin +plugins can be used to generate schema as a build time artifact. This allows you to seamlessly integrate with various +GraphQL tools that may require a schema artifact as an input (e.g. to perform backwards compatibility checks, etc). + +## Generating SDL + +GraphQL schema can be generated directly from your source code using reflections. `graphqlGenerateSDL` will scan your +classpath looking for classes implementing `Query`, `Mutation` and `Subscription` marker interfaces and then generates the +corresponding GraphQL schema using `graphql-kotlin-schema-generator` and default `NoopSchemaGeneratorHooks`. In order to +limit the amount of packages to scan, this task requires users to provide a list of `packages` that can contain GraphQL +types. + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.graphql + +graphql { + schema { + packages = listOf("com.example") + } +} +``` + +Above configuration is equivalent to the following task definition + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateSDLTask + +val graphqlGenerateSDL by tasks.getting(GraphQLGenerateSDLTask::class) { + packages.set(listOf("com.example")) +} +``` + + + + +```groovy +// build.gradle +graphql { + schema { + packages = ["com.example"] + } +} +``` + +Above configuration is equivalent to the following task definition + +```groovy +//build.gradle +graphqlGenerateSDL { + packages = ["com.example"] +} +``` + + + + +:::info +This task does not automatically configure itself to be part of your build lifecycle. You will need to explicitly +invoke it OR configure it as a dependency of some other task. +::: + +## Using Custom Hooks Provider + +Plugin will default to use `NoopSchemaGeneratorHooks` to generate target GraphQL schema. If your project uses custom hooks +or needs to generate the federated GraphQL schema, you will need to provide an instance of `SchemaGeneratorHooksProvider` +service provider that will be used to create an instance of your custom hooks. + +`graphqlGenerateSDL` utilizes [ServiceLoader](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html) +mechanism to dynamically load available `SchemaGeneratorHooksProvider` service providers from the classpath. Service provider +can be provided as part of your project, included in one of your project dependencies or through explicitly provided artifact. +See [Schema Generator Hooks Provider](./hooks-provider.mdx) for additional details on how to create custom hooks service provider. +Configuration below shows how to configure GraphQL Kotlin plugin with explicitly provided artifact to generate federated +GraphQL schema. + + + + + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.graphql + +graphql { + schema { + packages = listOf("com.example") + } +} + +dependencies { + graphqlSDL("com.expediagroup:graphql-kotlin-federated-hooks-provider:$graphQLKotlinVersion") +} +``` + +Above configuration is equivalent to the following task definition + +```kotlin +// build.gradle.kts +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateSDLTask + +val graphqlGenerateSDL by tasks.getting(GraphQLGenerateSDLTask::class) { + packages.set(listOf("com.example")) +} + +dependencies { + graphqlSDL("com.expediagroup:graphql-kotlin-federated-hooks-provider:$graphQLKotlinVersion") +} +``` + + + + +```groovy +// build.gradle +graphql { + schema { + packages = ["com.example"] + } +} + +dependencies { + graphqlSDL "com.expediagroup:graphql-kotlin-federated-hooks-provider:$DEFAULT_PLUGIN_VERSION" +} +``` + +Above configuration is equivalent to the following task definition + +```groovy +//build.gradle +graphqlGenerateSDL { + packages = ["com.example"] +} + +dependencies { + graphqlSDL "com.expediagroup:graphql-kotlin-federated-hooks-provider:$DEFAULT_PLUGIN_VERSION" +} +``` + + + + +:::info +This task does not automatically configure itself to be part of your build lifecycle. You will need to explicitly +invoke it OR configure it as a dependency of some other task. +::: diff --git a/website/versioned_docs/version-8.x.x/plugins/hooks-provider.mdx b/website/versioned_docs/version-8.x.x/plugins/hooks-provider.mdx new file mode 100644 index 0000000000..3d7b90cc7d --- /dev/null +++ b/website/versioned_docs/version-8.x.x/plugins/hooks-provider.mdx @@ -0,0 +1,138 @@ +--- +id: hooks-provider +title: Schema Generator Hooks Provider +--- + + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +GraphQL Kotlin plugins can generate GraphQL schema as your build artifact directly from your source code. Plugins will scan +your classpath for classes implementing `graphql-kotlin-server` marker `Query`, `Mutation` and `Subscription` interfaces +and then generate corresponding GraphQL schema using `graphql-kotlin-schema-generator`. By default, plugins will generate +the schema using `NoopSchemaGeneratorHooks`. If your project uses custom hooks or needs to generate the federated GraphQL +schema, you will need to provide an instance of `SchemaGeneratorHooksProvider` that will be used to create an instance of +your custom hooks. + +`SchemaGeneratorHooksProvider` is a service provider interface that exposes a single `hooks` method to generate an instance +of `SchemaGeneratorHooks` that will be used to generate your schema. By utilizing Java [ServiceLoader](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html) +we can dynamically load your custom provider from the classpath. Service provider can be provided as part of your project +sources, included inside of one of your project dependencies or through explicitly provided artifact. Since we need to be +able to deterministically choose a single hooks provider, generation of schema will fail if there are multiple providers +on the classpath. + +## Federated Hooks Provider + +If no custom hook providers are specified, `graphql-kotlin` plugins will default to use `NoopSchemaGeneratorHooks`. If you +want to generate Federated schemas, you need to explicitly specify a dependency on `graphql-kotlin-federated-hooks-provider`. + + + + + +```kotlin +// build.gradle.kts +graphqlSDL("com.expediagroup", "graphql-kotlin-federated-hooks-provider", latestVersion) +``` + + + + +```xml + + com.expediagroup + graphql-kotlin-federated-hooks-provider + ${latestVersion} + +``` + + + + +## Creating Custom Hooks Service Provider + +### Add dependency on graphql-kotlin-hooks-provider + +`SchemaGeneratorHooksProvider` interface is defined in `graphql-kotlin-hooks-provider` module. + + + + + +```kotlin +// build.gradle.kts +implementation("com.expediagroup", "graphql-kotlin-hooks-provider", latestVersion) +``` + + + + +```xml + + com.expediagroup + graphql-kotlin-hooks-provider + ${latestVersion} + +``` + + + + +### Create new SchemaGeneratorHooksProvider implementation + +Service provider implementation has to implement `SchemaGeneratorHooksProvider` interface that provides a way to instantiate +schema generator hooks that will be used to generate the GraphQL schema. + +```kotlin +package com.example + +class MyCustomSchemaGeneratorHooksProvider : SchemaGeneratorHooksProvider { + override fun hooks(): SchemaGeneratorHooks = MyCustomHooks() +} +``` + +### Create provider configuration file + +Service loader provider configuration file should be created under JAR `/META-INF/services` directory (e.g. `src/main/resources/META-INF/services` +in default project structure). Name of the provider configuration should be fully qualified service provider interface name, i.e. +`com.expediagroup.graphql.plugin.schema.hooks.SchemaGeneratorHooksProvider` and contain single entry - a fully qualified +name of the service provider implementation. + +Using the example service provider implementation from the above, our project structure should look like + +``` +my-project +|- src + |- main + |- kotlin + |- com + |- example + |- MyCustomSchemaGeneratorHooksProvider.kt + |- resources + |- META-INF + |- services + |- com.expediagroup.graphql.plugin.schema.hooks.SchemaGeneratorHooksProvider + +``` +Our service provider configuration file should have following content + +```text +com.example.MyCustomSchemaGeneratorHooksProvider +``` + +## Limitations + +We don't support Java 9 module mechanism for declaring `ServiceLoader` implementations. As a workaround, you have to define +your service providers in the provider configuration file under `META-INF/services`. diff --git a/website/versioned_docs/version-8.x.x/plugins/maven-plugin-goals.md b/website/versioned_docs/version-8.x.x/plugins/maven-plugin-goals.md new file mode 100644 index 0000000000..92f5e8c1c1 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/plugins/maven-plugin-goals.md @@ -0,0 +1,259 @@ +--- +id: maven-plugin-goals +title: Maven Plugin Goals +sidebar_label: Goals Overview +--- + +GraphQL Kotlin Maven Plugin provides functionality to generate a lightweight GraphQL HTTP client and generate GraphQL +schema directly from your source code. + +:::info +This plugin is dependent on Kotlin compiler plugin as it generates Kotlin source code that needs to be compiled. +::: + +## Goals + +You can find detailed information about `graphql-kotlin-maven-plugin` and all its goals by running `mvn help:describe -Dplugin=com.expediagroup:graphql-kotlin-maven-plugin -Ddetail`. + +### download-sdl + +GraphQL endpoints are often public and as such many servers might disable introspection queries in production environment. +Since GraphQL schema is needed to generate type safe clients, as alternative GraphQL servers might expose private +endpoints (e.g. accessible only from within network, etc) that could be used to download schema in Schema Definition +Language (SDL) directly. This Mojo attempts to download schema from the specified `graphql.endpoint`, validates the +result whether it is a valid schema and saves it locally in a specified target file (defaults to `schema.graphql` under +build directory). In general, this goal provides limited functionality by itself and instead should be used to generate +input for the subsequent `generate-client` goal. + +**Attributes** + +* *Default Lifecycle Phase*: `generate-sources` + +**Parameters** + +| Property | Type | Required | Description | +| -------- | ---- | -------- | ----------- | +| `endpoint` | String | yes | Target GraphQL server SDL endpoint that will be used to download schema.
**User property is**: `graphql.endpoint`. | +| `headers` | `Map` | | Optional HTTP headers to be specified on a SDL request. +| `timeoutConfiguration` | TimeoutConfiguration | | Optional timeout configuration (in milliseconds) to download schema from SDL endpoint before we cancel the request.
**Default values are:**
connect timeout = 5000
read timeout = 15000.
| +| `schemaFile` | File | | Target schema file.
**Default value is**: `${project.build.directory}/schema.graphql`
**User property is**: `graphql.schemaFile`. | + +**Parameter Details** + +* *timeoutConfiguration* - Timeout configuration that allows you to specify connect and read timeout values in milliseconds. + +```xml + + + 1000 + 30000 + +``` + +### generate-client + +Generate GraphQL client code based on the provided GraphQL schema and target queries. + +**Attributes** + +* *Default Lifecycle Phase*: `generate-sources` +* *Requires Maven Project* +* Generated classes are automatically added to the list of compiled sources. + +**Parameters** + +| Property | Type | Required | Description | +|---------------------------|----------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `allowDeprecatedFields` | Boolean | | Boolean flag indicating whether selection of deprecated fields is allowed or not.
**Default value is:** `false`.
**User property is**: `graphql.allowDeprecatedFields`. | +| `customScalars` | `List` | | List of custom GraphQL scalar to converter mappings containing information about corresponding Java type and converter that should be used to serialize/deserialize values. | +| `outputDirectory` | File | | Target directory where to store generated files.
**Default value is**: `${project.build.directory}/generated-sources/graphql` | +| `packageName` | String | yes | Target package name for generated code.
**User property is**: `graphql.packageName`. | +| `parserOptions` | GraphQLParserOptions | | Configures the settings used when parsing GraphQL queries and schema definition language documents. | +| `queryFileDirectory` | File | | Directory file containing GraphQL queries. Instead of specifying a directory you can also specify list of query file by using `queryFiles` property instead.
**Default value is:** `src/main/resources`. | +| `queryFiles` | `List` | | List of query files to be processed. Instead of a list of files to be processed you can also specify `queryFileDirectory` directory containing all the files. If this property is specified it will take precedence over the corresponding directory property. | +| `schemaFile` | String | | GraphQL schema file that will be used to generate client code.
**Default value is**: `${project.build.directory}/schema.graphql`
**User property is**: `graphql.schemaFile`. | +| `serializer` | GraphQLSerializer | | JSON serializer that will be used to generate the data classes.
**Default value is:** `GraphQLSerializer.JACKSON`. | +| `useOptionalInputWrapper` | Boolean | | Boolean opt-in flag to wrap nullable arguments in `OptionalInput` that distinguish between `null` and undefined/omitted value.
**Default value is:** `false`.
**User property is**: `graphql.useOptionalInputWrapper` | + +**Parameter Details** + + * *customScalars* - List of custom GraphQL scalars. Objects contain target GraphQL scalar name, corresponding Java type + and converter that should be used to serialize/deserialize values. + + ```xml + + + + UUID + + java.util.UUID + + com.example.UUIDScalarConverter + + + ``` + * *parserOptions* - Configure options for parsing GraphQL queries and schema definition language documents. Settings here override the defaults set by GraphQL Java. + + ```xml + + + 15000 + + 200000 + + 1048576 + + 500 + + false + + true + + true + + ``` + +### generate-graalvm-metadata + +Generates [GraalVM Reachability Metadata](https://www.graalvm.org/latest/reference-manual/native-image/metadata/) for +`graphql-kotlin` servers. Based on the GraphQL schema it will generate `native-image.properties`, `reflect-config.json` +and `resource-config.json` metadata files under `target/classes/META-INF/native-image///graphql` + +This task should be used in tandem with [GraalVM Native Plugin](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html) +to simplify generation of GraalVM native `graphql-kotlin` servers. + +**Attributes** + +* *Default Lifecycle Phase*: `process-classes` +* *Requires Maven Project* + +**Parameters** + +| Property | Type | Required | Description | +| -------- |------| -------- |-------------| +| `packages` | `List` | yes | List of supported packages that can be can contain GraphQL schema. | +| `mainClassName` | String | | Application main class name. | + +### generate-sdl + +Generates GraphQL schema in SDL format from your source code using reflections. Utilizes `graphql-kotlin-schema-generator` +to generate the schema from classes implementing `graphql-kotlin-server` marker `Query`, `Mutation` and `Subscription` interfaces. +In order to limit the amount of packages to scan, this mojo requires users to provide a list of `packages` that can contain +GraphQL types. + +This MOJO utilizes [ServiceLoader](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html) +mechanism to dynamically load available `SchemaGeneratorHooksProvider` service providers from the classpath. Service provider +can be provided as part of your project, included in one of your project dependencies or through explicitly provided artifact. +See [Schema Generator Hooks Provider](./hooks-provider.mdx) for additional details on how to create custom hooks service +provider. Configuration below shows how to configure GraphQL Kotlin plugin with explicitly provided artifact. + +**Attributes** + +* *Default Lifecycle Phase*: `process-classes` +* *Requires Maven Project* + +**Parameters** + +| Property | Type | Required | Description | +| -------- | ---- | -------- | ----------- | +| `packages` | `List` | yes | List of supported packages that can be scanned to generate SDL. | +| `schemaFile` | File | | Target GraphQL schema file to be generated.
**Default value is:** `${project.buildDir}/schema.graphql` | + +### generate-test-client + +Generate GraphQL test client code based on the provided GraphQL schema and target queries. + +**Attributes** + +* *Default Lifecycle Phase*: `generate-test-sources` +* *Requires Maven Project* +* Generated classes are automatically added to the list of test compiled sources. + +**Parameters** + +| Property | Type | Required | Description | +| -------- | ---- | -------- | ----------- | +| `allowDeprecatedFields` | Boolean | | Boolean flag indicating whether selection of deprecated fields is allowed or not.
**Default value is:** `false`.
**User property is**: `graphql.allowDeprecatedFields`. | +| `customScalars` | `List` | | List of custom GraphQL scalar to converter mappings containing information about corresponding Java type and converter that should be used to serialize/deserialize values. | +| `outputDirectory` | File | | Target directory where to store generated files.
**Default value is**: `${project.build.directory}/generated-test-sources/graphql` | +| `packageName` | String | yes | Target package name for generated code.
**User property is**: `graphql.packageName`. | +| `queryFileDirectory` | File | | Directory file containing GraphQL queries. Instead of specifying a directory you can also specify list of query file by using `queryFiles` property instead.
**Default value is:** `src/test/resources`. | +| `queryFiles` | `List` | | List of query files to be processed. Instead of a list of files to be processed you can also specify `queryFileDirectory` directory containing all the files. If this property is specified it will take precedence over the corresponding directory property. | +| `serializer` | GraphQLSerializer | | JSON serializer that will be used to generate the data classes.
**Default value is:** `GraphQLSerializer.JACKSON`. | +| `schemaFile` | String | | GraphQL schema file that will be used to generate client code.
**Default value is**: `${project.build.directory}/schema.graphql`
**User property is**: `graphql.schemaFile`. | +| `useOptionalInputWrapper` | Boolean | | Boolean opt-in flag to wrap nullable arguments in `OptionalInput` that distinguish between `null` and undefined/omitted value.
**Default value is:** `false`.
**User property is**: `graphql.useOptionalInputWrapper` | + +**Parameter Details** + + * *customScalars* - List of custom GraphQL scalars. Objects contain target GraphQL scalar name, corresponding Java type + and converter that should be used to serialize/deserialize values. + +```xml + + + + + UUID + + java.util.UUID + + com.example.UUIDScalarConverter + + + +``` + +* *parserOptions* - Configure options for parsing GraphQL queries and schema definition language documents. Settings here override the defaults set by GraphQL Java. + + ```xml + + + 15000 + + 200000 + + 1048576 + + 500 + + false + + true + + true + + ``` + +### introspect-schema + +Executes GraphQL introspection query against specified `graphql.endpoint` and saves the result locally to a target file +(defaults to `schema.graphql` under build directory). In general, this goal provides limited functionality by itself and +instead should be used to generate input for the subsequent `generate-client` goal. + +**Attributes** + +* *Default Lifecycle Phase*: `generate-sources` + +**Parameters** + +| Property | Type | Required | Description | +| -------- | ---- | -------- | ----------- | +| `endpoint` | String | yes | Target GraphQL server endpoint that will be used to execute introspection queries.
**User property is**: `graphql.endpoint`. | +| `headers` | `Map` | | Optional HTTP headers to be specified on an introspection query. | +| `timeoutConfiguration` | TimeoutConfiguration | | Optional timeout configuration (in milliseconds) to download schema from SDL endpoint before we cancel the request.
**Default values are:**
connect timeout = 5000
read timeout = 15000.
| +| `schemaFile` | File | | Target schema file.
**Default value is**: `${project.build.directory}/schema.graphql`
**User property is**: `graphql.schemaFile`. | +| `streamResponse` | Boolean | | Boolean property to indicate whether to use streamed (chunked) responses.
Default value is**: true. | + +**Parameter Details** + +* *timeoutConfiguration* - Timeout configuration that allows you to specify connect and read timeout values in milliseconds. + +```xml + + + 1000 + 30000 + +``` diff --git a/website/versioned_docs/version-8.x.x/plugins/maven-plugin-usage-client.md b/website/versioned_docs/version-8.x.x/plugins/maven-plugin-usage-client.md new file mode 100644 index 0000000000..d91a209ba1 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/plugins/maven-plugin-usage-client.md @@ -0,0 +1,456 @@ +--- +id: maven-plugin-usage-client +title: Maven Plugin Client Usage +sidebar_label: Generating Client +--- + +GraphQL Kotlin plugins can be used to generate a lightweight type-safe GraphQL HTTP clients. See examples below for more +information about the client generating tasks. + +## Downloading Schema SDL + +Download SDL Mojo requires target GraphQL server `endpoint` to be specified. Task can be executed directly from the +command line by explicitly specifying `graphql.endpoint` property. + +```shell script +$ mvn com.expediagroup:graphql-kotlin-maven-plugin:download-sdl -Dgraphql.endpoint="http://localhost:8080/sdl" +``` + +Mojo can also be configured in your Maven build file + +```xml + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + download-sdl + + + http://localhost:8080/sdl + + + + +``` + +By default, `download-sdl` goal will be executed as part of the `generate-sources` [build lifecycle phase](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html). + +## Introspecting Schema + +Introspection Mojo requires target GraphQL server `endpoint` to be specified. Task can be executed directly from the +command line by explicitly specifying `graphql.endpoint` property + +```shell script +$ mvn com.expediagroup:graphql-kotlin-maven-plugin:introspect-schema -Dgraphql.endpoint="http://localhost:8080/graphql" +``` + +Mojo can also be configured in your Maven build file + +```xml + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + introspect-schema + + + http://localhost:8080/graphql + + + + +``` + +By default, `introspect-schema` goal will be executed as part of the `generate-sources` [build lifecycle phase](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html). + +## Generating Client + +This Mojo generates GraphQL client code based on the provided queries using target GraphQL `schemaFile`. Classes are +generated under specified `packageName`. When using default configuration and storing GraphQL queries under `src/main/resources` +directories, task can be executed directly from the command line by explicitly providing required properties. + +```shell script +$ mvn com.expediagroup:graphql-kotlin-maven-plugin:generate-client -Dgraphql.schemaFile="mySchema.graphql" -Dgraphql.packageName="com.example.generated" +``` + +Mojo can also be configured in your Maven build file to become part of your build lifecycle. Plugin also provides additional +configuration options that are not available on command line. + +```xml + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + generate-client + + + com.example.generated + mySchema.graphql + + + + +``` + +This will process all GraphQL queries located under `src/main/resources` and generate corresponding GraphQL Kotlin client +data models. Generated classes will be automatically added to the project compile sources. + +:::note +You might need to explicitly add generated clients to your project sources for your IDE to recognize them. See +[build-helper-maven-plugin](https://www.mojohaus.org/build-helper-maven-plugin/) for details. +::: + +## Generating Client with Custom Scalars + +By default, all custom GraphQL scalars will be serialized as Strings. You can override this default behavior by specifying +custom [scalar converter](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-client/src/main/kotlin/com/expediagroup/graphql/client/converter/ScalarConverter.kt). + +For example given following custom scalar in our GraphQL schema + +```graphql +scalar UUID +``` + +We can create a custom converter to automatically convert this custom scalar to `java.util.UUID` + +```kotlin +package com.example + +import com.expediagroup.graphql.client.converter.ScalarConverter +import java.util.UUID + +class UUIDScalarConverter : ScalarConverter { + override fun toScalar(rawValue: Any): UUID = UUID.fromString(rawValue.toString()) + override fun toJson(value: UUID): String = value.toString() +} +``` + +Afterwards we need to configure our plugin to use this custom converter + +```xml + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + generate-client + + + false + + + + UUID + + java.util.UUID + + com.example.UUIDScalarConverter + + + com.example.generated + mySchema.graphql + + + + +``` + +## Generating Client using Kotlinx Serialization + +GraphQL Kotlin plugins default to generate client data models that are compatible with [Jackson](https://github.com/FasterXML/jackson). +We can change this default behavior by explicitly specifying serializer type and configuring `kotlinx.serialization` compiler +plugin. See [kotlinx.serialization documentation](https://github.com/Kotlin/kotlinx.serialization) for details. + +```xml + + + + + org.jetbrains.kotlinx + kotlinx-serialization-json + ${kotlinx-serialization.version} + + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + 1.8 + + kotlinx-serialization + + + + + compile + + compile + + + + test-compile + + test-compile + + + + + + org.jetbrains.kotlin + kotlin-maven-serialization + ${kotlin.version} + + + + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + generate-client + + + com.example.generated + mySchema.graphql + + KOTLINX + + + + + + + +``` + +## Generating Test Client + +This Mojo generates GraphQL Kotlin test client code based on the provided queries using target GraphQL `schemaFile`. Classes +are generated under specified `packageName`. When using default configuration and storing GraphQL queries under `src/test/resources` +directories, task can be executed directly from the command line by explicitly providing required properties. + +```shell script +$ mvn com.expediagroup:graphql-kotlin-maven-plugin:generate-test-client -Dgraphql.schemaFile="mySchema.graphql" -Dgraphql.packageName="com.example.generated" +``` + +Mojo can also be configured in your Maven build file to become part of your build lifecycle. Plugin also provides additional +configuration options that are not available on command line. + +```xml + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + generate-test-client + + + com.example.generated + mySchema.graphql + + + + +``` + +This will process all GraphQL queries located under `src/test/resources` and generate corresponding GraphQL Kotlin test clients. +Generated classes will be automatically added to the project test compile sources. + +:::note +You might need to explicitly add generated test clients to your project test sources for your IDE to recognize them. +See [build-helper-maven-plugin](https://www.mojohaus.org/build-helper-maven-plugin/) for details. +::: + +## Minimal Configuration Example + +Following is the minimal configuration that runs introspection query against a target GraphQL server and generates a corresponding schema. +This generated schema is subsequently used to generate GraphQL client code based on the queries provided under `src/main/resources` directory. + +```xml + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + introspect-schema + generate-client + + + http://localhost:8080/graphql + com.example.generated + + + + +``` + +:::info +Both `introspect-schema` and `generate-client` goals are bound to the same `generate-sources` Maven lifecycle phase. +As opposed to Gradle, Maven does not support explicit ordering of different goals bound to the same build phase. Maven +Mojos will be executed in the order they are defined in your `pom.xml` build file. +::: + +## Complete Configuration Example + +Following is a configuration example that downloads schema SDL from a target GraphQL server that is then used to generate +the GraphQL client data models using `kotlinx.serialization` that are based on the provided query. + +```xml + + + + + org.jetbrains.kotlinx + kotlinx-serialization-json + ${kotlinx-serialization.version} + + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + 1.8 + + kotlinx-serialization + + + + + compile + + compile + + + + test-compile + + test-compile + + + + + + org.jetbrains.kotlin + kotlin-maven-serialization + ${kotlin.version} + + + + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + download-sdl + generate-client + + + http://localhost:8080/sdl + com.example.generated + + ${project.build.directory}/mySchema.graphql + true + + + + UUID + + java.util.UUID + + com.example.UUIDScalarConverter + + + + My-Custom-Header + + + + 1000 + 30000 + + + ${project.basedir}/src/main/resources/queries/MyQuery.graphql + + KOTLINX + + + + + + + +``` + +## Generating Multiple Clients + +In order to generate GraphQL clients targeting multiple endpoints, we need to configure separate executions targeting +different endpoints. + +```xml + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + generate-first-client + + introspect-schema + generate-client + + + http://localhost:8080/graphql + com.example.generated.first + + ${project.basedir}/src/main/resources/queries/FirstQuery.graphql + + + + + generate-second-client + + introspect-schema + generate-client + + + http://localhost:8081/graphql + com.example.generated.second + + ${project.basedir}/src/main/resources/queries/SecondQuery.graphql + + + + + +``` diff --git a/website/versioned_docs/version-8.x.x/plugins/maven-plugin-usage-graalvm.md b/website/versioned_docs/version-8.x.x/plugins/maven-plugin-usage-graalvm.md new file mode 100644 index 0000000000..fce220184a --- /dev/null +++ b/website/versioned_docs/version-8.x.x/plugins/maven-plugin-usage-graalvm.md @@ -0,0 +1,538 @@ +--- +id: maven-plugin-usage-graalvm +title: Maven Plugin GraalVM Usage +sidebar_label: GraalVM Native Image +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +[GraalVm](https://www.graalvm.org/) is a high performance runtime from Oracle that supports Ahead-of-Time (AOT) compilation +that allows you to build native images. By shifting compilation to the build time, we can create binaries that are +**already optimized so they start almost instantaneously with immediate peak performance**. Compiled code is also much +more memory efficient as we no longer need the big memory overhead of running the JVM. + +In order to generate GraalVM Native image we need to provide the information about all the dynamic JVM features that our +application relies on. Since `graphql-kotlin` generates schema directly from your source code using reflections, we need +to capture this information to make it available at build time. By default, `graphql-kotlin` also relies on classpath scanning +to look up all polymorphic types implementations as well as to locate all the (Apollo) Federated entity types. + +## Ktor GraalVM Native Image + +Given following schema + +```kotlin +class NativeExampleQuery : Query { + fun helloWorld() = "Hello World" +} +``` + +We first need to configure our server to avoid class scanning. Even though our example schema does not contain any +polymorphic types, **we still need to explicitly opt-out of class scanning by providing type hierarchy**. + +```kotlin +fun Application.graphQLModule() { + install(GraphQL) { + schema { + packages = listOf("com.example") + queries = listOf( + HelloWorldQuery() + ) + // mapping between interfaces/union KClass and their implementation KClasses + typeHierarchy = mapOf() + } + } + install(Routing) { + graphQLPostRoute() + graphiQLRoute() + } +} +``` + +We then need to update our build with native configuration + + + + + +```xml + + + 4.0.0 + + com.example + example-graalvm-server + 1.0-SNAPSHOT + + + 17 + 1.7.22 + ${latestGraphqlKotlinVersion} + + 2.2.4 + 1.4.7 + + + + + com.expediagroup + graphql-kotlin-ktor-server + ${graphql-kotlin.version} + + + io.ktor + ktor-server-cio-jvm + ${ktor.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + ${kotlin.jvmTarget} + + + + compile + + compile + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + true + com.example.ApplicationKt + + + + + + + +``` + + + + +```xml + + + 4.0.0 + + com.example + example-graalvm-server + 1.0-SNAPSHOT + + + 17 + 1.7.22 + ${latestGraphqlKotlinVersion} + + 2.2.4 + 1.4.7 + 0.9.21 + + + + + com.expediagroup + graphql-kotlin-ktor-server + ${graphql-kotlin.version} + + + io.ktor + ktor-server-cio-jvm + ${ktor.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + ${kotlin.jvmTarget} + + + + compile + + compile + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + true + com.example.ApplicationKt + + + + + + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + generate-graalvm-metadata + + + com.example + com.example.ApplicationKt + + + + + + + + + + + native + + + + org.graalvm.buildtools + native-maven-plugin + ${native-maven-plugin.version} + true + + + build-native + + compile-no-fork + + package + + + + true + + --initialize-at-build-time=io.ktor,kotlin,ch.qos.logback,org.slf4j + -H:+ReportExceptionStackTraces + + + true + + + + + + + + +``` + +We need to make following changes to be able to generate GraalVM native image: + +1. Configure GraphQL Kotlin plugin to generate GraalVM metadata + +:::caution +This goal has to run AFTER `compile` but before `package` phase. It defaults to `process-classes` phase. +::: + +2. Configure [GraalVM Native Maven plugin](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html) + +:::info +GraalVM recommends to create separate profile that simplifies native image creation. Alternatively you can also generate +native image by explicitly executing `native-image` goal. +::: + + + + +Once the build is configured we can then generate our native image by running `package` command with `native` profile. + +```shell +> ./mvnw -Pnative package +``` + +Native executable image will then be generated under `target` directory. + +## Spring GraalVM Native Image + +Given following schema + +```kotlin +@Component +class NativeExampleQuery : Query { + fun helloWorld() = "Hello World" +} +``` + +We first need to configure our server to avoid class scanning. Even though our example schema does not contain any +polymorphic types, **we still need to explicitly opt-out of class scanning by providing type hierarchy**. + +```kotlin +@SpringBootApplication +class Application { + @Bean + fun typeResolver(): GraphQLTypeResolver = SimpleTypeResolver(mapOf()) +} + +fun main(args: Array) { + runApplication(*args) +} +``` + +We then need to update our build with native configuration + + + + + +```xml + + + 4.0.0 + + com.example + example-graalvm-server + 1.0-SNAPSHOT + + + 17 + 1.7.22 + ${latestGraphqlKotlinVersion} + + 3.0.6 + + + + + com.expediagroup + graphql-kotlin-spring-server + ${graphql-kotlin.version} + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + -Xjsr305=strict + + + spring + + ${kotlin.jvmTarget} + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + +``` + + + + +```xml + + + 4.0.0 + + com.example + example-graalvm-server + 1.0-SNAPSHOT + + + 17 + 1.7.22 + ${latestGraphqlKotlinVersion} + + 3.0.6 + 0.9.21 + + + + + com.expediagroup + graphql-kotlin-spring-server + ${graphql-kotlin.version} + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + -Xjsr305=strict + + + spring + + ${kotlin.jvmTarget} + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + generate-graalvm-metadata + + + com.example + com.example.ApplicationKt + + + + + + + + + + + native + + + + org.graalvm.buildtools + native-maven-plugin + ${native-maven-plugin.version} + true + + + build-native + + compile-no-fork + + package + + + + true + + true + + + + + + + + +``` + +We need to make following changes to be able to generate GraalVM native image: + +1. Configure GraphQL Kotlin plugin to generate GraalVM metadata + +:::caution +This goal has to run AFTER `compile` but before `package` phase. It defaults to `process-classes` phase. +::: + +2. Configure [GraalVM Native Maven plugin](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html) + +:::info +GraalVM recommends to create separate profile that simplifies native image creation. Alternatively you can also generate +native image by explicitly executing `native-image` goal. +::: + + + + +Once the build is configured we can then generate our native image by running `package` command with `native` profile. + +```shell +> ./mvnw -Pnative package +``` + +Native executable image will then be generated under `target` directory. diff --git a/website/versioned_docs/version-8.x.x/plugins/maven-plugin-usage-sdl.md b/website/versioned_docs/version-8.x.x/plugins/maven-plugin-usage-sdl.md new file mode 100644 index 0000000000..32eeb1fdef --- /dev/null +++ b/website/versioned_docs/version-8.x.x/plugins/maven-plugin-usage-sdl.md @@ -0,0 +1,77 @@ +--- +id: maven-plugin-usage-sdl +title: Maven Plugin SDL Usage +sidebar_label: Generating SDL +--- + +GraphQL Kotlin follows a code-first approach where schema is auto generated from your source code at runtime. GraphQL Kotlin +plugins can be used to generate schema as a build time artifact. This allows you to seamlessly integrate with various +GraphQL tools that may require a schema artifact as an input (e.g. to perform backwards compatibility checks, etc). + +## Generating SDL + +GraphQL schema can be generated directly from your source code using reflections. `generate-sdl` mojo will scan your +classpath looking for classes implementing `Query`, `Mutation` and `Subscription` marker interfaces and then generates the +corresponding GraphQL schema using `graphql-kotlin-schema-generator` and default `NoopSchemaGeneratorHooks`. In order to +limit the amount of packages to scan, this mojo requires users to provide a list of `packages` that can contain GraphQL +types. + +```xml + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + generate-sdl + + + + com.example + + + + + +``` + +## Using Custom Hooks Provider + +Plugin will default to use `NoopSchemaGeneratorHooks` to generate target GraphQL schema. If your project uses custom hooks +or needs to generate the federated GraphQL schema, you will need to provide an instance of `SchemaGeneratorHooksProvider` +service provider that will be used to create an instance of your custom hooks. + +`generate-sdl` mojo utilizes [ServiceLoader](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html) +mechanism to dynamically load available `SchemaGeneratorHooksProvider` service providers from the classpath. Service provider +can be provided as part of your project, included in one of your project dependencies or through explicitly provided artifact. +See [Schema Generator Hooks Provider](./hooks-provider.mdx) for additional details on how to create custom hooks service provider. +Configuration below shows how to configure GraphQL Kotlin plugin with explicitly provided artifact to generate federated +GraphQL schema. + +```xml + + com.expediagroup + graphql-kotlin-maven-plugin + ${graphql-kotlin.version} + + + + generate-sdl + + + + com.example + + + + + + + com.expediagroup + graphql-kotlin-federated-hooks-provider + ${graphql-kotlin.version} + + + +``` diff --git a/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/advanced-features.md b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/advanced-features.md new file mode 100644 index 0000000000..5d4ce8802a --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/advanced-features.md @@ -0,0 +1,30 @@ +--- +id: advanced-features +title: Advanced Features +--- +## Adding Custom Additional Types + +There are a couple ways you can add more types to the schema without having them be directly consumed by a type in your schema. +This may be required for [Apollo Federation](../federation/apollo-federation.mdx), or maybe adding other interface implementations that are not picked up. + +### `SchemaGenerator::generateSchema` + +When generating a schema you can optionally specify additional types and input types to be included in the schema. This will +allow you to link to those types from your custom `SchemaGeneratorHooks` implementation using GraphQL reference instead of +manually creating the underlying GraphQL type. + +```kotlin +val myConfig = SchemaGeneratorConfig(supportedPackages = listOf("com.example")) +val generator = SchemaGenerator(myConfig) + +val schema = generator.generateSchema( + queries = myQueries, + additionalTypes = setOf(MyCustomObject::class.createType()), + additionalInputTypes = setOf(MyCustomInputObject::class.createType()) +) +``` + +### `SchemaGenerator::addAdditionalTypesWithAnnotation` + +This method is protected so if you override the `SchemaGenerator` used you can call this method to add types that have a specific annotation. +You can see how this is used in `graphql-kotlin-federation` as [an example](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/FederatedSchemaGenerator.kt). diff --git a/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/annotations.md b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/annotations.md new file mode 100644 index 0000000000..72fd37f546 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/annotations.md @@ -0,0 +1,14 @@ +--- +id: annotations +title: Annotations +--- +`graphql-kotlin-schema-generator` ships with a number of annotation classes to allow you to enhance your GraphQL schema +for things that can't be directly derived from Kotlin reflection. + +- [@GraphQLDescription](./documenting-schema) - Provide a description for a GraphQL field +- [@GraphQLDirective](./directives) - Registers directive on a GraphQL field +- [@GraphQLIgnore](./excluding-fields) - Exclude field from the GraphQL schema +- [@GraphQLName](./renaming-fields) - Override the name used for the type +- Kotlin built in [@Deprecated](./deprecating-schema) - Apply the GraphQL `@deprecated` directive on the field +- [@GraphQLDeprecated](./deprecating-schema) - Apply the GraphQL `@deprecated` directive but only in the schema, not in your own Kotlin code with `@Deprecated` +- [@GraphQLType](./custom-type-reference) - Allows specifying a return type that is not the Kotlin code diff --git a/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/custom-type-reference.md b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/custom-type-reference.md new file mode 100644 index 0000000000..4c76113d0e --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/custom-type-reference.md @@ -0,0 +1,97 @@ +--- +id: custom-type-reference +title: Custom Types +--- + +Code-first has many advantages and removes duplication. However, one downside is that the types defined have to match +compiled Kotlin code. In some cases, it is possible to define a schema that is valid in SDL but it would be impossible to +return a Kotlin type that matches exactly that type. In these cases, you can pass in custom types in the schema +generator config and annotate the schema with the type info. + +A common example is when you need to return a type or union defined in library JAR, but you can not change the code. +For example, let's say there is a type in a library. You can not change the fields, add annotations, or have it implement interfaces. + +```kotlin +// Defined in external library +class Foo(val number: Int) +``` + +If you want to have this type be used in a new interface or union defined in your API, it is not possible to do in Kotlin code +since you can not modify the compiled code. + +```kotlin +// New interface +interface TypeWithNumber { val number: Int } +// New union +interface TypeWithAnyField + +// Error: We are not able to return Foo for any of these functions +fun customInterface(): TypeWithNumber = Foo(1) +fun customUnion(): TypeWithAnyField = Foo(1) +``` + +## `@GraphQLType` +You can use this annotation to change the return type of a field. The annotation accepts the type name, which will be +added as a type reference in the schema. This means that you will have to define the type and its schema with the same name in the configuration. + +Doing this could still be serialization issues, so you should make sure that the data you return from the field matches the defined schema of the type. + +```kotlin +// Defined in external library or can not be modified +class Foo(val number: Int) +class Bar(val value: String) + +// Might return Foo or Bar +@GraphQLType("FooOrBar") +fun customUnion(): Any = if (Random.nextBoolean()) Foo(1) else Bar("hello") + +// Will throw runtime error when serialized data does not match the schema +@GraphQLType("FooOrBar") +fun invalidType(): String = "hello" +``` + +### Custom Type Configuration +In our above example there is no Kotlin code for the type `FooOrBar`. It only exists by reference right now. +To add the type into the schema, specify the additional types in the [SchemaGeneratorConfiguration](./generator-config). +This is using the [grapqhl-java schema object builders](https://www.graphql-java.com/documentation/schema#union). + + +```kotlin +val fooCustom = GraphQLUnionType.newUnionType() + .name("FooOrBar") + .possibleType(GraphQLTypeReference("Foo")) + .possibleType(GraphQLTypeReference("Bar")) + .typeResolver { /* Logic for how to resolve types */ } + .build() +val config = SchemaGeneratorConfig(supportedPackages, additionalTypes = setOf(fooCustom)) +``` + +## Adding Missing Kotlin Types +In our above example, since the return type of the Kotlin code did not reference the Kotlin types `Foo` or `Bar`, +reflection will not pick those up by default. They will also need to be added as additional Kotlin types (`KType`) when generating the schema. + +```kotlin +val generator = SchemaGenerator(config) +val schema = generator.use { + it.generateSchema( + queries = listOf(TopLevelObject(Query())), + additionalTypes = setOf( + Foo::class.createType(), + Bar::class.createType(), + ) + ) +} +``` + +## Final Result +With all the above code, the final resulting schema should like this: + +```graphql +type Query { + customUnion: FooOrBar! +} + +union FooOrBar = Foo | Bar +type Foo { number: Int! } +type Bar { value: String! } +``` diff --git a/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/deprecating-schema.md b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/deprecating-schema.md new file mode 100644 index 0000000000..a4837f6ab4 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/deprecating-schema.md @@ -0,0 +1,46 @@ +--- +id: deprecating-schema +title: Deprecating Schema +--- + +GraphQL schemas supports deprecation directive on +the fields (which correspond to Kotlin properties and functions), input fields and enum values. + +Deprecation of arguments is currently not supported [in Kotlin](https://youtrack.jetbrains.com/issue/KT-25643). + +## Kotlin.Deprecated + +Instead of creating a custom annotation, +`graphql-kotlin-schema-generator` just looks for the `@kotlin.Deprecated` annotation and will use that annotation message +for the deprecated reason. + +```kotlin +class SimpleQuery { + @Deprecated(message = "this query is deprecated", replaceWith = ReplaceWith("shinyNewQuery")) + fun simpleDeprecatedQuery(): Boolean = false + + fun shinyNewQuery(): Boolean = true +} +``` + +The above query would produce the following GraphQL schema: + +```graphql +type Query { + simpleDeprecatedQuery: Boolean! @deprecated(reason: "this query is deprecated, replace with shinyNewQuery") + + shinyNewQuery: Boolean! +} +``` + +## GraphQLDeprecated + +A side-effect of using `@Deprecated` is that it marks your own Kotlin code as being deprecated, which may not be what you want. Using `@GraphQLDeprecated` you can add the `@deprecated` directive to the GraphQL schema, but not have your Kotlin code show up as deprecated in your editor. + +```kotlin +class SimpleQuery { + @GraphQLDeprecated(message = "this query is deprecated", replaceWith = ReplaceWith("shinyNewQuery")) + fun simpleDeprecatedQuery(): Boolean = false + + fun shinyNewQuery(): Boolean = true +} diff --git a/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/directives.md b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/directives.md new file mode 100644 index 0000000000..e351b277be --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/directives.md @@ -0,0 +1,210 @@ +--- +id: directives +title: Directives +--- +GraphQL directives can be used to transform the schema types, fields and arguments as well as modify the runtime +behavior of the query (e.g. implement access control, etc). Common use cases involve limiting functionality based on the +user authentication and authorization. While [GraphQL +spec](https://graphql.github.io/graphql-spec/draft/#sec-Type-System.Directives) specifies two types of directives - +`executable` (aka query) and `type system` (aka schema) directives, only the latter one is supported by +`graphql-kotlin-schema-generator`. + +## Default Directives + +`@deprecated` - schema directive used to represent deprecated portion of the schema. +See [@Deprecated and @GraphQLDeprecated](deprecating-schema.md) annotation documentation for more details + +```graphql +type Query { + deprecatedQuery: Boolean! @deprecated(reason: "No longer supported") +} +``` + +`@skip` - query directive that allows for conditional exclusion of fields or fragments + +```graphql +query myQuery($shouldSkip: Boolean) { + myField @skip(if: $shouldSkip) +} +``` + +`@include` - query directive that allows for conditional inclusion of fields or fragments + +```graphql +query myQuery($shouldInclude: Boolean) { + myField @include(if: $shouldInclude) +} +``` + +## Custom Directives + +Custom directives can be added to the schema using custom annotations: + +```kotlin +@GraphQLDirective( + name = "awesome", + description = "This element is great", + locations = [FIELD_DEFINITION] +) +annotation class AwesomeDirective(val value: String) + +class MyQuery { + @AwesomeDirective("cool stuff") + val somethingGreat: String = "Hello World" +} +``` + +The directive will then added to the schema as: + +```graphql +# This element is great +directive @awesome(value: String) on FIELD_DEFINITION + +type MyQuery { + somethingGreat: String @awesome("cool stuff") +} +``` + +Directives can be added to various places in the schema. See the +[graphql.introspection.Introspection.DirectiveLocation](https://github.com/graphql-java/graphql-java/blob/v13.0/src/main/java/graphql/introspection/Introspection.java#L332) +enum from `graphql-java` for a full list of valid locations. + +:::note +GraphQL directives are currently not available through introspection and you have to use SDL directly +instead (you can use convenient `print` extension function of `GraphQLSchema`). See [GraphQL +issue](https://github.com/facebook/graphql/issues/300) and corresponding [graphql-java +issue](https://github.com/graphql-java/graphql-java/issues/1017) for more details about the introspection issue. +::: + +### Naming Convention + +As described in the example above, the directive name in the schema will by default come from the +`@GraphQLDirective.name` attribute which should follow `lowerCamelCase` format. If this value is not specified, the +directive name will default to the normalized decapitalized name of the annotated annotation (eg: `awesomeDirective` in +the example above). + +### Customizing Behavior + +Directives allow you to customize the behavior of your schema based on some predefined conditions. Simplest way to +modify the default behavior of your GraphQLTypes is by providing your custom `KotlinSchemaDirectiveWiring` through +`KotlinDirectiveWiringFactory` factory used by your `SchemaGeneratorHooks`. + +Example of a directive that converts field to lowercase + +```kotlin +@GraphQLDirective(name = "lowercase", description = "Modifies the string field to lowercase") +annotation class LowercaseDirective + +class LowercaseSchemaDirectiveWiring : KotlinSchemaDirectiveWiring { + + override fun onField(environment: KotlinFieldDirectiveEnvironment): GraphQLFieldDefinition { + val field = environment.element + val originalDataFetcher: DataFetcher = environment.getDataFetcher() + + val lowerCaseFetcher = DataFetcherFactories.wrapDataFetcher( + originalDataFetcher, + BiFunction{ _, value -> value.toString().toLowerCase() } + ) + environment.setDataFetcher(lowerCaseFetcher) + return field + } +} +``` + +While you can manually apply all the runtime wirings to the corresponding GraphQL types directly in +`SchemaGeneratorHooks#onRewireGraphQLType`, we recommend the usage of our +[KotlinDirectiveWiringFactory](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/directives/KotlinDirectiveWiringFactory.kt) +to simplify the integrations. `KotlinDirectiveWiringFactory` accepts a mapping of directives to corresponding wirings or +could be extended to provide the wirings through `KotlinDirectiveWiringFactory#getSchemaDirectiveWiring` that accepts +`KotlinSchemaDirectiveEnvironment`. + +```kotlin +val queries = ... +val customWiringFactory = KotlinDirectiveWiringFactory( + manualWiring = mapOf("lowercase" to LowercaseSchemaDirectiveWiring())) +val customHooks = object : SchemaGeneratorHooks { + override val wiringFactory: KotlinDirectiveWiringFactory + get() = customWiringFactory +} +val schemaGeneratorConfig = SchemaGeneratorConfig(hooks = customHooks) +val schema = toSchema(queries = queries, config = schemaGeneratorConfig) +``` + +While providing directives on different schema elements you will be able to modify the underlying GraphQL types. Keep in +mind though that data fetchers are used to resolve the fields so only field directives (and by association their +arguments directives) can modify runtime behavior based on the context and user input. + +:::caution +`graphql-kotlin` prioritizes manual wiring mappings over the wirings provided by the `KotlinDirectiveWiringFactory#getSchemaDirectiveWiring`. +This is a different behavior than `graphql-java` which will first attempt to use `WiringFactory` and then fallback to manual overrides. +::: + +For more details please refer to the example usage of directives in our [example +app](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/server/spring-server). + +### Repeatable Directives + +GraphQL supports repeatable directives (e.g. Apollo federation allows developers to specify multiple `@key` directives). +By default, Kotlin does not allow applying same annotation multiple times. In order to make your directives repeatable, +you need to annotate it with `kotlin.annotation.Repeatable` annotation. + +```kotlin +@Repeatable +@GraphQLDirective(locations = [DirectiveLocation.OBJECT, DirectiveLocation.INTERFACE]) +annotation class MyRepeatableDirective(val value: String) +``` + +Generates the above directive as + +```graphql +directive @myRepeatableDirective(value: String!) repeatable on OBJECT | INTERFACE +``` + +## Directive Chaining + +Directives are applied in the order annotations are declared on the given object. Given + +```kotlin +@Directive1 +@Directive2 +fun doSomething(): String { +// does something +} +``` + +`Directive1` will be applied first followed by the `Directive2`. + +## Ignoring Directive Arguments + +Normally if you wanted to exclude a field or argument from the schema, you could use [@GraphQLIgnore](./excluding-fields.md). +However, due to reflection and kotlin limitations, the generated JVM code for interface arguments can only have annotations on getters. + +This is easily fixable though using the [`@get:` target prefix](https://kotlinlang.org/docs/reference/annotations.html#annotation-use-site-targets) +See [graphql-kotlin#763](https://github.com/ExpediaGroup/graphql-kotlin/pull/763) for more details. + +```kotlin +@GraphQLDirective +annotation class DirectiveWithIgnoredArgs( + val string: String, + + @get:GraphQLIgnore + val ignoreMe: String +) +``` + +This will generate the following schema + +```graphql +directive @directiveWithIgnoredArgs( + string: String! +) on ... +``` + +## Limitations + +GraphQL specification allows usage of any valid input objects as directive arguments. Since we rely on Kotlin annotation +functionality to define our custom directives, we are limited in what can be used as annotation parameter - only primitives (or scalars), +Strings, Enums, other annotations or an array of any of the above are supported. + +Support for input objects can be added by providing that object representation as an annotation class and then adding support +for it through custom schema generator hooks. diff --git a/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/documenting-schema.md b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/documenting-schema.md new file mode 100644 index 0000000000..9516b2535d --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/documenting-schema.md @@ -0,0 +1,41 @@ +--- +id: documenting-schema +title: Documenting Schema +--- +Since Javadocs are not available at runtime for introspection, `graphql-kotlin-schema-generator` includes an annotation +class `@GraphQLDescription` that can be used to add schema descriptions to _any_ GraphQL schema element. The string value can be in the Markdown format. + +```kotlin +@GraphQLDescription("A useful widget") +data class Widget( + @GraphQLDescription("The widget's value that can be `null`") + val value: Int? +) + +class WidgetQuery { + @GraphQLDescription("Creates new widget for given ID") + fun widgetById(@GraphQLDescription("The special ingredient") id: Int): Widget? = Widget(id) +} +``` + +The above query would produce the following GraphQL schema: + +```graphql +schema { + query: Query +} + +type Query { + """Creates new widget for given ID""" + widgetById( + """The special ingredient""" + id: Int! + ): Widget +} + +"""A useful widget""" +type Widget { + """The widget's value that can be `null`""" + value: Int +} +``` diff --git a/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/excluding-fields.md b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/excluding-fields.md new file mode 100644 index 0000000000..1e4b82e90d --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/excluding-fields.md @@ -0,0 +1,29 @@ +--- +id: excluding-fields +title: Excluding Fields +--- +There are two ways to ensure the GraphQL schema generation omits fields when using Kotlin reflection: + +- The first is by marking the field as non-`public` scope (`private`, `protected`, `internal`) +- The second method is by annotating the field with `@GraphQLIgnore`. + +```kotlin +class SimpleQuery { + @GraphQLIgnore + fun notPartOfSchema() = "ignore me!" + + private fun privateFunctionsAreNotVisible() = "ignored private function" + + fun doSomething(value: Int): Boolean = true +} +``` + +The above query would produce the following GraphQL schema: + +```graphql +type Query { + doSomething(value: Int!): Boolean! +} +``` + +Note that the public method `notPartOfSchema` is not included in the schema. diff --git a/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/generator-config.md b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/generator-config.md new file mode 100644 index 0000000000..4739d86a21 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/generator-config.md @@ -0,0 +1,76 @@ +--- +id: generator-config +title: Generator Configuration & Hooks +--- + +`graphql-kotlin-schema-generator` provides a single function, `toSchema,` to generate a schema from Kotlin objects. This +function accepts four arguments: config, queries, mutations and subscriptions. + +## TopLevelObjects + +* The queries, mutations and subscriptions are a list of +[TopLevelObjects](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/TopLevelObject.kt) +and will be used to generate corresponding GraphQL root types. +* Annotated schema `TopLevelObject` will be used to generate any schema directives + +## SchemaGeneratorConfig + +The [config](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/SchemaGeneratorConfig.kt) +contains all the extra information you need to pass, including custom hooks, supported packages and name overrides. +`SchemaGeneratorConfig` has some default settings but you can override them and add custom behaviors for generating your +schema. + +- `supportedPackages` **[Required]** - List of Kotlin packages that can contain schema objects. Limits the scope of + packages that can be scanned using reflections. +- `topLevelNames` _[Optional]_ - Set the name of the top level GraphQL fields, defaults to `Query`, `Mutation` and + `Subscription` +- `hooks` _[Optional]_ - Set custom behaviors for generating the schema, see below for details. +- `dataFetcherFactory` _[Optional]_ - Sets custom behavior for generating data fetchers +- `introspectionEnabled` _[Optional]_ - Boolean flag indicating whether introspection queries are enabled, introspection queries are enabled by default +- `additionalTypes` _[Optional]_ - Set of additional GraphQL types to include when generating the schema. +- `schemaObject` _[Optional]_ - Object that contains schema directive information + +## SchemaGeneratorHooks + +Hooks are lifecycle events that are called and triggered while the schema is building that allow users to customize the +schema. + +For exact names and details of every hook, see the comments and descriptions in our latest +[javadocs](https://www.javadoc.io/doc/com.expediagroup/graphql-kotlin-schema-generator) or directly in the source file: +[SchemaGeneratorHooks.kt](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/hooks/SchemaGeneratorHooks.kt) + +As an example here is how you would write a custom hook and provide it through the configuration + +```kotlin +class MyCustomHooks : SchemaGeneratorHooks { + // Only generate functions that start with "dog" + // This would probably be better just to use @GraphQLIgnore, but this is just an example + override fun isValidFunction(function: KFunction<*>) = function.name.startsWith("dog") +} + +class Query { + fun dogSound() = "bark" + + fun catSound() = "meow" +} + +val config = SchemaGeneratorConfig(supportedPackages = listOf("org.example"), hooks = MyCustomHooks()) + +val queries = listOf(TopLevelObject(Query())) + +toSchema(queries = queries, config = config) +``` + +will generate + +```graphql +schema { + query: Query +} + +type Query { + dogSound: String! +} +``` + +Notice there is no `catSound` function. diff --git a/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/renaming-fields.md b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/renaming-fields.md new file mode 100644 index 0000000000..2a1e1c5309 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/renaming-fields.md @@ -0,0 +1,73 @@ +--- +id: renaming-fields +title: Renaming Fields +--- +By default, the schema generator will use the simple name of the underlying class for the type names and function/property names for fields. +You can change this default behavior by annotating the target class/field with `@GraphQLName`. The following Kotlin `Widget` class +will be renamed to `MyCustomName` GraphQL type and its fields will also be renamed. + +```kotlin +@GraphQLName("MyCustomName") +data class Widget( + @GraphQLName("myCustomField") + val value: Int? +) +``` + +```graphql +type MyCustomName { + myCustomField: Int +} +``` + +## Known Issues + +Due to how we deserialize input classes, if you rename a field of an input class or an enum value you must also annotate +it with the Jackson annotation `@JsonProperty`. See [issue 493](https://github.com/ExpediaGroup/graphql-kotlin/issues/493) +for more info. + +```kotlin +data class MyInputClass( + @JsonProperty("renamedField") + @GraphQLName("renamedField") + val field1: String +) + +// GraphQL enums should use UPPER_CASE naming if possible, but any case is supported +enum class Selection { + + @JsonProperty("first") + @GraphQLName("first") + ONE, + + @JsonProperty("second") + @GraphQLName("second") + TWO +} + +class QueryClass { + fun parseData(arg: MyInputClass) = "You sent ${arg.field1}" + + fun chooseValue(selection: Selection): String = when (selection) { + Selection.ONE -> "You chose the first value" + Selection.TWO -> "You chose the second value" + } +} +``` + +```graphql +input MyInputClassInput { + # This only works if both @JsonProperty and @GraphQLName are present + renamedField: String! +} + +enum Selection { + first, + second +} + +type Query { + parseData(arg: MyInputClass!): String! + chooseValue(selection: Selection!): String! +} +``` diff --git a/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/restricting-input-output.md b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/restricting-input-output.md new file mode 100644 index 0000000000..f2eea65f64 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/customizing-schemas/restricting-input-output.md @@ -0,0 +1,29 @@ +--- +id: restricting-input-output +title: Restricting Input and Output Types +--- + +Since we are using Kotlin classes to represent both GraphQL input and output objects we can use the same class for both and the generator will handle type conflicts. + +If you want to enforce that a type should never be used as an input or output you can use the `@GraphQLValidObjectLocations` annotation. +If the class was used in the schema in an invalid location an exception will be thrown. + +```kotlin +class SimpleClass(val value: String) + +@GraphQLValidObjectLocations([Locations.INPUT_OBJECT]) +class InputOnly(val value: String) + +@GraphQLValidObjectLocations([Locations.OBJECT]) +class OutputOnly(val value: String) + +// Valid Usage +fun output1() = SimpleClass("foo") +fun output2() = OutputOnly("foo") +fun input1(input: SimpleClass) = "value was ${input.value}" +fun input2(input: InputOnly) = "value was ${input.value}" + +// Throws Exception +fun output3() = InputOnly("foo") +fun input3(input: OutputOnly) = "value was ${input.value}" +``` diff --git a/website/versioned_docs/version-8.x.x/schema-generator/execution/async-models.md b/website/versioned_docs/version-8.x.x/schema-generator/execution/async-models.md new file mode 100644 index 0000000000..386df7b8be --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/execution/async-models.md @@ -0,0 +1,151 @@ +--- +id: async-models +title: Async Models +--- +By default, `graphql-kotlin-schema-generator` will resolve all functions synchronously, i.e. it will block the +underlying thread while executing the target function. While you could configure your GraphQL server with execution +strategies that execute each query in parallel on some thread pools, instead we highly recommend to utilize asynchronous +programming models. + +## Coroutines + +`graphql-kotlin-schema-generator` has built-in support for Kotlin coroutines. Provided default +[FunctionDataFetcher](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/execution/FunctionDataFetcher.kt) +will automatically asynchronously execute suspendable functions and convert the result to `CompletableFuture` expected +by `graphql-java`. + +Example + +```kotlin +data class User(val id: String, val name: String) + +class Query { + suspend fun getUser(id: String): User { + // Your coroutine logic to get user data + } +} +``` + +will produce the following schema + +```graphql +type Query { + getUser(id: String!): User +} + +type User { + id: String! + name: String! +} +``` + +### Structured Concurrency + +`graphql-java` relies on `CompletableFuture` for asynchronous execution of the incoming requests. `CompletableFuture` is +unaware of any contextual information which means we have to rely on additional mechanism to propagate the coroutine context. +`graphql-java` v17 introduced `GraphQLContext` map as the default mechanism to propagate the contextual information about +the request. In order to preserve coroutine context, we need to populate `GraphQLContext` map with a `CoroutineScope` that +should be used to execute any suspendable functions. + +```kotlin +val graphQLExecutionScope = CoroutineScope(coroutineContext + SupervisorJob()) +val contextMap = mapOf( + CoroutineScope::class to graphQLExecutionScope +) + +val executionInput = ExecutionInput.newExecutionInput() + .graphQLContext(contextMap) + .query(queryString) + .build() +graphql.executeAsync(executionInput) +``` + +:::info +`graphql-kotlin-server` automatically populates `GraphQLContext` map with appropriate coroutine scope. Users can customize +the coroutine context by providing `CoroutineContext::class` entry in custom context using `GraphQLContextFactory`. +::: + +## CompletableFuture + +`graphql-java` relies on Java `CompletableFuture` for asynchronously processing the requests. In order to simplify the +interop with `graphql-java`, `graphql-kotlin-schema-generator` has a built-in hook which will automatically unwrap a +`CompletableFuture` and use the inner class as the return type in the schema. + +```kotlin +data class User(val id: String, val name: String) + +class Query { + fun getUser(id: String): CompletableFuture { + // Your logic to get data asynchronously + } +} +``` + +will result in the exactly the same schema as in the coroutine example above. + +## RxJava/Reactor + +If you want to use a different monad type, like `Single` from [RxJava](https://github.com/ReactiveX/RxJava) or `Mono` from +[Project Reactor](https://projectreactor.io/), you have to: + +1. Create custom `SchemaGeneratorHook` that implements `willResolveMonad` to provide the necessary logic + to correctly unwrap the monad and return the inner class to generate valid schema + +```kotlin +class MonadHooks : SchemaGeneratorHooks { + override fun willResolveMonad(type: KType): KType = when (type.classifier) { + Mono::class -> type.arguments.firstOrNull()?.type + else -> type + } ?: type +} +``` + +2. Provide custom data fetcher that will properly process those monad types. + +```kotlin +class CustomFunctionDataFetcher(target: Any?, fn: KFunction<*>, objectMapper: ObjectMapper) : FunctionDataFetcher(target, fn, objectMapper) { + override fun get(environment: DataFetchingEnvironment): Any? = when (val result = super.get(environment)) { + is Mono<*> -> result.toFuture() + else -> result + } +} + +class CustomDataFetcherFactoryProvider( + private val objectMapper: ObjectMapper +) : SimpleKotlinDataFetcherFactoryProvider(objectMapper) { + + override fun functionDataFetcherFactory(target: Any?, kFunction: KFunction<*>): DataFetcherFactory = DataFetcherFactory { + CustomFunctionDataFetcher( + target = target, + fn = kFunction, + objectMapper = objectMapper) + } +} +``` + +With the above you can then create your schema as follows: + +```kotlin +class ReactorQuery { + fun asynchronouslyDo(): Mono = Mono.just(1) +} + +val configWithReactorMonoMonad = SchemaGeneratorConfig( + supportedPackages = listOf("myPackage"), + hooks = MonadHooks(), + dataFetcherFactoryProvider = CustomDataFetcherFactoryProvider()) + +toSchema(queries = listOf(TopLevelObject(ReactorQuery())), config = configWithReactorMonoMonad) +``` + +This will produce + +```graphql +type Query { + asynchronouslyDo: Int +} +``` + +You can find additional example on how to configure the hooks in our [unit +tests](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/SchemaGeneratorAsyncTests.kt) +and [example app](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/examples/spring/src/main/kotlin/com/expediagroup/graphql/examples/query/AsyncQuery.kt). diff --git a/website/versioned_docs/version-8.x.x/schema-generator/execution/contextual-data.md b/website/versioned_docs/version-8.x.x/schema-generator/execution/contextual-data.md new file mode 100644 index 0000000000..573f230f93 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/execution/contextual-data.md @@ -0,0 +1,30 @@ +--- +id: contextual-data +title: Contextual Data +--- + +All GraphQL servers have a concept of a "context". A GraphQL context contains metadata that is useful to the GraphQL +server, but shouldn't necessarily be part of the GraphQL schema. A prime example of something that is appropriate +for the GraphQL context would be trace headers for an OpenTracing system such as +[Haystack](https://expediadotcom.github.io/haystack). The GraphQL query does not need the information to perform +its function, but the server needs the information to ensure observability. + +The contents of the GraphQL context vary across applications and it is up to the GraphQL server developers to decide +what it should contain. `graphql-kotlin-server` provides a simple mechanism to build a context per operation with the +[GraphQLContextFactory](../../server/graphql-context-factory.md). +If a custom factory is defined, it will then be used to populate GraphQL context based on the incoming request and make +it available during execution. + +## GraphQL Context Map +In graphql-java v17 a new context map was added to the `DataFetchingEnvironment`. This is now the way of saving info for execution, and +you can access this map through the [DataFetchingEnvironment](./data-fetching-environment.md). + +```kotlin +class ContextualQuery : Query { + fun contextualQuery( + dataFetchingEnvironment: DataFetchingEnvironment, + value: Int + ): String = + "The custom value was ${dataFetchingEnvironment.graphQLContext.get("foo")} and the value was $value" +} +``` diff --git a/website/versioned_docs/version-8.x.x/schema-generator/execution/data-fetching-environment.md b/website/versioned_docs/version-8.x.x/schema-generator/execution/data-fetching-environment.md new file mode 100644 index 0000000000..eb8eddba7c --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/execution/data-fetching-environment.md @@ -0,0 +1,73 @@ +--- +id: data-fetching-environment +title: Data Fetching Environment +--- +Each resolver has access to a `DataFetchingEnvironment` that provides additional information about the currently executed query including information about what data is requested +as well as details about current execution state. For more details on the `DataFetchingEnvironment` please refer to [graphql-java documentation](https://www.graphql-java.com/documentation/data-fetching/) + +You can access this info by including the `DataFetchingEnvironment` as one of the arguments to a Kotlin function. This argument will be automatically populated and injected +during the query execution but will not be included in the schema definition. + +```kotlin +class Query { + fun printEnvironmentInfo(parentField: String): MyObject = MyObject() +} + +class MyObject { + fun printParentField(childField: String, environment: DataFetchingEnvironment): String { + val parentField = environment.executionStepInfo.parent.getArgument("parentField") + return "The parentField was $parentField and the childField was $childField" + } +} +``` + +This will produce the following schema + +```graphql +type Query { + printEnvironmentInfo(parentField: String!): MyObject! +} + +type MyObject { + printParentField(childField: String!): String! +} +``` + +Then the following query would return `"The parentField was foo and the childField was bar"` + +```graphql +{ + printEnvironmentInfo(parentField: "foo") { + printParentField(childField: "bar") + } +} +``` + +You can also use this to retrieve arguments and query information from higher up the query chain. You can see a working +example in the `graphql-kotlin-spring-example` module [[link](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/examples/server/spring-server/src/main/kotlin/com/expediagroup/graphql/examples/server/spring/query/EnvironmentQuery.kt)]. + +```kotlin +class ProductQueryService : Query { + + fun products(environment: DataFetchingEnvironment): Product { + environment.selectionSet.fields.forEach { println("field: ${it.name}") } + + return Product(1, "Product title", 100) + } +} + +``` + +```graphql +{ + product { + id + title + price + } +} +``` + +You can also use `selectionSet` to access the selected fields of the current field. It can be useful to know which selections have been requested so the data fetcher can optimize the data access queries. For example, in an SQL-backed system, the data fetcher can access the database and use the field selection criteria to specifically retrieve only the columns that have been requested by the client. +what selection has been asked for so the data fetcher can optimise the data access queries. +For example an SQL backed system may be able to use the field selection to only retrieve the columns that have been asked for. diff --git a/website/versioned_docs/version-8.x.x/schema-generator/execution/exceptions.md b/website/versioned_docs/version-8.x.x/schema-generator/execution/exceptions.md new file mode 100644 index 0000000000..faa64fb455 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/execution/exceptions.md @@ -0,0 +1,37 @@ +--- +id: exceptions +title: Exceptions and Partial Data +--- +## Returning GraphQL Errors + +Exceptions thrown during execution of an operation will result in an empty data response and a GraphQLError that is added to a list of errors of the result. +See [graphql-java documentation](https://www.graphql-java.com/documentation/execution#exceptions-while-fetching-data) for more details on how to customize your exception handling. + +```kotlin +fun getRandomNumberOrError(): Int { + val num = Random().nextInt(100) + return if (num <= 50) num else throw Exception("number is greater than 50") +} +``` + +## Returning Data and Errors + +GraphQL allows you to return both data and errors in a single response, as long as the data returned still matches the schema. Depending on the criticality of the encountered error, instead of throwing an exception, you may want to return +default data or use a nullable field, but still include more information in the `errors` block. In Kotlin, functions return only a single value, which means that in order to return both data +and errors you have to explicitly return them wrapped in a `DataFetcherResult` object. + +```kotlin +class DataAndErrorsQuery { + fun returnDataAndErrors(): DataFetcherResult { + val data: String? = getData() + val error = if (data == null) MyError() else null + + return DataFetcherResult.newResult() + .data(data) + .error(error) + .build() + } +} +``` + +An example of a query returning partial data is available in our [spring example app](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/examples/server/spring-server/src/main/kotlin/com/expediagroup/graphql/examples/server/spring/query/DataAndErrorsQuery.kt). diff --git a/website/versioned_docs/version-8.x.x/schema-generator/execution/fetching-data.md b/website/versioned_docs/version-8.x.x/schema-generator/execution/fetching-data.md new file mode 100644 index 0000000000..a3ac043328 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/execution/fetching-data.md @@ -0,0 +1,57 @@ +--- +id: fetching-data +title: Fetching Data +--- +Each field exposed in the GraphQL schema has a corresponding resolver (aka data fetcher) associated with it. `graphql-kotlin-schema-generator` generates the GraphQL schema +directly from the source code, automatically mapping all the fields either to use +[FunctionDataFetcher](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/execution/FunctionDataFetcher.kt) +to resolve underlying functions or the [PropertyDataFetcher](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/execution/PropertyDataFetcher.kt) +to read a value from an underlying Kotlin property. + +While all the fields in a GraphQL schema are resolved independently to produce a final result, whether a field is backed by a function or a property can have significant +performance repercussions. For example, given the following schema: + +```graphql +type Query { + product(id: ID!): Product +} + +type Product { + id: ID! + name: String! + reviews: [Review!]! +} + +type Review { + id: ID! + text: String! +} +``` + +We can define `Product` as + +```kotlin +data class Product(val id: ID, val name: String, reviews: List) +``` + +or + +```kotlin +class Product(val id: ID, val name: String) { + suspend fun reviews(): List { + // logic to fetch reviews here + } +} +``` + +If we expose the `reviews` field as a property it will always be populated regardless whether or not your client actually asks for it. On the other hand if `reviews` is backed +by a function, it will only be called if your client asks for this data. In order to minimize the over-fetching of data from your underlying data sources we recommend to +expose all your GraphQL fields that require some additional computations through functions. + +### Customizing Default Behavior + +You can provide your own custom data fetchers to resolve functions and properties by providing an instance of +[KotlinDataFetcherFactoryProvider](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/execution/KotlinDataFetcherFactoryProvider.kt#L31) +to your [SchemaGeneratorConfig](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/SchemaGeneratorConfig.kt). + +See our [spring example app](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/server/spring-server) for an example of `CustomDataFetcherFactoryProvider`. diff --git a/website/versioned_docs/version-8.x.x/schema-generator/execution/introspection.md b/website/versioned_docs/version-8.x.x/schema-generator/execution/introspection.md new file mode 100644 index 0000000000..814f9c5ea6 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/execution/introspection.md @@ -0,0 +1,46 @@ +--- +id: introspection +title: Introspection +--- +By default, GraphQL servers expose a built-in system, called **introspection**, that exposes details about the underlying schema. +Clients can use introspection to obtain information about all the supported queries as well as all the types exposed in the schema. + +## Introspection types + +- _\_\_schema_ - root level query field that provides information about all entry points (e.g. `queryType`), all types exposed + by the schema (including built-in scalars and introspection types) as well as all directives supported by the system +- _\_\_type(name: String!)_ - root level query field that provides information about the requested type (if it exists) +- **_typename_** - field that can be added to _ANY_ selection and will return the name of the enclosing type, + is often used in polymorphic queries in order to easily determine underlying implementation type +- **_Directive, DirectiveLocation, EnumValue, Field, InputValue, Schema, Type, TypeKind_** - built-in + introspection types that are used to describe the schema. + +For example, the query below will return a root Query object name as well as names of all types and all directives. + +```graphql +query { + __schema { + queryType { + name + } + types { + name + } + directives { + name + } + } +} +``` + +Additional information on introspection can be found on [GraphQL.org](https://graphql.org/learn/introspection/). + +## Disabling Introspection + +Introspection system can be disabled by specifying `introspectionEnabled=false` configuration option on an instance of +`SchemaGeneratorConfig` that will be used by the `SchemaGenerator` to generate the GraphQL schema. + +Many GraphQL tools (e.g. [GraphQL Playground](https://github.com/prisma-labs/graphql-playground) or [GraphiQL](https://github.com/graphql/graphiql)) +rely on introspection queries to function properly. Disabling introspection will prevent clients from accessing `__schema` +and `__type` fields. This may break some of the functionality that your clients might rely on and should be used with +extreme caution. diff --git a/website/versioned_docs/version-8.x.x/schema-generator/execution/optional-undefined-arguments.md b/website/versioned_docs/version-8.x.x/schema-generator/execution/optional-undefined-arguments.md new file mode 100644 index 0000000000..8504e2f227 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/execution/optional-undefined-arguments.md @@ -0,0 +1,75 @@ +--- +id: optional-undefined-arguments +title: Optional Undefined Arguments +--- +In the GraphQL world, input types can be optional which means that the client can either: + +- Not specify a value at all +- Send null explicitly +- Send a non-null value + +This is in contrast with the JVM world where objects can either have some specific value or don't have any value (i.e. +are `null`). As a result, when using default serialization logic it is not possible to distinguish between missing/unspecified +value and explicit `null` value. + +## Using OptionalInput wrapper + +`OptionalInput` is a convenient sealed class wrapper that provides distinction between undefined, null, and non-null +values. If the argument is not specified in the request it will be represented as a `OptionalInput.Undefined` object, otherwise the +value will be wrapped in `OptionalInput.Defined` class. As a best practice, we highly recommend to set appropriate +[default values to all optional arguments](https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/writing-schemas/arguments#default-values). + +```kotlin +fun optionalInput(input: OptionalInput = OptionalInput.Undefined): String = when (input) { + is OptionalInput.Undefined -> "input was not specified" + is OptionalInput.Defined -> "input value: ${input.value}" +} +``` + +```graphql +query OptionalInputQuery { + undefined: optionalInput # input was not specified + null: optionalInput(value: null) # input value: null + foo: optionalInput(value: "foo") # input value: foo +} +``` + +:::info +Regardless whether the generic type of `OptionalInput` is specified as nullable or not it will always result in a nullable +value in `Defined` class, i.e. `OptionalInput` will appear as nullable `String` in the GraphQL schema and in the wrapped value. +::: + +## Using DataFetchingEnvironment + +Optional input types can be represented as nullable parameters in Kotlin + +```kotlin +fun optionalInput(value: String? = null): String? = value +``` + +```graphql +query OptionalInputQuery { + undefined: optionalInput # null + null: optionalInput(value: null) # null + foo: optionalInput(value: "foo") # foo +} +``` + +By default, if an optional input value is not specified, then the execution engine will fallback to the argument default +value (in our example above `null`). This means that you can not tell, by just the value alone, whether the request did +not contain any argument or the client explicitly passed in the default value. + +Instead, you can inspect all passed in arguments using the [DataFetchingEnvironment](./data-fetching-environment.md). + +```kotlin +fun optionalInput(value: String? = null, dataFetchingEnvironment: DataFetchingEnvironment): String = + if (dataFetchingEnvironment.containsArgument("value")) { + "The value was $value" + } else { + "The value was undefined" + } +``` + +## Kotlin Default Values + +If you don't need logic for when the client specified a value, you can still use [Kotlin default values](../writing-schemas/arguments.md) diff --git a/website/versioned_docs/version-8.x.x/schema-generator/execution/subscriptions.md b/website/versioned_docs/version-8.x.x/schema-generator/execution/subscriptions.md new file mode 100644 index 0000000000..be5598633d --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/execution/subscriptions.md @@ -0,0 +1,63 @@ +--- +id: subscriptions +title: Subscriptions +--- +Subscriptions are supported with `graphql-java`. See their documentation first: + +https://www.graphql-java.com/documentation/subscriptions + +To make a function a subscription function you just have to have the return type wrapped in an implementation of a +reactive-streams `Publisher`. As an example, here is a function that uses Spring WebFlux to return a random number every +second. Since `Flux` is an implementation of `Publisher` this is a valid method. + +```kotlin +fun counter(): Flux = Flux.interval(Duration.ofSeconds(1)).map { Random.nextInt() } +``` + +Then in the `toSchema` method you just have to provide a `List` the same way as queries and mutations +are provided with the `subscriptions` argument. + +```kotlin +toSchema( + config = schemaConfig, + queries = queries.toTopLevelObjects(), + mutations = mutations.toTopLevelObjects(), + subscriptions = subscriptions.toTopLevelObjects() +) +``` + +## Flow Support + +`graphql-kotlin` provides support for Kotlin `Flow` through `FlowSubscriptionSchemaGeneratorHooks` and `FlowSubscriptionExecutionStrategy`. +Both hooks and execution strategy have to be configured in order to support `Flow` in your GraphQL server. + +`FlowSubscriptionSchemaGeneratorHooks` are custom hooks that provide support for using `Flow` return type within the +GraphQL server. + +`FlowSubscriptionExecutionStrategy` is a reimplementation of the `graphql-java` default `SubscriptionExecutionStrategy` +that adds support for handling Kotlin `Flow` types. Thanks to the Kotlin coroutines interoperability, this strategy works +with any `Publisher` and will automatically convert any `Flow`s to a `Publisher`. + +## Subscription Hooks + +### `willResolveMonad` + +This hooks is called before resolving Kotlin return type to a GraphQL type and can be used to provide support for additional +monads (e.g. Kotlin `Flow`). + +### `didGenerateSubscriptionType` +This hook is called after a new subscription type is generated but before it is added to the schema. The other generator +hooks are still called so you can add logic for the types and validation of subscriptions the same as queries and mutations. + +### `isValidSubscriptionReturnType` +This hook is called when generating the functions for each subscription. It allows for changing the rules of what classes +can be used as the return type. By default, graphql-java supports `org.reactivestreams.Publisher`. + +To effectively use this hook, you should also override the `willResolveMonad` hook to support the additional subscription +return type. Your GraphQL server may also require a custom subscription execution strategy in order to process it at runtime. + +## Server Implementation + +The server that runs your GraphQL schema will have to support some method for subscriptions, like WebSockets. +`graphql-kotlin-spring-server` provides a default WebSocket based implementation. See more details in the +[server documentation](../../server/server-subscriptions.md). diff --git a/website/versioned_docs/version-8.x.x/schema-generator/federation/apollo-federation.mdx b/website/versioned_docs/version-8.x.x/schema-generator/federation/apollo-federation.mdx new file mode 100644 index 0000000000..bab83f01be --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/federation/apollo-federation.mdx @@ -0,0 +1,122 @@ +--- +id: apollo-federation +title: Apollo Federation +--- + + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +In many cases, exposing single GraphQL API that exposes unified view of all the available data provides tremendous value +to their clients. As the underlying graph scales, managing single monolithic GraphQL server might become less and less +feasible making it much harder to manage and leading to unnecessary bottlenecks. Migrating towards federated model with +an API gateway and a number of smaller GraphQL services behind it alleviates some of those problems and allows teams to +scale their graphs more easily. + +[Apollo Federation](https://www.apollographql.com/docs/federation/) is an architecture for composing multiple GraphQL +services into a single graph. Federated schemas rely on a number of custom directives to instrument the behavior of the +underlying graph and convey the relationships between different schema types. Each individual GraphQL server generates a +valid GraphQL schema and can be run independently. This is in contrast with a traditional schema stitching approach where +relationships between individual services, i.e. linking configuration, is configured at the GraphQL gateway level. + +## Install + +Using a JVM dependency manager, link `graphql-kotlin-federation` to your project. + + + + + +```kotlin +implementation("com.expediagroup", "graphql-kotlin-federation", latestVersion) +``` + + + + +```xml + + com.expediagroup + graphql-kotlin-federation + ${latestVersion} + +``` + + + + +## Usage + +`graphql-kotlin-federation` is build on top of `graphql-kotlin-schema-generator` and adds a few extra methods and class to use to generate federation compliant schemas. + +### `toFederatedSchema` + +Just like the basic [toSchema](../schema-generator-getting-started.mdx), `toFederatedSchema` accepts five parameters: `config`, `queries`, `mutations`, `subscriptions` and `schemaObject`. +The difference is that the `config` class is of type [FederatedSchemaGeneratorConfig](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/FederatedSchemaGeneratorConfig.kt). +This class extends the [base configuration class](../customizing-schemas/generator-config.md) and adds some default logic. You can override the logic if needed, but do so with caution as you may no longer generate a spec compliant schema. + +You can see the definition for `toFederatedSchema` [in the +source](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/toFederatedSchema.kt). + +### Example + +```kotlin +@KeyDirective(fields = FieldSet("id")) +data class User( + val id: ID, + val name: String +) + +class Query { + fun getUsers(): List = getUsersFromDB() +} + +val config = FederatedSchemaGeneratorConfig( + supportedPackages = "com.example", + hooks = FederatedSchemaGeneratorHooks(emptyList()) +) + +toFederatedSchema( + config = config, + queries = listOf(TopLevelObject(Query())) +) +``` + +will generate + +```graphql +schema @link(import : ["@key", "FieldSet"], url : "https://specs.apollo.dev/federation/v2.6"){ + query: Query +} + +directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE +directive @link(as: String, import: [link__Import], url: String!) repeatable on SCHEMA + +type Query { + getUsers: [User!]! + + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! +} + +type User @key(fields : "id", resolvable : true) { + id: ID! + name: String! +} + +union _Entity = User + +type _Service { + sdl: String! +} + +scalar FieldSet +scalar _Any +scalar link__Import +``` diff --git a/website/versioned_docs/version-8.x.x/schema-generator/federation/federated-directives.md b/website/versioned_docs/version-8.x.x/schema-generator/federation/federated-directives.md new file mode 100644 index 0000000000..ecf9ecff49 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/federation/federated-directives.md @@ -0,0 +1,686 @@ +--- +id: federated-directives +title: Federated Directives +--- +`graphql-kotlin` supports a number of directives that can be used to annotate a schema and direct certain behaviors. + +For more details, see the [Apollo Federation Specification](https://www.apollographql.com/docs/federation/subgraph-spec/). + +## `@authenticated` directive + +:::info +Available since Federation v2.5 +::: + +```graphql +directive @authenticated on + ENUM + | FIELD_DEFINITION + | INTERFACE + | OBJECT + | SCALAR +``` + +Directive that is used to indicate that the target element is accessible only to the authenticated supergraph users. For more granular access control, see the +[`@requiresScopes`[#requirescope-directive] directive usage. Refer to the [Apollo Router documentation](https://www.apollographql.com/docs/router/configuration/authorization#authenticated) +for additional details. + +## `@composeDirective` directive + +:::info +Available since Federation v2.1 +::: + +```graphql +directive @composeDirective(name: String!) repeatable on SCHEMA +``` + +By default, Supergraph schema excludes all custom directives. The `@composeDirective` is used to specify custom directives that should be exposed in the Supergraph schema. + +In order to use composed directive, you subgraph needs to +1. contain your custom directive definition +2. import your custom directive from a corresponding link spec +3. apply `@composeDirective` with custom directive name on your schema + +Example: +Given `@custom` directive we can preserve it in the Supergraph schema + +```kotlin +// 1. directive definition +@GraphQLDirective(name = "custom", locations = [Introspection.DirectiveLocation.FIELD_DEFINITION]) +annotation class CustomDirective + +@LinkDirective(url = "https://myspecs.dev/myCustomDirective/v1.0", import = ["@custom"]) // 2. import custom directive from a spec +@ComposeDirective(name = "custom") // 3. apply @composeDirective to preserve it in the schema +class CustomSchema + +class SimpleQuery { + @CustomDirective + fun helloWorld(): String = "Hello World" +} +``` + +it will generate following schema + +```graphql +schema +@composeDirective(name: "@custom") +@link(import : ["@custom"], url: "https://myspecs.dev/myCustomDirective/v1.0") +@link(url : "https://specs.apollo.dev/federation/v2.5") +{ + query: Query +} + +directive @custom on FIELD_DEFINITION + +type Query { + helloWorld: String! @custom +} +``` + +See [@composeDirective definition](https://www.apollographql.com/docs/federation/federated-types/federated-directives/#composedirective) for more information. + +## `@contact` directive + +```graphql +directive @contact( + "Contact title of the subgraph owner" + name: String! + "URL where the subgraph's owner can be reached" + url: String + "Other relevant notes can be included here; supports markdown links" + description: String +) on SCHEMA +``` + +Contact schema directive can be used to provide team contact information to your subgraph schema. This information is automatically parsed and displayed by Apollo Studio. +See [Subgraph Contact Information](https://www.apollographql.com/docs/studio/federated-graphs/#subgraph-contact-info) for additional details. + +#### Example usage on the schema class: + +```kotlin +@ContactDirective( + name = "My Team Name", + url = "https://myteam.slack.com/archives/teams-chat-room-url", + description = "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall)." +) +class MySchema +``` + +will generate + +```graphql +schema @contact(description : "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall).", name : "My Team Name", url : "https://myteam.slack.com/archives/teams-chat-room-url"){ + query: Query +} +``` + +## `@extends` directive + +:::caution +**`@extends` directive is deprecated**. Federation v2 no longer requires `@extends` directive due to the smart entity type +merging. All usage of `@extends` directive should be removed from your Federation v2 schemas. +::: + +```graphql +directive @extends on OBJECT | INTERFACE +``` + +`@extends` directive is used to represent type extensions in the schema. Native type extensions are currently +unsupported by the `graphql-kotlin` libraries. Federated extended types should have corresponding `@key` directive +defined that specifies primary key required to fetch the underlying object. + +#### Example + +```kotlin +@KeyDirective(FieldSet("id")) +@ExtendsDirective +class Product(@ExternalDirective val id: String) { + fun newFunctionality(): String = "whatever" +} +``` + +will generate + +```graphql +type Product @key(fields : "id") @extends { + id: String! @external + newFunctionality: String! +} +``` + +## `@external` directive + +```graphql +directive @external on OBJECT | FIELD_DEFINITION +``` + +The `@external` directive is used to mark a field as owned by another service. This allows service A to use fields from +service B while also knowing at runtime the types of that field. All the external fields should either be referenced from +the `@key`, `@requires` or `@provides` directives field sets. + +Due to the smart merging of entity types, Federation v2 no longer requires `@external` directive on `@key` fields and can +be safely omitted from the schema. `@external` directive is only required on fields referenced by the `@requires` and +`@provides` directive. + +#### Example + +```kotlin +@KeyDirective(FieldSet("id")) +class Product(val id: String) { + @ExternalDirective + var externalField: String by Delegates.notNull() + + @RequiresDirective(FieldSet("externalField")) + fun newFunctionality(): String { ... } +} +``` + +will generate + +```graphql +type Product @key(fields : "id") { + externalField: String! @external + id: String! + newFunctionality: String! +} +``` + +## `@inaccessible` directive + +:::info +Available since Federation v2.0 +::: + +```graphql +directive @inaccessible on FIELD_DEFINITION + | OBJECT + | INTERFACE + | UNION + | ENUM + | ENUM_VALUE + | SCALAR + | INPUT_OBJECT + | INPUT_FIELD_DEFINITION + | ARGUMENT_DEFINITION +``` + +Inaccessible directive marks location within schema as inaccessible from the GraphQL Gateway. While `@inaccessible` fields are not exposed by the gateway to the clients, +they are still available for query plans and can be referenced from `@key` and `@requires` directives. This allows you to not expose sensitive fields to your clients but +still make them available for computations. Inaccessible can also be used to incrementally add schema elements (e.g. fields) to multiple subgraphs without breaking composition. + +See [@inaccessible specification](https://specs.apollo.dev/inaccessible/v0.2) for additional details. + +:::caution +Location within schema will be inaccessible from the GraphQL Gateway as long as **ANY** of the subgraphs marks that location as `@inacessible`. +::: + +#### Example + +```kotlin +class Product( + val id: String, + @InaccessibleDirective + val secret: String +) +``` + +will be generated by the subgraph as + +```graphql +type Product { + id: String! + secret: String! @inaccessible +} +``` + +but will be exposed on the GraphQL Gateway as + +```graphql +type Product { + id: String! +} +``` + +## `@interfaceObject` directive + +:::info +Available since Federation v2.3 +::: + +```graphql +directive @interfaceObject on OBJECT +``` + +This directive provides meta information to the router that this entity type defined within this subgraph is an interface in the supergraph. This allows you to extend functionality +of an interface across the supergraph without having to implement (or even be aware of) all its implementing types. + +Example: +Given an interface that is defined somewhere in our supergraph + +```graphql +interface Product @key(fields: "id") { + id: ID! + description: String +} + +type Book implements Product @key(fields: "id") { + id: ID! + description: String + pages: Int! +} + +type Movie implements Product @key(fields: "id") { + id: ID! + description: String + duration: Int! +} +``` + +We can extend `Product` entity in our subgraph and a new field directly to it. This will result in making this new field available to ALL implementing types. + +```kotlin +@InterfaceObjectDirective +@KeyDirective(fields = FieldSet("id")) +data class Product(val id: ID) { + fun reviews(): List = TODO() +} +``` + +Which generates the following subgraph schema + +```graphql +type Product @key(fields: "id") @interfaceObject { + id: ID! + reviews: [Review!]! +} +``` + +## `@key` directive + +```graphql +directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE +``` + +The `@key` directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an +object or interface. The specified field set can represent single field (e.g. `"id"`), multiple fields (e.g. `"id name"`) or +nested selection sets (e.g. `"id user { name }"`). Multiple keys can be specified on a target type. + +Key directives should be specified on all entities (objects that can resolve its fields across multiple subgraphs). Key +fields specified in the directive field set should correspond to a valid field on the underlying GraphQL interface/object. + +#### Basic Example + +```kotlin +@KeyDirective(FieldSet("id")) +@KeyDirective(FieldSet("upc")) +class Product(val id: String, val upc: String, val name: String) +``` + +will generate + +```graphql +type Product @key(fields: "id") @key(fields: "upc") { + id: String! + name: String! + upc: String! +} +``` + +#### Referencing External Entities + +Entity types can be referenced from other subgraphs without contributing any additional fields, i.e. we can update type within our schema with a reference to a federated type. In order to generate +a valid schema, we need to define **stub** for federated entity that contains only key fields and also mark it as not resolvable within our subgraph. For example, if we have `Review` entity defined +in our supergraph, we can reference it in our product schema using following code + +```kotlin +@KeyDirective(fields = FieldSet("id")) +class Product(val id: String, val name: String, val reviews: List) + +// review stub referencing just the key fields +@KeyDirective(fields = FieldSet("id"), resolvable = false) +class Review(val id: String) +``` + +which will generate + +```graphql +type Product @key(fields: "id") { + id: String! + name: String! + reviews: [Review!]! +} + +type Review @key(fields: "id", resolvable: false) { + id: String! +} +``` + +This allows end users to query GraphQL Gateway for any product review fields and they will be resolved by calling the appropriate subgraph. + +## `@link` directive + +:::info +Available since Federation v2.0 +::: + +:::caution +While both custom namespace (`as`) and `import` arguments are optional in the schema definition, due to [#1830](https://github.com/ExpediaGroup/graphql-kotlin/issues/1830) +we currently always require those values to be explicitly provided. +::: + +```graphql +directive @link(url: String!, as: String, import: [Import]) repeatable on SCHEMA +scalar Import +``` + +The `@link` directive links definitions within the document to external schemas. See [@link specification](https://specs.apollo.dev/link/v1.0) for details. + +External schemas are identified by their `url`, which ends with a name and version with the following format: `{NAME}/v{MAJOR}.{MINOR}`, +e.g. `url = "https://specs.apollo.dev/federation/v2.5"`. + +External types are associated with the target specification by annotating it with `@LinkedSpec` meta annotation. External +types defined in the specification will be automatically namespaced (prefixed with `{NAME}__`) unless they are explicitly +imported. Namespace should default to the specification name from the imported spec url. Custom namespace can be provided +by specifying `as` argument value. + +External types can be imported using the same name or can be aliased to some custom name. + +```kotlin +@LinkDirective(`as` = "custom", imports = [LinkImport(name = "@foo"), LinkImport(name = "@bar", `as` = "@myBar")], url = "https://myspecs.dev/custom/v1.0") +class MySchema +``` + +This will generate following schema: + +```graphql +schema @link(as: "custom", import : ["@foo", { name: "@bar", as: "@myBar" }], url : "https://myspecs.dev/custom/v1.0") { + query: Query +} +``` + +### `@LinkedSpec` annotation + +When importing custom specifications, we need to be able to identify whether given element is part of the referenced specification. +`@LinkedSpec` is a meta annotation that is used to indicate that given directive/type is associated with imported `@link` specification. + +In order to ensure consistent behavior, `@LinkedSpec` value have to match default specification name as it appears in the +imported url and not the aliased value. + +Example usage: + +``` +@LinkedSpec("custom") +@GraphQLDirective( + name = "foo", + locations = [DirectiveLocation.FIELD_DEFINITION] +) +annotation class Foo +``` + +In the example above, we specify that `@foo` directive is part of the `custom` specification. We can then reference `@foo` +in the `@link` specification imports + +```graphql +schema @link(as: "custom", import : ["@foo"], url : "https://myspecs.dev/custom/v1.0") { + query: Query +} + +directive @foo on FIELD_DEFINITION +``` + +If we don't import the directive, then it will automatically namespaced to the spec + +```graphql +schema @link(as: "custom", url : "https://myspecs.dev/custom/v1.0") { + query: Query +} + +directive @custom__foo on FIELD_DEFINITION +``` + +## `@override` directive + +:::info +Available since Federation v2.0 +::: + +```graphql +directive @override(from: String!) on FIELD_DEFINITION +``` + +The `@override` directive is used to indicate that the current subgraph is taking responsibility for resolving the marked field away from the subgraph specified in the `from` argument, +i.e. it is used for migrating a field from one subgraph to another. Name of the subgraph to be overriden has to match the name of the subgraph that was used to publish their schema. See +[Publishing schema to Apollo Studio](https://www.apollographql.com/docs/rover/subgraphs/#publishing-a-subgraph-schema-to-apollo-studio) for additional details. + +:::caution +Only one subgraph can `@override` any given field. If multiple subgraphs attempt to `@override` the same field, a composition error occurs. +::: + +#### Example + +Given `SubgraphA` + +```graphql +type Product @key(fields: "id") { + id: String! + description: String! +} +``` + +We can override gateway `description` field resolution to resolve it in the `SubgraphB` + +```graphql +type Product @key(fields: "id") { + id: String! + name: String! + description: String! @override(from: "SubgraphA") +} +``` + +## `@policy` directive + +:::info +Available since Federation v2.6 +::: + +```graphql +directive @policy(policies: [[Policy!]!]!) on + ENUM + | FIELD_DEFINITION + | INTERFACE + | OBJECT + | SCALAR +``` + +Directive that is used to indicate that access to the target element is restricted based on authorization policies that are evaluated in a Rhai script or coprocessor. Refer to the +[Apollo Router documentation](https://www.apollographql.com/docs/router/configuration/authorization#policy) for additional details. + + +## `@provides` directive + +```graphql +directive @provides(fields: FieldSet!) on FIELD_DEFINITION +``` + +The `@provides` directive is a router optimization hint specifying field set that can be resolved locally at the given subgraph through this particular query path. This allows you to +expose only a subset of fields from the underlying entity type to be selectable from the federated schema without the need to call other subgraphs. Provided fields specified in the +directive field set should correspond to a valid field on the underlying GraphQL interface/object type. `@provides` directive can only be used on fields returning entities. + +:::info +Federation v2 does not require `@provides` directive if field can **always** be resolved locally. `@provides` should be omitted in this situation. +::: + +#### Example 1: + +We might want to expose only name of the user that submitted a review. + +```kotlin +@KeyDirective(FieldSet("id")) +class Review(val id: String) { + @ProvidesDirective(FieldSet("name")) + fun user(): User = getUserByReviewId(id) +} + +@KeyDirective(FieldSet("userId")) +class User( + val userId: String, + @ExternalDirective val name: String +) +``` + +will generate + +```graphql +type Review @key(fields : "id") { + id: String! + user: User! @provides(fields : "name") +} + +type User @key(fields : "userId") { + userId: String! + name: String! @external +} +``` + +#### Example 2: + +Within our service, one of the queries could resolve all fields locally while other requires resolution from other subgraph + +```graphql +type Query { + remoteResolution: Foo + localOnly: Foo @provides("baz") +} + +type Foo @key("id") { + id: ID! + bar: Bar + baz: Baz @external +} +``` + +In the example above, if user selects `baz` field, it will be resolved locally from `localOnly` query but will require another subgraph invocation from `remoteResolution` query. + +## `@requires` directive + +```graphql +directive @requires(fields: FieldSet!) on FIELD_DEFINITON +``` + +The `@requires` directive is used to specify external (provided by other subgraphs) entity fields that are needed to resolve target field. It is used to develop a query plan where +the required fields may not be needed by the client, but the service may need additional information from other subgraphs. Required fields specified in the directive field set should +correspond to a valid field on the underlying GraphQL interface/object and should be instrumented with `@external` directive. + +All the leaf fields from the specified in the `@requires` selection set have to be marked as `@external` OR any of the parent fields on the path to the leaf is marked as `@external`. + +Fields specified in the `@requires` directive will only be specified in the queries that reference those fields. This is problematic for Kotlin as the non-nullable primitive properties +have to be initialized when they are declared. Simplest workaround for this problem is to initialize the underlying property to some default value (e.g. null) that will be used if +it is not specified. This approach might become problematic though as it might be impossible to determine whether fields was initialized with the default value or the invalid/default +value was provided by the federated query. Another potential workaround is to rely on delegation to initialize the property after the object gets created. This will ensure that exception +will be thrown if queries attempt to resolve fields that reference the uninitialized property. + +#### Example + +```kotlin +@KeyDirective(FieldSet("id")) +class Product(val id: String) { + @ExternalDirective + var weight: Double by Delegates.notNull() + + @RequiresDirective(FieldSet("weight")) + fun shippingCost(): String { ... } +} +``` + +will generate + +```graphql +type Product @key(fields : "id") { + id: String! + shippingCost: String! @requires(fields : "weight") + weight: Float! @external +} +``` + +## `@requiresScopes` directive + +:::info +Available since Federation v2.5 +::: + +```graphql +directive @requiresScopes(scopes: [[Scope!]!]!) on + ENUM + | FIELD_DEFINITION + | INTERFACE + | OBJECT + | SCALAR +``` + +Directive that is used to indicate that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes. Refer to the +[Apollo Router documentation](https://www.apollographql.com/docs/router/configuration/authorization#requiresscopes) for additional details. + +## `@shareable` directive + +:::info +Available since Federation v2.0 +::: + +```graphql +directive @shareable repeatable on FIELD_DEFINITION | OBJECT +``` + +Shareable directive indicates that given object and/or field can be resolved by multiple subgraphs. If an object is marked as `@shareable` then all its fields are automatically shareable without the +need for explicitly marking them with `@shareable` directive. All fields referenced from `@key` directive are automatically shareable as well. + +:::caution +Objects/fields have to specify same shareability (i.e. `@shareable` or not) mode across ALL subgraphs. +::: + +#### Example + +```graphql +type Product @key(fields: "id") { + id: ID! # shareable because id is a key field + name: String # non-shareable + description: String @shareable # shareable +} + +type User @key(fields: "email") @shareable { + email: String # shareable because User is marked shareable + name: String # shareable because User is marked shareable +} +``` + +## `@tag` directive + +```graphql +directive @tag(name: String!) repeatable on FIELD_DEFINITION + | OBJECT + | INTERFACE + | UNION + | ARGUMENT_DEFINITION + | SCALAR + | ENUM + | ENUM_VALUE + | INPUT_OBJECT + | INPUT_FIELD_DEFINITION +``` + +Tag directive allows users to annotate fields and types with additional metadata information. Used by [Apollo Contracts](https://www.apollographql.com/docs/studio/contracts/) to expose +different graph variants to different customers. See [@tag specification](https://specs.apollo.dev/tag/v0.2/) for details. + +#### Example + +```graphql +type Product @tag(name: "MyCustomTag") { + id: String! + name: String! +} +``` + +:::caution +Apollo Contracts behave slightly differently depending on which version of Apollo Federation your graph uses (1 or 2). See [documentation](https://www.apollographql.com/docs/studio/contracts/#federation-1-limitations) +for details. +::: diff --git a/website/versioned_docs/version-8.x.x/schema-generator/federation/federated-schemas.md b/website/versioned_docs/version-8.x.x/schema-generator/federation/federated-schemas.md new file mode 100644 index 0000000000..11c95a4b21 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/federation/federated-schemas.md @@ -0,0 +1,180 @@ +--- +id: federated-schemas +title: Federated Schemas +--- +`graphql-kotlin-federation` library extends the functionality of the `graphql-kotlin-schema-generator` and allows you to +easily generate federated GraphQL schemas directly from the code. Federated schema is generated by calling +`toFederatedSchema` function that accepts federated configuration as well as a list of regular queries, mutations and +subscriptions exposed by the schema. + +All [federated directives](federated-directives) are provided as annotations that are used to decorate your classes, +properties and functions. Since federated types might not be accessible through the regular query execution path, they +are explicitly picked up by the schema generator based on their directives. Due to the above, we also need to provide +a way to instantiate the underlying federated objects by implementing corresponding `FederatedTypeResolvers`. See +[type resolution wiki](type-resolution) for more details on how federated types are resolved. Final federated schema +is then generated by invoking the `toFederatedSchema` function +([link](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/toFederatedSchema.kt#L34)). + +**In order to generate valid federated schemas, you will need to annotate your entities in all your subgraphs**. +Federated Gateway (e.g. Apollo) will then combine the individual graphs to form single federated graph. + +:::caution +If you are using custom `Query` type then all of you federated GraphQL services have to use the same type. It is +not possible for federated services to have different definitions of `Query` type. +::: + +### Subgraph A + +Federation v2 relaxed entity ownership and now every subgraph that defines given entity is its owner. In the example +below, we define `Product` type with `id` and `description` fields. `id` is the primary key that uniquely +identifies the `Product` type object and is specified in `@key` directive. Since it might be possible to resolve +`Product` entity from other subgraphs, we also should specify an "entry point" for the federated type - we need to +create a `FederatedTypeResolver` that will be used to instantiate the federated `Product` type when processing federated +queries. + +```kotlin +@KeyDirective(fields = FieldSet("id")) +data class Product(val id: Int, val description: String) + +class ProductQuery { + fun product(id: Int): Product? { + // grabs product from a data source, might return null + } +} + +// Resolve a "Product" type from the _entities query +class ProductResolver : FederatedTypeSuspendResolver { + override val typeName = "Product" + + override suspend fun resolve( + environment: DataFetchingEnvironment, + representation: Map + ): Product? = + representation["id"]?.toString()?.toIntOrNull()?.let { id -> Product(id) } +} + +// Generate the schema +val resolvers = listOf(ProductResolver()) +val hooks = FederatedSchemaGeneratorHooks(resolvers) +val config = FederatedSchemaGeneratorConfig(supportedPackages = listOf("org.example"), hooks = hooks) +val queries = listOf(TopLevelObject(ProductQuery())) + +toFederatedSchema(config, queries) +``` + +Example above generates the following schema with additional federated types: + +```graphql +schema { + query: Query +} + +union _Entity = Product + +type Product @key(fields : "id") { + description: String! + id: Int! +} + +type Query { + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + product(id: Int!): Product +} + +type _Service { + sdl: String! +} +``` + +### Subgraph B + +Each subgraph can extend and provide new functionality to entities defined in other subgraphs. In the example below, +`Product` type is extended to add new `reviews` field to it. Primary key needed to instantiate the `Product` type (i.e. `id`) +has to match one of the entity `@key` definitions defined in other subgraphs. Finally, we also need to specify an "entry point" +for the federated type - we need to create a FederatedTypeResolver. + +```kotlin +@KeyDirective(fields = FieldSet("id")) +data class Product(val id: Int) { + // Add the "reviews" field to the type + suspend fun reviews(): List = getReviewByProductId(id) +} + +data class Review(val reviewId: String, val text: String) + +// Resolve a "Product" type from the _entities query +class ProductResolver : FederatedTypeSuspendResolver { + override val typeName = "Product" + + override suspend fun resolve( + environment: DataFetchingEnvironment, + representation: Map + ): Product? = + representation["id"]?.toString()?.toIntOrNull()?.let { id -> Product(id) } +} + +// Generate the schema +val resolvers = listOf(ProductResolver()) +val hooks = FederatedSchemaGeneratorHooks(resolvers) +val config = FederatedSchemaGeneratorConfig(supportedPackages = listOf("org.example"), hooks = hooks) + +toFederatedSchema(config) +``` + +Our extended schema will then be generated as: + +```graphql +schema { + query: Query +} + +union _Entity = Product + +type Product @key(fields : "id") { + id: Int! + reviews: [Review!]! +} + +type Query { + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! +} + +type Review { + reviewId: String! + text: String! +} + +type _Service { + sdl: String! +} +``` + +### Federated Supergraph + +Once we have both GraphQL subgraphs up and running, we will also need to configure Federated Gateway +to combine them into a single supergraph schema. Using the examples above, our final federated schema will be generated as: + +```graphql +schema { + query: Query +} + +type Product { + description: String! + id: Int! + reviews: [Review!]! +} + +type Review { + reviewId: String! + text: String! +} + +type Query { + product(id: String!): Product +} +``` + +See our [federation example](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/federation) for additional details. diff --git a/website/versioned_docs/version-8.x.x/schema-generator/federation/federation-tracing.md b/website/versioned_docs/version-8.x.x/schema-generator/federation/federation-tracing.md new file mode 100644 index 0000000000..690a932c0a --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/federation/federation-tracing.md @@ -0,0 +1,40 @@ +--- +id: federation-tracing +title: Federation Tracing +--- + +`graphql-kotlin-federation` module relies on [apollographql/federation-jvm](https://github.com/apollographql/federation-jvm) +package to provide support for Apollo Federation tracing. Tracing is turned on by including `FederatedTracingInstrumentation` +in your GraphQL instance. In order for the `FederatedTracingInstrumentation` to know whether incoming request should be +traced, we need to provide it a `apollo-federation-include-trace` header value. + +```kotlin +val schema = toFederatedSchema(myFederatedConfig, listOf(TopLevelObject(MyFederatedQuery()))) +val graphQL = GraphQL.newGraphQL(schema) + .instrumentation(FederatedTracingInstrumentation()) + .build() +``` + +### GraphQL Context Map + +:::note +Default `GraphQLContextFactory` provided by `graphql-kotlin-spring-server` populates this header information automatically. +::: + +Tracing header information can be provided by populating info directly on the GraphQL context map. + +```kotlin +val contextMap = mutableMapOf() + .also { map -> + request.headers().firstHeader(FEDERATED_TRACING_HEADER_NAME)?.let { headerValue -> + map[FEDERATED_TRACING_HEADER_NAME] = headerValue + } + } + +val executionInput = ExecutionInput.newExecutionInput() + .graphQLContext(contextMap) + .query(queryString) + .build() + +graphql.executeAsync(executionInput) +``` diff --git a/website/versioned_docs/version-8.x.x/schema-generator/federation/type-resolution.md b/website/versioned_docs/version-8.x.x/schema-generator/federation/type-resolution.md new file mode 100644 index 0000000000..3d68a2a3a4 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/federation/type-resolution.md @@ -0,0 +1,128 @@ +--- +id: type-resolution +title: Federated Type Resolution +--- +In traditional (i.e. non-federated) GraphQL servers, each one of the output types is accessible through a traversal of +the GraphQL schema from a corresponding query, mutation or subscription root type. Since federated GraphQL types might +be accessed outside of the query path we need a mechanism to access them in a consistent manner. + +## `_entities` query + +A federated GraphQL server provides a custom `_entities` query that allows retrieving any of the federated extended types. +The `_entities` query accept list of "representation" objects that provide all required fields to resolve the type and +return an `_Entity` union type of all supported federated types. Representation objects are just a map of all the fields +referenced in `@key` directives as well as the target `__typename` information. If federated query type fragments also +reference fields with `@requires` and `@provides` directives, then those referenced fields should also be specified in +the target representation object. + +```graphql +query ($_representations: [_Any!]!) { + _entities(representations: $_representations) { + ... on SomeFederatedType { + fieldA + fieldB + } + } +} +``` + +:::note +`_entities` queries are automatically handled by a federated gateway and their usage is transparent for the gateway clients. +::: + +## Federated Type Resolver + +In order to simplify the integrations, `graphql-kotlin-federation` provides a default `_entities` query data fetcher or resolver that +invokes the [TypeResolver](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/FederatedTypeResolver.kt) +that is used to resolve the specified `__typename`. + +`FederatedTypeResolver.typeName` specifies the GraphQL type name that should match the `__typename` field in the `_entities` query. + +There are two interfaces that implement the `FederatedTypeResolver`: +1. [FederatedTypeSuspendResolver](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/FederatedTypeSuspendResolver.kt) +2. [FederatedTypePromiseResolver](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/execution/FederatedTypePromiseResolver.kt) + +### Federated Type Suspend Resolver + +`FederatedTypeSuspendResolver.resolve` receives a representation of the target `__typename` and will execute +the suspending function on a `CoroutineScope` to **asynchronously wait** to complete the target entity or `NULL` if entity cannot be resolved. + +`FederatedTypeSuspendResolver.resolve` will be invoked based on how many representations of the target entity were +requested in the `_entities` query. + +```kotlin +// This service extends "Product" type with new fields +@KeyDirective(fields = FieldSet("id")) +class Product(val id: String) { + fun newField(): String = getNewFieldByProductId(id) +} + +// This is how the "Product" class is created from the "_entities" query using suspending resolver +class ProductResolver : FederatedTypeSuspendResolver { + override val typeName: String = "Product" + + override suspend fun resolve( + environment: DataFetchingEnvironment, + representation: Map + ): Product? { + val id = representation["id"]?.toString() + // Instantiate product using id, otherwise return null + return if (id != null) { + Product(id) + } else { + null + } + } +} +``` + +:::note +this suspend implementation relies on the same coroutine scope propagation as the +default `FunctionDataFetcher`. See [asynchronous models documentation](../execution/async-models.md) for additional details. +Additionally, you can also use `FederatedTypePromiseResolver` which is compatible with `DataLoader`'s async model given that returns +a `CompletableFuture`, that way you get advantage of batching and deduplication of transactions to downstream. +::: + +### Federated Type Promise Resolver + +`FederatedTypePromiseResolver.resolve` receives a representation of the target `__typename` and provides a `CompletableFuture` of +a nullable instance of target entity. + +```kotlin +// This service extends "Product" type with new fields +@KeyDirective(fields = FieldSet("id")) +class Product(val id: String) { + fun newField(): String = getNewFieldByProductId(id) +} + +// This is how the "Product" class is created from the "_entities" query using promise resolver +class ProductResolver : FederatedTypePromiseResolver { + override val typeName: String = "Product" + + override fun resolve( + environment: DataFetchingEnvironment, + representation: Map + ): CompletableFuture { + val id = representation["id"]?.toString() + // use dataloader to resolve Product by id + return environment.getDataLoader("ProductDataLoader").load(id) + } +} +``` + +## Provide FederatedTypeResolvers to FederatedSchema + +Provide a `List` to the `FederatedSchemaGeneratorHooks` and `graphql-kotlin` will create the +data fetcher or resolver using your custom federated type resolvers + +```kotlin +val resolvers = listOf(productResolver) +val hooks = FederatedSchemaGeneratorHooks(resolvers) +val config = FederatedSchemaGeneratorConfig(supportedPackages = listOf("org.example"), hooks = hooks) +val schema = toFederatedSchema(config) +``` + +:::note +If you are using `graphql-kotlin-spring-server`, each of your FederatedTypeResolvers can be marked as Spring Beans +and will automatically be added to the `FederatedSchemaGeneratorHooks` by using autoconfiguration. +::: diff --git a/website/versioned_docs/version-8.x.x/schema-generator/schema-generator-getting-started.mdx b/website/versioned_docs/version-8.x.x/schema-generator/schema-generator-getting-started.mdx new file mode 100644 index 0000000000..d9d06e0f40 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/schema-generator-getting-started.mdx @@ -0,0 +1,144 @@ +--- +id: schema-generator-getting-started +title: Getting Started +--- + + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## Install + +Using a JVM dependency manager, link `graphql-kotlin-schema-generator` to your project. + + + + + +```kotlin +implementation("com.expediagroup", "graphql-kotlin-schema-generator", latestVersion) +``` + + + + +```xml + + com.expediagroup + graphql-kotlin-schema-generator + ${latestVersion} + +``` + + + + +## Usage + +`graphql-kotlin-schema-generator` provides a single function, `toSchema`, to generate a schema from Kotlin objects. + +```kotlin +data class Widget(val id: Int, val value: String) + +class WidgetQuery { + fun widgetById(id: Int): Widget? { + // grabs widget from a data source + } +} + +class WidgetMutation { + fun saveWidget(value: String): Widget { + // some logic for saving widget + } +} + +val widgetQuery = WidgetQuery() +val widgetMutation = WidgetMutation() +val schema = toSchema( + config = yourCustomConfig(), + queries = listOf(TopLevelObject(widgetQuery)), + mutations = listOf(TopLevelObject(widgetMutation)) +) +``` + +will generate: + +```graphql +schema { + query: Query + mutation: Mutation +} + +type Query { + widgetById(id: Int!): Widget +} + +type Mutation { + saveWidget(value: String!): Widget! +} + +type Widget { + id: Int! + value: String! +} +``` + +Any `public` functions defined on a query, mutation, or subscription Kotlin class will be translated into GraphQL fields on the object +type. `toSchema` will then recursively apply Kotlin reflection on the specified classes to generate all +remaining object types, their properties, functions, and function arguments. + +The generated `GraphQLSchema` can then be used to expose a GraphQL API endpoint. + +## `toSchema` + +This function accepts five arguments: `config`, `queries`, `mutations`, `subscriptions` and a `schemaObject`. The `queries`, `mutations` +and `subscriptions` are a list of `TopLevelObject`s and will be used to generate corresponding GraphQL root types. `schemaObject` is an +optional `TopLevelObject` reference to the annotated schema object. See below on why we use this wrapper class. The `config` contains all +the extra information you need to pass, including custom hooks, supported packages, and name overrides. See the [Generator Configuration](./customizing-schemas/generator-config.md) +documentation for more information. + +You can see the definition for `toSchema` [in the +source](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/toSchema.kt). + +## `TopLevelObject` + +`toSchema` uses Kotlin reflection to build a GraphQL schema from given classes using `graphql-java`'s schema builder. We +don't just pass a `KClass` though, we have to actually pass an object, because the functions on the object are +transformed into the data fetchers. In most cases, a `TopLevelObject` can be constructed with just an object: + +```kotlin +class Query { + fun getNumber() = 1 +} + +val topLevelObject = TopLevelObject(Query()) + +toSchema(config = config, queries = listOf(topLevelObject)) +``` + +In the above case, `toSchema` will use `topLevelObject::class` as the reflection target, and `Query` as the data fetcher +target. + + +### Dynamic `TopLevelObject` +In a lot of cases, such as with Spring AOP, the object (or bean) being used to generate a schema is a dynamic proxy. In +this case, `topLevelObject::class` is not `Query`, but rather a generated class that will confuse the schema generator. +To specify the `KClass` to use for reflection on a proxy, pass the class to `TopLevelObject`: + +```kotlin +@Component +class Query { + fun getNumber() = 1 +} + +val query = getObjectFromBean() +val customDef = TopLevelObject(query, Query::class) + +toSchema(config, listOf(customDef)) +``` diff --git a/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/arguments.md b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/arguments.md new file mode 100644 index 0000000000..175eb5f744 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/arguments.md @@ -0,0 +1,109 @@ +--- +id: arguments +title: Arguments +--- +Method arguments are automatically exposed as part of the arguments to the corresponding GraphQL fields. + +```kotlin +class Query { + fun doSomething(value: Int): Boolean = true +} +``` + +The above Kotlin code will generate following GraphQL schema: + +```graphql +type Query { + doSomething(value: Int!): Boolean! +} +``` + +This behavior is true for all arguments except for the special classes for the [GraphQLContext](../execution/contextual-data) and the [DataFetchingEnvironment](../execution/data-fetching-environment) + +## Input Types + +Query, Mutation, and Subscription function arguments are automatically converted to GraphQL input fields. GraphQL makes a +distinction between input and output types and requires unique names for all the types. Since we can use the same +objects for input and output in our Kotlin functions, `graphql-kotlin-schema-generator` will automatically append +an `Input` suffix to the GraphQL name of input objects. + +For example, the following code: + +```kotlin +class WidgetMutation { + fun processWidget(widget: Widget): Widget { + if (widget.value == null) { + widget.value = 42 + } + return widget + } +} + +data class Widget(var value: Int? = null) { + fun multiplyValueBy(multiplier: Int): Int? = value?.times(multiplier) +} +``` + +Will generate the following schema: + +```graphql +type Mutation { + processWidget(widget: WidgetInput!): Widget! +} + +type Widget { + value: Int + multiplyValueBy(multiplier: Int!): Int +} + +input WidgetInput { + value: Int +} +``` + +Note that only fields are exposed in the input objects. Functions will only be available on the GraphQL output types. + +:::caution +All input object fields have to be exposed through a public primary constructor. This primary constructor is used to instantiate +the input objects at runtime when resolving the queries. +::: + +If you know a type will only be used for input types you can call your class something like `CustomTypeInput`. The library will not +append `Input` if the class name already ends with `Input` but that means you can not use this type as output because +the schema would have two types with the same name and that would be invalid. + +If you would like to restrict an Kotlin class to only being used as input or output, see how to use [GraphQLValidObjectLocations](../customizing-schemas/restricting-input-output.md) + +## Optional fields + +Kotlin requires variables/values to be initialized upon their declaration either from the user input OR by providing +defaults (even if they are marked as nullable). + +Therefore, in order for a GraphQL input field to be optional, **it needs to be nullable and must have a default value**. + +```kotlin +fun doSomethingWithOptionalInput(requiredValue: Int, optionalValue: Int? = null): String { + return "requiredValue=$requiredValue, optionalValue=$optionalValue" +} +``` + +## Default values + +Default Kotlin values are supported, however the default value information is not available to the schema due to the [reflection limitations of Kotlin](https://github.com/ExpediaGroup/graphql-kotlin/issues/53). +The parameters must also be defined as optional (nullable) in the schema, as the only way a default value will be used is when the client does not specify any value in the request. + +```kotlin +fun print(message: String? = "hello"): String? = message +``` + +The following operations will return the message in the comments + +```graphql +query PrintMessages { + first: print(message = "foo") # foo + second: print(message = null) # null + third: print # hello +} +``` + +If you need logic to determine when a client passed in a value vs when the default value was used (aka the argument was missing in the request), see [optional undefined arguments](../execution/optional-undefined-arguments.md). diff --git a/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/enums.md b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/enums.md new file mode 100644 index 0000000000..ebebf04c3f --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/enums.md @@ -0,0 +1,76 @@ +--- +id: enums +title: Enums +--- +Enums are automatically mapped to GraphQL enum type. + +```kotlin +enum class MyEnumType { + ONE, + TWO +} +``` + +Above enum will be generated as following GraphQL object + +```graphql +enum MyEnumType { + ONE + TWO +} +``` + +### Converting a Java enum to a GraphQL Enum + +If you want to use Java enums from another package, but you **don't** want +include everything from that package using [`supportedPackages`][config] or you want +to customize the GraphQL type, you can use [schema generator hooks][config] to +associate the Java enum with a runtime [`GraphQLEnumType`][javadoc]. + +[config]: ../customizing-schemas/generator-config.md + +[javadoc]: https://javadoc.io/doc/com.graphql-java/graphql-java/latest/index.html + +Step 1: Create a GraphQLEnumType using the Java enum values + +```java +// in some other package +public enum Status { + APPROVED, + DECLINED +} +``` + +```kotlin +val statusEnumType = GraphQLEnumType.newEnum() + .name("Status") + .values(Status.values().map { + GraphQLEnumValueDefinition.newEnumValueDefinition() + .value(it.name) + .build() + }) + .build() +``` + +Step 2: Add a schema generation hook + +```kotlin +class CustomSchemaGeneratorHooks : SchemaGeneratorHooks { + + override fun willGenerateGraphQLType(type: KType): GraphQLType? { + return when (type.classifier as? KClass<*>) { + Status::class.java -> statusEnumType + else -> super.willGenerateGraphQLType(type) + } + } +} +``` + +Step 3. Use your Java enum anywhere in your schema + +```kotlin +@Component +class StatusQuery : Query { + fun currentStatus: Status = getCurrentStatus() +} +``` diff --git a/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/fields.md b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/fields.md new file mode 100644 index 0000000000..20787bb3d4 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/fields.md @@ -0,0 +1,24 @@ +--- +id: fields +title: Types and Fields +--- +`graphql-kotlin-schema-generator` uses [reflection](https://kotlinlang.org/docs/reflection.html) to automatically map +Kotlin classes and enums to the corresponding GraphQL types. + +By default, all public properties and functions with a [valid GraphQL name](https://spec.graphql.org/draft/#sec-Names) +and a supported return type will be mapped to a corresponding GraphQL field. Kotlin return types have to be either one of the +[supported scalars](./scalars.md) or a custom Kotlin type defined within supported packages. Nullability information is +automatically inferred from the underlying Kotlin return type. + +Additional built-in validations +* function types aka lambdas property types are currently not supported by the schema generator +* automatically generated data class methods (`componentN`, `copy`, `equals`, `hashCode`, `toString`) are filtered out + +Default behavior can be customized. Fields can be [renamed](../customizing-schemas/renaming-fields.md) or [excluded](../customizing-schemas/excluding-fields.md). +Support for additional types or validations can be added by providing an instance of custom [SchemaGeneratorHook](../customizing-schemas/generator-config.md#schemageneratorhooks). + +## Type Inheritance + +`graphql-kotlin-schema-generator` provides support for both GraphQL [unions](./unions.md) and [interfaces](./interfaces.md). +Superclasses and interfaces can be excluded from the schema by marking them with `@GraphQLIgnore` annotation or by providing +custom filtering logic in a custom [SchemaGeneratorHook](../customizing-schemas/generator-config.md#schemageneratorhooks). diff --git a/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/interfaces.md b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/interfaces.md new file mode 100644 index 0000000000..38cf079f5f --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/interfaces.md @@ -0,0 +1,129 @@ +--- +id: interfaces +title: Interfaces +--- +Kotlin interfaces, abstract and sealed classes will be mapped to a GraphQL interface. Due to the GraphQL distinction between interface +and a [union type](./unions.md), Kotlin interfaces need to specify at least one common field (property or a function). Superclasses and +interfaces can be excluded from the schema by marking them with `@GraphQLIgnore` annotation or by providing custom filtering logic in a +custom schema generator hook. + +:::note +[The GraphQL spec](http://spec.graphql.org/June2018/#sec-Interfaces) does not allow interfaces to be used as input. +This means that while it is valid Kotlin code to have an interface as an argument, upon schema generation, an exception will be thrown. +::: + +```kotlin +interface Animal { + val type: AnimalType + fun sound(): String +} + +enum class AnimalType { + CAT, + DOG +} + +class Dog : Animal { + override val type = AnimalType.DOG + + override fun sound() = "bark" + + fun barkAtEveryone(): String = "bark at everyone" +} + +class Cat : Animal { + override val type = AnimalType.CAT + + override fun sound() = "meow" + + fun ignoreEveryone(): String = "ignore everyone" +} + +class PolymorphicQuery { + + fun animal(type: AnimalType): Animal? = when (type) { + AnimalType.CAT -> Cat() + AnimalType.DOG -> Dog() + else -> null + } +} +``` + +The above code will produce the following GraphQL schema: + +```graphql +interface Animal { + type: AnimalType! + sound: String! +} + +enum AnimalType { + CAT + DOG +} + +type Cat implements Animal { + type: AnimalType! + ignoreEveryone: String! + sound: String! +} + +type Dog implements Animal { + type: AnimalType! + barkAtEveryone: String! + sound: String! +} + +type TopLevelQuery { + animal(type: AnimalType!): Animal +} +``` + +## Abstract and Sealed Classes + +[Abstract](https://kotlinlang.org/docs/reference/classes.html#abstract-classes) and [sealed](https://kotlinlang.org/docs/reference/sealed-classes.html) classes can also be used for GraphQL interface types. + +```kotlin +abstract class Shape(val area: Double) +class Circle(radius: Double) : Shape(PI * radius * radius) +class Square(sideLength: Double) : Shape(sideLength * sideLength) + +sealed class Pet(val name: String) { + class Dog(name: String, val goodBoysReceived: Int) : Pet(name) + class Cat(name: String, val livesRemaining: Int) : Pet(name) +} +``` + +## Nested Interfaces + +Interfaces can implement other interfaces. + +```kotlin +interface Foo { + val foo: String +} + +interface Bar : Foo { + val bar: String +} + +class Baz(override val foo: String, override val bar: String) : Bar +``` + +Code above generates following schema + +```graphql +interface Foo { + foo: String! +} + +interface Bar implements Foo { + bar: String! + foo: String! +} + +type Baz implements Bar & Foo { + bar: String! + foo: String! +} +``` diff --git a/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/lists.md b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/lists.md new file mode 100644 index 0000000000..108fbb4491 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/lists.md @@ -0,0 +1,45 @@ +--- +id: lists +title: Lists +--- +`kotlin.collections.List` is automatically mapped to the GraphQL `List` type. Type arguments provided to Kotlin collections +are used as the type arguments in the GraphQL `List` type. + +```kotlin +class SimpleQuery { + fun generateList(): List { + // some logic here that generates list + } + + fun doSomethingWithIntList(ints: List): String { + // some logic here that processes list + } +} +``` + +The above Kotlin class would produce the following GraphQL schema: + +```graphql +type Query { + generateList: [String!]! + doSomethingWithIntList(ints: [Int!]!): String! +} +``` + +## Arrays and Unsupported Collection Types + +Currently, the GraphQL spec only supports `Lists`. Therefore, even though Java and Kotlin support number of other collection +types, `graphql-kotlin-schema-generator` only explicitly supports `Lists`. Other collection types such as `Sets` (see [#201](https://github.com/ExpediaGroup/graphql-kotlin/issues/201)) +and arbitrary `Map` data structures are not supported out of the box. While we do not recommend using `Map` or `Set` in the schema, +they could be supported with the use of the schema hooks. + +Due to the [argument deserialization issues](https://github.com/ExpediaGroup/graphql-kotlin/pull/1379), arrays are currently not supported + +```kotlin +override fun willResolveMonad(type: KType): KType = when (type.classifier) { + Set::class -> List::class.createType(type.arguments) + else -> type +} +``` + +See [Discussion #1110](https://github.com/ExpediaGroup/graphql-kotlin/discussions/1110) for more details. diff --git a/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/nested-arguments.md b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/nested-arguments.md new file mode 100644 index 0000000000..19de6db8a7 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/nested-arguments.md @@ -0,0 +1,76 @@ +--- +id: nested-arguments +title: Nested Resolvers and Shared Arguments +--- +There are a few ways in which shared arguments can be accessed from different resolver levels. Say we have the following schema: + +```graphql +type Query { + findUser(id: String!): User +} + +type User { + photos(numberOfPhotos: Int!): [Photo!]! +} + +type Photo { + url: String! +} +``` + +In Kotlin code, when we are resolving `photos`, if we want access to the parent field `findUser` and its arguments there +are a couple ways we can access it, + +## DataFetchingEnvironment + +You can add the `DataFetchingEnvironment` as an argument. This class will be ignored by the schema generator and will allow +you to view the entire query sent to the server. See more in the [DataFetchingEnvironment documentation](../execution/data-fetching-environment.md) + +```kotlin +class User { + fun photos(environment: DataFetchingEnvironment, numberOfPhotos: Int): List { + val username = environment.executionStepInfo.parent.arguments["id"] + return getPhotosFromDataSource(username, numberOfPhotos) + } +} +``` + +## GraphQL Context Object (Deprecated) + +:::danger +Support for custom GraphQL context object is deprecated and will be removed in future releases. Please migrate to use +generic GraphQL context map. +::: + +You can add the `GraphQLContext` as an argument. This class will be ignored by the schema generator and will allow you to +view the context object you set up in the data fetchers. See more in the [GraphQLContext documentation](../execution/contextual-data.md) + +```kotlin +class User { + fun photos(context: MyContextObject, numberOfPhotos: Int): List { + val userId = context.getDataFromMyCustomFunction() + return getPhotosFromDataSource(userId, numberOfPhotos) + } +} +``` + +## Excluding Arguments from the Schema + +You can construct the child objects by passing down arguments as non-public fields or annotate the argument with [@GraphQLIgnore](../customizing-schemas/excluding-fields.md) + +```kotlin +class User(private val userId: String) { + + fun photosProperty(numberOfPhotos: Int): List { + return getPhotosFromDataSource(userId, numberOfPhotos) + } + + fun photosIgnore(@GraphQLIgnore userId: String, numberOfPhotos: Int): List { + return getPhotosFromDataSource(userId, numberOfPhotos) + } +} +``` + +## Spring Integration + +See [Writing Schemas with Spring](../../server/spring-server/spring-schema.md) for integration details. diff --git a/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/nullability.md b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/nullability.md new file mode 100644 index 0000000000..fe8575f27c --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/nullability.md @@ -0,0 +1,28 @@ +--- +id: nullability +title: Nullability +--- +Both GraphQL and Kotlin have a concept of `nullable` as a marked typed. As a result we can automatically generate null +safe schemas from Kotlin code. + +```kotlin +class SimpleQuery { + + fun generateNullableNumber(): Int? { + val num = Random().nextInt(100) + return if (num < 50) num else null + } + + fun generateNumber(): Int = Random().nextInt(100) +} +``` + +The above Kotlin code would produce the following GraphQL schema: + +```graphql +type Query { + generateNullableNumber: Int + + generateNumber: Int! +} +``` diff --git a/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/scalars.md b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/scalars.md new file mode 100644 index 0000000000..e2d6986207 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/scalars.md @@ -0,0 +1,269 @@ +--- +id: scalars +title: Scalars +--- +## Primitive Types + +`graphql-kotlin-schema-generator` can directly map most Kotlin "primitive" types to standard GraphQL scalar types or +extended scalar types provided by `graphql-java`. + +| Kotlin Type | GraphQL Type | +|-------------------| ------------ | +| `kotlin.String` | `String` | +| `kotlin.Boolean` | `Boolean` | +| `kotlin.Int` | `Int` | +| `kotlin.Double` | `Float` | +| `kotlin.Float` | `Float` | + +:::note +The GraphQL spec uses the term `Float` for signed double‐precision fractional values. `graphql-java` maps this to a `java.lang.Double` for the execution. The generator will map both `kotlin.Double` and `kotlin.Float` to GraphQL `Float` but we recommend you use `kotlin.Double`. +::: + +## GraphQL ID + +GraphQL supports the scalar type `ID`, a unique identifier that is not intended to be human-readable. IDs are +serialized as a `String`. To expose a GraphQL `ID` field, you must use the `com.expediagroup.graphql.generator.scalars.ID` +class, which is an *inline value class* that wraps the underlying `String` value. + +:::note +`graphql-java` supports additional types (`String`, `Int`, `Long`, or `UUID`) but [due to serialization issues](https://github.com/ExpediaGroup/graphql-kotlin/issues/317) we can only directly support Strings. +::: + +Since `ID` is a value class, it may be represented at runtime as a wrapper or directly as underlying type. Due to the generic +nature of the query processing logic we *always* end up with up a wrapper type when resolving the field value. As a result, +in order to ensure that underlying scalar value is correctly serialized, we need to explicitly unwrap it by registering +`IDValueUnboxer` with your GraphQL instance. + +```kotlin +// registering custom value unboxer +val graphQL = GraphQL.newGraphQL(graphQLSchema) + .valueUnboxer(IDValueUnboxer()) + .build() +``` + +:::note +`IDValueUnboxer` bean is automatically configured by `graphql-kotlin-spring-server`. +::: + +```kotlin +data class Person( + val id: ID, + val name: String +) + +fun findPersonById(id: ID) = Person(id, "John Smith") + +fun generateRandomId(): ID = ID(UUID.randomUUID().toString()) +``` + +This would produce the following schema: + +```graphql +schema { + query: Query +} + +type Query { + findPersonById(id: ID!): Person! + generateRandomId: ID! +} + +type Person { + id: ID! + name: String! +} +``` + +## Custom Scalars + +By default, `graphql-kotlin-schema-generator` uses Kotlin reflections to generate all schema objects. If you want to +apply custom behavior to the objects, you can also define your own custom scalars. Custom scalars have to be explicitly +added to the schema through `SchemaGeneratorHooks.willGenerateGraphQLType`. +See the [Generator Configuration](../customizing-schemas/generator-config.md) documentation for more information. + +Example usage + +```kotlin +class CustomSchemaGeneratorHooks : SchemaGeneratorHooks { + + override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier as? KClass<*>) { + UUID::class -> graphqlUUIDType + else -> null + } +} + +val graphqlUUIDType = GraphQLScalarType.newScalar() + .name("UUID") + .description("A type representing a formatted java.util.UUID") + .coercing(UUIDCoercing) + .build() + +object UUIDCoercing : Coercing { + override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): UUID = runCatching { + UUID.fromString(serialize(input, graphQLContext, locale)) + }.getOrElse { + throw CoercingParseValueException("Expected valid UUID but was $input") + } + + override fun parseLiteral(input: Value<*>, variables: CoercedVariables, graphQLContext: GraphQLContext, locale: Locale): UUID { + val uuidString = (input as? StringValue)?.value + return runCatching { + UUID.fromString(uuidString) + }.getOrElse { + throw CoercingParseLiteralException("Expected valid UUID literal but was $uuidString") + } + } + + override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): String = runCatching { + dataFetcherResult.toString() + }.getOrElse { + throw CoercingSerializeException("Data fetcher result $dataFetcherResult cannot be serialized to a String") + } +} +``` + +Once the scalars are registered you can use them anywhere in the schema as regular objects. + +### Inline Value Classes + +It is often beneficial to create a wrapper around the underlying primitive type to better represent its meaning. Inline value classes can be used +to optimize such use cases - Kotlin compiler will attempt to use underlying type directly whenever possible and only keep the wrapper classes +whenever it is necessary. + +:::note +Nullable value class types may result in a runtime `IllegalArgumentException` due to https://youtrack.jetbrains.com/issue/KT-31141. This should be resolved in Kotlin 1.7.0+. +::: + +#### Representing Unwrapped Value Classes in the Schema as the Underlying Type + +In order to represent unwrapped inline value classes in your schema as the underlying type, you need to register it using hooks and also provide value unboxer that will be used by +`graphql-java` when dealing with its wrapper object. + +```kotlin +@JvmInline +value class MyValueClass( + val value: String +) + +class MyQuery : Query { + fun inlineValueClassQuery(value: MyValueClass? = null): MyValueClass = value ?: MyValueClass("default") +} + +class MySchemaGeneratorHooks : SchemaGeneratorHooks { + override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier) { + MyValueClass::class -> Scalars.GraphQLString + else -> null + } +} + +class MyValueUnboxer : IDValueUnboxer() { + override fun unbox(value: Any?): Any? = when (value) { + is MyValueClass -> `object`.value + else -> super.unbox(`object`) + } +} + +val config = SchemaGeneratorConfig( + supportedPackages = listOf("com.example"), + hooks = MySchemaGeneratorHooks() +) +val schema = toSchema( + config = config, + queries = listOf(TopLevelObject(MyQuery())) +) +val graphQL = GraphQL.newGraphQL(graphQLSchema) + .valueUnboxer(MyValueUnboxer()) + .build() +``` + +This will generate a schema that exposes value classes as the corresponding wrapped type: + +```graphql +type Query { + inlineValueClassQuery(value: String): String! +} +``` + +:::note +GraphQL ID scalar type is represented using inline value class. When registering additional inline value classes you should extend the `IDValueUnboxer` to ensure IDs will be correctly processed. Alternatively, extend `DefaultValueUnboxer` and handle the `ID` value class as above. + +If you are using `graphql-kotlin-spring-server` you should create an instance of your bean as + +```kotlin +@Bean +fun idValueUnboxer(): IDValueUnboxer = MyValueUnboxer() +``` +::: + +#### Representing Unwrapped Value Classes in the Schema as a Custom Scalar Type + +In many cases, it may be useful to represent value classes in the schema as a custom scalar type, as the additional type information is often useful for clients. In this form, the value class is unwrapped, but uses a custom scalar type to preserve the extra type information. + +To do this, define a coercer for the value class that transforms it to and from the underlying type, and register it with the custom schema hooks: + +```kotlin +val graphqlMyValueClassType: GraphQLScalarType = GraphQLScalarType.newScalar() + .name("MyValueClass") + .description( + """ + |Represents my value class as a String value. + |""".trimMargin() + ) + .coercing(MyValueClassCoercing) + .build() + +object MyValueClassCoercing : Coercing { + override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): MyValueClass = ... + override fun parseLiteral(input: Value<*>, variables: CoercedVariables, graphQLContext: GraphQLContext, locale: Locale): MyValueClass = ... + override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): String = ... +} + +class CustomSchemaGeneratorHooks : SchemaGeneratorHooks { + override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier as? KClass<*>) { + MyValueClass::class -> graphqlMyValueClassType + else -> null + } +} +``` + +This will generate the schema that exposes value classes as a scalar type: + +```graphql +scalar MyValueClass + +type Query { + inlineValueClassQuery(value: MyValueClass): MyValueClass! +} +``` + +#### Representing Value Classes in the Schema as Objects + +To do this, simply use the value class directly without defining any coercers or unboxers as in the previous sections. + +This will generate the schema that exposes value classes as a wrapped type, similar to a regular class: + +```graphql +input MyValueClassInput { + value: String! +} + +type MyValueClass { + value: String! +} + +type Query { + inlineValueClassQuery(value: MyValueClassInput): MyValueClass! +} +``` + +## Common Issues + +### Extended Scalars + +By default, `graphql-kotlin` only supports the primitive scalar types listed above. If you are looking to use common java types as scalars, you need to include the [graphql-java-extended-scalars](https://github.com/graphql-java/graphql-java-extended-scalars) library and set up the hooks (see above), or write the logic yourself for how to resolve these custom scalars. + +The most popular types that require extra configuration are: `LocalDate`, `DateTime`, `Instant`, `ZonedDateTime`, `URL`, `UUID` + +### `TypeNotSupportedException` + +If you see the following message `Cannot convert ** since it is not a valid GraphQL type or outside the supported packages ***`. This means that you need to update the [generator configuration](../customizing-schemas/generator-config.md) to include the package of your type or you did not properly set up the hooks to register the new type. diff --git a/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/schema.md b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/schema.md new file mode 100644 index 0000000000..494edf1f28 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/schema.md @@ -0,0 +1,53 @@ +--- +id: schema +title: Schema +--- +## Schema Object + +`SchemaGenerator` automatically generates schema object from the provided list of `TopLevelObjects` representing `queries`, `mutations` and `subscriptions`. + +In order to provide [schema description](../customizing-schemas/documenting-schema.md) or to specify [schema directives](../customizing-schemas/directives.md), we need to provide `TopLevelObject` reference to a schema class. + +:::caution +Only annotations are processed on the schema object. Generator will throw an exception if schema class contains any properties or functions. +::: + +In the example below, we provide custom description and apply `@contact` directive on the schema object. + +```kotlin +@ContactDirective( + name = "My Team Name", + url = "https://myteam.slack.com/archives/teams-chat-room-url", + description = "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall)." +) +@GraphQLDescription("My schema description") +class MySchema + +class HelloWorldQuery { + fun helloWorld() = "Hello World!" +} + +// generate schema +val schema = toSchema( + config = yourCustomConfig(), + queries = listOf(TopLevelObject(HelloWorldQuery())), + schemaObject = TopLevelObject(MySchema()) +) +``` + +Will generate + +```graphql +schema @contact(description : "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall).", name : "My Team Name", url : "https://myteam.slack.com/archives/teams-chat-room-url"){ + query: Query +} + +type Query { + helloWorld: String! +} +``` + +:::note +`graphql-java` currently does not include schema description in the generated SDL (it is available in the introspection results only). +Fixed in https://github.com/graphql-java/graphql-java/pull/2856. +::: diff --git a/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/unions.md b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/unions.md new file mode 100644 index 0000000000..e562c1da7c --- /dev/null +++ b/website/versioned_docs/version-8.x.x/schema-generator/writing-schemas/unions.md @@ -0,0 +1,139 @@ +--- +id: unions +title: Unions +--- + +GraphQL Kotlin allows for two ways of defining unions in the schema + +## Marker Interfaces + +Marker interfaces (i.e. interfaces without any common fields or methods) are exposed as GraphQL union types. All the +types that implement the marker interface, and are available on the classpath, will be automatically exposed as +objects in the schema. + +:::note +[The GraphQL spec](http://spec.graphql.org/June2018/#sec-Unions) does not allow unions to be used as input. +This means that while it is valid Kotlin code to have a marker inteface as an argument, upon schema generation, an exception will be thrown. +::: + +```kotlin +interface BodyPart + +class LeftHand(val field: String): BodyPart + +class RightHand(val property: Int): BodyPart + +class PolymorphicQuery { + fun whichHand(whichHand: String): BodyPart = when (whichHand) { + "right" -> RightHand(12) + else -> LeftHand("hello world") + } +} +``` + +The above will generate following GraphQL schema + +```graphql +union BodyPart = LeftHand | RightHand + +type LeftHand { + field: String! +} + +type RightHand { + property: Int! +} + +type Query { + whichHand(whichHand: String!): BodyPart! +} +``` + +## `@GraphQLUnion` +:::note +Instead of this custom annotation, the [@GraphQLType](../customizing-schemas/custom-type-reference.md) annotation may be a better option +::: + +The downside to marker interface unions is that you can not edit classes included in dependencies to implement new schema unions. +For example in an SDL-First world you could have this Kotlin class defined in some library. + +```kotlin +class SharedModel(val foo: String) +``` + +And then write your schema as the following + + +```graphql +# From library +type SharedModel { + foo: String! +} + +# Defined in our schema +type ServiceModel { + bar: String! +} + +# Defined in our schema +union CustomUnion = SharedModel | ServiceModel + +type Query { + getModel: CustomUnion +} +``` + +But this is not currently possible in the full code-generation approach. Instead, you will need to use the `@GraphQLUnion` annotation on your functions or properties. + +### Example Usage +```kotlin +// Defined in some other library +class SharedModel(val foo: String) + +// Our code +class ServiceModel(val bar: String) + +class Query { + @GraphQLUnion( + name = "CustomUnion", + possibleTypes = [SharedModel::class, ServiceModel::class], + description = "Return one or the other model" + ) + fun getModel(): Any = ServiceModel("abc") +} +``` + +If directives are needed, this can also be used as a meta-annotation + +### Example Usage +```kotlin +// Defined in some other library +class SharedModel(val foo: String) + +// Our code +class ServiceModel(val bar: String) + + +@SomeDirective +@GraphQLUnion( + name = "CustomUnion", + possibleTypes = [SharedModel::class, ServiceModel::class], + description = "Return one or the other model" +) +annotation class CustomUnion + +class Query { + @CustomUnion + fun getModel(): Any = ServiceModel("abc") +} +``` + +The annotation requires the `name` of the new union to create and the `possibleTypes` that this union can return. +However since we can not enforce the type checks anymore, you must use `Any` as the return type. + +### Limitations +Even when using it as a meta-annotation, it is not always possible to add directives to the union definition +if the directive annotation cannot apply to an annotation class. +You will have to modify the type with [schema generator hooks](../customizing-schemas/generator-config.md). + +[@GraphQLType](../customizing-schemas/custom-type-reference.md) annotation can be used as a workaround to this issue. diff --git a/website/versioned_docs/version-8.x.x/server/automatic-persisted-queries/automatic-persisted-queries.md b/website/versioned_docs/version-8.x.x/server/automatic-persisted-queries/automatic-persisted-queries.md new file mode 100644 index 0000000000..8050ddab50 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/automatic-persisted-queries/automatic-persisted-queries.md @@ -0,0 +1,147 @@ +--- +id: automatic-persisted-queries +title: Automatic Persisted Queries (APQ) +--- + +[APQ is technique created by Apollo](https://www.apollographql.com/docs/apollo-server/performance/apq/) to improve +GraphQL network performance with zero build-time configuration by sending smaller [GraphQL HTTP requests](https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md). +A smaller request payload reduces bandwidth utilization and speeds up GraphQL Client loading times. + +A persisted query is a parsed and validated query document that is cached on a GraphQL Server, along with a unique identifier (SHA-256 hash). +This way clients can send this identifier instead of the corresponding query which will drastically reduce the request size. + +To persist a query, a GraphQL Server must first receive it from a Client, then, subsequent requests can just include the identifier +instead of the query. + +```mermaid +sequenceDiagram; + Client->>GraphQL Kotlin Server: Sends SHA-256 hash of query to execute + Note over GraphQL Kotlin Server: Fails to find persisted query + GraphQL Kotlin Server->>Client: Responds with error + Client->>GraphQL Kotlin Server: Sends both query AND SHA-256 hash + Note over GraphQL Kotlin Server: Persists query and SHA-256 hash + GraphQL Kotlin Server->>Client: Executes query and returns result + Note over Client: Time passes + Client->>GraphQL Kotlin Server: Sends SHA-256 hash of query to execute + Note over GraphQL Kotlin Server: Finds persisted query previously parsed and validated + GraphQL Kotlin Server->>Client: Executes query and returns result + Note over Client: Client can opt-out from sending SHA-256 hash + Client->>GraphQL Kotlin Server: Sends query without SHA-256 + Note over GraphQL Kotlin Server: Still persist parsed and validated query + GraphQL Kotlin Server->>Client: Executes query and returns result + Client->>GraphQL Kotlin Server: Sends query without SHA-256 + Note over GraphQL Kotlin Server: Finds parsed and validated query +``` + +## AutomaticPersistedQueriesProvider + +To support APQ we have created `AutomaticPersistedQueriesProvider` which is an implementation of +the [graphql-java PreparsedDocumentProvider](https://github.com/graphql-java/graphql-java/blob/master/src/main/java/graphql/execution/preparsed/PreparsedDocumentProvider.java), +which is the interface that allows caching for parsed and validated GraphQL Documents (AST). + +`AutomaticPersistedQueriesProvider` requires the implementation of `AutomaticPersistedQueriesCache` as a constructor argument. +`AutomaticPersistedQueriesCache` is an interface that specifies the contract of how to retrieve documents. + +**Note:** `graphql-kotlin-automatic-persisted-queries` provides a default in-memory cache implementation of `AutomaticPersistedQueriesCache` +called `DefaultAutomaticPersistedQueriesCache`. In order to take full advantage of Automatic Persisted Queries, it's recommended +to use an external cache mechanism like Redis. + +### Usage in GraphQL Kotlin Server + +Provide an instance of `AutomaticPersistedQueryProvider` in the GraphQL engine builder `preparsedDocumentProvider` method: + +```kotlin +val schema = "your schema" +val runtimeWiring = RuntimeWiring.newRuntimeWiring().build() // your runtime wiring +val automaticPersistedQueryProvider = AutomaticPersistedQueriesProvider(DefaultAutomaticPersistedQueriesCache()) + +val graphQL = GraphQL + .newGraphQL(SchemaGenerator().makeExecutableSchema(SchemaParser().parse(schema), runtimeWiring)) + .preparsedDocumentProvider(automaticPersistedQueryProvider) + .build() +``` + +:::info +`graphql-kotlin-spring-server` provides convenient integration of Automatic Persisted Queries functionality through simple configuration. +Automatic Persisted Queries can be enabled by configuring following properties: +```yaml +graphql: + automaticPersistedQueries: + enabled: true +``` +::: + +If you want to implement a different cache mechanism using Caffeine or REDIS you can just simply provide a Spring Bean and the +autoconfigured `AutomaticPersistedQueryProvider` will use your custom implementation of the cache. + +```kotlin +class REDISAutomaticPersistedQueriesCache : AutomaticPersistedQueriesCache { + override fun getOrElse(key: String, supplier: () -> PreparsedDocumentEntry): CompletableFuture { + // your implementation + } +} + +@Configuration +class ApplicationConfiguration { + @Bean + fun redisAutomaticPersistedQueriesCache(): PreparsedDocumentProvider = REDISAutomaticPersistedQueriesCache() +} +``` +### Usage in Clients + +Clients that want to use APQ will need to calculate the unique identifier (SHA-256 hash) of a query and send it +in the extension section of a [GraphQL HTTP request](https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#json-encoding) + +Example Payload: + +```json +{ + "variables": { + "foo": "bar" + }, + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38" + } + } +} +``` + +As seen, in an APQ request, the query field of the request payload is optional. + +### Errors + +All errors are going to be provided in the `errors` array in the JSON Response: + +1. `PersistedQueryNotFound` if `query` was not provided and preparsed document could not be found in the cache using the provided `sha256Hash` +the GraphQL Error response will be: +```json +{ + "errors": [ + { + "message": "PersistedQueryNotFound", + "extensions": { + "persistedQueryId": "ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38", + "classification": "PersistedQueryNotFound" + } + } + ] +} +``` + +2. `PersistedQueryIdInvalid` if `query` was provided but the provided `sha256Hash` does not match with the `query` +the GraphQL Error response will be: +```json +{ + "errors": [ + { + "message": "PersistedQueryIdInvalid", + "extensions": { + "persistedQueryId": "ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38", + "classification": "PersistedQueryIdInvalid" + } + } + ] +} +``` diff --git a/website/versioned_docs/version-8.x.x/server/data-loader/data-loader-instrumentation.mdx b/website/versioned_docs/version-8.x.x/server/data-loader/data-loader-instrumentation.mdx new file mode 100644 index 0000000000..6568aa8a8b --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/data-loader/data-loader-instrumentation.mdx @@ -0,0 +1,223 @@ +--- +id: data-loader-instrumentation +title: Data Loader Instrumentations +--- + + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +`graphql-kotlin-dataloader-instrumentation` is set of custom [Instrumentations](https://www.graphql-java.com/documentation/instrumentation/) +that will calculate the right moment to dispatch `KotlinDataLoader`s across single or batch GraphQL operations. + +These custom instrumentations follow the similar approach as the default [DataLoaderDispatcherInstrumentation](https://github.com/graphql-java/graphql-java/blob/master/src/main/java/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentation.java) +from `graphql-java`, the main difference is that regular instrumentations apply to a single `ExecutionInput` aka [GraphQL Operation](https://www.graphql-java.com/documentation/execution#queries), +whereas these custom instrumentations apply to multiple GraphQL operations (say a BatchRequest) and stores their state in the `GraphQLContext` +allowing batching and deduplication of transactions across those multiple GraphQL operations. + +By default, each GraphQL operation is processed independently of each other. Multiple operations can be processed +together as if they were single GraphQL request if they are part of the same batch request. + +The `graphql-kotlin-dataloader-instrumentation` module contains 1 custom `DataLoader` instrumentation. + +## Dispatching by synchronous execution exhaustion + +The most optimal time to dispatch all data loaders is when all possible synchronous execution paths across all batch +operations were exhausted. Synchronous execution path is considered exhausted (or completed) when all currently processed +data fetchers were either resolved to a scalar or a future promise. + +Let's analyze how GraphQL execution works, but first lets check some GraphQL concepts: + +**DataFetcher** + +Each field in GraphQL has a resolver aka `DataFetcher` associated with it, some fields will use specialized `DataFetcher`s +that knows how to go to a database or make a network request to get field information while most simply take +data from the returned memory objects. + + +**Execution Strategy** + +The process of finding values for a list of fields from the GraphQL Query, using a recursive strategy. + +### Example + +You can find additional examples in our [unit tests](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentationTest.kt). + + + + +```graphql +query Q1 { + astronaut(id: 1) { # async + id + name + missions { # async + id + designation + } + } +} + +query Q2 { + nasa { #sync + astronaut(id: 2) { # async + id + name + missions { # async + id + designation + } + } + address { # sync + street + zipCode + } + phoneNumber + } +} +``` + + + +![Image of data loader level dispatched instrumentation](../../assets/data-loader-level-sync-execution-exhausted-instrumentation.png) + +**The order of execution of the queries will be:** + +***for Q1*** +1. Start an `ExecutionStrategy` for the `root` field of the query, to concurrently resolve `astronaut` field. + * `astronaut` **DataFetcher** will invoke the `AstronautDataLoader` and will return a `CompletableFuture` so we can consider this path exhausted. + +***for Q2*** +1. Start an `ExecutionStrategy` for the `root` field of the query, to concurrently resolve `nasa` field. + * `nasa` **DataFetcher** will synchronously return a `Nasa` object, so we can descend more that path. +2. Start an `ExecutionStrategy` for the `nasa` field of the `root` field of the query to concurrently resolve `astronaut`, `address` and `phoneNumber`. + * `astronaut` **DataFetcher** will invoke the `AstronautDataLoader` and will return a `CompletableFuture` so we can consider this path exhausted + * `address` **DataFetcher** will synchronously return an `Address` object, so we can descend more that path. + * `phoneNumber` **DataFetcher** will return a scalar, so we can consider this path exhausted. +3. Start an `ExecutionStrategy` for the `address` field of the `nasa` field to concurrently resolve `street` and `zipCode`. + * `street` **DataFetcher** will return a scalar, so we can consider this path exhausted. + * `zipCode` **DataFetcher** will return a scalar, so we can consider this path exhausted. + +**At this point we can consider the synchronous execution exhausted and the `AstronautDataLoader` has 2 keys to be dispatched, +if we proceed dispatching all data loaders the execution will continue as following:** + +***for Q1*** +1. Start and `ExecutionStrategy` for the `astronaut` field of the `root` field of the query to concurrently resolve `id`, `name` and `mission` fields. + * `id` **DataFetcher** will return a scalar, so we can consider this path exhausted. + * `name` **DataFetcher** will return a scalar, so we can consider this path exhausted. + * `missions` **DataFetcher** will invoke the `MissionsByAstronautDataLoader` and will return a `CompletableFuture>` so we can consider this path exhausted. + +***for Q2*** +1. Start and `ExecutionStrategy` for the `astronaut` field of the `nasa` field of the query to concurrently resolve `id`, `name` and `mission` fields. + * `id` **DataFetcher** will return a scalar, so we can consider this path exhausted. + * `name` **DataFetcher** will return a scalar, so we can consider this path exhausted. + * `missions` **DataFetcher** will invoke the `MissionsByAstronautDataLoader` and will return a `CompletableFuture>` so we can consider this path exhausted. + +**At this point we can consider the synchronous execution exhausted and the `MissionsByAstronautDataLoader` has 2 keys to be dispatched, +if we proceed dispatching all data loaders the execution will continue to just resolve scalar fields.** + + + + +### Usage + +In order to enable batching by synchronous execution exhaustion, you need to configure your GraphQL instance with the `DataLoaderSyncExecutionExhaustedInstrumentation`. + +```kotlin +val graphQL = GraphQL.Builder() + .doNotAddDefaultInstrumentations() + .instrumentation(DataLoaderSyncExecutionExhaustedInstrumentation()) + // configure schema, type wiring, etc. + .build() +``` + +This data loader instrumentation relies on a global state object that needs be stored in the GraphQLContext map + +```kotlin +val graphQLContext = mapOf( + SyncExecutionExhaustedState::class to SyncExecutionExhaustedState( + queries.size, + kotlinDataLoaderRegistry + ) +) +``` + +:::info +`graphql-kotlin-spring-server` provides convenient integration of batch loader functionality through simple configuration. +Batching by synchronous execution exhaustion can be enabled by configuring following properties: +```yaml +graphql: + batching: + enabled: true + strategy: SYNC_EXHAUSTION +``` +::: + +## Multiple data loaders per field data fetcher + +There are some cases when a GraphQL Schema doesn't match the data source schema, a field can require data from multiple +sources to be fetched and you will still want to do batching with data loaders. + +### DispatchIfNeeded + +`graphql-kotlin-dataloader-instrumentation` includes a helpful extension function of the `CompletableFuture` class +so that you can easily instruct the [previously selected data loader instrumentation](./data-loader-instrumentation#dispatching-by-level) +that you want to apply batching and deduplication to a chained `DataLoader` in your `DataFetcher` (resolver). + +### Example + +```graphql +type Query { + astronaut(id: ID!): Astronaut +} + +# In the data source, let's say a database, +# an `Astronaut` can have multiple `Mission`s and a `Mission` can have multiple `Planet`s. +type Astronaut { + id: ID! + name: String! + # The schema exposes the `Astronaut` `Planet`s, without traversing his `Mission`s. + planets: [Planet!]! +} + +type Planet { + id: ID! + name: String! +} +``` +The `Astronaut` `planets` data fetcher (resolver) will contain the logic to chain two data loaders, +first collect missions by astronaut, and then, planets by mission. + +**DataLoaders** + +For this specific example we would need 2 `DataLoader`s + +1. **MissionsByAstronaut:** to retrieve `Mission`s by a given `Astronaut`. +2. **PlanetsByMission:** to retrieve `Planet`s by a given `Mission`. + +**Fetching logic** +```kotlin +class Astronaut { + fun getPlanets( + astronautId: Int, + environment: DataFetchingEnvironment + ): CompletableFuture> { + val missionsByAstronautDataLoader = environment.getDataLoader("MissionsByAstronautDataLoader") + val planetsByMissionDataLoader = environment.getDataLoader("PlanetsByMissionDataLoader") + return missionsByAstronautDataLoader + .load(astronautId) + // chain data loader + .thenCompose { missions -> + planetsByMissionDataLoader + .loadMany(missions.map { mission -> mission.id }) + // extension function to schedule a dispatch of registry if needed + .dispatchIfNeeded(environment) + } +} +``` diff --git a/website/versioned_docs/version-8.x.x/server/data-loader/data-loader.md b/website/versioned_docs/version-8.x.x/server/data-loader/data-loader.md new file mode 100644 index 0000000000..d155d24f4f --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/data-loader/data-loader.md @@ -0,0 +1,210 @@ +--- +id: data-loader +title: Data Loaders +--- +Data Loaders are a popular caching pattern from the [JavaScript GraphQL implementation](https://github.com/graphql/dataloader). +`graphql-java` provides [support for this pattern](https://www.graphql-java.com/documentation/v16/batching/) +using the `DataLoader` and `DataLoaderRegistry`. + +Since `graphql-kotlin` allows you to abstract the schema generation and data fetching code, you may not even need +data loaders if instead you have some persistant cache on your server. + +```kotlin +class User(val id: ID) { + // The friendService and userService, which have nothing to do with GraphQL, + // should be concerned with caching and batch calls instead of your schema classes + fun getFriends(): List { + val friends: List = friendService.getFriends(id) + return userService.getUsers(friends) + } + +} +``` + +If you still want to use data loaders though, they are supported through the common interfaces. + +`graphql-kotlin-dataloader` module provides convenient abstractions over the [java-dataloader](https://github.com/graphql-java/java-dataloader). + +## `KotlinDataLoader` + +To help in the registration of `DataLoaders`, we have created an interface `KotlinDataLoader`: + +```kotlin +interface KotlinDataLoader { + val dataLoaderName: String + fun getDataLoader(graphQLContext: GraphQLContext): DataLoader +} +``` + +This allows for library users to still have full control over the creation of the `DataLoader` and its various configuration +options but also allows common server code to handle the registration, generation and execution of the request. + +Additionally, the `getDataLoader` method provides access to the `graphQLContext` that was created from the [GraphQLContextFactory](../graphql-context-factory.md), +allowing you to use the `graphQLContext` as [context of the DataLoader](https://www.graphql-java.com/documentation/batching/#passing-context-to-your-data-loader). + +## `KotlinDataLoaderRegistryFactory` + +The [GraphQLRequestHandler](../graphql-request-handler.md) accepts an optional `KotlinDataLoaderRegistryFactory`. +which generates a new `KotlinDataLoaderRegistry` on every request. The registry is a map of a unique data loader names to a `DataLoader` object that handles the cache for an output type in your graph. +A `DataLoader` caches the types by some unique value, usually by the type id, and can handle different types of batch requests. + +```kotlin +class UserDataLoader : KotlinDataLoader { + override val dataLoaderName = "UserDataLoader" + override fun getDataLoader(graphQLContext: GraphQLContext) = + DataLoaderFactory.newDataLoader { ids -> + CompletableFuture.supplyAsync { + ids.map { id -> userService.getUser(id) } + } + } +} + +class FriendsDataLoader : KotlinDataLoader> { + override val dataLoaderName = "FriendsDataLoader" + override fun getDataLoader(graphQLContext: GraphQLContext) = + DataLoaderFactory.newDataLoader( + { ids, batchLoaderEnvironment -> + val context = batchLoaderEnvironment.getContext() + // do something with graphQLContext + CompletableFuture.supplyAsync { + ids.map { id -> + val friends: List = friendService.getFriends(id) + userService.getUsers(friends) + } + } + }, + DataLoaderOptions.newOptions() + .setCachingEnabled(false) + .setBatchLoaderContextProvider { graphQLContext } + ) +} + +val dataLoaderRegistryFactory = KotlinDataLoaderRegistryFactory( + UserDataLoader(), FriendsDataLoader() +) + +val dataLoaderRegistry = dataLoaderRegistryFactory.generate() +``` + +## `KotlinDataLoaderRegistry` + +[KotlinDataLoaderRegistry](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/executions/graphql-kotlin-dataloader/src/main/kotlin/com/expediagroup/graphql/dataloader/KotlinDataLoaderRegistry.kt) +is a decorator of the original `graphql-java` [DataLoaderRegistry](https://github.com/graphql-java/java-dataloader/blob/master/src/main/java/org/dataloader/DataLoaderRegistry.java) +that keeps track of all underlying `DataLoader`s futures. By keeping track of to cache map containing returned futures, +we get more granular control when to dispatch data loader calls. + +## `getValueFromDataLoader` + +`graphql-kotlin-server` includes a helpful extension function on the `DataFetchingEnvironment` so that you can easily retrieve values from the data loaders in your schema code. + +```kotlin +class User(val id: ID) { + @GraphQLDescription("Get the users friends using data loader") + fun getFriends(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture> { + return dataFetchingEnvironment.getValueFromDataLoader("FriendsDataLoader", id) + } +} +``` + +## DataLoaders and Coroutines + +`graphql-java` relies on `CompletableFuture`s for scheduling and asynchronously executing GraphQL operations. +While we can provide native support for coroutines for data fetchers (aka field resolvers) because they are resolved +independently, we cannot easily provide native support for the `DataLoader` pattern as it relies +on `CompletableFuture` state machine internals and we cannot update it to use coroutines without fully rewriting +GraphQL Java execution engine. + +If you would like to use `DataLoader` pattern in your project, you have to update your data fetchers (aka field resolvers) to return +`CompletableFuture` from the invoked `DataLoader`. + +### Example + +Consider the following query: + +```graphql +fragment UserFragment on User { + id + name +} +query GetUsersFriends { + user_1: user(id: 1) { + ...UserFragment + } + user_2: user(id: 2) { + ...UserFragment + } +} +``` + +And the corresponding code that will autogenerate schema: + +```kotlin +class MyQuery( + private val userService: UserService +) : Query { + suspend fun getUser(id: Int): User = userService.getUser(id) +} + +class UserService { + suspend fun getUser(id: Int): User = // async logic to get user + suspend fun getUsers(ids: List): List = // async logic to get users +} +``` + +When we execute the above query, we will end up calling `UserService#getUser` twice which will result in two independent +downstream service/database calls. This problem is called N+1 problem. By using `DataLoader` pattern, +we can solve this problem and only make a single downstream request/query. + +Lets create the `UserDataLoader`: + +```kotlin +class UserDataLoader : KotlinDataLoader { + override val dataLoaderName = "UserDataLoader" // 1 + override fun getDataLoader(graphQLContext: GraphQLContext) = // 2 + DataLoaderFactory.newDataLoader( + { ids, batchLoaderEnvironment -> + val coroutineScope = // 3 + batchLoaderEnvironment.getContext()?.get() + ?: CoroutineScope(EmptyCoroutineContext) // 4 + + coroutineScope.future { // 5 + userService.getUsers(ids) + } + }, + DataLoaderOptions.newOptions() + .setBatchLoaderContextProvider { graphQLContext } + ) +} + +``` + +There are some things going on here: + +1. We define the `UserDataLoader` with name "UserDataLoader". +2. The `KotlinDataLoader#getDataLoader(GraphQLContext)` method returns a `DataLoader`, which `BatchLoader` function should return a `List` and `BatchLoaderEnvironment` context is the `GraphQLContext`. +3. Given that we **don't want** to change our `UserService` async model that is using coroutines, we need a `CoroutineScope`, [which is conveniently available](../../schema-generator/execution/async-models/#coroutines) in the `GraphQLContext` and accessible through the `BatchLoaderEnvironment`. +4. After retrieving the `CoroutineScope` from the `batchLoaderEnvironment` we will be able to execute the `userService.getUsers(ids)` suspendable function. +5. We interoperate the suspendable function result to a `CompletableFuture` using [coroutineScope.future](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/future.html). + +Finally, we need to update `user` field resolver, to return the `CompletableFuture` from the invoked `DataLoader`. +Make sure to update method signature to also accept the `dataFetchingEnvironment` as you need to pass it to `DataLoader#load` method to be able to execute the request in appropriate coroutine scope. + +```kotlin +class MyQuery( + private val userService: UserService +) : Query { + fun getUser(id: Int, dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture = + dataFetchingEnvironment + .getDataLoader("UserDataLoader") + .load(id, dataFetchingEnvironment) +} + +class UserService { + suspend fun getUser(id: Int): User { + // logic to get user + } + suspend fun getUsers(ids: List): List { + // logic to get users, this method is called from the DataLoader + } +} +``` diff --git a/website/versioned_docs/version-8.x.x/server/graphql-context-factory.md b/website/versioned_docs/version-8.x.x/server/graphql-context-factory.md new file mode 100644 index 0000000000..283c936bd1 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/graphql-context-factory.md @@ -0,0 +1,66 @@ +--- +id: graphql-context-factory +title: GraphQLContextFactory +--- + +:::note +If you are using `graphql-kotlin-spring-server`, see the [Spring specific documentation](./spring-server/spring-graphql-context.md). +::: + +`GraphQLContextFactory` provides a generic mechanism for generating a GraphQL context for each request. + +```kotlin +interface GraphQLContextFactory { + suspend fun generateContext(request: Request): GraphQLContext = + emptyMap().toGraphQLContext() +} +``` + +Given the generic server request, the interface should attempt to create a `GraphQLContext` to be used for every new operation. +interface from `graphql-kotlin-schema-generator`. See [execution context](../schema-generator/execution/contextual-data.md) +for more info on how the context can be used in the schema functions. + +## Coroutine Context + +By default, `graphql-kotlin-server` creates a supervisor scope with currently available coroutine context. You can provide +additional context elements using `GraphQLContextFactory` by populating `CoroutineContext::class` entry in the context map +or by implementing `graphQLCoroutineContext()` (deprecated) on a custom context object. + +```kotlin +@Component +class MyCustomContextFactory : GraphQLContextFactory() { + override suspend fun generateContext(request: ServerRequest): GraphQLContext = + mapOf( + CoroutineContext::class to MDCContext() + ).toGraphQLContext() +} +``` + +`GraphQLServer` will then attempt to create supervisor coroutine scope by combining current coroutine context with custom +coroutine context provided by the `GraphQLContextFactory`. This scope will then be used by `FunctionDataFetcher` to execute +all suspendable functions. + +## Suspendable Factory +The interface is marked as a `suspend` function to allow the asynchronous fetching of context data. +This may be helpful if you need to call some other services to calculate a context value. + +## Server-Specific Abstractions + +A specific `graphql-kotlin-*-server` library may provide an abstract class on top of this interface so users only have to +be concerned with the context class and not the server class type. +For example the `graphql-kotlin-spring-server` provides the following class, which sets the request type: + +```kotlin +abstract class SpringGraphQLContextFactory : GraphQLContextFactory +``` + +## HTTP Headers and Cookies + +For common use cases around authorization, authentication, or tracing you may need to read HTTP headers and cookies. +This should be done in the `GraphQLContextFactory` and relevant data should be added to the context to be accessible during schema execution. + +## Federated Tracing + +See [federation tracing support](../schema-generator/federation/federation-tracing.md) documentation for details. + +The reference server implementation `graphql-kotlin-spring-server` [supports federated tracing in the context](./spring-server/spring-graphql-context.md). diff --git a/website/versioned_docs/version-8.x.x/server/graphql-request-handler.md b/website/versioned_docs/version-8.x.x/server/graphql-request-handler.md new file mode 100644 index 0000000000..c914f70bea --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/graphql-request-handler.md @@ -0,0 +1,13 @@ +--- +id: graphql-request-handler +title: GraphQLRequestHandler +--- +The `GraphQLRequestHandler` is an open and extendable class that contains the basic logic to get a `GraphQLResponse`. + +It requires a `GraphQLSchema` and a [KotlinDataLoaderRegistryFactory](./data-loader/data-loader.md) in the constructor. +For each request, it accepts a `GraphQLRequest` and an optional [GraphQLContext](graphql-context-factory.md), +and calls the `KotlinDataLoaderRegistryFactory` to generate a new `KotlinDataLoaderRegistry`. Then all of these objects are sent to the schema for +execution and the result is mapped to a `GraphQLResponse`. + +There shouldn't be much need to change this class but if you wanted to add custom logic +or logging it is possible to override it or just create your own. diff --git a/website/versioned_docs/version-8.x.x/server/graphql-request-parser.md b/website/versioned_docs/version-8.x.x/server/graphql-request-parser.md new file mode 100644 index 0000000000..fd12e5aaa1 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/graphql-request-parser.md @@ -0,0 +1,39 @@ +--- +id: graphql-request-parser +title: GraphQLRequestParser +--- +The `GraphQLRequestParser` interface is required to parse the library-specific HTTP request object into the common `GraphQLServerRequest` class. + +```kotlin +interface GraphQLRequestParser { + suspend fun parseRequest(request: Request): GraphQLServerRequest? +} +``` + +While not officially part of the spec, there is a standard format used by most GraphQL clients and servers for [serving GraphQL over HTTP](https://graphql.org/learn/serving-over-http/). +Following the above convention, GraphQL clients should generally use HTTP POST requests with the following body structure + +```json +{ + "query": "...", + "operationName": "...", + "variables": { "myVariable": "someValue" } +} +``` + +where + +- `query` is a required field and contains the operation (query, mutation, or subscription) to be executed +- `operationName` is an optional string, only required if multiple operations are specified in the `query` string. +- `variables` is an optional map of JSON objects that are referenced as input arguments in the `query` string + +GraphQL Kotlin server supports both single and batch GraphQL requests. Batch requests are represented as a list of individual +GraphQL requests. When processing batch requests, the same context will be used for processing all requests and the server will +respond with a list of GraphQL responses. + +If the request is not a valid GraphQL format, the interface should return `null` and let the server specific code return a bad request status to the client. +This is not the same as a GraphQL error or an exception thrown by the schema. +Those types of errors should still parse the request and return a valid response with errors set via the [GraphQLRequestHandler](./graphql-request-handler.md). + +This interface should only be concerned with parsing the request, not about forwarding info to the context or execution. +That is handled by the [GraphQLContextFactory](./graphql-context-factory.md). diff --git a/website/versioned_docs/version-8.x.x/server/graphql-server.md b/website/versioned_docs/version-8.x.x/server/graphql-server.md new file mode 100644 index 0000000000..9a51638c97 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/graphql-server.md @@ -0,0 +1,35 @@ +--- +id: graphql-server +title: GraphQLServer +--- +`graphql-kotlin-server` provides common code and basic interfaces to setup a GraphQL server in any framework. + +The official reference implementations are: + +- [graphql-kotlin-spring-server](./spring-server/spring-overview.mdx) + +We recommend using one of the implementations as the common code has very little logic but you can still use the common +package to create implementation for other libraries (Ktor, Spark, etc). + +There are demos of how to use these server libraries in the `/examples` folder of the repo. + +## `GraphQLServer` + +The top level object in the common package is `GraphQLServer`. +This class is open for extensions and requires that you specify the type of the http requests you will be handling. + +- For [Spring Reactive](https://spring.io/reactive) we would define a `GraphQLServer` +- For [Ktor](https://ktor.io/) we would define a `GraphQLServer` + +In its simplest form, a GraphQL server has the following responsibilties: + +- Parse the GraphQL request info from the HTTP request +- Create a `GraphQLContext` object from the HTTP request to be used during execution +- Send the request and the context to the GraphQL schema to execute and get a response (may contain `data` or `errors`) +- Send the reponse back to the client over HTTP + +Most of the logic in a GraphQL server that is specific to your application is already in the schema, so if we have interfaces for all these +common functions, we can abstract away the library specific features. + +The one method we don't have an interface for is sending back the response to the client. Once you get the response back from `GraphQLServer`, +we leave the rest up to your application to call it's server specific methods to encode and send the response. diff --git a/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-configuration.md b/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-configuration.md new file mode 100644 index 0000000000..275ebf5bb4 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-configuration.md @@ -0,0 +1,201 @@ +--- +id: ktor-configuration +title: Ktor Plugin Configuration +--- + +`graphql-kotlin-ktor-server` plugin can be configured by using DSL when installing the plugin. Configuration is broken into multiple +groups related to specific functionality. See sections below for details. + +```kotlin +install(GraphQL) { + schema { + // configuration that controls schema generation logic + } + engine { + // configurations that control GraphQL execution engine + } + server { + // configurations that control GraphQL HTTP server + } +} +``` + +## Configuration Files + +Ktor supports specifying configurations in `application.conf` (HOCON) or `application.yaml` file. By default, only HOCON format +is supported. To use a YAML configuration file, you need to add the `ktor-server-config-yaml` dependency to your project dependencies. +See [Ktor documentation](https://ktor.io/docs/configuration-file.html) for details. + +:::caution +Not all configuration properties can be specified in your configuration file. You will need to use DSL to configure more advanced features +that cannot be represented in the property file syntax (e.g. any instantiated objects). +::: + +All configuration options in `application.conf` format, with their default values are provided below. + +```kotlin +graphql { + schema { + // this is a required property that you need to set to appropriate value + // example value is just provided for illustration purposes + packages = [ + "com.example" + ] + federation { + enabled = false + tracing { + enabled = true + debug = false + } + } + } + engine { + automaticPersistedQueries { + enabled = false + } + batching { + enabled = false + strategy = LEVEL_DISPATCHED + } + introspection { + enabled = true + } + } +} +``` + +## Schema Configuration + +This section configures `graphql-kotlin-schema-generation` logic and is the **only** section that has to be configured. +At a minimum you need to configure the list of packages that can contain your GraphQL schema definitions and a list of queries. + +All configuration options, with their default values are provided below. + +```kotlin +schema { + // this is a required property that you need to set to appropriate value + // example value is just provided for illustration purposes + packages = listOf("com.example") + // non-federated schemas, require at least a single query + queries = listOf() + mutations = listOf() + subscriptions = listOf() + schemaObject = null + // federated schemas require federated hooks + hooks = NoopSchemaGeneratorHooks + topLevelNames = TopLevelNames() + federation { + enabled = false + tracing { + enabled = true + debug = false + } + } + // required for GraalVM native servers + typeHierarchy = null +} +``` + +## GraphQL Execution Engine Configuration + +This section configures `graphql-java` execution engine that will be used to process your GraphQL requests. + +All configuration options, with their default values are provided below. + +```kotlin +engine { + automaticPersistedQueries { + enabled = false + } + // DO NOT enable default batching logic if specifying custom provider + batching { + enabled = false + strategy = SYNC_EXHAUSTION + } + introspection { + enabled = true + } + dataFetcherFactoryProvider = SimpleKotlinDataFetcherFactoryProvider() + dataLoaderRegistryFactory = KotlinDataLoaderRegistryFactory() + exceptionHandler = SimpleDataFetcherExceptionHandler() + executionIdProvider = null + idValueUnboxer = IDValueUnboxer() + instrumentations = emptyList() + // DO NOT specify custom provider if enabling default batching logic + preparsedDocumentProvider = null +} +``` + +## Server Configuration + +This section configures your GraphQL HTTP server. + +All configuration options, with their default values are provided below. + +```kotlin +server { + contextFactory = DefaultKtorGraphQLContextFactory() + jacksonConfiguration = { } + requestParser = KtorGraphQLRequestParser(jacksonObjectMapper().apply(jacksonConfiguration)) +} +``` + +## Routes Configuration + +GraphQL Kotlin Ktor Plugin DOES NOT automatically configure any routes. You need to explicitly configure `Routing` +plugin with GraphQL routes. This allows you to selectively enable routes and wrap them in some additional logic (e.g. `Authentication`). + +GraphQL Kotlin Ktor Plugin provides following `Route` extensions that can be called when configuring `Routing` plugin. + +### GraphQL POST route + +This is the main route for processing your GraphQL requests. By default, it will use `/graphql` endpoint and respond +using chunked encoding. + +```kotlin +fun Route.graphQLPostRoute(endpoint: String = "graphql", streamingResponse: Boolean = true, jacksonConfiguration: ObjectMapper.() -> Unit = {}): Route +``` + +### GraphQL GET route + +:::caution +Only `Query` operations are supported by the GET route. +::: + +GraphQL route for processing GET requests. By default, it will use `/graphql` endpoint and respond using chunked encoding. + +```kotlin +fun Route.graphQLGetRoute(endpoint: String = "graphql", streamingResponse: Boolean = true, jacksonConfiguration: ObjectMapper.() -> Unit = {}): Route +``` + +### GraphQL Subscriptions route + +GraphQL route for processing subscriptions. By default, it will use `/subscriptions` endpoint and handle +requests using [graphql-transport-ws](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) protocol handler. + +```kotlin +fun Route.graphQLSubscriptionsRoute( + endpoint: String = "subscriptions", + protocol: String? = null, + handlerOverride: KtorGraphQLSubscriptionHandler? = null, +) +``` + +See related [Subscriptions](./ktor-subscriptions.md) document for more info. + +### GraphQL SDL route + +Convenience route to expose endpoint that returns your GraphQL schema in SDL format. + +```kotlin +fun Route.graphQLSDLRoute(endpoint: String = "sdl"): Route +``` + +### GraphiQL IDE route + +[GraphiQL IDE](https://github.com/graphql/graphiql) is a convenient tool that helps you to easily interact +with your GraphQL server. + +```kotlin +fun Route.graphiQLRoute(endpoint: String = "graphiql", graphQLEndpoint: String = "graphql"): Route +``` diff --git a/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-graphql-context.md b/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-graphql-context.md new file mode 100644 index 0000000000..d4772d0f3d --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-graphql-context.md @@ -0,0 +1,48 @@ +--- +id: ktor-graphql-context +title: Generating GraphQL Context +--- + +`graphql-kotlin-ktor-server` provides a Ktor specific implementation of [GraphQLContextFactory](../graphql-context-factory.md) +and the context. + +* `KtorGraphQLContextFactory` - Generates GraphQL context map with federated tracing information per request + +If you are using `graphql-kotlin-ktor-server`, you should extend `DefaultKtorGraphQLContextFactory` to automatically +support federated tracing. + +```kotlin +class CustomGraphQLContextFactory : DefaultKtorGraphQLContextFactory() { + override suspend fun generateContext(request: ApplicationRequest): GraphQLContext = + super.generateContext(request).plus( + mapOf("myCustomValue" to (request.headers["my-custom-header"] ?: "defaultContext")) + ) +} + +fun Application.graphQLModule() { + install(GraphQL) { + schema { + packages = listOf("com.example") + queries = listOf(...) + } + server { + contextFactory = CustomGraphQLContextFactory() + } + } + routing { + graphQLPostRoute() + } +} +``` + +Once your application is configured to build your custom GraphQL context, you can then access it through a data fetching +environment argument. While executing the query, data fetching environment will be automatically injected to the function input arguments. +This argument will not appear in the GraphQL schema. + +For more details, see the [Contextual Data Documentation](../../schema-generator/execution/contextual-data.md). + +## Federated Context + +If you need [federation tracing support](../../schema-generator/federation/federation-tracing.md), you can set the appropriate +[configuration properties](./ktor-configuration.md). The provided `DefaultKtorGraphQLContextFactory` populates the required +information for federated tracing, so as long as you extend this context class you will maintain feature support. diff --git a/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-http-request-response.md b/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-http-request-response.md new file mode 100644 index 0000000000..cf1285a21f --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-http-request-response.md @@ -0,0 +1,68 @@ +--- +id: ktor-http-request-response +title: HTTP request and response +--- + +Ktor HTTP request/response can be intercepted by installing various plugins to your module or by intercepting specific +phases of application call pipeline. By installing `graphql-kotlin-ktor-server` plugin you will configure following pipeline + +```mermaid +flowchart LR + A(Request) --> B(ContentNegotiation) + B --> C(Routing) + C --> D(GraphQL) + D --> E(Response) +``` + +## Installing Additional Plugins + +You can install additional plugins in your module next to the `GraphQL` module. See [Ktor docs](https://ktor.io/docs/plugins.html) +for details. + +```kotlin +fun Application.myModule() { + // install additional plugins + install(CORS) { ... } + install(Authentication) { ... } + install(StatusPages) { ... } + + // install graphql plugin + install(GraphQL) { + schema { + packages = listOf("com.example") + queries = listOf(TestQuery()) + } + } + // install authenticated GraphQL routes + install(Routing) { + authenticate("auth-basic") { + graphQLPostRoute() + } + } +} +``` + +## Intercepting Pipeline Phases + +You can intercept requests/responses in various phases of application call pipeline by specifying an interceptor. See +[Ktor docs](https://ktor.io/docs/custom-plugins-base-api.html#call-handling) for details. + +```kotlin +fun Application.myModule() { + install(GraphQL) { + schema { + packages = listOf("com.example") + queries = listOf(TestQuery()) + } + } + install(Routing) { + graphQLPostRoute() + } + + intercept(ApplicationCallPipeline.Monitoring) { + call.request.origin.apply { + println("Request URL: $scheme://$localHost:$localPort$uri") + } + } +} +``` diff --git a/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-overview.mdx b/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-overview.mdx new file mode 100644 index 0000000000..7a66b0ef18 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-overview.mdx @@ -0,0 +1,116 @@ +--- +id: ktor-overview +title: Ktor Server Overview +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +[graphql-kotlin-ktor-server](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/servers/graphql-kotlin-ktor-server) +is a Ktor Server Plugin that simplifies setup of your GraphQL server. + +## Setup + +The simplest way to create a new Ktor Server app is by generating one using https://start.ktor.io/. + +![Image of https://start.ktor.io/](../../assets/ktor-initializer.png) + +Once you get the sample application setup locally, you will need to add `graphql-kotlin-ktor-server` dependency: + + + + + +```kotlin +implementation("com.expediagroup", "graphql-kotlin-ktor-server", latestVersion) +``` + + + + +```xml + + com.expediagroup + graphql-kotlin-ktor-server + ${latestVersion} + +``` + + + + +## Configuration + +`graphql-kotlin-ktor-server` is a Ktor Server Plugin, and you need to manually install it in your [module](https://ktor.io/docs/modules.html). + +```kotlin +class HelloWorldQuery : Query { + fun hello(): String = "Hello World!" +} + +fun Application.graphQLModule() { + install(GraphQL) { + schema { + packages = listOf("com.example") + queries = listOf( + HelloWorldQuery() + ) + } + } + install(Routing) { + graphQLPostRoute() + } + install(StatusPages) { + defaultGraphQLStatusPages() + } +} +``` + +If you use `EngineMain` to start your Ktor server, you can specify your module configuration in your `application.conf` (default) +or `application.yaml` (requires additional `ktor-server-config-yaml` dependency) file. + +``` +ktor { + application { + modules = [ com.example.ApplicationKt.graphQLModule ] + } +} +``` + +## Content Negotiation + +:::caution +`graphql-kotlin-ktor-server` automatically configures `ContentNegotiation` plugin with [Jackson](https://github.com/FasterXML/jackson) +serialization for GraphQL GET/POST routes. `kotlinx-serialization` is currently not supported. +::: + +## Routing + +`graphql-kotlin-ktor-server` plugin DOES NOT automatically configure any routes. You need to explicitly configure `Routing` +plugin with GraphQL routes. This allows you to selectively enable routes and wrap them in some additional logic (e.g. `Authentication`). + +GraphQL plugin provides following `Route` extension functions + +- `Route#graphQLGetRoute` - GraphQL route for processing GET query requests +- `Route#graphQLPostRoute` - GraphQL route for processing POST query requests +- `Route#graphQLSDLRoute` - GraphQL route for exposing schema in Schema Definition Language (SDL) format +- `Route#graphiQLRoute` - GraphQL route for exposing [an official IDE](https://github.com/graphql/graphiql) from the GraphQL Foundation + +## StatusPages + +`graphql-kotlin-ktor-server` plugin differs from Spring as it relies on Ktor's StatusPages plugin to perform error handling. +It is recommended to use the default settings, however, if you would like to customize your error handling you can create +your own handler. One example might be if you need to catch a custom Authorization error to return a 401 status code. +Please see [Ktor's Official Documentation for StatusPages](https://ktor.io/docs/server-status-pages.html) + +## GraalVm Native Image Support + +GraphQL Kotlin Ktor Server can be compiled to a [native image](https://www.graalvm.org/latest/reference-manual/native-image/) +using GraalVM Ahead-of-Time compilation. See [Gradle plugin](../../plugins/gradle-plugin-usage-graalvm.mdx) and/or +[Maven plugin](../../plugins/maven-plugin-usage-graalvm.md) documentation for details. diff --git a/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-schema.md b/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-schema.md new file mode 100644 index 0000000000..7026542270 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-schema.md @@ -0,0 +1,61 @@ +--- +id: ktor-schema +title: Writing Schemas with Ktor +--- + +GraphQL schema, queries and mutation objects have to implement the corresponding marker interface. You can then configure +GraphQL plugin with references to your objects. + +```kotlin +@ContactDirective( + name = "My Team Name", + url = "https://myteam.slack.com/archives/teams-chat-room-url", + description = "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall)." +) +@GraphQLDescription("My schema description") +class MySchema : Schema + + +class HelloWorldQuery : Query { + fun hello(): String = "Hello World!" +} + +class UpdateGreetingMutation : Mutation { + fun updateGreeting(greeting: String): String = TODO() +} + +fun Application.graphQLModule() { + install(GraphQL) { + schema { + packages = listOf("com.example") + queries = listOf( + HelloWorldQuery() + ) + mutations = listOf( + UpdateGreetingMutation() + ) + schemaObject = MySchema() + } + } + routing { + graphQLPostRoute() + } +} +``` + +Above code will generate following GraphQL schema + +```graphql +schema @contact(description : "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall).", name : "My Team Name", url : "https://myteam.slack.com/archives/teams-chat-room-url"){ + query: Query + mutation: Mutation +} + +type Query { + hello: String! +} + +type Mutation { + updateGreeting(greeting: String!): String! +} +``` diff --git a/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-subscriptions.md b/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-subscriptions.md new file mode 100644 index 0000000000..428f360fb4 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/ktor-server/ktor-subscriptions.md @@ -0,0 +1,60 @@ +--- +id: ktor-subscriptions +title: Subscriptions +--- +_To see more details on how to implement subscriptions in your schema, see the schema generator docs on [executing subscriptions](../../schema-generator/execution/subscriptions.md). +This page lists the `graphql-kotlin-ktor-server` specific features._ + +## Prerequisites + +To start using Subscriptions, you may need install [WebSockets](https://ktor.io/docs/websocket.html) plugin to your Ktor server config. +```kotlin +install(WebSockets) +``` +See [plugin docs](https://ktor.io/docs/websocket.html#configure) to get more info about the `WebSocketOptions` configuration. + +## Flow Support + +`graphql-kotlin-ktor-server` provides support for Kotlin `Flow` by automatically configuring schema generation process with `FlowSubscriptionSchemaGeneratorHooks` +and GraphQL execution with `FlowSubscriptionExecutionStrategy`. + +:::info +If you define your subscriptions using Kotlin `Flow`, make sure to extend `FlowSubscriptionSchemaGeneratorHooks` whenever you need to provide some custom hooks. +::: + +## Subscription Protocols + +### `graphql-transport-ws` subprotocol + +We have implemented subscriptions in Ktor WebSockets following the [`graphql-transport-ws`](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) sub-protocol +from [The Guild](https://the-guild.dev/). This requires that your client send and parse messages in a specific format. +See protocol documentation for expected messages. + +```kotlin +install(Routing) { + graphQLSubscriptionsRoute() +} +``` + +## Subscription Execution Hooks + +Subscription execution hooks allow you to "hook-in" to the various stages of the connection lifecycle and execute custom logic based on the event. By default, all subscription execution hooks are no-op. +If you would like to provide some custom hooks, you can do so by providing your own implementation of `KtorGraphQLSubscriptionHooks`. + +### `onConnect` +Allows validation of connectionParams prior to starting the connection. +You can reject the connection by throwing an exception. +A `GraphQLContext` returned from this hook will be later passed to subsequent hooks. + +### `onOperation` +Called when the client executes a GraphQL operation. + +### `onOperationComplete` +Called when client's unsubscribes + +### `onDisconnect` +Called when the client disconnects + +## Example + +You can see an example implementation of a `Subscription` in the [example app](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/examples/server/ktor-server/src/main/kotlin/com/expediagroup/graphql/examples/server/ktor/schema/ExampleSubscriptionService.kt). diff --git a/website/versioned_docs/version-8.x.x/server/server-subscriptions.md b/website/versioned_docs/version-8.x.x/server/server-subscriptions.md new file mode 100644 index 0000000000..a9d53eaabb --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/server-subscriptions.md @@ -0,0 +1,13 @@ +--- +id: server-subscriptions +title: Subscriptions +--- + +GraphQL Kotlin provides `WebSocket` subscription support with the generic `Flow` based implementation of [`GraphQL WS` subscription +protocol](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md). Server implementations should extend generic abstract `GraphQLWebSocketServer` class and fill server +specific details on how to read incoming messages from the WebSocket session as well as how to send responses back to the client. + +If you are using one of the official server implementations for GraphQL Kotlin, it will have subscription handling setup for you. + +- See `graphql-kotlin-spring-server` [subscriptions](spring-server/spring-subscriptions.md) +- See `graphql-kotlin-ktor-server` [subscriptions](ktor-server/ktor-subscriptions.md) diff --git a/website/versioned_docs/version-8.x.x/server/spring-server/spring-beans.md b/website/versioned_docs/version-8.x.x/server/spring-server/spring-beans.md new file mode 100644 index 0000000000..81ad73e4c2 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/spring-server/spring-beans.md @@ -0,0 +1,103 @@ +--- +id: spring-beans +title: Automatically Created Beans +--- +`graphql-kotlin-spring-server` automatically creates all the necessary beans to start a GraphQL server. +Many of the beans are conditionally created and the default behavior can be customized by providing custom overriding beans in your application context. + +## Execution + +| Bean | Description | +|:---------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| DataFetcherExceptionHandler | GraphQL exception handler used from the various execution strategies, defaults to [SimpleDataFetcherExceptionHandler](https://www.graphql-java.com/documentation/v16/execution/) from graphql-java. | +| KotlinDataFetcherFactoryProvider | Factory used during schema construction to obtain `DataFetcherFactory` that should be used for target function (using Spring aware `SpringDataFetcher`) and property resolution. | +| KotlinDataLoader (optional) | Any number of beans created that implement `KotlinDataLoader`. See [Data Loaders](../data-loader/data-loader.md) for more details. | +| KotlinDataLoaderRegistryFactory | A factory class that creates a `KotlinDataLoaderRegistry` of all the `KotlinDataLoaders`. Defaults to empty registry. | + +## Non-Federated Schema + +:::note + +_Created only if federation is **disabled**_ + +::: + +| Bean | Description | +|:----------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| GraphQLSchema | GraphQL schema generated based on the schema generator configuration and `Query`, `Mutation` and `Subscription` objects available in the application context. | +| SchemaGeneratorConfig | Schema generator configuration information, see [Schema Generator Configuration](../../schema-generator/customizing-schemas/generator-config.md) for details. Can be customized by providing `TopLevelNames`, [SchemaGeneratorHooks](../../schema-generator/customizing-schemas/generator-config.md) and `KotlinDataFetcherFactoryProvider` beans. | +| GraphQLTypeResolver | GraphQL type resolver that is used to lookup polymorphic type hierarchy. Defaults to use `ClassGraph` to load polymorphic information directly from classpath.

**NOTE: When generating GraalVM Native Images you cannot use classpath scanning and have to explicitly provide this information.** | + +## Federated Schema + +:::note + +_Created only if federation is **enabled**_ + +::: + +| Bean | Description | +|:--------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| FederatedGraphQLTypeResolver | GraphQL type resolver that is used to lookup polymorphic type hierarchy and locate federated entities. Defaults to use `ClassGraph` to load information directly from classpath.

**NOTE: When generating GraalVM Native Images you cannot use classpath scanning and have to explicitly provide this information.** | +| FederatedTypeResolvers | List of `FederatedTypeResolvers` marked as beans that should be added to hooks. See [Federated Type Resolution](../../schema-generator/federation/type-resolution.md) for more details | +| FederatedSchemaGeneratorHooks | Schema generator hooks used to build federated schema | +| FederatedSchemaGeneratorConfig | Federated schema generator configuration information. You can customize the configuration by providing `TopLevelNames`, `FederatedSchemaGeneratorHooks` and `KotlinDataFetcherFactoryProvider` beans | +| FederatedTracingInstrumentation | If `graphql.federation.tracing.enabled` is true, it adds tracing info to the response via the [apollo federation-jvm](https://github.com/apollographql/federation-jvm) library. | +| GraphQLSchema | GraphQL schema generated based on the federated schema generator configuration and `Query`, `Mutation` and `Subscription` objects available in the application context. | + +## GraphQL Configuration + +| Bean | Description | +|:--------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Instrumentation (optional) | Any number of beans created that implement `graphql-java` [Instrumentation](https://www.graphql-java.com/documentation/v16/instrumentation/) will be pulled in. The beans can be ordered by implementing the Spring `Ordered` interface. | +| ExecutionIdProvider (optional) | Any number of beans created that implement `graphql-java` [ExecutionIdProvider](https://github.com/graphql-java/graphql-java/blob/master/src/main/java/graphql/execution/ExecutionIdProvider.java) will be pulled in. | +| PreparsedDocumentProvider (optional) | Any number of beans created that implement `graphql-java` [PreparsedDocumentProvider](https://github.com/graphql-java/graphql-java/blob/master/src/main/java/graphql/execution/preparsed/PreparsedDocumentProvider.java) will be pulled in. | +| GraphQL | GraphQL execution object generated using `GraphQLSchema` with default async execution strategies. The GraphQL object can be customized by optionally providing the above beans in the application context. | +| SpringGraphQLRequestParser | Provides the Spring specific logic for parsing the HTTP request into a common GraphQLRequest. See [GraphQLRequestParser](../graphql-request-parser.md) | +| SpringGraphQLContextFactory | Spring specific factory that uses the `ServerRequest`. The `GraphQLContext` generated can be any object. See [GraphQLContextFactory](../graphql-context-factory.md). | +| GraphQLRequestHandler | Handler invoked from `GraphQLServer` that executes the incoming request, defaults to [GraphQLRequestHandler](../graphql-request-handler.md). | +| SpringGraphQLServer | Spring specific object that takes in a `ServerRequest` and returns a `GraphQLResponse` using all the above implementations. See [GraphQLServer](../graphql-server.md) | +| IDValueUnboxer | Value unboxer that provides support for handling ID value class | + +## Subscriptions + +:::note + +_Created only if the `Subscription` marker interface is used_ + +::: + +| Bean | Description | +|:----------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| FlowSubscriptionSchemaGeneratorHooks | Schema generator hooks that provide support for using `Flow` in your subscriptions | +| WebSocketHandlerAdapter | Spring class for handling web socket http requests. See [Spring documentation](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/socket/server/support/WebSocketHandlerAdapter.html) | +| HandlerMapping | Maps websocket URL to the corresponding web socket handler | + +### graphql-transport-ws + +| Bean | Description | +|:----------------------------------------|:----------------------------------------------------------------------------------| +| SpringSubscriptionGraphQLContextFactory | Generates GraphQL subscription context based on the WebSocket session information | +| SpringGraphQLSubscriptionRequestParser | Parses incoming WebSocket messages | +| SpringGraphQLSubscriptionHooks | Provides hooks into the subscription request lifecycle | +| SubscriptionWebSocketHandler | WebSocketHandler that implements the `graphql-transport-ws` subscription protocol | + +### (deprecated) subscription-transport-ws + +| Bean | Description | +|:----------------------------------------|:----------------------------------------------------------------------------------------------------------------------| +| ApolloSubscriptionHooks | Provides hooks into the subscription request lifecycle. See [the subscription docs](spring-subscriptions.md) | +| SpringSubscriptionGraphQLContextFactory | Spring specific factory that uses the `WebSocketSession`. See [GraphQLContextFactory](../graphql-context-factory.md). | +| ApolloSubscriptionProtocolHandler | Implementation of the `subscription-transport-ws` subscription protocol | +| ApolloSubscriptionWebSocketHandler | WebSocketHandler that delegates handling of the messages to the `ApolloSubscriptionProtocolHandler` bean | + +## Fixed Beans + +The following beans cannot be overridden, but may have options to disable them: + +- Route handler for GraphQL queries and mutations endpoint. +- Route handler for the SDL endpoint. Created only if sdl route is enabled. +- Route handler for [GraphQL graphiql browser IDE](https://github.com/graphql/graphiql). Created only if graphiql is enabled. +- Route handler for the subscriptions endpoint. Created only if subscriptions are used. +- `ApolloSubscriptionProtocolHandler` for handling GraphQL subscriptions using the [`graphql-ws` protocol](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md). Created only if subscriptions are used. +- `SubscriptionWebSocketHandler` that utilizes above subscription protocol handler. Created only if subscriptions are used. diff --git a/website/versioned_docs/version-8.x.x/server/spring-server/spring-graphql-context.md b/website/versioned_docs/version-8.x.x/server/spring-server/spring-graphql-context.md new file mode 100644 index 0000000000..97e962532a --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/spring-server/spring-graphql-context.md @@ -0,0 +1,33 @@ +--- +id: spring-graphql-context +title: Generating GraphQL Context +--- +`graphql-kotlin-spring-server` provides a Spring specific implementation of [GraphQLContextFactory](../graphql-context-factory.md) +and the context. + +* `SpringGraphQLContextFactory` - Generates GraphQL context map with federated tracing information per request + +If you are using `graphql-kotlin-spring-server`, you should extend `DefaultSpringGraphQLContextFactory` to automatically +support federated tracing. + +```kotlin +@Component +class MyGraphQLContextFactory : DefaultSpringGraphQLContextFactory() { + override suspend fun generateContext(request: ServerRequest): GraphQLContext = + super.generateContext(request) + mapOf( + "myCustomValue" to (request.headers().firstHeader("MyHeader") ?: "defaultContext") + ) +} +``` + +Once your application is configured to build your custom GraphQL context, you can then access it through a data fetching +environment argument. While executing the query, data fetching environment will be automatically injected to the function input arguments. +This argument will not appear in the GraphQL schema. + +For more details, see the [Contextual Data Documentation](../../schema-generator/execution/contextual-data.md). + +## Federated Context + +If you need [federation tracing support](../../schema-generator/federation/federation-tracing.md), you can set the appropriate [configuration properties](./spring-properties.md). +The provided `DefaultSpringGraphQLContextFactory` populates the required information for federated tracing, so as long as +you extend this context class you will maintain feature support. diff --git a/website/versioned_docs/version-8.x.x/server/spring-server/spring-http-request-response.md b/website/versioned_docs/version-8.x.x/server/spring-server/spring-http-request-response.md new file mode 100644 index 0000000000..db9acb61ea --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/spring-server/spring-http-request-response.md @@ -0,0 +1,7 @@ +--- +id: spring-http-request-response +title: HTTP Request and Response +--- + +To access the HTTP request and response methods, use [Spring WebFilter](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/server/WebFilter.html). +From these filters you can modify the request and response, both before and after the GraphQL execution. diff --git a/website/versioned_docs/version-8.x.x/server/spring-server/spring-overview.mdx b/website/versioned_docs/version-8.x.x/server/spring-server/spring-overview.mdx new file mode 100644 index 0000000000..a3f4dcf899 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/spring-server/spring-overview.mdx @@ -0,0 +1,85 @@ +--- +id: spring-overview +title: Spring Server Overview +--- + + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +[graphql-kotlin-spring-server](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/servers/graphql-kotlin-spring-server) +is a Spring Boot auto-configuration library that automatically configures beans required to start up a reactive GraphQL +web server. + +## WebFlux vs WebMVC + +This library is built on a [Spring WebFlux (reactive)](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) stack which is a non-blocking alternative to a traditional [Spring Web MVC (servlet)](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html) based stack. +Since the frameworks utilize different threading models they cannot and should not be intermixed. +When building a GraphQL server using `graphql-kotlin-spring-server` all your queries and mutations should follow one of the supported [asynchronous execution models](../../schema-generator/execution/async-models.md). + +## Setup + +The simplest way to create a new Kotlin Spring Boot app is by generating one using [Spring Initializr.](https://start.spring.io/) + +![Image of https://start.spring.io/](../../assets/spring-initializer.png) + +Once you get the sample application setup locally, you will need to add `graphql-kotlin-spring-server` dependency: + + + + + +```kotlin +implementation("com.expediagroup", "graphql-kotlin-spring-server", latestVersion) +``` + + + + +```xml + + com.expediagroup + graphql-kotlin-spring-server + ${latestVersion} + +``` + + + + +## Configuration + +At a minimum, in order for `graphql-kotlin-spring-server` to automatically configure your GraphQL web server you need to +specify a list of supported packages that can be scanned for exposing your schema objects through reflections. + +You can do this through the spring application config or by overriding the `SchemaGeneratorConfig` bean. See customization below. + +```yaml +graphql: + packages: + - "com.your.package" +``` + +## Default Routes + +Your newly created GraphQL server starts up with following preconfigured default routes: + +- **/graphql** - GraphQL server endpoint used for processing queries and mutations +- **/subscriptions** - GraphQL server endpoint used for processing subscriptions +- **/sdl** - Convenience endpoint that returns current schema in Schema Definition Language format +- **/graphiql** - [An official IDE](https://github.com/graphql/graphiql) under the GraphQL Foundation + +Note: **/playground** - [Prisma Labs GraphQL Playground IDE](https://github.com/graphql/graphql-playground) endpoint +is still available, but need to be configured through the [Spring Properties](./spring-properties.md) + +## GraalVm Native Image Support + +GraphQL Kotlin Spring Server can be compiled to a [native image](https://www.graalvm.org/latest/reference-manual/native-image/) +using GraalVM Ahead-of-Time compilation. See [Gradle plugin](../../plugins/gradle-plugin-usage-graalvm.mdx) and/or +[Maven plugin](../../plugins/maven-plugin-usage-graalvm.md) documentation for details. diff --git a/website/versioned_docs/version-8.x.x/server/spring-server/spring-properties.md b/website/versioned_docs/version-8.x.x/server/spring-server/spring-properties.md new file mode 100644 index 0000000000..f79f48fb9d --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/spring-server/spring-properties.md @@ -0,0 +1,33 @@ +--- +id: spring-properties +title: Configuration Properties +--- + +`graphql-kotlin-spring-server` relies +on [GraphQLConfigurationProperties](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/servers/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/server/spring/GraphQLConfigurationProperties.kt) +to provide various customizations of the auto-configuration library. All applicable configuration properties +expose [configuration +metadata](https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html) that provide +details on the supported configuration properties. + +| Property | Description | Default Value | +|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|-------------------------------| +| graphql.endpoint | GraphQL server endpoint | graphql | +| graphql.packages | List of supported packages that can contain GraphQL schema type definitions | | +| graphql.printSchema | Boolean flag indicating whether to print the schema after generator creates it | false | +| graphql.federation.enabled | Boolean flag indicating whether to generate federated GraphQL model | false | +| graphql.federation.tracing.enabled | Boolean flag indicating whether add federated tracing data to the extensions | true (if federation enabled) | +| graphql.federation.tracing.debug | Boolean flag to log debug info in the federated tracing | false (if federation enabled) | +| graphql.introspection.enabled | Boolean flag indicating whether introspection queries are enabled | true | +| graphql.playground.enabled | Boolean flag indicating whether to enable Prisma Labs Playground GraphQL IDE | false | +| graphql.playground.endpoint | Prisma Labs Playground GraphQL IDE endpoint | playground | +| graphql.graphiql.enabled | Boolean flag indicating whether to enable GraphiQL GraphQL IDE | true | +| graphql.graphiql.endpoint | Prisma Labs Playground GraphQL IDE endpoint | graphiql | +| graphql.sdl.enabled | Boolean flag indicating whether to expose SDL endpoint | true | +| graphql.sdl.endpoint | GraphQL SDL endpoint | sdl | +| graphql.subscriptions.connectionInitTimeout | Server timeout (in milliseconds) between establishing web socket connection and receiving connection-init message | 60_000 | +| graphql.subscriptions.endpoint | GraphQL subscriptions endpoint | subscriptions | +| graphql.subscriptions.keepAliveInterval | **Deprecated**. Keep the websocket alive and send a message to the client every interval in ms. Defaults to not sending messages | null | +| graphql.subscriptions.protocol | WebSocket based subscription protocol. Supported protocols: APOLLO_SUBSCRIPTIONS_WS / GRAPHQL_WS | GRAPHQL_WS | +| graphql.batching.enabled | Boolean flag indicating whether to enable custom dataloader instrumentations for 1 or more GraphQL Operations | false | +| graphql.batching.strategy | Configure which custom dataloader instrumentation will be used | SYNC_EXHAUSTION | diff --git a/website/versioned_docs/version-8.x.x/server/spring-server/spring-schema.md b/website/versioned_docs/version-8.x.x/server/spring-server/spring-schema.md new file mode 100644 index 0000000000..fdfc89b3f7 --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/spring-server/spring-schema.md @@ -0,0 +1,94 @@ +--- +id: spring-schema +title: Writing Schemas with Spring +--- + +In order to expose your schema directives, queries, mutations, and subscriptions in the GraphQL schema create beans that +implement the corresponding marker interface and they will be automatically picked up by `graphql-kotlin-spring-server` +auto-configuration library. + +```kotlin +@ContactDirective( + name = "My Team Name", + url = "https://myteam.slack.com/archives/teams-chat-room-url", + description = "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall)." +) +@GraphQLDescription("My schema description") +@Component +class MySchema : Schema + +data class Widget(val id: ID, val value: String) + +@Component +class WidgetQuery : Query { + fun widget(id: ID): Widget = getWidgetFromDB(id) +} + +@Component +class WidgetMutation : Mutation { + fun updateWidget(id: ID, value: String): Boolean = updateWidgetInDB(id, value) +} + +@Component +class WidgetSubscription : Subscription { + fun widgetChanges(id: ID): Publisher = getPublisherOfUpdates(id) +} +``` + +will result in a Spring Boot reactive GraphQL web application with following schema. + +```graphql +schema @contact(description : "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall).", name : "My Team Name", url : "https://myteam.slack.com/archives/teams-chat-room-url"){ + query: Query + mutation: Mutation + subscription: Subscription +} + +type Widget { + id: ID! + value: String! +} + +type Query { + widget(id: ID!): Widget! +} + +type Mutation { + updateWidget(id: ID!, value: String!): Boolean! +} + +type Subscription { + widgetChanges(id: ID!): Widget! +} +``` + +## Spring Beans + +Since the top level objects are Spring components, Spring will automatically autowire dependent beans as normal. Refer to [Spring Documentation](https://docs.spring.io/spring/docs/current/spring-framework-reference/) for details. + +```kotlin +@Component +class WidgetQuery(private val repository: WidgetRepository) : Query { + fun getWidget(id: Int): Widget = repository.findWidget(id) +} +``` + +## Spring Beans in Arguments + +`graphql-kotlin-spring-server` provides Spring-aware data fetcher that automatically autowires Spring beans when they are +specified as function arguments. `@Autowired` arguments should be explicitly excluded from the GraphQL schema by also +specifying `@GraphQLIgnore`. + +```kotlin +@Component +class SpringQuery : Query { + fun getWidget(@GraphQLIgnore @Autowired repository: WidgetRepository, id: Int): Widget = repository.findWidget(id) +} +``` + +:::note +If you are using custom data fetcher make sure that you extend `SpringDataFetcher` instead of the base `FunctionDataFetcher` to keep this functionallity. +::: + +We have examples of these techniques implemented in Spring boot in the [example +app](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/examples/server/spring-server/src/main/kotlin/com/expediagroup/graphql/examples/server/spring/query/NestedQueries.kt). diff --git a/website/versioned_docs/version-8.x.x/server/spring-server/spring-subscriptions.md b/website/versioned_docs/version-8.x.x/server/spring-server/spring-subscriptions.md new file mode 100644 index 0000000000..db6524e41f --- /dev/null +++ b/website/versioned_docs/version-8.x.x/server/spring-server/spring-subscriptions.md @@ -0,0 +1,61 @@ +--- +id: spring-subscriptions +title: Subscriptions +--- +_To see more details on how to implement subscriptions in your schema, see the schema generator docs on [executing subscriptions](../../schema-generator/execution/subscriptions.md). +This page lists the `graphql-kotlin-spring-server` specific features._ + +## Flow Support + +`graphql-kotlin-spring-server` provides automatic support for Kotlin `Flow` by automatically configuring `FlowSubscriptionSchemaGeneratorHooks` +and `FlowSubscriptionExecutionStrategy` beans. + +:::info +If you define your subscriptions using Kotlin `Flow`, make sure to extend `FlowSubscriptionSchemaGeneratorHooks` whenever you need to provide some custom hooks. +::: + +## Subscription Protocols + +### `graphql-transport-ws` subprotocol + +We have implemented subscriptions in Spring WebSockets following the [`graphql-transport-ws` subprotocol](https://github.com/enisdenjo/graphql-ws) +from [The Guild](https://the-guild.dev/). This requires that your client send and parse messages in a specific format. +See protocol documentation for expected messages. + +`graphql-transport-ws` is the default subscription protocol that is enabled by default. + +### (deprecated) `subscriptions-transport-ws` subprotocol + +:::caution +`subscriptions-transport-ws` was deprecated in favor of [`graphql-transport-ws` protocol](https://github.com/enisdenjo/graphql-ws). +::: + +We have implemented subscriptions in Spring WebSockets following the [`subscriptions-transport-ws` subprotocol](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md) +defined by [Apollo](https://www.apollographql.com/). This requires that your client send and parse messages in a specific +format. See protocol documentation for expected messages. + +`subscription-transport-ws` is deprecated. In order to use it, you need to explicitly opt-in by specifying `graphql.subscriptions.protocol=APOLLO_SUBSCRIPTIONS_WS` +configuration property. Support for this protocol will be removed in the next major release. + +## Subscription Hooks + +In line with the Apollo protocol, we have implemented hooks to execute functions at different stages of the connection lifecycle. +If you would like to implement your own subscription hooks, you can provide a primary spring bean for `ApolloSubscriptionHooks` that overrides the [default one](./spring-beans.md) which do not perform any actions. + +### `onConnect` / `onConnectWithContext` +Allows validation of connectionParams prior to starting the connection. +You can reject the connection by throwing an exception. +If you need to forward state to execution, update and return the [GraphQLContext](./spring-graphql-context.md). + +### `onOperation` / `onOperationWithContext` +Called when the client executes a GraphQL operation. The context can not be updated here, it is read only. + +### `onOperationComplete` +Called when client's unsubscribes + +### `onDisconnect` +Called when the client disconnects + +## Example + +You can see an example implementation of a `Subscription` in the [example app](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/examples/server/spring-server/src/main/kotlin/com/expediagroup/graphql/examples/server/spring/subscriptions/SimpleSubscription.kt). diff --git a/website/versioned_sidebars/version-8.x.x-sidebars.json b/website/versioned_sidebars/version-8.x.x-sidebars.json new file mode 100644 index 0000000000..5f199193d4 --- /dev/null +++ b/website/versioned_sidebars/version-8.x.x-sidebars.json @@ -0,0 +1,140 @@ +{ + "docs": { + "Introduction": [ + "getting-started", + "examples", + "framework-comparison", + "blogs-and-videos" + ], + "Schema Generator": [ + "schema-generator/schema-generator-getting-started", + { + "type": "category", + "label": "Writing schemas with Kotlin", + "items": [ + "schema-generator/writing-schemas/schema", + "schema-generator/writing-schemas/fields", + "schema-generator/writing-schemas/nullability", + "schema-generator/writing-schemas/arguments", + "schema-generator/writing-schemas/scalars", + "schema-generator/writing-schemas/enums", + "schema-generator/writing-schemas/lists", + "schema-generator/writing-schemas/interfaces", + "schema-generator/writing-schemas/unions", + "schema-generator/writing-schemas/nested-arguments" + ] + }, + { + "type": "category", + "label": "Customizing Schema", + "items": [ + "schema-generator/customizing-schemas/annotations", + "schema-generator/customizing-schemas/generator-config", + "schema-generator/customizing-schemas/documenting-schema", + "schema-generator/customizing-schemas/excluding-fields", + "schema-generator/customizing-schemas/renaming-fields", + "schema-generator/customizing-schemas/directives", + "schema-generator/customizing-schemas/deprecating-schema", + "schema-generator/customizing-schemas/custom-type-reference", + "schema-generator/customizing-schemas/restricting-input-output", + "schema-generator/customizing-schemas/advanced-features" + ] + }, + { + "type": "category", + "label": "Execution", + "items": [ + "schema-generator/execution/fetching-data", + "schema-generator/execution/async-models", + "schema-generator/execution/exceptions", + "schema-generator/execution/data-fetching-environment", + "schema-generator/execution/contextual-data", + "schema-generator/execution/optional-undefined-arguments", + "schema-generator/execution/subscriptions", + "schema-generator/execution/introspection" + ] + }, + { + "type": "category", + "label": "Federation", + "items": [ + "schema-generator/federation/apollo-federation", + "schema-generator/federation/federated-schemas", + "schema-generator/federation/federated-directives", + "schema-generator/federation/type-resolution", + "schema-generator/federation/federation-tracing" + ] + } + ], + "Server": [ + "server/graphql-server", + "server/graphql-request-parser", + "server/graphql-context-factory", + "server/graphql-request-handler", + "server/server-subscriptions", + "server/automatic-persisted-queries/automatic-persisted-queries", + { + "type": "category", + "label": "Data Loader", + "items": [ + "server/data-loader/data-loader", + "server/data-loader/data-loader-instrumentation" + ] + }, + { + "type": "category", + "label": "Spring Server", + "items": [ + "server/spring-server/spring-overview", + "server/spring-server/spring-schema", + "server/spring-server/spring-graphql-context", + "server/spring-server/spring-http-request-response", + "server/spring-server/spring-beans", + "server/spring-server/spring-properties", + "server/spring-server/spring-subscriptions" + ] + }, + { + "type": "category", + "label": "Ktor Server Plugin", + "items": [ + "server/ktor-server/ktor-overview", + "server/ktor-server/ktor-schema", + "server/ktor-server/ktor-graphql-context", + "server/ktor-server/ktor-http-request-response", + "server/ktor-server/ktor-configuration", + "server/ktor-server/ktor-subscriptions" + ] + } + ], + "Client": [ + "client/client-overview", + "client/client-features", + "client/client-customization", + "client/client-serialization" + ], + "Build Plugins": [ + { + "type": "category", + "label": "Gradle Plugin", + "items": [ + "plugins/gradle-plugin-tasks", + "plugins/gradle-plugin-usage-client", + "plugins/gradle-plugin-usage-sdl", + "plugins/gradle-plugin-usage-graalvm" + ] + }, + { + "type": "category", + "label": "Maven Plugin", + "items": [ + "plugins/maven-plugin-goals", + "plugins/maven-plugin-usage-client", + "plugins/maven-plugin-usage-sdl", + "plugins/maven-plugin-usage-graalvm" + ] + }, + "plugins/hooks-provider" + ] + } +} diff --git a/website/versions.json b/website/versions.json index 592e110065..71667cf366 100644 --- a/website/versions.json +++ b/website/versions.json @@ -1,4 +1,5 @@ [ + "8.x.x", "7.x.x", "6.x.x", "5.x.x",