├── .gitattributes ├── .gitignore ├── README.md ├── assets └── graphql-jetpack.svg ├── build.gradle.kts ├── docs ├── .nojekyll ├── 404.html ├── assets │ ├── css │ │ ├── styles.3b9537d1.css │ │ ├── styles.5627add0.css │ │ └── styles.f27a1287.css │ ├── images │ │ ├── docsVersionDropdown-dda80f009a926fb2dd92bab8faa6c4d8.png │ │ ├── docusaurus-plushie-banner-a60f7593abca1e3eef26a9afa244e4fb.jpeg │ │ └── localeDropdown-0052c3f08ccaf802ac733b23e655f498.png │ └── js │ │ ├── 01a85c17.2866b8b0.js │ │ ├── 0310a20f.72845f8b.js │ │ ├── 031793e1.dac0c04e.js │ │ ├── 05a97949.99a990f1.js │ │ ├── 096bfee4.bbbacde3.js │ │ ├── 0c9aa132.0976d679.js │ │ ├── 0e384e19.15a544ed.js │ │ ├── 0e384e19.33d5c4eb.js │ │ ├── 0e384e19.8112e2b3.js │ │ ├── 0e384e19.99237bb3.js │ │ ├── 0e384e19.ddb8d3c0.js │ │ ├── 17896441.43403607.js │ │ ├── 17896441.a4fc16d1.js │ │ ├── 18c41134.20c36610.js │ │ ├── 18c41134.4c0efd0b.js │ │ ├── 18c41134.685e2f2e.js │ │ ├── 1be78505.293f16ce.js │ │ ├── 1be78505.dc431d29.js │ │ ├── 1e4232ab.7cc50be3.js │ │ ├── 1e4232ab.cc81d1ed.js │ │ ├── 1e4232ab.e503d658.js │ │ ├── 1f391b9e.5b42d93a.js │ │ ├── 30a24c52.e46e8633.js │ │ ├── 32ccf324.0ba9d63a.js │ │ ├── 393be207.88cdc1d9.js │ │ ├── 393be207.df71830d.js │ │ ├── 3cf5728e.d3f8223f.js │ │ ├── 434adc12.8df89bed.js │ │ ├── 459.cb5b974b.js │ │ ├── 4c9e35b1.12c2f4b2.js │ │ ├── 52ee2736.3f4b9da9.js │ │ ├── 533a09ca.085cdcc6.js │ │ ├── 533a09ca.5ab24b40.js │ │ ├── 533a09ca.a454e868.js │ │ ├── 5723cd43.d30de6c9.js │ │ ├── 59362658.71367552.js │ │ ├── 59362658.e31987cf.js │ │ ├── 5c868d36.025c8334.js │ │ ├── 5c868d36.2b266bfa.js │ │ ├── 5c868d36.54e29fb9.js │ │ ├── 5c868d36.7f8494d9.js │ │ ├── 608.7e9bee13.js │ │ ├── 608ae6a4.013eec30.js │ │ ├── 66406991.7725253d.js │ │ ├── 6875c492.bdecbd6f.js │ │ ├── 73664a40.7f6a2e9c.js │ │ ├── 73664a40.b291e96b.js │ │ ├── 75.0b33d0ed.js │ │ ├── 7661071f.47a9d1c9.js │ │ ├── 7661071f.73e5b34e.js │ │ ├── 7e6ecff5.015ef961.js │ │ ├── 814f3328.0ea51136.js │ │ ├── 814f3328.79f86f78.js │ │ ├── 822bd8ab.1d24cd5a.js │ │ ├── 822bd8ab.8276ef09.js │ │ ├── 822bd8ab.e9193f46.js │ │ ├── 85f39fdd.169f218b.js │ │ ├── 8717b14a.d6eab331.js │ │ ├── 8717b14a.f414651d.js │ │ ├── 925b3f96.1b2f48cd.js │ │ ├── 925b3f96.73617a1f.js │ │ ├── 935f2afb.280163d5.js │ │ ├── 935f2afb.48c3538a.js │ │ ├── 935f2afb.65f46b0b.js │ │ ├── 935f2afb.7f0ec282.js │ │ ├── 935f2afb.9b6f293c.js │ │ ├── 9420aa17.c7e331db.js │ │ ├── 9e4087bc.e9665571.js │ │ ├── a6aa9e1f.13045362.js │ │ ├── a7023ddc.9d17a05d.js │ │ ├── a80da1cf.55305d51.js │ │ ├── aacd46d6.bfa30325.js │ │ ├── b2b675dd.783ff5e3.js │ │ ├── b2f554cd.f3988ae0.js │ │ ├── c4f5d8e4.8f79fe78.js │ │ ├── ccc49370.c354cc39.js │ │ ├── ccc49370.ea0ff06e.js │ │ ├── d9f32620.942f3294.js │ │ ├── d9f32620.fec6cd97.js │ │ ├── dc1470e1.c9307389.js │ │ ├── dff1c289.713e38ea.js │ │ ├── dff1c289.ee3eeefc.js │ │ ├── dff1c289.f7f293ff.js │ │ ├── e16015ca.fa2ac98e.js │ │ ├── e273c56f.191daee3.js │ │ ├── e273c56f.ff3ac3fb.js │ │ ├── e44a2883.228e3e95.js │ │ ├── e44a2883.3bf8d5c1.js │ │ ├── e44a2883.ba3996ed.js │ │ ├── ee2bce59.a12ff003.js │ │ ├── f1b6e701.54203ebd.js │ │ ├── f4f34a3a.b025930a.js │ │ ├── f4f34a3a.df493265.js │ │ ├── f55d3e7a.67e6d2d3.js │ │ ├── f55d3e7a.e90be5c0.js │ │ ├── f55d3e7a.fdfca893.js │ │ ├── main.77d6cb5b.js │ │ ├── main.77d6cb5b.js.LICENSE.txt │ │ ├── main.86dab851.js │ │ ├── main.86dab851.js.LICENSE.txt │ │ ├── main.94535b4b.js │ │ ├── main.94535b4b.js.LICENSE.txt │ │ ├── main.bca5ab5b.js │ │ ├── main.bca5ab5b.js.LICENSE.txt │ │ ├── main.c2a88135.js │ │ ├── main.c2a88135.js.LICENSE.txt │ │ ├── main.cdb64fe5.js │ │ ├── main.cdb64fe5.js.LICENSE.txt │ │ ├── runtime~main.10896c46.js │ │ ├── runtime~main.2b8afd0c.js │ │ ├── runtime~main.77ded8e0.js │ │ ├── runtime~main.a3828ab4.js │ │ └── runtime~main.d5f0dd6a.js ├── blog │ ├── archive │ │ └── index.html │ ├── atom.xml │ ├── first-blog-post │ │ └── index.html │ ├── index.html │ ├── long-blog-post │ │ └── index.html │ ├── mdx-blog-post │ │ └── index.html │ ├── rss.xml │ ├── tags │ │ ├── docusaurus │ │ │ └── index.html │ │ ├── facebook │ │ │ └── index.html │ │ ├── hello │ │ │ └── index.html │ │ ├── hola │ │ │ └── index.html │ │ └── index.html │ └── welcome │ │ └── index.html ├── docs │ ├── intro │ │ └── index.html │ ├── tutorial-basics │ │ ├── congratulations │ │ │ └── index.html │ │ ├── create-a-blog-post │ │ │ └── index.html │ │ ├── create-a-document │ │ │ └── index.html │ │ ├── create-a-page │ │ │ └── index.html │ │ ├── deploy-your-site │ │ │ └── index.html │ │ └── markdown-features │ │ │ └── index.html │ └── tutorial-extras │ │ ├── manage-docs-versions │ │ └── index.html │ │ └── translate-your-site │ │ └── index.html ├── img │ ├── docusaurus.png │ ├── favicon.ico │ ├── logo.svg │ ├── tutorial │ │ ├── docsVersionDropdown.png │ │ └── localeDropdown.png │ ├── undraw_docusaurus_mountain.svg │ ├── undraw_docusaurus_react.svg │ └── undraw_docusaurus_tree.svg ├── index.html ├── intro │ └── index.html ├── markdown-page │ └── index.html ├── sitemap.xml ├── tutorial-basics │ ├── congratulations │ │ └── index.html │ ├── create-a-blog-post │ │ └── index.html │ ├── create-a-document │ │ └── index.html │ ├── create-a-page │ │ └── index.html │ ├── deploy-your-site │ │ └── index.html │ └── markdown-features │ │ └── index.html └── tutorial-extras │ ├── manage-docs-versions │ └── index.html │ └── translate-your-site │ └── index.html ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── graphql-core ├── build.gradle.kts └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── wickedev │ │ │ └── graphql │ │ │ └── exceptions │ │ │ └── ApolloException.java │ └── kotlin │ │ └── io │ │ └── github │ │ └── wickedev │ │ └── graphql │ │ ├── annotations │ │ ├── GlobalId.kt │ │ └── Relation.kt │ │ ├── directives │ │ ├── AppendEdgeDirective.kt │ │ ├── AppendNodeDirective.kt │ │ ├── AssignableDirective.kt │ │ ├── ConnectionDirective.kt │ │ ├── DeleteEdgeDirective.kt │ │ ├── DeleteRecordDirective.kt │ │ ├── InlineDirective.kt │ │ ├── MatchDirective.kt │ │ ├── ModuleDirective.kt │ │ ├── NoInlineDirective.kt │ │ ├── PreLoadableDirective.kt │ │ ├── PrependEdgeDirective.kt │ │ ├── PrependNodeDirective.kt │ │ ├── RawResponseTypeDirective.kt │ │ ├── RefetchableDirective.kt │ │ ├── RelayDirective.kt │ │ ├── RelayTestOperationDirective.kt │ │ ├── RequiredDirective.kt │ │ ├── StreamConnectionDirective.kt │ │ └── UpdatableDirective.kt │ │ ├── enums │ │ └── RequiredFieldAction.kt │ │ ├── exceptions │ │ ├── ApolloError.kt │ │ └── ErrorExtension.kt │ │ ├── extentions │ │ ├── DecodeBase64.kt │ │ ├── EncodeBase64.kt │ │ └── Types.kt │ │ ├── interfases │ │ └── Node.kt │ │ ├── scalars │ │ ├── CustomScalars.kt │ │ ├── GraphQLIDScalar.kt │ │ ├── SetScalar.kt │ │ └── SkipScalar.kt │ │ ├── types │ │ ├── Backward.kt │ │ ├── Connection.kt │ │ ├── ConnectionParam.kt │ │ ├── Direction.kt │ │ ├── Edge.kt │ │ ├── Forward.kt │ │ ├── ID.kt │ │ ├── Order.kt │ │ └── PageInfo.kt │ │ └── utils │ │ ├── Base64Decoder.kt │ │ └── Base64Encoder.kt │ └── test │ └── kotlin │ └── io │ └── github │ └── wickedev │ └── graphql │ ├── directives │ ├── AppendEdgeDirectiveTest.kt │ ├── AppendNodeDirectiveTest.kt │ ├── AssignableDirectiveTest.kt │ ├── ConnectionDirectiveTest.kt │ ├── DeleteEdgeDirectiveTest.kt │ ├── DeleteRecordDirectiveTest.kt │ ├── InlineDirectiveTest.kt │ ├── MatchDirectiveTest.kt │ ├── ModuleDirectiveTest.kt │ ├── NoInlineDirectiveTest.kt │ ├── PreLoadableDirectiveTest.kt │ ├── PrependEdgeDirectiveTest.kt │ ├── PrependNodeDirectiveTest.kt │ ├── RawResponseTypeDirectiveTest.kt │ ├── RefetchableDirectiveTest.kt │ ├── RelayDirectiveTest.kt │ ├── RelayTestOperationDirectiveTest.kt │ ├── RequiredDirectiveTest.kt │ ├── StreamConnectionDirectiveTest.kt │ ├── TestSchemaGenerator.kt │ └── UpdatableDirectiveTest.kt │ └── scalars │ └── GraphQLIDScalarTest.kt ├── graphql-jetpack-autoconfigure ├── build.gradle.kts └── src │ └── main │ ├── kotlin │ └── io │ │ └── github │ │ └── wickedev │ │ └── graphql │ │ └── kotlin │ │ ├── JetpackAutoConfiguration.kt │ │ ├── JetpackFederatedSchemaAutoConfiguration.kt │ │ ├── JetpackGraphQLExecutionConfiguration.kt │ │ ├── JetpackGraphQLSchemaConfiguration.kt │ │ ├── JetpackNonFederatedSchemaAutoConfiguration.kt │ │ └── JetpackSchemaGeneratorHooksProvider.kt │ └── resources │ └── META-INF │ ├── services │ └── com.expediagroup.graphql.plugin.schema.hooks.SchemaGeneratorHooksProvider │ └── spring.factories ├── graphql-jetpack-starter └── build.gradle.kts ├── graphql-jetpack ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── io │ └── github │ └── wickedev │ └── graphql │ ├── kotlin │ ├── JetpackDataFetcherFactoryProvider.kt │ ├── JetpackFederatedSchemaGeneratorHooks.kt │ ├── JetpackSchemaGeneratorHooks.kt │ └── JetpackSpringGraphQLContextFactory.kt │ └── security │ └── CustomDefaultMethodSecurityExpressionHandler.kt ├── graphql-kotlin-spring-security ├── README.md ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── io │ │ └── github │ │ └── wickedev │ │ └── graphql │ │ ├── AuthDataFetcher.kt │ │ ├── AuthDirective.kt │ │ ├── AuthDirectiveWiringFactory.kt │ │ ├── AuthSchemaDirectiveWiring.kt │ │ ├── Extentions.kt │ │ └── JetpackFunctionDataFetcher.kt │ └── test │ ├── kotlin │ └── io │ │ └── github │ │ └── wickedev │ │ └── graphql │ │ ├── GraphQLSpringSecurityTest.kt │ │ ├── MethodSecurityExpressionHandlerTest.kt │ │ ├── MethodSecurityMetadataSourceAdvisorTest.kt │ │ └── TestApplication.kt │ └── resources │ ├── application.yml │ └── keys │ ├── ec256-private.pem │ └── ec256-public.pem ├── graphql-kotlin-spring-webflux-upload ├── README.md ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── io │ │ └── github │ │ └── wickedev │ │ └── graphql │ │ ├── extentions │ │ └── OptionalUnwrap.kt │ │ ├── parser │ │ ├── JetpackSpringGraphQLRequestParser.kt │ │ ├── Parse.kt │ │ └── StringToJsonPath.kt │ │ ├── request │ │ └── RequestFromMultipartFormData.kt │ │ ├── scalars │ │ └── UploadScalar.kt │ │ └── types │ │ └── Upload.kt │ └── test │ ├── kotlin │ └── io │ │ └── github │ │ └── wickedev │ │ └── graphql │ │ ├── parser │ │ └── ToJsonPathTest.kt │ │ └── request │ │ ├── TestApplication.kt │ │ └── UploadWebRequestTest.kt │ └── resources │ ├── a.txt │ ├── application.yml │ ├── b.txt │ └── c.txt ├── graphql-postgresql-scalars ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── io │ └── github │ └── wickedev │ └── graphql │ └── scalars │ ├── PostgresIntervalScalar.kt │ └── PostgresJsonScalar.kt ├── kotlin-coroutine-reactive-extensions ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── io │ └── github │ └── wickedev │ └── coroutine │ └── reactive │ └── extensions │ ├── flux │ └── Await.kt │ └── mono │ └── Await.kt ├── settings.gradle.kts ├── spring-data-graphql-commons ├── build.gradle.kts └── src │ └── main │ ├── java │ └── io │ │ └── github │ │ └── wickedev │ │ └── graphql │ │ └── repository │ │ └── GraphQLDataLoaderByIdRepository.java │ └── kotlin │ └── io │ └── github │ └── wickedev │ └── graphql │ └── repository │ ├── GraphQLDataLoaderBackwardConnectionsRepository.kt │ ├── GraphQLDataLoaderConnectionsRepository.kt │ ├── GraphQLDataLoaderForwardConnectionsRepository.kt │ ├── GraphQLDataLoaderRepository.kt │ ├── GraphQLNodeRepository.kt │ └── GraphQLRepository.kt ├── spring-data-graphql-r2dbc-autoconfigure ├── build.gradle.kts └── src │ └── main │ ├── kotlin │ └── io │ │ └── github │ │ └── wickedev │ │ └── graphql │ │ └── spring │ │ └── data │ │ └── r2dbc │ │ └── configuration │ │ ├── EnableGraphQLR2dbcRepositories.kt │ │ ├── GraphQLR2dbcDataAutoConfiguration.kt │ │ ├── GraphQLR2dbcRepositoriesAutoConfiguration.kt │ │ ├── GraphQLR2dbcRepositoriesAutoConfigureRegistrar.kt │ │ ├── GraphQLR2dbcRepositoriesRegistrar.kt │ │ └── GraphQLR2dbcRepositoryConfigurationExtension.kt │ └── resources │ └── META-INF │ └── spring.factories ├── spring-data-graphql-r2dbc-starter └── build.gradle.kts ├── spring-data-graphql-r2dbc ├── README.md ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ ├── io │ │ └── github │ │ │ └── wickedev │ │ │ └── graphql │ │ │ └── spring │ │ │ └── data │ │ │ └── r2dbc │ │ │ ├── converter │ │ │ ├── GraphQLMappingR2dbcConverter.kt │ │ │ ├── IDToLongWritingConverter.kt │ │ │ └── LongToIDReadingConverter.kt │ │ │ ├── extentions │ │ │ ├── GraphQL.kt │ │ │ ├── R2dbc.kt │ │ │ ├── Strings.kt │ │ │ └── Types.kt │ │ │ ├── factory │ │ │ ├── GraphQLMappingRelationalEntityInformation.kt │ │ │ ├── GraphQLR2dbcRepositoryFactoryBean.kt │ │ │ └── GraphQLSimpleR2dbcRepositoryFactory.kt │ │ │ ├── mapping │ │ │ ├── GraphQLR2dbcMappingContext.kt │ │ │ ├── GraphQLRelationalPersistentProperty.kt │ │ │ └── GraphQLTypeInformation.kt │ │ │ ├── query │ │ │ ├── GraphQLCollectionPartTreeR2dbcQuery.kt │ │ │ ├── GraphQLConnectionPartTreeR2dbcQuery.kt │ │ │ ├── GraphQLR2dbcQueryMethod.kt │ │ │ ├── GraphQLSimplePartTreeR2dbcQuery.kt │ │ │ └── redefine │ │ │ │ ├── RedefineConnectionMethod.kt │ │ │ │ ├── RedefineConnectionNextMethod.kt │ │ │ │ ├── RedefineCountExistsMethod.kt │ │ │ │ ├── RedefineFindMethod.kt │ │ │ │ └── RedefineMethod.kt │ │ │ ├── repository │ │ │ ├── SimpleGraphQLNodeRepository.kt │ │ │ ├── SimpleGraphQLR2dbcRepository.kt │ │ │ ├── base │ │ │ │ ├── PropertyBaseRepository.kt │ │ │ │ └── PropertyRepository.kt │ │ │ ├── interfaces │ │ │ │ └── GraphQLR2dbcRepository.kt │ │ │ └── mixin │ │ │ │ ├── GraphQLDataLoaderBackwardConnectionsRepositoryMixin.kt │ │ │ │ ├── GraphQLDataLoaderByIdRepositoryMixin.kt │ │ │ │ ├── GraphQLDataLoaderConnectionsRepositoryMixin.kt │ │ │ │ ├── GraphQLDataLoaderForwardConnectionsRepositoryMixin.kt │ │ │ │ ├── GraphQLRepositoryMixin.kt │ │ │ │ ├── R2dbcAllInQueryMixin.kt │ │ │ │ ├── R2dbcRepositoryMixin.kt │ │ │ │ ├── ReactiveCrudRepositoryMixin.kt │ │ │ │ ├── ReactiveQueryByExampleExecutorMixin.kt │ │ │ │ └── ReactiveSortingRepositoryMixin.kt │ │ │ └── strategy │ │ │ ├── AdditionalIsNewStrategy.kt │ │ │ ├── DefaultIDTypeStrategy.kt │ │ │ ├── GraphQLAdditionalIsNewStrategy.kt │ │ │ ├── IDTypeFiller.kt │ │ │ └── IDTypeStrategy.kt │ │ └── org │ │ └── springframework │ │ └── data │ │ └── r2dbc │ │ ├── core │ │ └── GraphQLR2dbcEntityTemplate.kt │ │ └── repository │ │ ├── query │ │ └── AbstractGraphQRepositoryQuery.kt │ │ └── support │ │ ├── GraphQLR2dbcQueryLookupStrategy.kt │ │ └── ReactiveFluentQueryByExample.kt │ └── test │ ├── kotlin │ └── io │ │ └── github │ │ └── wickedev │ │ ├── ByteBuddyTest.kt │ │ └── graphql │ │ └── spring │ │ └── data │ │ └── r2dbc │ │ ├── GraphQLDataLoaderConnectionsRepositoryTest.kt │ │ ├── GraphQLDataLoaderNodeRepositoryTest.kt │ │ ├── GraphQLDataLoaderRepositoryFindAllTest.kt │ │ ├── GraphQLDataLoaderRepositoryTest.kt │ │ ├── GraphQLQueryCreationRepositoryTest.kt │ │ ├── PostRepository.kt │ │ ├── ProjectConfig.kt │ │ ├── QueryCreationInternalTest.kt │ │ ├── RepositoryTest.kt │ │ ├── SubRepositoryTest.kt │ │ ├── TestingApp.kt │ │ ├── UserRepository.kt │ │ └── utils │ │ ├── DataLoader.kt │ │ └── Fixture.kt │ └── resources │ ├── db │ └── schema.sql │ └── logback-test.xml └── website ├── .gitignore ├── README.md ├── babel.config.js ├── build.gradle.kts ├── docs ├── backup_intro.md ├── img.png ├── intro.md ├── tutorial-basics │ ├── _category_.json │ ├── congratulations.md │ ├── create-a-blog-post.md │ ├── create-a-document.md │ ├── create-a-page.md │ ├── deploy-your-site.md │ └── markdown-features.mdx └── tutorial-extras │ ├── _category_.json │ ├── manage-docs-versions.md │ └── translate-your-site.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src ├── components │ └── HomepageFeatures │ │ ├── index.js │ │ └── styles.module.css ├── css │ └── custom.css └── pages │ ├── index.js │ ├── index.module.css │ └── markdown-page.md └── static ├── .nojekyll └── img ├── docusaurus.png ├── favicon.ico ├── logo.svg ├── tutorial ├── docsVersionDropdown.png └── localeDropdown.png ├── undraw_docusaurus_mountain.svg ├── undraw_docusaurus_react.svg └── undraw_docusaurus_tree.svg /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | .idea 7 | .DS_Store 8 | maven-repo 9 | bin -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | subprojects { 2 | val groupId: String by project 3 | val version: String by project 4 | 5 | apply { 6 | plugin("java-library") 7 | plugin("maven-publish") 8 | } 9 | 10 | repositories { 11 | mavenCentral() 12 | maven("https://github.com/wickedev/spring-security-jwt-webflux/raw/deploy/maven-repo") 13 | } 14 | 15 | val sourcesJar by tasks.registering(Jar::class) { 16 | archiveClassifier.set("sources") 17 | from(project.the()["main"].allSource) 18 | } 19 | 20 | artifacts { 21 | add("archives", sourcesJar) 22 | } 23 | 24 | publishing { 25 | repositories { 26 | maven { 27 | url = uri("$rootDir/maven-repo") 28 | } 29 | } 30 | 31 | publications { 32 | publications.create("maven") { 33 | artifactId = project.name 34 | setGroupId(groupId) 35 | setVersion(version) 36 | from(components["java"]) 37 | artifact(sourcesJar.get()) 38 | } 39 | } 40 | } 41 | 42 | } 43 | 44 | fun Project.publishing(configure: Action): Unit = 45 | (this as ExtensionAware).extensions.configure("publishing", configure) 46 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/docs/.nojekyll -------------------------------------------------------------------------------- /docs/assets/images/docsVersionDropdown-dda80f009a926fb2dd92bab8faa6c4d8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/docs/assets/images/docsVersionDropdown-dda80f009a926fb2dd92bab8faa6c4d8.png -------------------------------------------------------------------------------- /docs/assets/images/docusaurus-plushie-banner-a60f7593abca1e3eef26a9afa244e4fb.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/docs/assets/images/docusaurus-plushie-banner-a60f7593abca1e3eef26a9afa244e4fb.jpeg -------------------------------------------------------------------------------- /docs/assets/images/localeDropdown-0052c3f08ccaf802ac733b23e655f498.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/docs/assets/images/localeDropdown-0052c3f08ccaf802ac733b23e655f498.png -------------------------------------------------------------------------------- /docs/assets/js/0310a20f.72845f8b.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[510],{3769:function(e){e.exports=JSON.parse('{"plugin":{"name":"docusaurus-plugin-content-docs","id":"default"}}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/031793e1.dac0c04e.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[633],{2511:function(e){e.exports=JSON.parse('{"allTagsPath":"/blog/tags","name":"facebook","count":1,"permalink":"/blog/tags/facebook"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/05a97949.99a990f1.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[206],{6039:function(e){e.exports=JSON.parse('{"permalink":"/graphql-jetpack/blog","page":1,"postsPerPage":10,"totalPages":1,"totalCount":4,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/096bfee4.bbbacde3.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[178],{5010:function(e){e.exports=JSON.parse('{"permalink":"/blog/tags/facebook","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/0c9aa132.0976d679.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[671],{194:function(a){a.exports=JSON.parse('{"allTagsPath":"/graphql-jetpack/blog/tags","name":"hola","count":1,"permalink":"/graphql-jetpack/blog/tags/hola"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/30a24c52.e46e8633.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[453],{8605:function(e){e.exports=JSON.parse('{"allTagsPath":"/blog/tags","name":"hello","count":2,"permalink":"/blog/tags/hello"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/32ccf324.0ba9d63a.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[9],{4636:function(e){e.exports=JSON.parse('{"permalink":"/graphql-jetpack/blog/tags/hello","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/3cf5728e.d3f8223f.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[304],{9662:function(e){e.exports=JSON.parse('{"permalink":"/graphql-jetpack/blog/tags/hola","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/434adc12.8df89bed.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[364],{1460:function(e){e.exports=JSON.parse('{"permalink":"/graphql-jetpack/blog/tags/facebook","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/4c9e35b1.12c2f4b2.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[35],{499:function(e){e.exports=JSON.parse('{"permalink":"/blog/tags/hola","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/52ee2736.3f4b9da9.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[534],{5745:function(e){e.exports=JSON.parse('{"plugin":{"name":"docusaurus-plugin-content-pages","id":"default"}}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/5723cd43.d30de6c9.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[811],{3259:function(e){e.exports=JSON.parse('{"allTagsPath":"/graphql-jetpack/blog/tags","name":"hello","count":2,"permalink":"/graphql-jetpack/blog/tags/hello"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/608.7e9bee13.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[608],{4608:function(e,t,n){n.r(t),n.d(t,{default:function(){return i}});var a=n(7294),l=n(2600),o=n(5999),r=n(5979);function i(){return a.createElement(a.Fragment,null,a.createElement(r.d,{title:(0,o.I)({id:"theme.NotFound.title",message:"Page Not Found"})}),a.createElement(l.Z,null,a.createElement("main",{className:"container margin-vert--xl"},a.createElement("div",{className:"row"},a.createElement("div",{className:"col col--6 col--offset-3"},a.createElement("h1",{className:"hero__title"},a.createElement(o.Z,{id:"theme.NotFound.title",description:"The title of the 404 page"},"Page Not Found")),a.createElement("p",null,a.createElement(o.Z,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page"},"We could not find what you were looking for.")),a.createElement("p",null,a.createElement(o.Z,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page"},"Please contact the owner of the site that linked you to the original URL and let them know their link is broken.")))))))}}}]); -------------------------------------------------------------------------------- /docs/assets/js/608ae6a4.013eec30.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[938],{4545:function(e){e.exports=JSON.parse('{"permalink":"/blog/tags/docusaurus","page":1,"postsPerPage":10,"totalPages":1,"totalCount":4,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/66406991.7725253d.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[110],{711:function(e){e.exports=JSON.parse('{"permalink":"/blog/tags/hello","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/75.0b33d0ed.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[75],{4608:function(e,t,n){n.r(t),n.d(t,{default:function(){return i}});var a=n(7294),l=n(2600),o=n(5999),r=n(5979);function i(){return a.createElement(a.Fragment,null,a.createElement(r.d,{title:(0,o.I)({id:"theme.NotFound.title",message:"Page Not Found"})}),a.createElement(l.Z,null,a.createElement("main",{className:"container margin-vert--xl"},a.createElement("div",{className:"row"},a.createElement("div",{className:"col col--6 col--offset-3"},a.createElement("h1",{className:"hero__title"},a.createElement(o.Z,{id:"theme.NotFound.title",description:"The title of the 404 page"},"Page Not Found")),a.createElement("p",null,a.createElement(o.Z,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page"},"We could not find what you were looking for.")),a.createElement("p",null,a.createElement(o.Z,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page"},"Please contact the owner of the site that linked you to the original URL and let them know their link is broken.")))))))}}}]); -------------------------------------------------------------------------------- /docs/assets/js/7e6ecff5.015ef961.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[203],{1307:function(e){e.exports=JSON.parse('{"allTagsPath":"/graphql-jetpack/blog/tags","name":"facebook","count":1,"permalink":"/graphql-jetpack/blog/tags/facebook"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/814f3328.0ea51136.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[535],{5641:function(e){e.exports=JSON.parse('{"title":"Recent posts","items":[{"title":"Welcome","permalink":"/graphql-jetpack/blog/welcome"},{"title":"MDX Blog Post","permalink":"/graphql-jetpack/blog/mdx-blog-post"},{"title":"Long Blog Post","permalink":"/graphql-jetpack/blog/long-blog-post"},{"title":"First Blog Post","permalink":"/graphql-jetpack/blog/first-blog-post"}]}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/814f3328.79f86f78.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[535],{5641:function(e){e.exports=JSON.parse('{"title":"Recent posts","items":[{"title":"Welcome","permalink":"/blog/welcome"},{"title":"MDX Blog Post","permalink":"/blog/mdx-blog-post"},{"title":"Long Blog Post","permalink":"/blog/long-blog-post"},{"title":"First Blog Post","permalink":"/blog/first-blog-post"}]}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/85f39fdd.169f218b.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[675],{7489:function(a){a.exports=JSON.parse('{"allTagsPath":"/graphql-jetpack/blog/tags","name":"docusaurus","count":4,"permalink":"/graphql-jetpack/blog/tags/docusaurus"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/9420aa17.c7e331db.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[530],{6961:function(e){e.exports=JSON.parse('{"permalink":"/graphql-jetpack/blog/tags/docusaurus","page":1,"postsPerPage":10,"totalPages":1,"totalCount":4,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/9e4087bc.e9665571.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[608],{3012:function(e,t,a){a.r(t),a.d(t,{default:function(){return o}});var r=a(7294),n=a(2600),l=a(9960),c=a(5999),i=a(5979);function m(e){var t=e.year,a=e.posts;return r.createElement(r.Fragment,null,r.createElement("h3",null,t),r.createElement("ul",null,a.map((function(e){return r.createElement("li",{key:e.metadata.date},r.createElement(l.Z,{to:e.metadata.permalink},e.metadata.formattedDate," - ",e.metadata.title))}))))}function s(e){var t=e.years;return r.createElement("section",{className:"margin-vert--lg"},r.createElement("div",{className:"container"},r.createElement("div",{className:"row"},t.map((function(e,t){return r.createElement("div",{key:t,className:"col col--4 margin-vert--lg"},r.createElement(m,e))})))))}function o(e){var t,a,l=e.archive,m=(0,c.I)({id:"theme.blog.archive.title",message:"Archive",description:"The page & hero title of the blog archive page"}),o=(0,c.I)({id:"theme.blog.archive.description",message:"Archive",description:"The page & hero description of the blog archive page"}),u=(t=l.blogPosts,a=t.reduceRight((function(e,t){var a,r=t.metadata.date.split("-")[0],n=null!=(a=e.get(r))?a:[];return e.set(r,[t].concat(n))}),new Map),Array.from(a,(function(e){return{year:e[0],posts:e[1]}})));return r.createElement(r.Fragment,null,r.createElement(i.d,{title:m,description:o}),r.createElement(n.Z,null,r.createElement("header",{className:"hero hero--primary"},r.createElement("div",{className:"container"},r.createElement("h1",{className:"hero__title"},m),r.createElement("p",{className:"hero__subtitle"},o))),r.createElement("main",null,u.length>0&&r.createElement(s,{years:u}))))}}}]); -------------------------------------------------------------------------------- /docs/assets/js/a7023ddc.9d17a05d.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[713],{3457:function(a){a.exports=JSON.parse('{"facebook":{"allTagsPath":"/blog/tags","name":"facebook","count":1,"permalink":"/blog/tags/facebook"},"hello":{"allTagsPath":"/blog/tags","name":"hello","count":2,"permalink":"/blog/tags/hello"},"docusaurus":{"allTagsPath":"/blog/tags","name":"docusaurus","count":4,"permalink":"/blog/tags/docusaurus"},"hola":{"allTagsPath":"/blog/tags","name":"hola","count":1,"permalink":"/blog/tags/hola"}}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/a80da1cf.55305d51.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[205],{4863:function(s){s.exports=JSON.parse('{"allTagsPath":"/blog/tags","name":"docusaurus","count":4,"permalink":"/blog/tags/docusaurus"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/b2b675dd.783ff5e3.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[533],{8017:function(e){e.exports=JSON.parse('{"permalink":"/blog","page":1,"postsPerPage":10,"totalPages":1,"totalCount":4,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/e16015ca.fa2ac98e.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[700],{5688:function(e){e.exports=JSON.parse('{"allTagsPath":"/blog/tags","name":"hola","count":1,"permalink":"/blog/tags/hola"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/ee2bce59.a12ff003.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[494],{4469:function(e){e.exports=JSON.parse('{"plugin":{"name":"docusaurus-plugin-content-blog","id":"default"}}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/f1b6e701.54203ebd.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[774],{5309:function(a){a.exports=JSON.parse('{"facebook":{"allTagsPath":"/graphql-jetpack/blog/tags","name":"facebook","count":1,"permalink":"/graphql-jetpack/blog/tags/facebook"},"hello":{"allTagsPath":"/graphql-jetpack/blog/tags","name":"hello","count":2,"permalink":"/graphql-jetpack/blog/tags/hello"},"docusaurus":{"allTagsPath":"/graphql-jetpack/blog/tags","name":"docusaurus","count":4,"permalink":"/graphql-jetpack/blog/tags/docusaurus"},"hola":{"allTagsPath":"/graphql-jetpack/blog/tags","name":"hola","count":1,"permalink":"/graphql-jetpack/blog/tags/hola"}}')}}]); -------------------------------------------------------------------------------- /docs/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/docs/img/docusaurus.png -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/docs/img/favicon.ico -------------------------------------------------------------------------------- /docs/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/img/tutorial/docsVersionDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/docs/img/tutorial/docsVersionDropdown.png -------------------------------------------------------------------------------- /docs/img/tutorial/localeDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/docs/img/tutorial/localeDropdown.png -------------------------------------------------------------------------------- /docs/sitemap.xml: -------------------------------------------------------------------------------- 1 | https://wickedev.github.io/graphql-jetpack/weekly0.5https://wickedev.github.io/graphql-jetpack/introweekly0.5https://wickedev.github.io/graphql-jetpack/tutorial-basics/congratulationsweekly0.5https://wickedev.github.io/graphql-jetpack/tutorial-basics/create-a-blog-postweekly0.5https://wickedev.github.io/graphql-jetpack/tutorial-basics/create-a-documentweekly0.5https://wickedev.github.io/graphql-jetpack/tutorial-basics/create-a-pageweekly0.5https://wickedev.github.io/graphql-jetpack/tutorial-basics/deploy-your-siteweekly0.5https://wickedev.github.io/graphql-jetpack/tutorial-basics/markdown-featuresweekly0.5https://wickedev.github.io/graphql-jetpack/tutorial-extras/manage-docs-versionsweekly0.5https://wickedev.github.io/graphql-jetpack/tutorial-extras/translate-your-siteweekly0.5 -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | groupId=io.github.wickedev 2 | version=0.5.8 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /graphql-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.6.10" 3 | } 4 | 5 | dependencies { 6 | implementation(kotlin("stdlib-jdk8")) 7 | api(kotlin("reflect")) 8 | api("com.graphql-java:graphql-java:17.3") 9 | api("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") 10 | 11 | /* testing */ 12 | testImplementation("io.kotest:kotest-runner-junit5:5.0.3") 13 | testImplementation("io.kotest:kotest-assertions-core:5.0.3") 14 | testImplementation("com.expediagroup:graphql-kotlin-schema-generator:5.3.2") 15 | } 16 | 17 | tasks.withType { 18 | useJUnitPlatform() 19 | } -------------------------------------------------------------------------------- /graphql-core/src/main/java/io/github/wickedev/graphql/exceptions/ApolloException.java: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.exceptions; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import graphql.*; 5 | import graphql.execution.ResultPath; 6 | import graphql.language.SourceLocation; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import static graphql.Assert.assertNotNull; 13 | 14 | public class ApolloException extends GraphQLException implements GraphQLError, ErrorClassification { 15 | 16 | private final List path; 17 | private final List locations; 18 | private final Map extensions; 19 | 20 | public ApolloException(String code, String message, ResultPath path, SourceLocation sourceLocation) { 21 | super(message); 22 | this.path = assertNotNull(path).toList(); 23 | this.locations = Collections.singletonList(sourceLocation); 24 | this.extensions = Collections.singletonMap(code, new ErrorExtension(this)); 25 | 26 | } 27 | 28 | @Override 29 | public List getLocations() { 30 | return locations; 31 | } 32 | 33 | @Override 34 | public ErrorClassification getErrorType() { 35 | return ErrorType.DataFetchingException; 36 | } 37 | 38 | @Override 39 | public List getPath() { 40 | return path; 41 | } 42 | 43 | @JsonIgnore 44 | @Override 45 | public Map getExtensions() { 46 | return extensions; 47 | } 48 | 49 | @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") 50 | @Override 51 | public boolean equals(Object o) { 52 | return GraphqlErrorHelper.equals(this, o); 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | return GraphqlErrorHelper.hashCode(this); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/annotations/GlobalId.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.annotations 2 | 3 | @Retention(AnnotationRetention.RUNTIME) 4 | @Target(AnnotationTarget.FIELD) 5 | annotation class GlobalId(val type: String) -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/annotations/Relation.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.annotations 2 | 3 | import kotlin.reflect.KClass 4 | 5 | @Retention(AnnotationRetention.RUNTIME) 6 | @Target(AnnotationTarget.FIELD) 7 | annotation class Relation(val type: KClass<*>) -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/AppendEdgeDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.introspection.Introspection 4 | import graphql.schema.GraphQLDirective 5 | import graphql.schema.GraphQLList.list 6 | import graphql.schema.GraphQLNonNull.nonNull 7 | import io.github.wickedev.graphql.scalars.GraphQLIDScalar 8 | 9 | internal const val APPEND_EDGE_DIRECTIVE_NAME = "appendEdge" 10 | private val APPEND_EDGE_DIRECTIVE_DESCRIPTION = """ 11 | (Relay Only) 12 | 13 | For use within mutations. After the mutation request is complete, this edge 14 | will be prepended to its parent connection. 15 | 16 | [Read More](https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updating-data-once-a-request-is-complete) 17 | """.trimIndent() 18 | 19 | val APPEND_EDGE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 20 | .name(APPEND_EDGE_DIRECTIVE_NAME) 21 | .description(APPEND_EDGE_DIRECTIVE_DESCRIPTION) 22 | .validLocations(Introspection.DirectiveLocation.FIELD) 23 | .argument { 24 | it.name("connections") 25 | it.type(nonNull(list(nonNull(GraphQLIDScalar)))) 26 | } 27 | .build() 28 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/AppendNodeDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.Scalars 4 | import graphql.introspection.Introspection 5 | import graphql.schema.GraphQLDirective 6 | import graphql.schema.GraphQLList.list 7 | import graphql.schema.GraphQLNonNull.nonNull 8 | import io.github.wickedev.graphql.scalars.GraphQLIDScalar 9 | 10 | internal const val APPEND_NODE_DIRECTIVE_NAME = "appendNode" 11 | private val APPEND_NODE_DIRECTIVE_DESCRIPTION = """ 12 | (Relay Only) 13 | 14 | For use within mutations. After the mutation request is complete, this node 15 | will be appended to its parent connection. 16 | 17 | [Read More](https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updating-data-once-a-request-is-complete) 18 | """.trimIndent() 19 | 20 | val APPEND_NODE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 21 | .name(APPEND_NODE_DIRECTIVE_NAME) 22 | .description(APPEND_NODE_DIRECTIVE_DESCRIPTION) 23 | .validLocations(Introspection.DirectiveLocation.FIELD) 24 | .argument { 25 | it.name("connections") 26 | it.type(nonNull(list(nonNull(GraphQLIDScalar)))) 27 | } 28 | .argument { 29 | it.name("edgeTypeName") 30 | it.type(nonNull(Scalars.GraphQLString)) 31 | } 32 | .build() 33 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/AssignableDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.introspection.Introspection 4 | import graphql.schema.GraphQLDirective 5 | 6 | internal const val ASSIGNABLE_DIRECTIVE_NAME = "assignable" 7 | private val ASSIGNABLE_DIRECTIVE_DESCRIPTION = """ 8 | (Relay Only) 9 | 10 | Marks a given fragment as assignable. 11 | 12 | [Read More](https://fb.quip.com/4FZaADvkQPPl) 13 | """.trimIndent() 14 | 15 | val ASSIGNABLE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 16 | .name(ASSIGNABLE_DIRECTIVE_NAME) 17 | .description(ASSIGNABLE_DIRECTIVE_DESCRIPTION) 18 | .validLocations(Introspection.DirectiveLocation.FRAGMENT_DEFINITION) 19 | .build() 20 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/ConnectionDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.Scalars 4 | import graphql.introspection.Introspection 5 | import graphql.schema.GraphQLDirective 6 | import graphql.schema.GraphQLList.list 7 | import graphql.schema.GraphQLNonNull.nonNull 8 | 9 | 10 | internal const val CONNECTION_DIRECTIVE_NAME = "connection" 11 | private val CONNECTION_DIRECTIVE_DESCRIPTION = """ 12 | (Relay Only) 13 | 14 | A directive which declares that a field implements the connection spec. 15 | 16 | [Read More](https://relay.dev/docs/guided-tour/list-data/pagination/) 17 | """.trimIndent() 18 | 19 | val CONNECTION_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 20 | .name(CONNECTION_DIRECTIVE_NAME) 21 | .description(CONNECTION_DIRECTIVE_DESCRIPTION) 22 | .validLocations(Introspection.DirectiveLocation.FIELD) 23 | .argument { 24 | it.name("key") 25 | it.type(nonNull(Scalars.GraphQLString)) 26 | } 27 | .argument { 28 | it.name("filters") 29 | it.type(list(Scalars.GraphQLString)) 30 | } 31 | .argument { 32 | it.name("handler") 33 | it.type(Scalars.GraphQLString) 34 | } 35 | .argument { 36 | it.name("dynamicKey_UNSTABLE") 37 | it.type(Scalars.GraphQLString) 38 | } 39 | .build() 40 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/DeleteEdgeDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.introspection.Introspection 4 | import graphql.schema.GraphQLDirective 5 | import graphql.schema.GraphQLList.list 6 | import graphql.schema.GraphQLNonNull.nonNull 7 | import io.github.wickedev.graphql.scalars.GraphQLIDScalar 8 | 9 | internal const val DELETE_EDGE_DIRECTIVE_NAME = "deleteEdge" 10 | private val DELETE_EDGE_DIRECTIVE_DESCRIPTION = """ 11 | (Relay Only) 12 | 13 | For use within mutations. After the mutation request is complete, this edge 14 | will be removed from its parent connection. 15 | 16 | [Read More](https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updating-data-once-a-request-is-complete) 17 | """.trimIndent() 18 | 19 | val DELETE_EDGE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 20 | .name(DELETE_EDGE_DIRECTIVE_NAME) 21 | .description(DELETE_EDGE_DIRECTIVE_DESCRIPTION) 22 | .validLocations(Introspection.DirectiveLocation.FIELD) 23 | .argument { 24 | it.name("connections") 25 | it.type(nonNull(list(nonNull(GraphQLIDScalar)))) 26 | } 27 | .build() 28 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/DeleteRecordDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.introspection.Introspection 4 | import graphql.schema.GraphQLDirective 5 | 6 | internal const val DELETE_RECORD_DIRECTIVE_NAME = "deleteRecord" 7 | private val DELETE_RECORD_DIRECTIVE_DESCRIPTION = """ 8 | (Relay Only) 9 | 10 | For use within mutations. After the mutation request is complete, this field 11 | will be removed from the store. 12 | 13 | [Read More](https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updating-data-once-a-request-is-complete) 14 | """.trimIndent() 15 | 16 | val DELETE_RECORD_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 17 | .name(DELETE_RECORD_DIRECTIVE_NAME) 18 | .description(DELETE_RECORD_DIRECTIVE_DESCRIPTION) 19 | .validLocations(Introspection.DirectiveLocation.FIELD) 20 | .build() 21 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/InlineDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.introspection.Introspection 4 | import graphql.schema.GraphQLDirective 5 | 6 | internal const val INLINE_DIRECTIVE_NAME = "inline" 7 | private val INLINE_DIRECTIVE_DESCRIPTION = """ 8 | (Relay only) 9 | 10 | The hooks APIs that Relay exposes allow you to read data from the store only 11 | during the render phase. In order to read data from outside of the render 12 | phase (or from outside of React), Relay exposes the `@inline` directive. The 13 | data from a fragment annotated with `@inline` can be read using `readInlineData`. 14 | 15 | [Read More](https://relay.dev/docs/api-reference/graphql-and-directives/#inline) 16 | """.trimIndent() 17 | 18 | val INLINE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 19 | .name(INLINE_DIRECTIVE_NAME) 20 | .description(INLINE_DIRECTIVE_DESCRIPTION) 21 | .validLocations(Introspection.DirectiveLocation.FRAGMENT_DEFINITION) 22 | .build() 23 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/MatchDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.Scalars 4 | import graphql.introspection.Introspection 5 | import graphql.schema.GraphQLDirective 6 | 7 | internal const val MATCH_DIRECTIVE_NAME = "match" 8 | private val MATCH_DIRECTIVE_DESCRIPTION = """ 9 | (Relay Only) 10 | 11 | A directive that, when used in combination with `@module`, allows users to 12 | download specific JS components alongside the rest of the GraphQL payload if 13 | the field decorated with [`@match`](https://relay.dev/docs/glossary/#match) 14 | has a certain type. See [3D](https://relay.dev/docs/glossary/#3d). 15 | 16 | [Read More](https://relay.dev/docs/glossary/#match) 17 | """.trimIndent() 18 | 19 | val MATCH_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 20 | .name(MATCH_DIRECTIVE_NAME) 21 | .description(MATCH_DIRECTIVE_DESCRIPTION) 22 | .argument { 23 | it.name("key") 24 | it.type(Scalars.GraphQLString) 25 | } 26 | .validLocations(Introspection.DirectiveLocation.FIELD) 27 | .build() 28 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/ModuleDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.Scalars 4 | import graphql.introspection.Introspection 5 | import graphql.schema.GraphQLDirective 6 | import graphql.schema.GraphQLNonNull.nonNull 7 | 8 | internal const val MODULE_DIRECTIVE_NAME = "module" 9 | private val MODULE_DIRECTIVE_DESCRIPTION = """ 10 | (Relay Only) 11 | 12 | A directive that, when used in combination with 13 | [`@match`](https://relay.dev/docs/glossary/#match), allows users to specify 14 | which JS components to download if the field decorated with @match has a 15 | certain type. See [3D](https://relay.dev/docs/glossary/#3d). 16 | 17 | [Read More](https://relay.dev/docs/glossary/#module) 18 | """.trimIndent() 19 | 20 | val MODULE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 21 | .name(MODULE_DIRECTIVE_NAME) 22 | .description(MODULE_DIRECTIVE_DESCRIPTION) 23 | .argument { 24 | it.name("name") 25 | it.type(nonNull(Scalars.GraphQLString)) 26 | } 27 | .validLocations(Introspection.DirectiveLocation.FRAGMENT_SPREAD) 28 | .build() 29 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/NoInlineDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.Scalars 4 | import graphql.introspection.Introspection 5 | import graphql.schema.GraphQLDirective 6 | 7 | internal const val NO_INLINE_DIRECTIVE_NAME = "no_inline" 8 | private val NO_INLINE_DIRECTIVE_DESCRIPTION = """ 9 | (Relay only) 10 | 11 | which can be used to prevent common fragments from being inlined, resulting in smaller generated files. 12 | """.trimIndent() 13 | 14 | val NO_INLINE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 15 | .name(NO_INLINE_DIRECTIVE_NAME) 16 | .description(NO_INLINE_DIRECTIVE_DESCRIPTION) 17 | .argument { 18 | it.name("raw_response_type") 19 | it.type(Scalars.GraphQLBoolean) 20 | } 21 | .validLocations(Introspection.DirectiveLocation.FRAGMENT_DEFINITION) 22 | .build() 23 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/PreLoadableDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.Scalars 4 | import graphql.introspection.Introspection 5 | import graphql.language.BooleanValue 6 | import graphql.schema.GraphQLDirective 7 | 8 | internal const val PRE_LOADABLE_DIRECTIVE_NAME = "preloadable" 9 | private val PRE_LOADABLE_DIRECTIVE_DESCRIPTION = """ 10 | (Relay Only) 11 | 12 | A directive that modifies queries and which causes Relay to generate 13 | `${'$'}Parameters.js` files and preloadable concrete requests. Required if the 14 | query is going to be used as part of an entry point. 15 | 16 | The `hackPreloader` argument is FB only and generates a Hack preloader file. 17 | 18 | [Read More](https://relay.dev/docs/glossary/#preloadable) 19 | """.trimIndent() 20 | 21 | val PRE_LOADABLE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 22 | .name(PRE_LOADABLE_DIRECTIVE_NAME) 23 | .description(PRE_LOADABLE_DIRECTIVE_DESCRIPTION) 24 | .argument { 25 | it.name("hackPreloader") 26 | it.type(Scalars.GraphQLBoolean) 27 | it.defaultValueLiteral(BooleanValue.of(false)) 28 | } 29 | .validLocations(Introspection.DirectiveLocation.QUERY) 30 | .build() 31 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/PrependEdgeDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.introspection.Introspection 4 | import graphql.schema.GraphQLDirective 5 | import graphql.schema.GraphQLList.list 6 | import graphql.schema.GraphQLNonNull.nonNull 7 | import io.github.wickedev.graphql.scalars.GraphQLIDScalar 8 | 9 | internal const val PREPEND_EDGE_DIRECTIVE_NAME = "prependEdge" 10 | private val PREPEND_EDGE_DIRECTIVE_DESCRIPTION = """ 11 | (Relay Only) 12 | 13 | For use within mutations. After the mutation request is complete, this edge 14 | will be prepended to its parent connection. 15 | 16 | [Read More](https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updating-data-once-a-request-is-complete) 17 | """.trimIndent() 18 | 19 | val PREPEND_EDGE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 20 | .name(PREPEND_EDGE_DIRECTIVE_NAME) 21 | .description(PREPEND_EDGE_DIRECTIVE_DESCRIPTION) 22 | .validLocations(Introspection.DirectiveLocation.FIELD) 23 | .argument { 24 | it.name("connections") 25 | it.type(nonNull(list(nonNull(GraphQLIDScalar)))) 26 | } 27 | .build() 28 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/PrependNodeDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.Scalars 4 | import graphql.introspection.Introspection 5 | import graphql.schema.GraphQLDirective 6 | import graphql.schema.GraphQLList.list 7 | import graphql.schema.GraphQLNonNull.nonNull 8 | import io.github.wickedev.graphql.scalars.GraphQLIDScalar 9 | 10 | internal const val PREPEND_NODE_DIRECTIVE_NAME = "prependNode" 11 | private val PREPEND_NODE_DIRECTIVE_DESCRIPTION = """ 12 | (Relay Only) 13 | 14 | For use within mutations. After the mutation request is complete, this node 15 | will be prepended to its parent connection. 16 | 17 | [Read More](https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updating-data-once-a-request-is-complete) 18 | """.trimIndent() 19 | 20 | val PREPEND_NODE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 21 | .name(PREPEND_NODE_DIRECTIVE_NAME) 22 | .description(PREPEND_NODE_DIRECTIVE_DESCRIPTION) 23 | .validLocations(Introspection.DirectiveLocation.FIELD) 24 | .argument { 25 | it.name("connections") 26 | it.type(nonNull(list(nonNull(GraphQLIDScalar)))) 27 | } 28 | .argument { 29 | it.name("edgeTypeName") 30 | it.type(nonNull(Scalars.GraphQLString)) 31 | } 32 | .build() 33 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/RawResponseTypeDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.introspection.Introspection 4 | import graphql.schema.GraphQLDirective 5 | 6 | internal const val RAW_RESPONSE_TYPE_DIRECTIVE_NAME = "raw_response_type" 7 | private val RAW_RESPONSE_TYPE_DIRECTIVE_DESCRIPTION = """ 8 | (Relay only) 9 | 10 | A directive added to queries which tells Relay to generate types that cover 11 | the `optimisticResponse` parameter to `commitMutation`. 12 | 13 | [Read More](https://relay.dev/docs/glossary/#raw_response_type) 14 | """.trimIndent() 15 | 16 | val RAW_RESPONSE_TYPE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 17 | .name(RAW_RESPONSE_TYPE_DIRECTIVE_NAME) 18 | .description(RAW_RESPONSE_TYPE_DIRECTIVE_DESCRIPTION) 19 | .validLocations(Introspection.DirectiveLocation.QUERY, Introspection.DirectiveLocation.MUTATION, Introspection.DirectiveLocation.SUBSCRIPTION) 20 | .build() 21 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/RefetchableDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.Scalars 4 | import graphql.introspection.Introspection 5 | import graphql.schema.GraphQLDirective 6 | import graphql.schema.GraphQLList.list 7 | import graphql.schema.GraphQLNonNull.nonNull 8 | 9 | internal const val REFETCHABLE_DIRECTIVE_NAME = "refetchable" 10 | private val REFETCHABLE_DIRECTIVE_DESCRIPTION = """ 11 | (Relay Only) 12 | 13 | For use with [`useRefetchableFragment`](https://relay.dev/docs/api-reference/use-refetchable-fragment/). 14 | 15 | The @refetchable directive can only be added to fragments that are 16 | "refetchable", that is, on fragments that are declared on Viewer or Query 17 | types, or on a type that implements `Node` (i.e. a type that has an id). 18 | 19 | [Read More](https://relay.dev/docs/api-reference/use-refetchable-fragment/#arguments) 20 | """.trimIndent() 21 | 22 | val REFETCHABLE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 23 | .name(REFETCHABLE_DIRECTIVE_NAME) 24 | .description(REFETCHABLE_DIRECTIVE_DESCRIPTION) 25 | .argument { 26 | it.name("queryName") 27 | it.type(nonNull(Scalars.GraphQLString)) 28 | } 29 | .argument { 30 | it.name("directives") 31 | it.type(list(nonNull(Scalars.GraphQLString))) 32 | } 33 | .validLocations(Introspection.DirectiveLocation.FRAGMENT_DEFINITION) 34 | .build() 35 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/RelayDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.Scalars 4 | import graphql.introspection.Introspection 5 | import graphql.language.BooleanValue 6 | import graphql.schema.GraphQLDirective 7 | 8 | internal const val RELAY_DIRECTIVE_NAME = "relay" 9 | private val RELAY_DIRECTIVE_DESCRIPTION = """ 10 | (Relay Only) 11 | 12 | A directive that allows you to turn off Relay's data masking. 13 | 14 | Read more 15 | [here](https://relay.dev/docs/api-reference/graphql-and-directives/#relayplural-boolean) 16 | and 17 | [here](https://relay.dev/docs/api-reference/graphql-and-directives/#relaymask-boolean). 18 | """.trimIndent() 19 | 20 | val RELAY_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 21 | .name(RELAY_DIRECTIVE_NAME) 22 | .description(RELAY_DIRECTIVE_DESCRIPTION) 23 | .argument { 24 | it.name("plural") 25 | it.type(Scalars.GraphQLBoolean) 26 | it.description("Marks a fragment as being backed by a GraphQLList.") 27 | } 28 | .argument { 29 | it.name("mask") 30 | it.type(Scalars.GraphQLBoolean) 31 | it.defaultValueLiteral(BooleanValue.of(true)) 32 | it.description("Marks a fragment spread which should be unmasked if provided false") 33 | } 34 | .validLocations( 35 | Introspection.DirectiveLocation.FRAGMENT_DEFINITION, 36 | Introspection.DirectiveLocation.FRAGMENT_SPREAD 37 | ) 38 | .build() 39 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/RelayTestOperationDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.introspection.Introspection 4 | import graphql.schema.GraphQLDirective 5 | 6 | internal const val RELAY_TEST_OPERATION_DIRECTIVE_NAME = "relay_test_operation" 7 | private val RELAY_TEST_OPERATION_DIRECTIVE_DESCRIPTION = """ 8 | (Relay Only) 9 | 10 | Will have additional metadata that will contain GraphQL type info for fields in the operation's selection 11 | 12 | [Read More](https://relay.dev/docs/guides/testing-relay-components/#relay_test_operation) 13 | """.trimIndent() 14 | 15 | val RELAY_TEST_OPERATION_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 16 | .name(RELAY_TEST_OPERATION_DIRECTIVE_NAME) 17 | .description(RELAY_TEST_OPERATION_DIRECTIVE_DESCRIPTION) 18 | .validLocations(Introspection.DirectiveLocation.QUERY, Introspection.DirectiveLocation.MUTATION, Introspection.DirectiveLocation.SUBSCRIPTION) 19 | .build() 20 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/RequiredDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.introspection.Introspection 4 | import graphql.schema.GraphQLDirective 5 | import graphql.schema.GraphQLNonNull.nonNull 6 | import io.github.wickedev.graphql.enums.REQUIRED_FIELD_ACTION 7 | 8 | internal const val REQUIRED_DIRECTIVE_NAME = "required" 9 | private val REQUIRED_DIRECTIVE_DESCRIPTION = """ 10 | (Relay Only) 11 | 12 | `@required` is a directive you can add to fields in your Relay queries to 13 | declare how null values should be handled at runtime. You can think of it as 14 | saying "if this field is ever null, its parent field is invalid and should be 15 | null". 16 | 17 | [Read More](https://relay.dev/docs/guides/required-directive/) 18 | """.trimIndent() 19 | 20 | val REQUIRED_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 21 | .name(REQUIRED_DIRECTIVE_NAME) 22 | .description(REQUIRED_DIRECTIVE_DESCRIPTION) 23 | .argument { 24 | it.name("action") 25 | it.type(nonNull(REQUIRED_FIELD_ACTION)) 26 | } 27 | .validLocations(Introspection.DirectiveLocation.FIELD) 28 | .build() 29 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/StreamConnectionDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.Scalars 4 | import graphql.introspection.Introspection 5 | import graphql.language.BooleanValue 6 | import graphql.schema.GraphQLDirective 7 | import graphql.schema.GraphQLList.list 8 | import graphql.schema.GraphQLNonNull.nonNull 9 | 10 | 11 | internal const val STREAM_CONNECTION_DIRECTIVE_NAME = "stream_connection" 12 | private val STREAM_CONNECTION_DIRECTIVE_DESCRIPTION = """ 13 | (Relay Only) 14 | 15 | A directive which declares that a field implements the connection spec. 16 | 17 | [Read More](https://relay.dev/docs/guided-tour/list-data/pagination/) 18 | """.trimIndent() 19 | 20 | 21 | val STREAM_CONNECTION_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 22 | .name(STREAM_CONNECTION_DIRECTIVE_NAME) 23 | .description(STREAM_CONNECTION_DIRECTIVE_DESCRIPTION) 24 | .validLocations(Introspection.DirectiveLocation.FIELD) 25 | .argument { 26 | it.name("key") 27 | it.type(nonNull(Scalars.GraphQLString)) 28 | } 29 | .argument { 30 | it.name("filters") 31 | it.type(list(Scalars.GraphQLString)) 32 | } 33 | .argument { 34 | it.name("handler") 35 | it.type(Scalars.GraphQLString) 36 | } 37 | .argument { 38 | it.name("initial_count") 39 | it.type(nonNull(Scalars.GraphQLInt)) 40 | } 41 | .argument { 42 | it.name("if") 43 | it.type(Scalars.GraphQLBoolean) 44 | it.defaultValueLiteral(BooleanValue.of(true)) 45 | } 46 | .argument { 47 | it.name("use_customized_batch") 48 | it.type(Scalars.GraphQLBoolean) 49 | it.defaultValueLiteral(BooleanValue.of(false)) 50 | } 51 | .argument { 52 | it.name("dynamicKey_UNSTABLE") 53 | it.type(Scalars.GraphQLString) 54 | } 55 | .build() 56 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/directives/UpdatableDirective.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.introspection.Introspection 4 | import graphql.schema.GraphQLDirective 5 | 6 | internal const val UPDATABLE_DIRECTIVE_NAME = "updatable" 7 | private val UPDATABLE_DIRECTIVE_DESCRIPTION = """ 8 | (Relay Only) 9 | 10 | Marks a given query or fragment as updatable. 11 | 12 | [Read More](https://fb.quip.com/4FZaADvkQPPl) 13 | """.trimIndent() 14 | 15 | val UPDATABLE_DIRECTIVE_TYPE: GraphQLDirective = GraphQLDirective.newDirective() 16 | .name(UPDATABLE_DIRECTIVE_NAME) 17 | .description(UPDATABLE_DIRECTIVE_DESCRIPTION) 18 | .validLocations(Introspection.DirectiveLocation.QUERY, Introspection.DirectiveLocation.FRAGMENT_DEFINITION) 19 | .build() 20 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/enums/RequiredFieldAction.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.enums 2 | 3 | import graphql.schema.GraphQLEnumType 4 | 5 | val REQUIRED_FIELD_ACTION = GraphQLEnumType.newEnum() 6 | .name("RequiredFieldAction") 7 | .value("NONE") 8 | .value("LOG") 9 | .value("THROW") 10 | .build() -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/exceptions/ApolloError.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package io.github.wickedev.graphql.exceptions 4 | 5 | import graphql.execution.ResultPath 6 | import graphql.language.SourceLocation 7 | 8 | open class ApolloError(code: String, message: String, path: ResultPath, sourceLocation: SourceLocation) : 9 | ApolloException(code, message, path, sourceLocation) { 10 | 11 | class SyntaxError(message: String, path: ResultPath, sourceLocation: SourceLocation) : 12 | ApolloError("GRAPHQL_PARSE_FAILED", message, path, sourceLocation) 13 | 14 | class ValidationError(message: String, path: ResultPath, sourceLocation: SourceLocation) : 15 | ApolloError("GRAPHQL_VALIDATION_FAILED", message, path, sourceLocation) 16 | 17 | class UserInputError(message: String, path: ResultPath, sourceLocation: SourceLocation) : 18 | ApolloError("BAD_USER_INPUT", message, path, sourceLocation) 19 | 20 | class AuthenticationError(message: String, path: ResultPath, sourceLocation: SourceLocation) : 21 | ApolloError("UNAUTHENTICATED", message, path, sourceLocation) 22 | 23 | class ForbiddenError(message: String, path: ResultPath, sourceLocation: SourceLocation) : 24 | ApolloError("FORBIDDEN", message, path, sourceLocation) 25 | 26 | class PersistedQueryNotFoundError(message: String, path: ResultPath, sourceLocation: SourceLocation) : 27 | ApolloError("PERSISTED_QUERY_NOT_FOUND", message, path, sourceLocation) 28 | 29 | class PersistedQueryNotSupportedError(message: String, path: ResultPath, sourceLocation: SourceLocation) : 30 | ApolloError("PERSISTED_QUERY_NOT_SUPPORTED", message, path, sourceLocation) 31 | 32 | class TokenNotExistError(message: String, path: ResultPath, sourceLocation: SourceLocation) : 33 | ApolloError("AUTHORIZATION_TOKEN_NOT_EXIST", message, path, sourceLocation) 34 | } 35 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/exceptions/ErrorExtension.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.exceptions 2 | 3 | import java.io.PrintWriter 4 | import java.io.StringWriter 5 | 6 | @Suppress("unused") 7 | class ErrorExtension( 8 | exception: Exception 9 | ) { 10 | companion object { 11 | private fun stackTraceToList(exception: Exception): List { 12 | val sw = StringWriter() 13 | val writer = PrintWriter(sw) 14 | try { 15 | exception.printStackTrace(writer) 16 | } catch (e: Error) { 17 | e.printStackTrace() 18 | } finally { 19 | writer.close() 20 | sw.close() 21 | } 22 | 23 | return sw.toString().split("\n") 24 | } 25 | } 26 | 27 | val message: String? = exception.message 28 | 29 | val localizedMessage: String? = exception.localizedMessage 30 | 31 | val cause = exception.cause 32 | 33 | val stackTrace: List = stackTraceToList(exception).take(10).map { it.replace("\t", " ") } 34 | } -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/extentions/DecodeBase64.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.extentions 2 | 3 | import io.github.wickedev.graphql.types.ID 4 | import io.github.wickedev.graphql.utils.decoder 5 | import java.nio.charset.StandardCharsets 6 | 7 | fun String.toLocalID(): ID { 8 | return try { 9 | val decoded = String(decoder.decode(this), StandardCharsets.UTF_8) 10 | val split = decoded.split(":".toRegex(), 2).toTypedArray() 11 | require(split.size == 2) { String.format("expecting a valid global id, got %s", this) } 12 | val type = split.getOrElse(0) { "" } 13 | val value = split.getOrElse(1) { "" } 14 | 15 | ID(type, value) 16 | } catch (e: IllegalArgumentException) { 17 | ID(this) 18 | } 19 | } -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/extentions/EncodeBase64.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.extentions 2 | 3 | import io.github.wickedev.graphql.utils.encoder 4 | import java.nio.charset.StandardCharsets 5 | 6 | internal fun String.encodeBase64(): String { 7 | return encoder.encodeToString(toByteArray(StandardCharsets.UTF_8)) 8 | } -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/extentions/Types.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.extentions 2 | 3 | import kotlin.reflect.KParameter 4 | import kotlin.reflect.KType 5 | import kotlin.reflect.full.isSubclassOf 6 | import kotlin.reflect.jvm.jvmErasure 7 | 8 | 9 | fun typeName(input: Any?): String? { 10 | return if (input == null) { 11 | "null" 12 | } else input.javaClass.simpleName 13 | } 14 | 15 | fun KParameter.isList(): Boolean { 16 | return type.isList() 17 | } 18 | 19 | fun KParameter.isArray(): Boolean { 20 | return type.isArray() 21 | } 22 | 23 | val KParameter.genericType: KType? 24 | get() = if (type.arguments.isNotEmpty()) type.arguments[0].type else null 25 | 26 | fun KType.isList(): Boolean { 27 | return jvmErasure.isSubclassOf(List::class) 28 | } 29 | 30 | fun KType.isArray(): Boolean { 31 | return javaClass.isArray 32 | } 33 | 34 | fun Any.isCollection(): Boolean { 35 | return this is Array<*> || this is Collection<*> || this is Map<*, *> 36 | } -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/interfases/Node.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.interfases 2 | 3 | import io.github.wickedev.graphql.types.ID 4 | 5 | interface Node { 6 | val id: ID 7 | } -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/scalars/CustomScalars.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package io.github.wickedev.graphql.scalars 4 | 5 | import graphql.schema.GraphQLType 6 | import kotlin.reflect.KClassifier 7 | import kotlin.reflect.KType 8 | 9 | class CustomScalars private constructor(private val scalars: Map) { 10 | 11 | companion object { 12 | fun of(vararg scalars: Pair): CustomScalars { 13 | return CustomScalars(mapOf(*scalars)) 14 | } 15 | } 16 | 17 | fun exists(type: KType?): Boolean = scalars.containsKey(type?.classifier) 18 | 19 | fun exists(type: KClassifier?): Boolean = scalars.containsKey(type) 20 | 21 | fun typeToGraphQLType(type: KClassifier?): GraphQLType? = scalars[type] 22 | } 23 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/scalars/GraphQLIDScalar.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.scalars 2 | 3 | import graphql.language.StringValue 4 | import graphql.schema.Coercing 5 | import graphql.schema.CoercingParseLiteralException 6 | import graphql.schema.CoercingParseValueException 7 | import graphql.schema.GraphQLScalarType 8 | import io.github.wickedev.graphql.extentions.toLocalID 9 | import io.github.wickedev.graphql.extentions.typeName 10 | import io.github.wickedev.graphql.types.ID 11 | 12 | val GraphQLIDScalar: GraphQLScalarType = GraphQLScalarType.newScalar() 13 | .name("ID") 14 | .description("Built-in ID") 15 | .coercing(object : Coercing { 16 | override fun serialize(input: Any): String { 17 | if (input is ID) { 18 | return input.encoded 19 | } 20 | 21 | throw CoercingParseValueException("Expected type 'ID' but was '${typeName(input)}'.") 22 | } 23 | 24 | override fun parseValue(input: Any): ID { 25 | if (input is String) { 26 | return input.toLocalID() 27 | } 28 | 29 | throw CoercingParseValueException("Expected type 'ID' but was '${typeName(input)}'.") 30 | } 31 | 32 | override fun parseLiteral(input: Any): ID { 33 | if (input is StringValue) { 34 | return input.value.toLocalID() 35 | } 36 | 37 | throw CoercingParseLiteralException("Expected AST type 'StringValue' but was '${typeName(input)}'.") 38 | } 39 | }) 40 | .build() -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/scalars/SetScalar.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.scalars 2 | 3 | import graphql.schema.Coercing 4 | import graphql.schema.GraphQLScalarType 5 | 6 | val SetScalar: GraphQLScalarType = GraphQLScalarType.newScalar() 7 | .name("SetScalar") 8 | .description("Collection of elements that does not support duplicate elements") 9 | .coercing(object : Coercing, List<*>> { 10 | override fun serialize(dataFetcherResult: Any): List<*> { 11 | return if (dataFetcherResult is Set<*>) { 12 | dataFetcherResult.toList() 13 | } else { 14 | emptyList() 15 | } 16 | } 17 | 18 | override fun parseValue(input: Any): Set<*> { 19 | return if (input is List<*>) { 20 | input.toSet() 21 | } else { 22 | emptySet() 23 | } 24 | } 25 | 26 | override fun parseLiteral(input: Any): Set<*> { 27 | return if (input is List<*>) { 28 | input.toSet() 29 | } else { 30 | emptySet() 31 | } 32 | } 33 | }) 34 | .build() -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/scalars/SkipScalar.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.scalars 2 | 3 | import graphql.schema.Coercing 4 | import graphql.schema.CoercingSerializeException 5 | import graphql.schema.GraphQLScalarType 6 | 7 | val SkipScalar: GraphQLScalarType = GraphQLScalarType.newScalar() 8 | .name("__Skip__") 9 | .coercing(object : Coercing { 10 | override fun serialize(dataFetcherResult: Any): Nothing { 11 | throw CoercingSerializeException("Skip types should never be used.") 12 | } 13 | 14 | override fun parseValue(input: Any): Nothing { 15 | throw CoercingSerializeException("Skip types should never be used.") 16 | } 17 | 18 | override fun parseLiteral(input: Any): Nothing { 19 | throw CoercingSerializeException("Skip types should never be used.") 20 | } 21 | }) 22 | .build() -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/types/Backward.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.types 2 | 3 | data class Backward( 4 | val last: Int? = null, 5 | val before: ID? = null, 6 | val orderBy: List? = null, 7 | ) : ConnectionParam -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/types/Connection.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.types 2 | 3 | import io.github.wickedev.graphql.interfases.Node 4 | 5 | data class Connection( 6 | val edges: List>, 7 | val pageInfo: PageInfo 8 | ) 9 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/types/ConnectionParam.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.types 2 | 3 | interface ConnectionParam 4 | 5 | const val DEFAULT_CONNECTION_SIZE = 10 -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/types/Direction.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.types 2 | 3 | enum class Direction { 4 | ASC, DESC 5 | } -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/types/Edge.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.types 2 | 3 | import io.github.wickedev.graphql.interfases.Node 4 | 5 | data class Edge( 6 | val node: T, 7 | val cursor: ID 8 | ) -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/types/Forward.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.types 2 | 3 | data class Forward( 4 | val first: Int? = null, 5 | val after: ID? = null, 6 | val orderBy: List? = null, 7 | ) : ConnectionParam -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/types/ID.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MemberVisibilityCanBePrivate", "unused") 2 | 3 | package io.github.wickedev.graphql.types 4 | 5 | import io.github.wickedev.graphql.extentions.encodeBase64 6 | import kotlin.reflect.KClass 7 | 8 | data class ID(val type: String, val value: String) { 9 | 10 | companion object { 11 | val Empty = ID("", "") 12 | } 13 | 14 | val encoded: String = if (type.isNotEmpty()) "$type:$value".encodeBase64() else value.encodeBase64() 15 | 16 | constructor(value: String) : this("", value) 17 | 18 | fun toGlobalId(type: String): ID = ID(type, value) 19 | 20 | fun toGlobalId(type: KClass<*>): ID = ID(type.simpleName ?: "", value) 21 | 22 | override fun toString(): String = if (type.isEmpty()) value else "ID($type:$value:$encoded)" 23 | } 24 | -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/types/Order.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.types 2 | 3 | data class Order( 4 | val property: String, 5 | val direction: Direction? 6 | ) -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/types/PageInfo.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.types 2 | 3 | data class PageInfo( 4 | val hasPreviousPage: Boolean, 5 | val hasNextPage: Boolean, 6 | val startCursor: String, 7 | val endCursor: String, 8 | ) -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/utils/Base64Decoder.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.utils 2 | 3 | import java.util.* 4 | 5 | internal val decoder = Base64.getUrlDecoder() -------------------------------------------------------------------------------- /graphql-core/src/main/kotlin/io/github/wickedev/graphql/utils/Base64Encoder.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.utils 2 | 3 | import java.util.* 4 | 5 | internal val encoder: Base64.Encoder = Base64.getUrlEncoder().withoutPadding() 6 | -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/AppendEdgeDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class AppendEdgeDirectiveTest : FunSpec({ 8 | test("APPEND_EDGE_DIRECTIVE_TYPE generate directive @appendEdge") { 9 | val schema = generateSchemaWithDirective(APPEND_EDGE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | For use within mutations. After the mutation request is complete, this edge 17 | will be prepended to its parent connection. 18 | 19 | [Read More](https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updating-data-once-a-request-is-complete) 20 | ""${'"'} 21 | directive @appendEdge(connections: [ID!]!) on FIELD 22 | """.trimIndent()) 23 | } 24 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/AppendNodeDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class AppendNodeDirectiveTest : FunSpec({ 8 | test("APPEND_NODE_DIRECTIVE_TYPE generate directive @appendNode") { 9 | val schema = generateSchemaWithDirective(APPEND_NODE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | For use within mutations. After the mutation request is complete, this node 17 | will be appended to its parent connection. 18 | 19 | [Read More](https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updating-data-once-a-request-is-complete) 20 | ""${'"'} 21 | directive @appendNode(connections: [ID!]!, edgeTypeName: String!) on FIELD 22 | """.trimIndent()) 23 | } 24 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/AssignableDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class AssignableDirectiveTest : FunSpec({ 8 | test("ASSIGNABLE_DIRECTIVE_TYPE generate directive @assignable") { 9 | val schema = generateSchemaWithDirective(ASSIGNABLE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | Marks a given fragment as assignable. 17 | 18 | [Read More](https://fb.quip.com/4FZaADvkQPPl) 19 | ""${'"'} 20 | directive @assignable on FRAGMENT_DEFINITION 21 | """.trimIndent()) 22 | } 23 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/ConnectionDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class ConnectionDirectiveTest : FunSpec({ 8 | test("CONNECTION_DIRECTIVE_TYPE generate directive @connection") { 9 | val schema = generateSchemaWithDirective(CONNECTION_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | A directive which declares that a field implements the connection spec. 17 | 18 | [Read More](https://relay.dev/docs/guided-tour/list-data/pagination/) 19 | ""${'"'} 20 | directive @connection(dynamicKey_UNSTABLE: String, filters: [String], handler: String, key: String!) on FIELD 21 | """.trimIndent()) 22 | } 23 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/DeleteEdgeDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class DeleteEdgeDirectiveTest : FunSpec({ 8 | test("DELETE_EDGE_DIRECTIVE_TYPE generate directive @deleteEdge") { 9 | val schema = generateSchemaWithDirective(DELETE_EDGE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | For use within mutations. After the mutation request is complete, this edge 17 | will be removed from its parent connection. 18 | 19 | [Read More](https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updating-data-once-a-request-is-complete) 20 | ""${'"'} 21 | directive @deleteEdge(connections: [ID!]!) on FIELD 22 | """.trimIndent()) 23 | } 24 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/DeleteRecordDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class DeleteRecordDirectiveTest : FunSpec({ 8 | test("DELETE_RECORD_DIRECTIVE_TYPE generate directive @deleteRecord") { 9 | val schema = generateSchemaWithDirective(DELETE_RECORD_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | For use within mutations. After the mutation request is complete, this field 17 | will be removed from the store. 18 | 19 | [Read More](https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updating-data-once-a-request-is-complete) 20 | ""${'"'} 21 | directive @deleteRecord on FIELD 22 | """.trimIndent()) 23 | } 24 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/InlineDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class InlineDirectiveTest : FunSpec({ 8 | test("INLINE_DIRECTIVE_TYPE generate directive @inline") { 9 | val schema = generateSchemaWithDirective(INLINE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay only) 15 | 16 | The hooks APIs that Relay exposes allow you to read data from the store only 17 | during the render phase. In order to read data from outside of the render 18 | phase (or from outside of React), Relay exposes the `@inline` directive. The 19 | data from a fragment annotated with `@inline` can be read using `readInlineData`. 20 | 21 | [Read More](https://relay.dev/docs/api-reference/graphql-and-directives/#inline) 22 | ""${'"'} 23 | directive @inline on FRAGMENT_DEFINITION 24 | """.trimIndent()) 25 | } 26 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/MatchDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class MatchDirectiveTest : FunSpec({ 8 | test("MATCH_DIRECTIVE_TYPE generate directive @match") { 9 | val schema = generateSchemaWithDirective(MATCH_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | A directive that, when used in combination with `@module`, allows users to 17 | download specific JS components alongside the rest of the GraphQL payload if 18 | the field decorated with [`@match`](https://relay.dev/docs/glossary/#match) 19 | has a certain type. See [3D](https://relay.dev/docs/glossary/#3d). 20 | 21 | [Read More](https://relay.dev/docs/glossary/#match) 22 | ""${'"'} 23 | directive @match(key: String) on FIELD 24 | """.trimIndent()) 25 | } 26 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/ModuleDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class ModuleDirectiveTest : FunSpec({ 8 | test("MODULE_DIRECTIVE_TYPE generate directive @module") { 9 | val schema = generateSchemaWithDirective(MODULE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | A directive that, when used in combination with 17 | [`@match`](https://relay.dev/docs/glossary/#match), allows users to specify 18 | which JS components to download if the field decorated with @match has a 19 | certain type. See [3D](https://relay.dev/docs/glossary/#3d). 20 | 21 | [Read More](https://relay.dev/docs/glossary/#module) 22 | ""${'"'} 23 | directive @module(name: String!) on FRAGMENT_SPREAD 24 | """.trimIndent()) 25 | } 26 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/NoInlineDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class NoInlineDirectiveTest : FunSpec({ 8 | test("NO_INLINE_DIRECTIVE_TYPE generate directive @no_inline") { 9 | val schema = generateSchemaWithDirective(NO_INLINE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay only) 15 | 16 | which can be used to prevent common fragments from being inlined, resulting in smaller generated files. 17 | ""${'"'} 18 | directive @no_inline(raw_response_type: Boolean) on FRAGMENT_DEFINITION 19 | """.trimIndent()) 20 | } 21 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/PreLoadableDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class PreLoadableDirectiveTest : FunSpec({ 8 | test("PRE_LOADABLE_DIRECTIVE_TYPE generate directive @preloadable") { 9 | val schema = generateSchemaWithDirective(PRE_LOADABLE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | A directive that modifies queries and which causes Relay to generate 17 | `${'$'}Parameters.js` files and preloadable concrete requests. Required if the 18 | query is going to be used as part of an entry point. 19 | 20 | The `hackPreloader` argument is FB only and generates a Hack preloader file. 21 | 22 | [Read More](https://relay.dev/docs/glossary/#preloadable) 23 | ""${'"'} 24 | directive @preloadable(hackPreloader: Boolean = false) on QUERY 25 | """.trimIndent()) 26 | } 27 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/PrependEdgeDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class PrependEdgeDirectiveTest : FunSpec({ 8 | test("PREPEND_EDGE_DIRECTIVE_TYPE generate directive @prependEdge") { 9 | val schema = generateSchemaWithDirective(PREPEND_EDGE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | For use within mutations. After the mutation request is complete, this edge 17 | will be prepended to its parent connection. 18 | 19 | [Read More](https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updating-data-once-a-request-is-complete) 20 | ""${'"'} 21 | directive @prependEdge(connections: [ID!]!) on FIELD 22 | """.trimIndent()) 23 | } 24 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/PrependNodeDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class PrependNodeDirectiveTest : FunSpec({ 8 | test("PREPEND_NODE_DIRECTIVE_TYPE generate directive @prependNode") { 9 | val schema = generateSchemaWithDirective(PREPEND_NODE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | For use within mutations. After the mutation request is complete, this node 17 | will be prepended to its parent connection. 18 | 19 | [Read More](https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updating-data-once-a-request-is-complete) 20 | ""${'"'} 21 | directive @prependNode(connections: [ID!]!, edgeTypeName: String!) on FIELD 22 | """.trimIndent()) 23 | } 24 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/RawResponseTypeDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class RawResponseTypeDirectiveTest : FunSpec({ 8 | test("RAW_RESPONSE_TYPE_DIRECTIVE_TYPE generate directive @raw_response_type") { 9 | val schema = generateSchemaWithDirective(RAW_RESPONSE_TYPE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay only) 15 | 16 | A directive added to queries which tells Relay to generate types that cover 17 | the `optimisticResponse` parameter to `commitMutation`. 18 | 19 | [Read More](https://relay.dev/docs/glossary/#raw_response_type) 20 | ""${'"'} 21 | directive @raw_response_type on QUERY | MUTATION | SUBSCRIPTION 22 | """.trimIndent()) 23 | } 24 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/RefetchableDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class RefetchableDirectiveTest : FunSpec({ 8 | test("REFETCHABLE_DIRECTIVE_TYPE generate directive @refetchable") { 9 | val schema = generateSchemaWithDirective(REFETCHABLE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | For use with [`useRefetchableFragment`](https://relay.dev/docs/api-reference/use-refetchable-fragment/). 17 | 18 | The @refetchable directive can only be added to fragments that are 19 | "refetchable", that is, on fragments that are declared on Viewer or Query 20 | types, or on a type that implements `Node` (i.e. a type that has an id). 21 | 22 | [Read More](https://relay.dev/docs/api-reference/use-refetchable-fragment/#arguments) 23 | ""${'"'} 24 | directive @refetchable(directives: [String!], queryName: String!) on FRAGMENT_DEFINITION 25 | """.trimIndent()) 26 | } 27 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/RelayDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class RelayDirectiveTest : FunSpec({ 8 | test("RELAY_DIRECTIVE_TYPE generate directive @relay") { 9 | val schema = generateSchemaWithDirective(RELAY_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | A directive that allows you to turn off Relay's data masking. 17 | 18 | Read more 19 | [here](https://relay.dev/docs/api-reference/graphql-and-directives/#relayplural-boolean) 20 | and 21 | [here](https://relay.dev/docs/api-reference/graphql-and-directives/#relaymask-boolean). 22 | ""${'"'} 23 | directive @relay( 24 | "Marks a fragment spread which should be unmasked if provided false" 25 | mask: Boolean = true, 26 | "Marks a fragment as being backed by a GraphQLList." 27 | plural: Boolean 28 | ) on FRAGMENT_DEFINITION | FRAGMENT_SPREAD 29 | """.trimIndent()) 30 | } 31 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/RelayTestOperationDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class RelayTestOperationDirectiveTest : FunSpec({ 8 | test("RELAY_TEST_OPERATION_DIRECTIVE_TYPE generate directive @relay_test_operation") { 9 | val schema = generateSchemaWithDirective(RELAY_TEST_OPERATION_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | Will have additional metadata that will contain GraphQL type info for fields in the operation's selection 17 | 18 | [Read More](https://relay.dev/docs/guides/testing-relay-components/#relay_test_operation) 19 | ""${'"'} 20 | directive @relay_test_operation on QUERY | MUTATION | SUBSCRIPTION 21 | """.trimIndent()) 22 | } 23 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/RequiredDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class RequiredDirectiveTest : FunSpec({ 8 | test("REQUIRED_DIRECTIVE_TYPE generate directive @required") { 9 | val schema = generateSchemaWithDirective(REQUIRED_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | `@required` is a directive you can add to fields in your Relay queries to 17 | declare how null values should be handled at runtime. You can think of it as 18 | saying "if this field is ever null, its parent field is invalid and should be 19 | null". 20 | 21 | [Read More](https://relay.dev/docs/guides/required-directive/) 22 | ""${'"'} 23 | directive @required(action: RequiredFieldAction!) on FIELD 24 | """.trimIndent()) 25 | } 26 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/StreamConnectionDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class StreamConnectionDirectiveTest : FunSpec({ 8 | test("STREAM_CONNECTION_DIRECTIVE_TYPE generate directive @stream_connection") { 9 | val schema = generateSchemaWithDirective(STREAM_CONNECTION_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | A directive which declares that a field implements the connection spec. 17 | 18 | [Read More](https://relay.dev/docs/guided-tour/list-data/pagination/) 19 | ""${'"'} 20 | directive @stream_connection(dynamicKey_UNSTABLE: String, filters: [String], handler: String, if: Boolean = true, initial_count: Int!, key: String!, use_customized_batch: Boolean = false) on FIELD 21 | """.trimIndent()) 22 | } 23 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/TestSchemaGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import graphql.Scalars 4 | import graphql.schema.GraphQLDirective 5 | import graphql.schema.GraphQLFieldDefinition 6 | import graphql.schema.GraphQLObjectType 7 | import graphql.schema.GraphQLSchema 8 | 9 | fun generateSchemaWithDirective(directive: GraphQLDirective): GraphQLSchema { 10 | return GraphQLSchema.newSchema() 11 | .query( 12 | GraphQLObjectType.newObject() 13 | .name("Query") 14 | .field( 15 | GraphQLFieldDefinition.newFieldDefinition() 16 | .name("test") 17 | .type(Scalars.GraphQLInt)) 18 | .build() 19 | ) 20 | .additionalDirectives(setOf(directive)) 21 | .build() 22 | } -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/directives/UpdatableDirectiveTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.directives 2 | 3 | import com.expediagroup.graphql.generator.extensions.print 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.string.shouldContain 6 | 7 | class UpdatableDirectiveTest : FunSpec({ 8 | test("UPDATABLE_DIRECTIVE_TYPE generate directive @updatable") { 9 | val schema = generateSchemaWithDirective(UPDATABLE_DIRECTIVE_TYPE) 10 | 11 | println(schema.print()) 12 | schema.print() shouldContain (""" 13 | ""${'"'} 14 | (Relay Only) 15 | 16 | Marks a given query or fragment as updatable. 17 | 18 | [Read More](https://fb.quip.com/4FZaADvkQPPl) 19 | ""${'"'} 20 | directive @updatable on QUERY | FRAGMENT_DEFINITION 21 | """.trimIndent()) 22 | } 23 | }) -------------------------------------------------------------------------------- /graphql-core/src/test/kotlin/io/github/wickedev/graphql/scalars/GraphQLIDScalarTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.scalars 2 | 3 | import graphql.language.StringValue 4 | import io.github.wickedev.graphql.extentions.encodeBase64 5 | import io.github.wickedev.graphql.types.ID 6 | import io.kotest.core.spec.style.DescribeSpec 7 | import io.kotest.matchers.shouldBe 8 | import io.kotest.matchers.types.shouldBeInstanceOf 9 | 10 | class GraphQLIDScalarTest : DescribeSpec({ 11 | 12 | describe("GraphQLIDScalar") { 13 | 14 | it("should serialize correctly") { 15 | val serialized = GraphQLIDScalar.coercing.serialize(ID("user", "521")) 16 | 17 | serialized shouldBe "user:521".encodeBase64() 18 | } 19 | 20 | it("should parseValue correctly") { 21 | val id = GraphQLIDScalar.coercing.parseValue("user:521".encodeBase64()) 22 | 23 | id.shouldBeInstanceOf() 24 | 25 | id.type shouldBe "user" 26 | id.value shouldBe "521" 27 | } 28 | 29 | it("should parseLiteral correctly") { 30 | val id = 31 | GraphQLIDScalar.coercing.parseLiteral(StringValue.newStringValue("user:521".encodeBase64()).build()) 32 | 33 | id.shouldBeInstanceOf() 34 | 35 | id.type shouldBe "user" 36 | id.value shouldBe "521" 37 | } 38 | } 39 | }) -------------------------------------------------------------------------------- /graphql-jetpack-autoconfigure/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.jetbrains.kotlin.jvm") version "1.6.10" 3 | id("org.jetbrains.kotlin.plugin.spring") version "1.6.10" 4 | id("org.springframework.boot") version "2.6.1" 5 | id("io.spring.dependency-management") version "1.0.11.RELEASE" 6 | id("ru.vyarus.pom") version "2.2.1" 7 | } 8 | 9 | dependencies { 10 | api(project(":graphql-jetpack")) 11 | api(project(":graphql-kotlin-spring-security")) 12 | api(project(":graphql-kotlin-spring-webflux-upload")) 13 | optional("com.graphql-java:graphql-java:17.3") 14 | optional("com.graphql-java:graphql-java-extended-scalars:17.0") 15 | optional("com.expediagroup:graphql-kotlin-hooks-provider:5.3.1") 16 | optional("org.springframework.boot:spring-boot-autoconfigure") 17 | optional("io.github.wickedev:spring-security-jwt-webflux:0.3.0") 18 | } 19 | 20 | tasks.withType { 21 | useJUnitPlatform() 22 | } -------------------------------------------------------------------------------- /graphql-jetpack-autoconfigure/src/main/kotlin/io/github/wickedev/graphql/kotlin/JetpackAutoConfiguration.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.kotlin 2 | 3 | import com.expediagroup.graphql.server.spring.GraphQLAutoConfiguration 4 | import org.springframework.boot.autoconfigure.AutoConfigureBefore 5 | import org.springframework.context.annotation.Configuration 6 | import org.springframework.context.annotation.Import 7 | 8 | @Configuration 9 | @Import( 10 | JetpackGraphQLSchemaConfiguration::class, 11 | GraphQLAutoConfiguration::class, 12 | ) 13 | @AutoConfigureBefore(GraphQLAutoConfiguration::class) 14 | class JetpackAutoConfiguration -------------------------------------------------------------------------------- /graphql-jetpack-autoconfigure/src/main/kotlin/io/github/wickedev/graphql/kotlin/JetpackFederatedSchemaAutoConfiguration.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.kotlin 2 | 3 | import com.expediagroup.graphql.generator.federation.FederatedSchemaGeneratorHooks 4 | import com.expediagroup.graphql.generator.federation.execution.FederatedTypeResolver 5 | import com.expediagroup.graphql.server.spring.FederatedSchemaAutoConfiguration 6 | import io.github.wickedev.graphql.AuthSchemaDirectiveWiring 7 | import io.github.wickedev.graphql.scalars.CustomScalars 8 | import org.springframework.boot.autoconfigure.AutoConfigureBefore 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 11 | import org.springframework.context.annotation.Bean 12 | import org.springframework.context.annotation.Import 13 | import java.util.* 14 | 15 | @ConditionalOnProperty(value = ["graphql.federation.enabled"], havingValue = "true") 16 | @Import(JetpackGraphQLExecutionConfiguration::class) 17 | @AutoConfigureBefore(FederatedSchemaAutoConfiguration::class) 18 | class JetpackFederatedSchemaAutoConfiguration { 19 | 20 | @Bean 21 | @ConditionalOnMissingBean 22 | fun federatedSchemaGeneratorHooks( 23 | resolvers: Optional>>, 24 | customScalars: CustomScalars, 25 | authSchemaDirectiveWiring: AuthSchemaDirectiveWiring, 26 | ): FederatedSchemaGeneratorHooks = 27 | JetpackFederatedSchemaGeneratorHooks( 28 | resolvers.orElse(emptyList()), 29 | customScalars, 30 | authSchemaDirectiveWiring 31 | ) 32 | } -------------------------------------------------------------------------------- /graphql-jetpack-autoconfigure/src/main/kotlin/io/github/wickedev/graphql/kotlin/JetpackNonFederatedSchemaAutoConfiguration.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.kotlin 2 | 3 | import com.expediagroup.graphql.server.spring.NonFederatedSchemaAutoConfiguration 4 | import io.github.wickedev.graphql.AuthSchemaDirectiveWiring 5 | import io.github.wickedev.graphql.scalars.CustomScalars 6 | import org.springframework.boot.autoconfigure.AutoConfigureBefore 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 9 | import org.springframework.context.annotation.Bean 10 | import org.springframework.context.annotation.Import 11 | 12 | @ConditionalOnProperty(value = ["graphql.federation.enabled"], havingValue = "false", matchIfMissing = true) 13 | @Import(JetpackGraphQLExecutionConfiguration::class) 14 | @AutoConfigureBefore(NonFederatedSchemaAutoConfiguration::class) 15 | class JetpackNonFederatedSchemaAutoConfiguration { 16 | @Bean 17 | @ConditionalOnMissingBean 18 | fun schemaGeneratorHooks( 19 | customScalars: CustomScalars, 20 | authSchemaDirectiveWiring: AuthSchemaDirectiveWiring, 21 | ): JetpackSchemaGeneratorHooks = 22 | JetpackSchemaGeneratorHooks( 23 | customScalars, 24 | authSchemaDirectiveWiring 25 | ) 26 | } -------------------------------------------------------------------------------- /graphql-jetpack-autoconfigure/src/main/kotlin/io/github/wickedev/graphql/kotlin/JetpackSchemaGeneratorHooksProvider.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.kotlin 2 | 3 | import com.expediagroup.graphql.generator.hooks.SchemaGeneratorHooks 4 | import com.expediagroup.graphql.plugin.schema.hooks.SchemaGeneratorHooksProvider 5 | 6 | @Suppress("unused") 7 | class JetpackSchemaGeneratorHooksProvider: SchemaGeneratorHooksProvider { 8 | override fun hooks(): SchemaGeneratorHooks { 9 | val configuration = JetpackGraphQLSchemaConfiguration() 10 | return JetpackSchemaGeneratorHooks( 11 | configuration.customScalars(), 12 | configuration.authSchemaDirectiveWiring() 13 | ) 14 | } 15 | } -------------------------------------------------------------------------------- /graphql-jetpack-autoconfigure/src/main/resources/META-INF/services/com.expediagroup.graphql.plugin.schema.hooks.SchemaGeneratorHooksProvider: -------------------------------------------------------------------------------- 1 | io.github.wickedev.graphql.kotlin.JetpackSchemaGeneratorHooksProvider -------------------------------------------------------------------------------- /graphql-jetpack-autoconfigure/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=io.github.wickedev.graphql.kotlin.JetpackAutoConfiguration 2 | -------------------------------------------------------------------------------- /graphql-jetpack-starter/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.jetbrains.kotlin.jvm") version "1.6.10" 3 | id("org.jetbrains.kotlin.plugin.spring") version "1.6.10" 4 | id("org.springframework.boot") version "2.6.1" 5 | id("io.spring.dependency-management") version "1.0.11.RELEASE" 6 | } 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | api(project(":graphql-jetpack-autoconfigure")) 14 | api("com.graphql-java:graphql-java:17.3") 15 | api("com.graphql-java:graphql-java-extended-scalars:17.0") 16 | api("com.expediagroup:graphql-kotlin-hooks-provider:5.3.1") 17 | api("io.github.wickedev:spring-security-jwt-webflux:0.3.0") 18 | } 19 | -------------------------------------------------------------------------------- /graphql-jetpack/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.6.10" 3 | id("org.jetbrains.kotlin.plugin.spring") version "1.6.10" 4 | id("org.springframework.boot") version "2.6.1" 5 | id("io.spring.dependency-management") version "1.0.11.RELEASE" 6 | } 7 | 8 | dependencies { 9 | implementation(kotlin("stdlib-jdk8")) 10 | 11 | api(project(":graphql-kotlin-spring-security")) 12 | api("com.expediagroup:graphql-kotlin-spring-server:5.3.1") 13 | api("org.springframework.security:spring-security-core") 14 | } 15 | 16 | tasks.withType { 17 | useJUnitPlatform() 18 | } -------------------------------------------------------------------------------- /graphql-jetpack/src/main/kotlin/io/github/wickedev/graphql/kotlin/JetpackDataFetcherFactoryProvider.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.kotlin 2 | 3 | import com.expediagroup.graphql.generator.execution.SimpleKotlinDataFetcherFactoryProvider 4 | import com.fasterxml.jackson.databind.ObjectMapper 5 | import graphql.schema.DataFetcherFactory 6 | import io.github.wickedev.graphql.JetpackFunctionDataFetcher 7 | import io.github.wickedev.graphql.scalars.CustomScalars 8 | import org.aopalliance.intercept.MethodInvocation 9 | import org.springframework.context.ApplicationContext 10 | import org.springframework.security.access.expression.SecurityExpressionHandler 11 | import kotlin.reflect.KFunction 12 | 13 | open class JetpackDataFetcherFactoryProvider( 14 | private val objectMapper: ObjectMapper, 15 | private val applicationContext: ApplicationContext, 16 | private val securityExpressionHandler: SecurityExpressionHandler, 17 | private val customScalars: CustomScalars 18 | ) : SimpleKotlinDataFetcherFactoryProvider(objectMapper) { 19 | 20 | override fun functionDataFetcherFactory(target: Any?, kFunction: KFunction<*>): DataFetcherFactory = 21 | DataFetcherFactory { 22 | JetpackFunctionDataFetcher( 23 | target, 24 | kFunction, 25 | objectMapper, 26 | applicationContext, 27 | securityExpressionHandler, 28 | customScalars 29 | ) 30 | } 31 | } -------------------------------------------------------------------------------- /graphql-jetpack/src/main/kotlin/io/github/wickedev/graphql/kotlin/JetpackFederatedSchemaGeneratorHooks.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.kotlin 2 | 3 | import com.expediagroup.graphql.generator.directives.KotlinDirectiveWiringFactory 4 | import com.expediagroup.graphql.generator.federation.FederatedSchemaGeneratorHooks 5 | import com.expediagroup.graphql.generator.federation.execution.FederatedTypeResolver 6 | import graphql.schema.GraphQLDirective 7 | import graphql.schema.GraphQLSchema 8 | import graphql.schema.GraphQLType 9 | import io.github.wickedev.graphql.AuthDirectiveWiringFactory 10 | import io.github.wickedev.graphql.AuthSchemaDirectiveWiring 11 | import io.github.wickedev.graphql.directives.* 12 | import io.github.wickedev.graphql.scalars.CustomScalars 13 | import org.reactivestreams.Publisher 14 | import kotlin.reflect.KType 15 | 16 | open class JetpackFederatedSchemaGeneratorHooks( 17 | resolvers: List>, 18 | private val customScalars: CustomScalars, 19 | private val authSchemaDirectiveWiring: AuthSchemaDirectiveWiring, 20 | ) : FederatedSchemaGeneratorHooks(resolvers) { 21 | 22 | override fun willGenerateGraphQLType(type: KType): GraphQLType? { 23 | return when (true) { 24 | customScalars.exists(type.classifier) -> customScalars.typeToGraphQLType(type.classifier) 25 | else -> super.willGenerateGraphQLType(type) 26 | } 27 | } 28 | 29 | override val wiringFactory: KotlinDirectiveWiringFactory 30 | get() = AuthDirectiveWiringFactory(authSchemaDirectiveWiring) 31 | 32 | override fun willResolveInputMonad(type: KType): KType = when (type.classifier) { 33 | Publisher::class -> type.arguments.firstOrNull()?.type 34 | else -> type 35 | } ?: type 36 | } 37 | -------------------------------------------------------------------------------- /graphql-jetpack/src/main/kotlin/io/github/wickedev/graphql/kotlin/JetpackSchemaGeneratorHooks.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.kotlin 2 | 3 | import com.expediagroup.graphql.generator.directives.KotlinDirectiveWiringFactory 4 | import com.expediagroup.graphql.generator.hooks.SchemaGeneratorHooks 5 | import graphql.schema.GraphQLType 6 | import io.github.wickedev.graphql.AuthDirectiveWiringFactory 7 | import io.github.wickedev.graphql.AuthSchemaDirectiveWiring 8 | import io.github.wickedev.graphql.scalars.CustomScalars 9 | import org.reactivestreams.Publisher 10 | import kotlin.reflect.KType 11 | 12 | open class JetpackSchemaGeneratorHooks( 13 | private val customScalars: CustomScalars, 14 | private val authSchemaDirectiveWiring: AuthSchemaDirectiveWiring, 15 | ) : SchemaGeneratorHooks { 16 | 17 | override fun willGenerateGraphQLType(type: KType): GraphQLType? { 18 | return when (true) { 19 | customScalars.exists(type.classifier) -> customScalars.typeToGraphQLType(type.classifier) 20 | else -> super.willGenerateGraphQLType(type) 21 | } 22 | } 23 | 24 | override val wiringFactory: KotlinDirectiveWiringFactory 25 | get() = AuthDirectiveWiringFactory(authSchemaDirectiveWiring) 26 | 27 | override fun willResolveInputMonad(type: KType): KType = when (type.classifier) { 28 | Publisher::class -> type.arguments.firstOrNull()?.type 29 | else -> type 30 | } ?: type 31 | } 32 | -------------------------------------------------------------------------------- /graphql-jetpack/src/main/kotlin/io/github/wickedev/graphql/kotlin/JetpackSpringGraphQLContextFactory.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.kotlin 2 | 3 | import com.expediagroup.graphql.server.spring.execution.SpringGraphQLContext 4 | import com.expediagroup.graphql.server.spring.execution.SpringGraphQLContextFactory 5 | import io.github.wickedev.coroutine.reactive.extensions.mono.await 6 | import org.springframework.security.core.context.ReactiveSecurityContextHolder 7 | import org.springframework.web.reactive.function.server.ServerRequest 8 | 9 | open class JetpackSpringGraphQLContextFactory : SpringGraphQLContextFactory() { 10 | 11 | @Suppress("OverridingDeprecatedMember") 12 | override suspend fun generateContext(request: ServerRequest): SpringGraphQLContext? = null 13 | 14 | override suspend fun generateContextMap(request: ServerRequest): Map<*, Any> { 15 | 16 | val security = ReactiveSecurityContextHolder.getContext().await() 17 | val authentication = security?.authentication 18 | 19 | return buildMap { 20 | put("request", request) 21 | if (authentication != null) put("authentication", authentication) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /graphql-jetpack/src/main/kotlin/io/github/wickedev/graphql/security/CustomDefaultMethodSecurityExpressionHandler.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.security 2 | 3 | import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler 4 | 5 | class CustomDefaultMethodSecurityExpressionHandler : DefaultMethodSecurityExpressionHandler() -------------------------------------------------------------------------------- /graphql-kotlin-spring-security/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.6.10" 3 | id("org.jetbrains.kotlin.plugin.spring") version "1.6.10" 4 | id("org.springframework.boot") version "2.6.1" 5 | id("io.spring.dependency-management") version "1.0.11.RELEASE" 6 | } 7 | 8 | dependencies { 9 | api(project(":graphql-core")) 10 | api(project(":kotlin-coroutine-reactive-extensions")) 11 | 12 | api("com.graphql-java:graphql-java:17.3") 13 | api("com.expediagroup:graphql-kotlin-spring-server:5.3.1") 14 | api("org.springframework.security:spring-security-core") 15 | api("com.zhokhov.graphql:graphql-java-datetime:4.1.0") 16 | api("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") 17 | implementation(kotlin("stdlib-jdk8")) 18 | 19 | /* testing */ 20 | testImplementation(project(":graphql-jetpack-autoconfigure")) 21 | testImplementation("io.github.wickedev:spring-security-jwt-webflux-starter:0.3.0") 22 | 23 | testImplementation("io.kotest:kotest-runner-junit5:5.0.3") 24 | testImplementation("io.kotest:kotest-assertions-core:5.0.3") 25 | testImplementation("io.kotest.extensions:kotest-extensions-spring:1.1.0") 26 | 27 | testImplementation("com.expediagroup:graphql-kotlin-spring-server:5.3.1") 28 | testImplementation("org.springframework.boot:spring-boot-starter-webflux") 29 | testImplementation("org.springframework.boot:spring-boot-starter-security") 30 | 31 | testImplementation("io.projectreactor:reactor-test") 32 | testImplementation("org.springframework.security:spring-security-test") 33 | testImplementation("org.bouncycastle:bcpkix-jdk15on:1.69") 34 | testImplementation("org.springframework.boot:spring-boot-starter-test") { 35 | exclude(group = "org.junit.vintage", module = "junit-vintage-engine") 36 | exclude(module = "mockito-core") 37 | } 38 | 39 | } 40 | 41 | tasks.withType { 42 | useJUnitPlatform() 43 | } -------------------------------------------------------------------------------- /graphql-kotlin-spring-security/src/main/kotlin/io/github/wickedev/graphql/AuthDataFetcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql 2 | 3 | import com.expediagroup.graphql.generator.directives.KotlinFieldDirectiveEnvironment 4 | import graphql.execution.DataFetcherResult 5 | import graphql.schema.DataFetcher 6 | import graphql.schema.DataFetchingEnvironment 7 | import io.github.wickedev.graphql.exceptions.ApolloError 8 | 9 | open class AuthDataFetcher( 10 | private val originalDataFetcher: DataFetcher<*>, 11 | private val directiveEnv: KotlinFieldDirectiveEnvironment, 12 | ) : DataFetcher { 13 | 14 | override fun get(environment: DataFetchingEnvironment): Any? { 15 | if (originalDataFetcher is JetpackFunctionDataFetcher) { 16 | val path = environment.executionStepInfo.path 17 | val sourceLocation = environment.field.sourceLocation 18 | val expression = directiveEnv.requires ?: return originalDataFetcher.get(environment) 19 | val message = "@auth(require: $expression)" 20 | 21 | val authentication = environment.graphQlContext.authentication 22 | ?: return newError(ApolloError.AuthenticationError(message, path, sourceLocation)) 23 | 24 | val result = originalDataFetcher.check(authentication, expression, environment) 25 | 26 | if (!result) { 27 | return newError(ApolloError.ForbiddenError(message, path, sourceLocation)) 28 | } 29 | } 30 | 31 | return originalDataFetcher.get(environment) 32 | } 33 | 34 | private fun newError(error: ApolloError): DataFetcherResult<*> { 35 | return DataFetcherResult.newResult() 36 | .error(error) 37 | .build() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /graphql-kotlin-spring-security/src/main/kotlin/io/github/wickedev/graphql/AuthDirective.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package io.github.wickedev.graphql 4 | 5 | import com.expediagroup.graphql.generator.annotations.GraphQLDirective 6 | import graphql.introspection.Introspection 7 | import org.springframework.security.access.prepost.PreAuthorize 8 | 9 | const val AUTH_DIRECTIVE_NAME = "auth" 10 | 11 | @GraphQLDirective( 12 | name = AUTH_DIRECTIVE_NAME, locations = [ 13 | Introspection.DirectiveLocation.FIELD, 14 | Introspection.DirectiveLocation.FIELD_DEFINITION 15 | ] 16 | ) 17 | annotation class Auth( 18 | val require: String = "isAuthenticated" 19 | ) -------------------------------------------------------------------------------- /graphql-kotlin-spring-security/src/main/kotlin/io/github/wickedev/graphql/AuthDirectiveWiringFactory.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql 2 | 3 | import com.expediagroup.graphql.generator.directives.KotlinDirectiveWiringFactory 4 | 5 | open class AuthDirectiveWiringFactory( 6 | authSchemaDirectiveWiring: AuthSchemaDirectiveWiring 7 | ) : KotlinDirectiveWiringFactory( 8 | manualWiring = mapOf( 9 | AUTH_DIRECTIVE_NAME to authSchemaDirectiveWiring, 10 | ) 11 | ) -------------------------------------------------------------------------------- /graphql-kotlin-spring-security/src/main/kotlin/io/github/wickedev/graphql/AuthSchemaDirectiveWiring.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql 2 | 3 | import com.expediagroup.graphql.generator.directives.KotlinFieldDirectiveEnvironment 4 | import com.expediagroup.graphql.generator.directives.KotlinSchemaDirectiveWiring 5 | import graphql.schema.DataFetcher 6 | import graphql.schema.GraphQLFieldDefinition 7 | 8 | open class AuthSchemaDirectiveWiring : KotlinSchemaDirectiveWiring { 9 | override fun onField(environment: KotlinFieldDirectiveEnvironment): GraphQLFieldDefinition { 10 | val field = environment.element 11 | val originalDataFetcher: DataFetcher<*> = environment.getDataFetcher() 12 | val fetcher = AuthDataFetcher(originalDataFetcher, environment) 13 | environment.setDataFetcher(fetcher) 14 | 15 | return field 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /graphql-kotlin-spring-security/src/main/kotlin/io/github/wickedev/graphql/Extentions.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package io.github.wickedev.graphql 4 | 5 | import com.expediagroup.graphql.generator.directives.KotlinFieldDirectiveEnvironment 6 | import graphql.GraphQLContext 7 | import graphql.schema.GraphQLDirective 8 | import org.springframework.security.core.Authentication 9 | import org.springframework.web.reactive.function.server.ServerRequest 10 | 11 | val KotlinFieldDirectiveEnvironment.hasAuthDirective: Boolean 12 | get() = directive.name == AUTH_DIRECTIVE_NAME 13 | 14 | val GraphQLDirective.requires: String? 15 | @Suppress("UNCHECKED_CAST") 16 | get() = getArgument("require").argumentValue.value as? String 17 | 18 | val KotlinFieldDirectiveEnvironment.requires: String? 19 | get() = if (hasAuthDirective) directive.requires else null 20 | 21 | val GraphQLContext.authentication: Authentication? 22 | get() = getOrDefault("authentication", null) 23 | 24 | val GraphQLContext.request: ServerRequest? 25 | get() = getOrDefault("request", null) 26 | -------------------------------------------------------------------------------- /graphql-kotlin-spring-security/src/test/kotlin/io/github/wickedev/graphql/MethodSecurityExpressionHandlerTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql 2 | 3 | import io.kotest.core.spec.style.DescribeSpec 4 | import io.kotest.matchers.shouldBe 5 | import org.springframework.boot.test.context.SpringBootTest 6 | import org.springframework.expression.EvaluationContext 7 | import org.springframework.security.access.expression.ExpressionUtils 8 | import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler 9 | import org.springframework.security.authentication.AnonymousAuthenticationToken 10 | import org.springframework.security.core.Authentication 11 | import org.springframework.security.core.authority.AuthorityUtils 12 | import org.springframework.security.util.MethodInvocationUtils 13 | 14 | @SpringBootTest 15 | class MethodSecurityExpressionHandlerTest( 16 | val expressionHandler: MethodSecurityExpressionHandler, 17 | ) : DescribeSpec({ 18 | describe("MethodSecurityExpressionHandler") { 19 | it("should expression evaluate true") { 20 | val controller = AnnotatedController() 21 | val parser = expressionHandler.expressionParser 22 | val expression = parser.parseExpression("hasRole('USER')") 23 | val authorities = AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS") 24 | val authentication: Authentication = AnonymousAuthenticationToken("key", "anonymous", authorities) 25 | 26 | val mi = MethodInvocationUtils.createFromClass( 27 | controller, 28 | AnnotatedController::class.java, 29 | AnnotatedController::preAuthorize.name, 30 | emptyArray(), 31 | emptyArray() 32 | ) 33 | 34 | val ctx: EvaluationContext = expressionHandler.createEvaluationContext(authentication, mi) 35 | 36 | val result = ExpressionUtils.evaluateAsBoolean(expression, ctx) 37 | result shouldBe false 38 | } 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /graphql-kotlin-spring-security/src/test/kotlin/io/github/wickedev/graphql/MethodSecurityMetadataSourceAdvisorTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql 2 | 3 | import io.kotest.core.spec.style.DescribeSpec 4 | import io.kotest.matchers.shouldBe 5 | import org.springframework.aop.framework.ProxyFactory 6 | import org.springframework.aop.support.AopUtils 7 | import org.springframework.boot.test.context.SpringBootTest 8 | import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor 9 | import reactor.core.publisher.Hooks 10 | import reactor.test.StepVerifier 11 | 12 | @SpringBootTest 13 | class MethodSecurityMetadataSourceAdvisorTest( 14 | val advisor: MethodSecurityMetadataSourceAdvisor, 15 | ) : DescribeSpec({ 16 | describe("MethodSecurityMetadataSourceAdvisor") { 17 | it("should protect preAuthorize method") { 18 | Hooks.onOperatorDebug() 19 | val controller = AnnotatedController() 20 | val canApply = AopUtils.canApply(advisor, AnnotatedController::class.java) 21 | canApply shouldBe true 22 | 23 | val pf = ProxyFactory() 24 | pf.addAdvisor(advisor) 25 | pf.setTarget(controller) 26 | 27 | val proxy = pf.proxy as AnnotatedController 28 | 29 | StepVerifier.create(proxy.preAuthorize()) 30 | .verifyErrorMessage("Denied") 31 | } 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /graphql-kotlin-spring-security/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | graphql: 2 | packages: 3 | - io.github.wickedev.graphql 4 | security: 5 | jwt: 6 | algorithm: EC 7 | issuer: https://graphql-jetpack.github.io 8 | private-key: classpath:keys/ec256-private.pem 9 | public-key: classpath:keys/ec256-public.pem 10 | access-token-expires-in: 1h 11 | refresh-token-expires-in: 60d -------------------------------------------------------------------------------- /graphql-kotlin-spring-security/src/test/resources/keys/ec256-private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg5hty10pgbCZMFgWT 3 | gg1bLg6AwnK9DvCRbRqwEgysltahRANCAAT2tzBcrZzGYurzB8L+kNj3e6RSFp4D 4 | 6DiBIOq0pdVKwKWsmxJBzrs+17m8I4p3cS96MQZ8eAmANlkmNDbnLQ5x 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /graphql-kotlin-spring-security/src/test/resources/keys/ec256-public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9rcwXK2cxmLq8wfC/pDY93ukUhae 3 | A+g4gSDqtKXVSsClrJsSQc67Pte5vCOKd3EvejEGfHgJgDZZJjQ25y0OcQ== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Kotlin Upload 2 | 3 | [GraphQL Multipart Request Spec](https://github.com/jaydenseric/graphql-multipart-request-spec) implementation of [GraphQL Kotlin](https://opensource.expediagroup.com/graphql-kotlin/docs/). 4 | 5 | # Getting Started 6 | 7 | ## Gradle 8 | 9 | ```kotlin 10 | repositories { 11 | maven("https://github.com/wickedev/graphql-jetpack/raw/deploy/maven-repo") 12 | } 13 | 14 | dependencies { 15 | implementation("io.github.wickedev:graphql-jetpack-starter:0.5.6") 16 | } 17 | ``` 18 | 19 | ## Example 20 | 21 | ```kotlin 22 | class Upload(filePart: FilePart) : FilePart by filePart 23 | 24 | @Compoment 25 | class SampleMutation : Mutation { 26 | fun uploads(files: List): String = "${files.map { it.filename() }} Upload Successfully" 27 | 28 | fun upload(file: Upload): String = "${file.filename()} Upload Successfully" 29 | } 30 | ``` -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.6.10" 3 | id("org.jetbrains.kotlin.plugin.spring") version "1.6.10" 4 | id("org.springframework.boot") version "2.6.1" 5 | id("io.spring.dependency-management") version "1.0.11.RELEASE" 6 | } 7 | 8 | dependencies { 9 | implementation(kotlin("stdlib-jdk8")) 10 | 11 | api(project(":kotlin-coroutine-reactive-extensions")) 12 | api("com.jayway.jsonpath:json-path:2.6.0") 13 | api("org.springframework:spring-webflux") 14 | api("com.expediagroup:graphql-kotlin-spring-server:5.3.1") 15 | 16 | /* testing */ 17 | testImplementation(project(":graphql-jetpack-starter")) 18 | testImplementation("io.kotest:kotest-runner-junit5:5.0.3") 19 | testImplementation("io.kotest:kotest-assertions-core:5.0.3") 20 | testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") 21 | testImplementation("io.projectreactor:reactor-test") 22 | testImplementation("io.kotest.extensions:kotest-extensions-spring:1.1.0") 23 | testImplementation("org.springframework.boot:spring-boot-starter-webflux") 24 | testImplementation("org.springframework.boot:spring-boot-starter-test") { 25 | exclude(group = "org.junit.vintage", module = "junit-vintage-engine") 26 | exclude(module = "mockito-core") 27 | } 28 | 29 | } 30 | 31 | tasks.withType { 32 | useJUnitPlatform() 33 | } -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/src/main/kotlin/io/github/wickedev/graphql/extentions/OptionalUnwrap.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.extentions 2 | 3 | import java.util.* 4 | 5 | fun Optional.unwrap(): T? = orElse(null) -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/src/main/kotlin/io/github/wickedev/graphql/parser/JetpackSpringGraphQLRequestParser.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.parser 2 | 3 | import com.expediagroup.graphql.server.spring.execution.SpringGraphQLRequestParser 4 | import com.expediagroup.graphql.server.types.GraphQLServerRequest 5 | import com.fasterxml.jackson.databind.ObjectMapper 6 | import io.github.wickedev.coroutine.reactive.extensions.mono.await 7 | import io.github.wickedev.graphql.extentions.unwrap 8 | import org.springframework.http.MediaType 9 | import org.springframework.http.codec.multipart.FormFieldPart 10 | import org.springframework.web.reactive.function.server.ServerRequest 11 | 12 | class JetpackSpringGraphQLRequestParser(objectMapper: ObjectMapper): SpringGraphQLRequestParser(objectMapper) { 13 | 14 | override suspend fun parseRequest(request: ServerRequest): GraphQLServerRequest? { 15 | val mediaType = request.headers().contentType().unwrap() ?: return super.parseRequest(request) 16 | 17 | return when (MediaType.MULTIPART_FORM_DATA) { 18 | MediaType(mediaType.type, mediaType.subtype) -> getRequestFromMultipartFormData(request) 19 | else -> super.parseRequest(request) 20 | } 21 | } 22 | 23 | private suspend fun getRequestFromMultipartFormData(serverRequest: ServerRequest): GraphQLServerRequest? { 24 | val multipartData = serverRequest.multipartData().await() ?: return null 25 | val operationsJson = (multipartData.getFirst("operations") as FormFieldPart).value() ?: return null 26 | val mapJson = (multipartData.getFirst("map") as FormFieldPart).value() ?: return null 27 | return parseUploadRequest(multipartData, operationsJson, mapJson) 28 | } 29 | } -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/src/main/kotlin/io/github/wickedev/graphql/parser/Parse.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.parser 2 | 3 | import com.expediagroup.graphql.server.types.GraphQLBatchRequest 4 | import com.expediagroup.graphql.server.types.GraphQLRequest 5 | import com.expediagroup.graphql.server.types.GraphQLServerRequest 6 | import com.jayway.jsonpath.DocumentContext 7 | import com.jayway.jsonpath.JsonPath.parse 8 | import io.github.wickedev.graphql.types.Upload 9 | import org.springframework.http.codec.multipart.FilePart 10 | import org.springframework.http.codec.multipart.Part 11 | import org.springframework.util.MultiValueMap 12 | 13 | fun parseUploadRequest( 14 | multipartData: MultiValueMap, 15 | operationsJson: String, 16 | mapJson: String, 17 | ): GraphQLServerRequest { 18 | val isBatchRequest = operationsJson.startsWith("[") 19 | 20 | val operationContext: DocumentContext = parse(operationsJson) 21 | val map: Map> = parse(mapJson).json() 22 | 23 | map.entries.forEach { 24 | val path = it.value.firstOrNull()?.toJsonPath() ?: return@forEach 25 | val upload = (multipartData.getFirst(it.key) as? FilePart)?.let { file -> Upload(file) } 26 | operationContext.set(path, upload) 27 | } 28 | 29 | if (isBatchRequest) { 30 | val queries: List = operationContext.read("$[*].query") 31 | val variables: List> = operationContext.read("$[*].variables") 32 | return GraphQLBatchRequest(queries.zip(variables).map { 33 | GraphQLRequest(query = it.first, variables = it.second) 34 | }) 35 | } 36 | 37 | val query: String = operationContext.read("$.query") 38 | val variables: Map = operationContext.read("$.variables") 39 | 40 | return GraphQLRequest(query = query, variables = variables) 41 | } 42 | -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/src/main/kotlin/io/github/wickedev/graphql/parser/StringToJsonPath.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.parser 2 | 3 | fun String.toJsonPath(): String { 4 | return "$.${this}".replace(".(\\d+)".toRegex(), "[$1]") 5 | } 6 | -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/src/main/kotlin/io/github/wickedev/graphql/request/RequestFromMultipartFormData.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.request 2 | 3 | import com.expediagroup.graphql.server.types.GraphQLServerRequest 4 | import io.github.wickedev.coroutine.reactive.extensions.mono.await 5 | import io.github.wickedev.graphql.parser.parseUploadRequest 6 | import org.springframework.http.codec.multipart.FormFieldPart 7 | import org.springframework.web.reactive.function.server.ServerRequest 8 | 9 | suspend fun requestFromMultipartFormData(serverRequest: ServerRequest): GraphQLServerRequest? { 10 | val multipartData = serverRequest.multipartData().await() ?: return null 11 | val mapJson = (multipartData.getFirst("map") as? FormFieldPart)?.value() ?: return null 12 | val operationsJson = (multipartData.getFirst("operations") as? FormFieldPart)?.value() ?: return null 13 | 14 | return parseUploadRequest(multipartData, operationsJson, mapJson) 15 | } 16 | 17 | -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/src/main/kotlin/io/github/wickedev/graphql/scalars/UploadScalar.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.scalars 2 | 3 | import graphql.schema.* 4 | import io.github.wickedev.graphql.types.Upload 5 | import org.springframework.http.codec.multipart.FilePart 6 | import org.springframework.http.codec.multipart.Part 7 | 8 | val GraphQLUploadScalar: GraphQLScalarType = GraphQLScalarType.newScalar() 9 | .name("Upload") 10 | .description("A file part in a multipart request") 11 | .coercing(object : Coercing { 12 | override fun serialize(dataFetcherResult: Any): Any { 13 | throw CoercingSerializeException("Upload is an input-only type") 14 | } 15 | 16 | override fun parseValue(input: Any): Upload { 17 | return when (input) { 18 | is FilePart -> Upload(input) 19 | else -> { 20 | throw CoercingParseValueException( 21 | "Expected type " + 22 | Part::class.java.name + 23 | " but was " + 24 | input.javaClass.name 25 | ) 26 | } 27 | } 28 | } 29 | 30 | override fun parseLiteral(input: Any): Upload { 31 | throw CoercingParseLiteralException( 32 | "Must use variables to specify Upload values" 33 | ) 34 | } 35 | }) 36 | .build() -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/src/main/kotlin/io/github/wickedev/graphql/types/Upload.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.types 2 | 3 | import org.springframework.http.codec.multipart.FilePart 4 | 5 | class Upload(filePart: FilePart) : FilePart by filePart -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/src/test/kotlin/io/github/wickedev/graphql/parser/ToJsonPathTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.parser 2 | 3 | import io.kotest.core.spec.style.DescribeSpec 4 | import io.kotest.matchers.shouldBe 5 | 6 | class ToJsonPathTest : DescribeSpec({ 7 | 8 | describe("toJsonPath") { 9 | 10 | it("should variables.files.0 to ${'$'}.variables.files[0]") { 11 | val path = "variables.files.0".toJsonPath() 12 | path shouldBe "${'$'}.variables.files[0]" 13 | } 14 | 15 | it("should 1.variables.files.0 to ${'$'}[1].variables.files[0]") { 16 | val path = "1.variables.files.0".toJsonPath() 17 | path shouldBe "${'$'}[1].variables.files[0]" 18 | } 19 | } 20 | }) -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/src/test/resources/a.txt: -------------------------------------------------------------------------------- 1 | a -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | graphql: 2 | packages: 3 | - io.github.wickedev.graphql -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/src/test/resources/b.txt: -------------------------------------------------------------------------------- 1 | b -------------------------------------------------------------------------------- /graphql-kotlin-spring-webflux-upload/src/test/resources/c.txt: -------------------------------------------------------------------------------- 1 | c -------------------------------------------------------------------------------- /graphql-postgresql-scalars/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.6.10" 3 | } 4 | 5 | dependencies { 6 | implementation(kotlin("stdlib-jdk8")) 7 | 8 | api(project(":graphql-core")) 9 | api("io.r2dbc:r2dbc-postgresql:0.8.10.RELEASE") 10 | api("com.graphql-java:graphql-java:17.3") 11 | api("com.graphql-java:graphql-java-extended-scalars:17.0") 12 | api("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") 13 | } -------------------------------------------------------------------------------- /graphql-postgresql-scalars/src/main/kotlin/io/github/wickedev/graphql/scalars/PostgresIntervalScalar.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.scalars 2 | 3 | import graphql.language.StringValue 4 | import graphql.schema.* 5 | import io.r2dbc.postgresql.codec.Interval 6 | import java.time.Duration 7 | import java.time.format.DateTimeParseException 8 | 9 | val INTERVAL_OBJECT_COERCING: Coercing = object : Coercing { 10 | private fun convertImpl(input: Any): Interval? { 11 | if (input is String) { 12 | try { 13 | return Interval.of(Duration.parse(input)) 14 | } catch (ignored: DateTimeParseException) { 15 | // nothing to-do 16 | } 17 | } else if (input is Interval) { 18 | return input 19 | } 20 | return null 21 | } 22 | 23 | override fun serialize(input: Any): String { 24 | return if (input is Interval) { 25 | input.duration.toString() 26 | } else { 27 | val result = convertImpl(input) ?: throw CoercingSerializeException("Invalid value '$input' for Duration") 28 | result.duration.toString() 29 | } 30 | } 31 | 32 | override fun parseValue(input: Any): Interval { 33 | return convertImpl(input) ?: throw CoercingParseValueException("Invalid value '$input' for Duration") 34 | } 35 | 36 | override fun parseLiteral(input: Any): Interval { 37 | val value = (input as StringValue).value 38 | return convertImpl(value) ?: throw CoercingParseLiteralException("Invalid value '$input' for Duration") 39 | } 40 | } 41 | 42 | val PostgresIntervalScalar: GraphQLScalarType = GraphQLScalarType.newScalar() 43 | .name("Interval") 44 | .description("ISO 8601 Interval") 45 | .coercing(INTERVAL_OBJECT_COERCING) 46 | .build() -------------------------------------------------------------------------------- /kotlin-coroutine-reactive-extensions/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.6.10" 3 | } 4 | 5 | dependencies { 6 | implementation(kotlin("stdlib-jdk8")) 7 | 8 | api("org.jetbrains.kotlinx:kotlinx-coroutines-reactive:1.6.0") 9 | api("io.projectreactor:reactor-core:3.4.13") 10 | } -------------------------------------------------------------------------------- /kotlin-coroutine-reactive-extensions/src/main/kotlin/io/github/wickedev/coroutine/reactive/extensions/flux/Await.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.coroutine.reactive.extensions.flux 2 | 3 | import kotlinx.coroutines.reactive.awaitSingle 4 | import reactor.core.publisher.Flux 5 | 6 | suspend fun Flux.await(): List = this.collectList().awaitSingle() 7 | -------------------------------------------------------------------------------- /kotlin-coroutine-reactive-extensions/src/main/kotlin/io/github/wickedev/coroutine/reactive/extensions/mono/Await.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.coroutine.reactive.extensions.mono 2 | 3 | import kotlinx.coroutines.CancellableContinuation 4 | import kotlinx.coroutines.reactive.awaitFirst 5 | import kotlinx.coroutines.reactive.awaitFirstOrNull 6 | import kotlinx.coroutines.suspendCancellableCoroutine 7 | import org.reactivestreams.Subscriber 8 | import org.reactivestreams.Subscription 9 | import reactor.core.publisher.Mono 10 | import kotlin.coroutines.resume 11 | import kotlin.coroutines.resumeWithException 12 | 13 | suspend inline fun Mono.await(): T { 14 | if (null is T) { 15 | // for Mono 16 | return awaitFirstOrNull() as T 17 | } 18 | 19 | return when (T::class) { 20 | // for Mono or Mono 21 | Unit::class, Void::class -> { 22 | @Suppress("UNCHECKED_CAST", "ReactiveStreamsSubscriberImplementation") 23 | suspendCancellableCoroutine { cont -> 24 | cont as CancellableContinuation 25 | subscribe(object : Subscriber { 26 | override fun onSubscribe(s: Subscription) { 27 | cont.invokeOnCancellation { 28 | s.cancel() 29 | } 30 | s.request(1) 31 | } 32 | 33 | override fun onNext(t: T?) { 34 | error("Mono or Mono cannot emit value") 35 | } 36 | 37 | override fun onError(t: Throwable) { 38 | cont.resumeWithException(t) 39 | } 40 | 41 | override fun onComplete() { 42 | cont.resume(null) 43 | } 44 | }) 45 | } 46 | } 47 | // for Mono 48 | else -> awaitFirst() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/7.3.1/userguide/multi_project_builds.html 8 | * This project uses @Incubating APIs which are subject to change. 9 | */ 10 | 11 | rootProject.name = "graphql-jetpack" 12 | include("graphql-core") 13 | include("graphql-jetpack") 14 | include("graphql-jetpack-autoconfigure") 15 | include("graphql-jetpack-starter") 16 | include("graphql-kotlin-spring-webflux-upload") 17 | include("graphql-kotlin-spring-security") 18 | include("kotlin-coroutine-reactive-extensions") 19 | include("spring-data-graphql-commons") 20 | include("spring-data-graphql-r2dbc") 21 | include("spring-data-graphql-r2dbc-autoconfigure") 22 | include("spring-data-graphql-r2dbc-starter") 23 | include("graphql-postgresql-scalars") 24 | include("website") 25 | -------------------------------------------------------------------------------- /spring-data-graphql-commons/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.6.10" 3 | id("org.springframework.boot") version "2.6.1" 4 | id("io.spring.dependency-management") version "1.0.11.RELEASE" 5 | } 6 | 7 | dependencies { 8 | implementation(kotlin("stdlib-jdk8")) 9 | 10 | api(project(":graphql-core")) 11 | api("org.springframework.data:spring-data-commons") 12 | api("org.springframework.data:spring-data-relational") 13 | api("com.graphql-java:graphql-java:17.3") 14 | } -------------------------------------------------------------------------------- /spring-data-graphql-commons/src/main/java/io/github/wickedev/graphql/repository/GraphQLDataLoaderByIdRepository.java: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.repository; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.github.wickedev.graphql.interfases.Node; 5 | import io.github.wickedev.graphql.types.ID; 6 | import org.springframework.data.repository.NoRepositoryBean; 7 | 8 | import java.util.List; 9 | import java.util.concurrent.CompletableFuture; 10 | 11 | /** 12 | * In Kotlin findById, due to the return type of T or T?, 13 | * the user cannot freely write the desired type due to strict classification, 14 | * so it is written in Java. 15 | */ 16 | @NoRepositoryBean 17 | public interface GraphQLDataLoaderByIdRepository extends GraphQLDataLoaderRepository { 18 | CompletableFuture findById(ID id, DataFetchingEnvironment env); 19 | 20 | CompletableFuture> findAllById(Iterable ids, DataFetchingEnvironment env); 21 | 22 | CompletableFuture existsById(ID id, DataFetchingEnvironment env); 23 | } 24 | -------------------------------------------------------------------------------- /spring-data-graphql-commons/src/main/kotlin/io/github/wickedev/graphql/repository/GraphQLDataLoaderBackwardConnectionsRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.repository 2 | 3 | import graphql.schema.DataFetchingEnvironment 4 | import io.github.wickedev.graphql.interfases.Node 5 | import io.github.wickedev.graphql.types.Backward 6 | import io.github.wickedev.graphql.types.Connection 7 | import org.springframework.data.relational.core.query.Criteria 8 | import org.springframework.data.repository.NoRepositoryBean 9 | import java.util.concurrent.CompletableFuture 10 | 11 | @NoRepositoryBean 12 | interface GraphQLDataLoaderBackwardConnectionsRepository : GraphQLDataLoaderRepository { 13 | fun connection( 14 | backward: Backward, env: DataFetchingEnvironment 15 | ): CompletableFuture> 16 | 17 | fun connection( 18 | backward: Backward, criteria: Criteria, env: DataFetchingEnvironment 19 | ): CompletableFuture> 20 | } 21 | -------------------------------------------------------------------------------- /spring-data-graphql-commons/src/main/kotlin/io/github/wickedev/graphql/repository/GraphQLDataLoaderConnectionsRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.repository 2 | 3 | import io.github.wickedev.graphql.interfases.Node 4 | import org.springframework.data.repository.NoRepositoryBean 5 | 6 | @NoRepositoryBean 7 | interface GraphQLDataLoaderConnectionsRepository 8 | : GraphQLDataLoaderForwardConnectionsRepository, 9 | GraphQLDataLoaderBackwardConnectionsRepository -------------------------------------------------------------------------------- /spring-data-graphql-commons/src/main/kotlin/io/github/wickedev/graphql/repository/GraphQLDataLoaderForwardConnectionsRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.repository 2 | 3 | import graphql.schema.DataFetchingEnvironment 4 | import io.github.wickedev.graphql.interfases.Node 5 | import io.github.wickedev.graphql.types.Connection 6 | import io.github.wickedev.graphql.types.Forward 7 | import org.springframework.data.relational.core.query.Criteria 8 | import org.springframework.data.repository.NoRepositoryBean 9 | import java.util.concurrent.CompletableFuture 10 | 11 | @NoRepositoryBean 12 | interface GraphQLDataLoaderForwardConnectionsRepository : GraphQLDataLoaderRepository { 13 | fun connection( 14 | forward: Forward, env: DataFetchingEnvironment 15 | ): CompletableFuture> 16 | 17 | fun connection( 18 | forward: Forward, criteria: Criteria, env: DataFetchingEnvironment 19 | ): CompletableFuture> 20 | } 21 | -------------------------------------------------------------------------------- /spring-data-graphql-commons/src/main/kotlin/io/github/wickedev/graphql/repository/GraphQLDataLoaderRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.repository 2 | 3 | import io.github.wickedev.graphql.interfases.Node 4 | import io.github.wickedev.graphql.types.ID 5 | import org.springframework.data.repository.NoRepositoryBean 6 | import org.springframework.data.repository.Repository 7 | 8 | @NoRepositoryBean 9 | interface GraphQLDataLoaderRepository: Repository -------------------------------------------------------------------------------- /spring-data-graphql-commons/src/main/kotlin/io/github/wickedev/graphql/repository/GraphQLNodeRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.repository 2 | 3 | import graphql.schema.DataFetchingEnvironment 4 | import io.github.wickedev.graphql.interfases.Node 5 | import io.github.wickedev.graphql.types.ID 6 | import org.springframework.data.repository.NoRepositoryBean 7 | import org.springframework.data.repository.Repository 8 | import java.util.concurrent.CompletableFuture 9 | 10 | @NoRepositoryBean 11 | interface GraphQLNodeRepository : Repository { 12 | 13 | fun findNodeById(id: ID, env: DataFetchingEnvironment): CompletableFuture 14 | } -------------------------------------------------------------------------------- /spring-data-graphql-commons/src/main/kotlin/io/github/wickedev/graphql/repository/GraphQLRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.repository 2 | 3 | import io.github.wickedev.graphql.interfases.Node 4 | import io.github.wickedev.graphql.types.ID 5 | import org.springframework.data.repository.NoRepositoryBean 6 | import org.springframework.data.repository.Repository 7 | 8 | @NoRepositoryBean 9 | interface GraphQLRepository : 10 | GraphQLDataLoaderConnectionsRepository, 11 | GraphQLDataLoaderByIdRepository, 12 | Repository -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc-autoconfigure/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.jetbrains.kotlin.jvm") version "1.6.10" 3 | id("org.jetbrains.kotlin.plugin.spring") version "1.6.10" 4 | id("org.springframework.boot") version "2.6.1" 5 | id("io.spring.dependency-management") version "1.0.11.RELEASE" 6 | id("ru.vyarus.pom") version "2.2.1" 7 | } 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | 14 | dependencies { 15 | api(project(":spring-data-graphql-r2dbc")) 16 | optional("org.springframework.data:spring-data-r2dbc") 17 | optional("org.springframework.boot:spring-boot-autoconfigure") 18 | } 19 | 20 | tasks.withType { 21 | useJUnitPlatform() 22 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc-autoconfigure/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/configuration/GraphQLR2dbcRepositoriesAutoConfiguration.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.configuration 2 | 3 | import io.github.wickedev.graphql.repository.GraphQLNodeRepository 4 | import io.github.wickedev.graphql.spring.data.r2dbc.factory.GraphQLR2dbcRepositoryFactoryBean 5 | import io.github.wickedev.graphql.spring.data.r2dbc.repository.interfaces.GraphQLR2dbcRepository 6 | import io.r2dbc.spi.ConnectionFactory 7 | import org.springframework.boot.autoconfigure.AutoConfigureAfter 8 | import org.springframework.boot.autoconfigure.AutoConfigureBefore 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass 11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean 12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty 13 | import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration 14 | import org.springframework.context.annotation.Configuration 15 | import org.springframework.context.annotation.Import 16 | import org.springframework.r2dbc.core.DatabaseClient 17 | 18 | 19 | @Configuration(proxyBeanMethods = false) 20 | @ConditionalOnClass( 21 | ConnectionFactory::class, 22 | GraphQLR2dbcRepository::class, 23 | GraphQLNodeRepository::class, 24 | ) 25 | @ConditionalOnBean(DatabaseClient::class) 26 | @ConditionalOnProperty( 27 | prefix = "spring.data.graphql.r2dbc.repositories", 28 | name = ["enabled"], 29 | havingValue = "true", 30 | matchIfMissing = true 31 | ) 32 | @ConditionalOnMissingBean(GraphQLR2dbcRepositoryFactoryBean::class) 33 | @Import(GraphQLR2dbcRepositoriesAutoConfigureRegistrar::class) 34 | @AutoConfigureBefore(R2dbcDataAutoConfiguration::class) 35 | @AutoConfigureAfter(GraphQLR2dbcDataAutoConfiguration::class) 36 | class GraphQLR2dbcRepositoriesAutoConfiguration -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc-autoconfigure/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/configuration/GraphQLR2dbcRepositoriesAutoConfigureRegistrar.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.configuration 2 | 3 | import org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport 4 | import org.springframework.data.repository.config.RepositoryConfigurationExtension 5 | 6 | class GraphQLR2dbcRepositoriesAutoConfigureRegistrar: AbstractRepositoryConfigurationSourceSupport() { 7 | 8 | override fun getAnnotation(): Class { 9 | return EnableGraphQLR2dbcRepositories::class.java 10 | } 11 | 12 | override fun getConfiguration(): Class<*> { 13 | return EnableGraphQLR2dbcRepositoriesConfiguration::class.java 14 | } 15 | 16 | override fun getRepositoryConfigurationExtension(): RepositoryConfigurationExtension { 17 | return GraphQLR2dbcRepositoryConfigurationExtension() 18 | } 19 | 20 | @EnableGraphQLR2dbcRepositories 21 | class EnableGraphQLR2dbcRepositoriesConfiguration 22 | } 23 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc-autoconfigure/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/configuration/GraphQLR2dbcRepositoriesRegistrar.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.configuration 2 | 3 | import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport 4 | import org.springframework.data.repository.config.RepositoryConfigurationExtension 5 | 6 | internal class GraphQLR2dbcRepositoriesRegistrar : RepositoryBeanDefinitionRegistrarSupport() { 7 | 8 | override fun getAnnotation(): Class { 9 | return EnableGraphQLR2dbcRepositories::class.java 10 | } 11 | 12 | override fun getExtension(): RepositoryConfigurationExtension { 13 | return GraphQLR2dbcRepositoryConfigurationExtension() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc-autoconfigure/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | io.github.wickedev.graphql.spring.data.r2dbc.configuration.GraphQLR2dbcRepositoriesAutoConfiguration,\ 3 | io.github.wickedev.graphql.spring.data.r2dbc.configuration.GraphQLR2dbcDataAutoConfiguration -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc-starter/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.jetbrains.kotlin.jvm") version "1.6.10" 3 | id("org.jetbrains.kotlin.plugin.spring") version "1.6.10" 4 | id("org.springframework.boot") version "2.6.1" 5 | id("io.spring.dependency-management") version "1.0.11.RELEASE" 6 | } 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | api(project(":spring-data-graphql-r2dbc-autoconfigure")) 14 | api(project(":spring-data-graphql-r2dbc")) 15 | } 16 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/converter/IDToLongWritingConverter.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.converter 2 | 3 | import io.github.wickedev.graphql.types.ID 4 | import org.springframework.core.convert.converter.Converter 5 | import org.springframework.data.convert.WritingConverter 6 | 7 | @WritingConverter 8 | class IDToLongWritingConverter : Converter { 9 | override fun convert(source: ID): Long? { 10 | return if (source.value.isNotEmpty()) { 11 | source.value.toLong() 12 | } else { 13 | null 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/converter/LongToIDReadingConverter.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.converter 2 | 3 | import io.github.wickedev.graphql.types.ID 4 | import org.springframework.core.convert.converter.Converter 5 | import org.springframework.data.convert.ReadingConverter 6 | 7 | @ReadingConverter 8 | class LongToIDReadingConverter : Converter { 9 | override fun convert(source: Long): ID { 10 | return ID("", source.toString()) 11 | } 12 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/extentions/GraphQL.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.extentions 2 | 3 | import graphql.schema.DataFetchingEnvironment 4 | import io.github.wickedev.graphql.types.Direction 5 | import io.github.wickedev.graphql.types.Order 6 | import kotlinx.coroutines.CoroutineScope 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.future.future 9 | import org.dataloader.DataLoader 10 | import org.dataloader.DataLoaderFactory 11 | import org.springframework.data.domain.Sort 12 | 13 | fun DataFetchingEnvironment.dataLoader( 14 | key: String, 15 | block: suspend (keys: List) -> List 16 | ): DataLoader { 17 | return dataLoaderRegistry.computeIfAbsent(key) { 18 | DataLoaderFactory.newDataLoader { keys -> 19 | CoroutineScope(Dispatchers.Unconfined).future { 20 | block(keys) 21 | } 22 | } 23 | } 24 | } 25 | 26 | fun Order.toSpringDataType(): Sort.Order { 27 | return when (direction) { 28 | Direction.DESC -> Sort.Order.desc(property) 29 | Direction.ASC -> Sort.Order.asc(property) 30 | null -> Sort.Order.by(property) 31 | } 32 | } 33 | 34 | fun List?.toSpringDataType(order: Sort.Order? = null): Sort { 35 | return Sort.by(listOfNotNull(order) + (this?.map { 36 | it.toSpringDataType() 37 | }?: emptyList())) 38 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/extentions/R2dbc.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.extentions 2 | 3 | import org.springframework.core.annotation.AnnotationUtils 4 | import org.springframework.data.domain.Sort 5 | import org.springframework.data.relational.core.mapping.Table 6 | import org.springframework.data.repository.core.RepositoryMetadata 7 | 8 | val Sort.Direction.inverted: Sort.Direction 9 | get() = if (isAscending) Sort.Direction.DESC else Sort.Direction.ASC 10 | 11 | val RepositoryMetadata.name: String 12 | get() = tableName ?: this.domainType.simpleName.lowercase() 13 | 14 | val RepositoryMetadata.tableName: String? 15 | get() = AnnotationUtils.findAnnotation( 16 | this.domainType, 17 | Table::class.java 18 | )?.value -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/extentions/Strings.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.extentions 2 | 3 | import java.util.* 4 | 5 | val camelRegex = "(?<=[a-zA-Z])[A-Z]".toRegex() 6 | 7 | fun String.camelToSnakeCase(): String { 8 | return camelRegex.replace(this) { 9 | "_${it.value}" 10 | }.lowercase(Locale.getDefault()) 11 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/extentions/Types.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.extentions 2 | 3 | import kotlin.reflect.KClass 4 | 5 | fun KClass.isAssignableFrom(cls: Class<*>): Boolean { 6 | return java.isAssignableFrom(cls) 7 | } 8 | 9 | inline fun Sequence<*>.`as`(): Sequence { 10 | @Suppress("UNCHECKED_CAST") 11 | return this as Sequence 12 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/factory/GraphQLMappingRelationalEntityInformation.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.factory 2 | 3 | import io.github.wickedev.graphql.spring.data.r2dbc.strategy.AdditionalIsNewStrategy 4 | import org.springframework.data.relational.core.mapping.RelationalPersistentEntity 5 | import org.springframework.data.relational.repository.support.MappingRelationalEntityInformation 6 | 7 | class GraphQLMappingRelationalEntityInformation( 8 | entity: RelationalPersistentEntity, 9 | private val additionalIsNewStrategy: AdditionalIsNewStrategy?, 10 | ) : 11 | MappingRelationalEntityInformation(entity) { 12 | 13 | private var valueType: Class<*> = entity.requiredIdProperty.type 14 | 15 | @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") 16 | private var valueLookup: (source: T) -> Any? = { source: T -> 17 | entity.getIdentifierAccessor(source).identifier 18 | } 19 | 20 | override fun isNew(obj: T): Boolean { 21 | val value = valueLookup(obj) 22 | 23 | return if (additionalIsNewStrategy?.isNew(valueType, value) == true) { 24 | true 25 | } else { 26 | super.isNew(obj) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/mapping/GraphQLR2dbcMappingContext.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.mapping 2 | 3 | import io.github.wickedev.graphql.spring.data.r2dbc.strategy.IDTypeStrategy 4 | import org.springframework.data.mapping.model.Property 5 | import org.springframework.data.mapping.model.SimpleTypeHolder 6 | import org.springframework.data.r2dbc.mapping.R2dbcMappingContext 7 | import org.springframework.data.relational.core.mapping.NamingStrategy 8 | import org.springframework.data.relational.core.mapping.RelationalPersistentEntity 9 | import org.springframework.data.relational.core.mapping.RelationalPersistentProperty 10 | 11 | class GraphQLR2dbcMappingContext( 12 | namingStrategy: NamingStrategy, 13 | private val idTypeStrategy: IDTypeStrategy, 14 | ): R2dbcMappingContext(namingStrategy) { 15 | 16 | override fun createPersistentProperty( 17 | property: Property, 18 | owner: RelationalPersistentEntity<*>, 19 | simpleTypeHolder: SimpleTypeHolder 20 | ): RelationalPersistentProperty { 21 | 22 | val persistentProperty = GraphQLRelationalPersistentProperty( 23 | property, owner, 24 | simpleTypeHolder, namingStrategy, 25 | idTypeStrategy, 26 | ) 27 | persistentProperty.isForceQuote = isForceQuote 28 | 29 | return persistentProperty 30 | } 31 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/mapping/GraphQLRelationalPersistentProperty.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.mapping 2 | 3 | import io.github.wickedev.graphql.spring.data.r2dbc.extentions.isAssignableFrom 4 | import io.github.wickedev.graphql.spring.data.r2dbc.strategy.IDTypeStrategy 5 | import org.springframework.data.mapping.PersistentEntity 6 | import org.springframework.data.mapping.model.Property 7 | import org.springframework.data.mapping.model.SimpleTypeHolder 8 | import org.springframework.data.relational.core.mapping.BasicRelationalPersistentProperty 9 | import org.springframework.data.relational.core.mapping.NamingStrategy 10 | import org.springframework.data.relational.core.mapping.RelationalPersistentProperty 11 | import org.springframework.data.util.TypeInformation 12 | import kotlin.properties.ReadOnlyProperty 13 | 14 | class GraphQLRelationalPersistentProperty( 15 | property: Property, 16 | owner: PersistentEntity<*, RelationalPersistentProperty?>, 17 | simpleTypeHolder: SimpleTypeHolder, 18 | namingStrategy: NamingStrategy, 19 | private val idTypeStrategy: IDTypeStrategy, 20 | ) : BasicRelationalPersistentProperty( 21 | property, owner, simpleTypeHolder, namingStrategy 22 | ) { 23 | override fun getTypeInformation(): TypeInformation<*> { 24 | return GraphQLTypeInformation(this, owner, idTypeStrategy, super.getTypeInformation()) 25 | } 26 | 27 | override fun isTransient(): Boolean { 28 | return super.isTransient() || ReadOnlyProperty::class.isAssignableFrom(this.type) 29 | || Lazy::class.isAssignableFrom(this.type) 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/mapping/GraphQLTypeInformation.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.mapping 2 | 3 | import io.github.wickedev.graphql.annotations.GlobalId 4 | import io.github.wickedev.graphql.annotations.Relation 5 | import io.github.wickedev.graphql.spring.data.r2dbc.strategy.IDTypeStrategy 6 | import io.github.wickedev.graphql.types.ID 7 | import org.springframework.data.mapping.PersistentEntity 8 | import org.springframework.data.mapping.PersistentProperty 9 | import org.springframework.data.relational.core.mapping.RelationalPersistentProperty 10 | import org.springframework.data.relational.core.mapping.Table 11 | import org.springframework.data.util.TypeInformation 12 | 13 | class GraphQLTypeInformation( 14 | private val property: PersistentProperty<*>, 15 | private val owner: PersistentEntity<*, RelationalPersistentProperty?>, 16 | private val idTypeStrategy: IDTypeStrategy, 17 | information: TypeInformation 18 | ) : TypeInformation by information { 19 | fun isGraphQLID(): Boolean { 20 | return type == ID::class.java 21 | } 22 | 23 | fun typeName(): String { 24 | if (owner.isIdProperty(property)) { 25 | val table = owner.findAnnotation(Table::class.java) 26 | return idTypeStrategy.convertType(owner.type, table?.value ?: owner.type.simpleName) 27 | } 28 | 29 | val gid = property.findAnnotation(GlobalId::class.java) 30 | val rid = property.findAnnotation(Relation::class.java) 31 | 32 | return idTypeStrategy.convertType(owner.type, property.requiredField, gid?.type, rid?.type) 33 | } 34 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/query/GraphQLR2dbcQueryMethod.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.query 2 | 3 | import graphql.schema.DataFetchingEnvironment 4 | import io.github.wickedev.graphql.types.Connection 5 | import org.springframework.data.mapping.context.MappingContext 6 | import org.springframework.data.projection.ProjectionFactory 7 | import org.springframework.data.r2dbc.repository.query.R2dbcQueryMethod 8 | import org.springframework.data.relational.core.mapping.RelationalPersistentEntity 9 | import org.springframework.data.relational.core.mapping.RelationalPersistentProperty 10 | import org.springframework.data.repository.core.RepositoryMetadata 11 | import org.springframework.data.repository.util.ClassUtils 12 | import java.lang.reflect.Method 13 | import java.util.concurrent.CompletableFuture 14 | 15 | class GraphQLR2dbcQueryMethod( 16 | private val method: Method, 17 | metadata: RepositoryMetadata, 18 | projectionFactory: ProjectionFactory, 19 | mappingContext: MappingContext, out RelationalPersistentProperty> 20 | ) : R2dbcQueryMethod( 21 | method, 22 | metadata, 23 | projectionFactory, 24 | mappingContext 25 | ) { 26 | 27 | private fun hasDataFetchingEnvironmentParameter(): Boolean { 28 | return ClassUtils.hasParameterOfType(method, DataFetchingEnvironment::class.java) 29 | } 30 | 31 | private fun returnTypeIsCompletableFuture(): Boolean { 32 | return method.returnType === CompletableFuture::class.java 33 | } 34 | 35 | fun isGraphQLDataLoaderQuery(): Boolean { 36 | return hasDataFetchingEnvironmentParameter() && returnTypeIsCompletableFuture() && !isModifyingQuery 37 | } 38 | 39 | fun isConnectionQuery(): Boolean { 40 | return method.name.startsWith("connection") && returnedObjectType == Connection::class.java 41 | } 42 | 43 | fun isCollectionGraphQLDataLoaderQuery(): Boolean { 44 | return isGraphQLDataLoaderQuery() && this.isCollectionQuery 45 | } 46 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/query/redefine/RedefineCountExistsMethod.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.query.redefine 2 | 3 | import graphql.schema.DataFetchingEnvironment 4 | import net.bytebuddy.description.type.TypeDefinition 5 | import net.bytebuddy.description.type.TypeDescription 6 | import org.springframework.data.repository.core.RepositoryMetadata 7 | import reactor.core.publisher.Mono 8 | import java.lang.reflect.Method 9 | 10 | 11 | fun redefineCountExistsMethod(method: Method, metadata: RepositoryMetadata): Method { 12 | val interfaceName = redefineInterfaceName(method) 13 | val methodName = redefineMethodName(method) 14 | val parameters = redefineParameters(method) 15 | val returnType = redefineReturnType(method, metadata) 16 | 17 | val unloadedType = makeType(interfaceName, methodName, parameters, returnType) 18 | ?: throw Error("unloaded type is null") 19 | 20 | val type = loadType(unloadedType) 21 | ?: throw Error("loaded type is null") 22 | 23 | return type.getMethod(methodName, *parametersForGetMethod(method)) 24 | } 25 | 26 | private fun redefineMethodName(method: Method): String { 27 | return method.name 28 | .replace("count", "countDataLoader") 29 | .replace("exists", "existsDataLoader") 30 | } 31 | 32 | private fun redefineParameters(method: Method): List { 33 | return method.parameters.filter { p -> p.type != DataFetchingEnvironment::class.java } 34 | .map { TypeDescription.ForLoadedType.of(it.type) } 35 | } 36 | 37 | private fun redefineReturnType(method: Method, metadata: RepositoryMetadata): TypeDefinition { 38 | val returnObjectType = metadata.getReturnedDomainClass(method) 39 | return TypeDescription.Generic.Builder 40 | .parameterizedType(Mono::class.java, returnObjectType).build() 41 | } 42 | 43 | private fun parametersForGetMethod(method: Method): Array> { 44 | return method.parameters.filter { p -> p.type != DataFetchingEnvironment::class.java } 45 | .map { it.type } 46 | .toTypedArray() 47 | } 48 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/query/redefine/RedefineMethod.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.query.redefine 2 | 3 | import net.bytebuddy.ByteBuddy 4 | import net.bytebuddy.description.type.TypeDefinition 5 | import net.bytebuddy.dynamic.DynamicType 6 | import net.bytebuddy.dynamic.loading.ClassLoadingStrategy 7 | import org.springframework.data.repository.core.RepositoryMetadata 8 | import java.lang.reflect.Method 9 | import java.lang.reflect.Modifier 10 | 11 | fun redefineMethod(method: Method, metadata: RepositoryMetadata): Method { 12 | return if (method.name.startsWith("find")) { 13 | redefineFindMethod(method, metadata) 14 | } else { 15 | redefineCountExistsMethod(method, metadata) 16 | } 17 | } 18 | 19 | 20 | fun redefineInterfaceName(method: Method): String { 21 | return "${method.declaringClass.name}${method.name.toUpperCaseFirstLetter()}${method.parameters.size}" 22 | } 23 | 24 | fun makeType( 25 | interfaceName: String, 26 | methodName: String, 27 | parameters: List, 28 | returnType: TypeDefinition, 29 | ): DynamicType.Unloaded? { 30 | return ByteBuddy() 31 | .makeInterface() 32 | .name(interfaceName) 33 | .defineMethod(methodName, returnType, Modifier.PUBLIC) 34 | .withParameters(parameters) 35 | .withoutCode() 36 | .make() 37 | } 38 | 39 | fun loadType( 40 | unloadedType: DynamicType.Unloaded 41 | ): Class? { 42 | return unloadedType.load( 43 | unloadedType.javaClass.classLoader, 44 | ClassLoadingStrategy.Default.WRAPPER 45 | ).loaded 46 | } 47 | 48 | private fun String.toUpperCaseFirstLetter(): String { 49 | return if (isEmpty()) this else (this[0].uppercaseChar() + this.substring(1)) 50 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/repository/SimpleGraphQLR2dbcRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.repository 2 | 3 | import io.github.wickedev.graphql.interfases.Node 4 | import io.github.wickedev.graphql.spring.data.r2dbc.repository.base.PropertyBaseRepository 5 | import io.github.wickedev.graphql.spring.data.r2dbc.repository.mixin.GraphQLRepositoryMixin 6 | import io.github.wickedev.graphql.types.ID 7 | import org.springframework.data.r2dbc.convert.R2dbcConverter 8 | import org.springframework.data.r2dbc.core.R2dbcEntityOperations 9 | import org.springframework.data.r2dbc.core.StatementMapper 10 | import io.github.wickedev.graphql.spring.data.r2dbc.repository.mixin.R2dbcRepositoryMixin 11 | import org.springframework.data.relational.repository.query.RelationalEntityInformation 12 | import org.springframework.data.repository.core.RepositoryInformation 13 | import org.springframework.r2dbc.core.DatabaseClient 14 | import org.springframework.transaction.annotation.Transactional 15 | 16 | @Transactional(readOnly = true) 17 | class SimpleGraphQLR2dbcRepository( 18 | databaseClient: DatabaseClient, 19 | statementMapper: StatementMapper, 20 | information: RepositoryInformation, 21 | entity: RelationalEntityInformation, 22 | entityOperations: R2dbcEntityOperations, 23 | converter: R2dbcConverter 24 | ) : PropertyBaseRepository( 25 | databaseClient, 26 | statementMapper, 27 | information, 28 | entity, 29 | entityOperations, 30 | converter 31 | ), GraphQLRepositoryMixin, 32 | R2dbcRepositoryMixin -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/repository/base/PropertyRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.repository.base 2 | 3 | import org.springframework.data.domain.Sort 4 | import org.springframework.data.r2dbc.convert.R2dbcConverter 5 | import org.springframework.data.r2dbc.core.R2dbcEntityOperations 6 | import org.springframework.data.r2dbc.core.StatementMapper 7 | import org.springframework.data.relational.core.mapping.RelationalPersistentProperty 8 | import org.springframework.data.relational.core.query.Criteria 9 | import org.springframework.data.relational.core.query.Query 10 | import org.springframework.data.relational.repository.query.RelationalEntityInformation 11 | import org.springframework.data.relational.repository.query.RelationalExampleMapper 12 | import org.springframework.data.repository.core.RepositoryInformation 13 | import org.springframework.data.util.Lazy 14 | import org.springframework.r2dbc.core.DatabaseClient 15 | import reactor.core.publisher.Mono 16 | 17 | interface PropertyRepository { 18 | val databaseClient: DatabaseClient 19 | val statementMapper: StatementMapper 20 | val information: RepositoryInformation 21 | val entity: RelationalEntityInformation 22 | val entityOperations: R2dbcEntityOperations 23 | val converter: R2dbcConverter 24 | val idProperty: Lazy 25 | val exampleMapper: RelationalExampleMapper 26 | val tableName: String 27 | 28 | fun getIdProperty(): RelationalPersistentProperty 29 | 30 | fun whereId(): Criteria.CriteriaStep 31 | 32 | fun emptyQuery(): Query 33 | 34 | fun query(criteria: Criteria?): Query 35 | 36 | fun getIdQuery(id: ID): Query 37 | 38 | fun getIdsInQuery(ids: Collection): Query 39 | 40 | fun whereIdGreaterThanQuery(after: ID?, criteria: Criteria?): Query 41 | 42 | fun whereIdLessThanQuery(before: ID?, criteria: Criteria?): Query 43 | 44 | fun findFirst(sort: Sort): Mono 45 | } 46 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/repository/interfaces/GraphQLR2dbcRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.repository.interfaces 2 | 3 | import io.github.wickedev.graphql.interfases.Node 4 | import io.github.wickedev.graphql.repository.GraphQLRepository 5 | import io.github.wickedev.graphql.types.ID 6 | import org.springframework.data.r2dbc.repository.R2dbcRepository 7 | import org.springframework.data.repository.NoRepositoryBean 8 | 9 | @NoRepositoryBean 10 | interface GraphQLR2dbcRepository : 11 | GraphQLRepository, 12 | R2dbcRepository -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/repository/mixin/GraphQLDataLoaderConnectionsRepositoryMixin.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.repository.mixin 2 | 3 | import io.github.wickedev.graphql.interfases.Node 4 | import io.github.wickedev.graphql.repository.GraphQLDataLoaderConnectionsRepository 5 | import org.springframework.data.repository.NoRepositoryBean 6 | 7 | @NoRepositoryBean 8 | interface GraphQLDataLoaderConnectionsRepositoryMixin 9 | : GraphQLDataLoaderConnectionsRepository, 10 | GraphQLDataLoaderForwardConnectionsRepositoryMixin, 11 | GraphQLDataLoaderBackwardConnectionsRepositoryMixin 12 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/repository/mixin/GraphQLRepositoryMixin.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.repository.mixin 2 | 3 | import io.github.wickedev.graphql.interfases.Node 4 | import io.github.wickedev.graphql.types.ID 5 | import org.springframework.data.repository.NoRepositoryBean 6 | 7 | @NoRepositoryBean 8 | interface GraphQLRepositoryMixin : 9 | GraphQLDataLoaderByIdRepositoryMixin, 10 | GraphQLDataLoaderConnectionsRepositoryMixin, 11 | R2dbcRepositoryMixin -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/repository/mixin/R2dbcAllInQueryMixin.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.repository.mixin 2 | 3 | import io.github.wickedev.graphql.spring.data.r2dbc.repository.base.PropertyRepository 4 | import org.springframework.data.repository.NoRepositoryBean 5 | import org.springframework.data.util.ClassTypeInformation 6 | import reactor.core.publisher.Flux 7 | 8 | @NoRepositoryBean 9 | interface R2dbcAllInQueryMixin : PropertyRepository { 10 | 11 | fun findAllByIdsIn(ids: List): Flux { 12 | return entityOperations.select(getIdsInQuery(ids), entity.javaType) 13 | } 14 | 15 | fun findAllIdByIdsIn(ids: List): Flux { 16 | val selectSpec = statementMapper.createSelect(entity.tableName) 17 | .withCriteria(whereId().`in`(ids)) 18 | .withProjection(getIdProperty().columnName) 19 | val operation = statementMapper.getMappedObject(selectSpec) 20 | val idTypeInfo = ClassTypeInformation.from(Long::class.java) 21 | return databaseClient.sql(operation) 22 | .map { row -> converter.readValue(row, idTypeInfo) as Long }.all() 23 | } 24 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/repository/mixin/R2dbcRepositoryMixin.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.repository.mixin 2 | 3 | import org.springframework.data.repository.NoRepositoryBean 4 | 5 | @NoRepositoryBean 6 | interface R2dbcRepositoryMixin : 7 | ReactiveSortingRepositoryMixin, 8 | ReactiveQueryByExampleExecutorMixin -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/repository/mixin/ReactiveSortingRepositoryMixin.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.repository.mixin 2 | 3 | import io.github.wickedev.graphql.spring.data.r2dbc.repository.base.PropertyRepository 4 | import org.springframework.data.domain.Sort 5 | import org.springframework.data.relational.core.query.Query 6 | import org.springframework.data.repository.NoRepositoryBean 7 | import org.springframework.data.repository.reactive.ReactiveSortingRepository 8 | import org.springframework.util.Assert 9 | import reactor.core.publisher.Flux 10 | 11 | @NoRepositoryBean 12 | interface ReactiveSortingRepositoryMixin : 13 | ReactiveSortingRepository, 14 | ReactiveCrudRepositoryMixin, 15 | PropertyRepository { 16 | 17 | /* (non-Javadoc) 18 | * @see org.springframework.data.repository.reactive.ReactiveSortingRepository#findAll(org.springframework.data.domain.Sort) 19 | */ 20 | override fun findAll(sort: Sort): Flux { 21 | Assert.notNull(sort, "Sort must not be null!") 22 | return entityOperations.select(Query.empty().sort(sort), entity.javaType) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/strategy/AdditionalIsNewStrategy.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.strategy 2 | 3 | interface AdditionalIsNewStrategy { 4 | fun isNew(type: Class<*>?, value: Any?): Boolean 5 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/strategy/DefaultIDTypeStrategy.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.strategy 2 | 3 | import io.github.wickedev.graphql.spring.data.r2dbc.extentions.camelToSnakeCase 4 | import java.lang.reflect.Field 5 | import kotlin.reflect.KClass 6 | 7 | class DefaultIDTypeStrategy : IDTypeStrategy { 8 | override fun convertType(type: Class<*>, tableName: String?): String { 9 | return type.simpleName.camelToSnakeCase() 10 | } 11 | 12 | override fun convertType(entity: Class<*>, property: Field, globalType: String?, relationType: KClass<*>?): String { 13 | if (globalType != null && relationType != null) { 14 | throw IllegalStateException("ID type property cannot declare both of @Relation and @GlobalId annotation on ${entity.name}::${property.name}") 15 | } 16 | 17 | return globalType ?: relationType?.simpleName?.camelToSnakeCase() ?: "" 18 | } 19 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/strategy/GraphQLAdditionalIsNewStrategy.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.strategy 2 | 3 | import io.github.wickedev.graphql.types.ID 4 | 5 | class GraphQLAdditionalIsNewStrategy : AdditionalIsNewStrategy { 6 | 7 | override fun isNew(type: Class<*>?, value: Any?): Boolean { 8 | return if (type == ID::class.java) 9 | (value as ID).value.isEmpty() 10 | else false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/strategy/IDTypeFiller.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.strategy 2 | 3 | import io.github.wickedev.graphql.spring.data.r2dbc.mapping.GraphQLTypeInformation 4 | import io.github.wickedev.graphql.types.ID 5 | import org.springframework.data.mapping.PersistentPropertyAccessor 6 | import org.springframework.data.mapping.context.MappingContext 7 | import org.springframework.data.relational.core.mapping.RelationalPersistentEntity 8 | import org.springframework.data.relational.core.mapping.RelationalPersistentProperty 9 | 10 | class IDTypeFiller(private val mappingContext: MappingContext, out RelationalPersistentProperty>) { 11 | 12 | @Suppress("UNCHECKED_CAST") 13 | fun setIDTypeIfNecessary(`object`: T): T { 14 | if (`object` is Number || `object` is String) { 15 | return `object` 16 | } 17 | 18 | var updated = false 19 | val entity = mappingContext.getRequiredPersistentEntity(`object`.javaClass) 20 | val propertyAccessor: PersistentPropertyAccessor<*> = entity.getPropertyAccessor(`object`) 21 | 22 | for (property in entity) { 23 | val value = propertyAccessor.getProperty(property) 24 | val type = property.typeInformation 25 | 26 | if (value is ID && value.type.isEmpty() && type is GraphQLTypeInformation<*>) { 27 | propertyAccessor.setProperty(property, convertToId(type, value.value)) 28 | updated = true 29 | } 30 | } 31 | 32 | return if (updated) propertyAccessor.bean as T else `object` 33 | } 34 | 35 | private fun convertToId(type: GraphQLTypeInformation, value: Any?): ID? { 36 | return value?.let { ID(type.typeName(), value.toString()) } 37 | } 38 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/strategy/IDTypeStrategy.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.strategy 2 | 3 | import java.lang.reflect.Field 4 | import kotlin.reflect.KClass 5 | 6 | interface IDTypeStrategy { 7 | fun convertType(type: Class<*>, tableName: String?): String 8 | 9 | fun convertType(entity: Class<*>, property: Field, globalType: String?, relationType: KClass<*>?): String 10 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/main/kotlin/org/springframework/data/r2dbc/core/GraphQLR2dbcEntityTemplate.kt: -------------------------------------------------------------------------------- 1 | package org.springframework.data.r2dbc.core 2 | 3 | import io.github.wickedev.graphql.spring.data.r2dbc.strategy.IDTypeFiller 4 | import org.springframework.data.r2dbc.convert.R2dbcConverter 5 | import org.springframework.data.r2dbc.dialect.R2dbcDialect 6 | import org.springframework.data.r2dbc.mapping.OutboundRow 7 | import org.springframework.data.relational.core.sql.SqlIdentifier 8 | import org.springframework.r2dbc.core.DatabaseClient 9 | import reactor.core.publisher.Mono 10 | 11 | class GraphQLR2dbcEntityTemplate( 12 | databaseClient: DatabaseClient, 13 | dialect: R2dbcDialect, 14 | r2dbcConverter: R2dbcConverter 15 | ) : R2dbcEntityTemplate(databaseClient, dialect, r2dbcConverter) { 16 | 17 | private val mappingContext = dataAccessStrategy.converter.mappingContext 18 | private val idTypeFiller = IDTypeFiller(mappingContext) 19 | 20 | override fun maybeCallAfterSave(`object`: T, row: OutboundRow, table: SqlIdentifier): Mono { 21 | return super.maybeCallAfterSave(idTypeFiller.setIDTypeIfNecessary(`object`), row, table) 22 | } 23 | 24 | override fun maybeCallAfterConvert(`object`: T, table: SqlIdentifier): Mono { 25 | return super.maybeCallAfterConvert(idTypeFiller.setIDTypeIfNecessary(`object`), table) 26 | } 27 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/test/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/GraphQLDataLoaderNodeRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc 2 | 3 | import graphql.schema.DataFetchingEnvironmentImpl.newDataFetchingEnvironment 4 | import io.github.wickedev.coroutine.reactive.extensions.mono.await 5 | import io.github.wickedev.graphql.repository.GraphQLNodeRepository 6 | import io.github.wickedev.graphql.spring.data.r2dbc.utils.dispatchThenAwait 7 | import io.github.wickedev.graphql.spring.data.r2dbc.utils.fixture 8 | import io.kotest.core.spec.style.DescribeSpec 9 | import io.kotest.matchers.shouldBe 10 | import io.kotest.matchers.shouldNotBe 11 | import org.dataloader.DataLoaderRegistry 12 | import org.springframework.test.context.ContextConfiguration 13 | 14 | 15 | @ContextConfiguration(classes = [TestingApp::class]) 16 | class GraphQLDataLoaderNodeRepositoryTest( 17 | private val userRepository: UserRepository, 18 | private val nodeRepository: GraphQLNodeRepository, 19 | ) : DescribeSpec({ 20 | lateinit var saved: User 21 | 22 | beforeSpec { 23 | saved = userRepository.save(fixture()).await() 24 | } 25 | 26 | describe("graphql node repository") { 27 | it("should be return node entity") { 28 | val env = newDataFetchingEnvironment() 29 | .dataLoaderRegistry(DataLoaderRegistry()) 30 | .build() 31 | 32 | val user = nodeRepository.findNodeById(saved.id, env).dispatchThenAwait(env) 33 | 34 | user shouldNotBe null 35 | user shouldBe saved 36 | } 37 | } 38 | }) -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/test/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/GraphQLDataLoaderRepositoryFindAllTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc 2 | 3 | import graphql.schema.DataFetchingEnvironmentImpl.newDataFetchingEnvironment 4 | import io.github.wickedev.coroutine.reactive.extensions.mono.await 5 | import io.github.wickedev.graphql.spring.data.r2dbc.utils.dispatchAll 6 | import io.github.wickedev.graphql.spring.data.r2dbc.utils.fixture 7 | import io.github.wickedev.graphql.types.ID 8 | import io.kotest.core.spec.style.DescribeSpec 9 | import io.kotest.matchers.shouldBe 10 | import kotlinx.coroutines.future.await 11 | import org.dataloader.DataLoaderRegistry 12 | import org.springframework.test.context.ContextConfiguration 13 | 14 | 15 | @ContextConfiguration(classes = [TestingApp::class]) 16 | class GraphQLDataLoaderRepositoryFindAllTest( 17 | private val userRepository: UserRepository, 18 | ) : DescribeSpec({ 19 | lateinit var saved: User 20 | 21 | beforeSpec { 22 | saved = userRepository.save(fixture()).await() 23 | } 24 | 25 | describe("graphql data loader repository") { 26 | it("existsById should be return true") { 27 | val env = newDataFetchingEnvironment() 28 | .dataLoaderRegistry(DataLoaderRegistry()) 29 | .build() 30 | 31 | val existsFuture = userRepository.existsById(saved.id, env) 32 | val nonExistsId = "290518" 33 | val nonExistsFuture = userRepository.existsById(ID(nonExistsId), env) 34 | 35 | env.dispatchAll() 36 | 37 | existsFuture.await() shouldBe true 38 | nonExistsFuture.await() shouldBe false 39 | } 40 | } 41 | }) -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/test/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/PostRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc 2 | 3 | import graphql.schema.DataFetchingEnvironment 4 | import io.github.wickedev.graphql.annotations.Relation 5 | import io.github.wickedev.graphql.interfases.Node 6 | import io.github.wickedev.graphql.repository.GraphQLDataLoaderByIdRepository 7 | import io.github.wickedev.graphql.types.ID 8 | import org.springframework.beans.factory.annotation.Autowired 9 | import org.springframework.data.annotation.Id 10 | import org.springframework.data.relational.core.mapping.Table 11 | import org.springframework.stereotype.Repository 12 | import java.util.concurrent.CompletableFuture 13 | 14 | @Table("post") 15 | data class Post( 16 | @Id override val id: ID, 17 | val title: String, 18 | val content: String, 19 | @Relation(User::class) val userId: ID, 20 | ) : Node { 21 | fun author( 22 | @Autowired userRepository: UserRepository, 23 | env: DataFetchingEnvironment 24 | ): CompletableFuture { 25 | return userRepository.findById(userId, env) 26 | } 27 | } 28 | 29 | @Repository 30 | interface PostRepository : GraphQLDataLoaderByIdRepository 31 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/test/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/ProjectConfig.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc 2 | 3 | import io.kotest.core.config.AbstractProjectConfig 4 | import io.kotest.extensions.spring.SpringExtension 5 | 6 | @Suppress("unused") 7 | object ProjectConfig : AbstractProjectConfig() { 8 | override fun extensions() = listOf(SpringExtension) 9 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/test/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/RepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc 2 | 3 | import io.github.wickedev.coroutine.reactive.extensions.mono.await 4 | import io.github.wickedev.graphql.spring.data.r2dbc.utils.fixture 5 | import io.kotest.core.spec.style.DescribeSpec 6 | import io.kotest.matchers.shouldNotBe 7 | import org.springframework.test.context.ContextConfiguration 8 | 9 | @ContextConfiguration(classes = [TestingApp::class]) 10 | class RepositoryTest( 11 | private val userRepository: UserRepository 12 | ) : DescribeSpec({ 13 | 14 | describe("user repository") { 15 | it("should be insert entity to database") { 16 | val user = userRepository.save(fixture()).await() 17 | user shouldNotBe null 18 | } 19 | } 20 | }) -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/test/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/SubRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc 2 | 3 | import io.kotest.core.spec.style.DescribeSpec 4 | import io.kotest.matchers.shouldNotBe 5 | import org.springframework.test.context.ContextConfiguration 6 | 7 | @ContextConfiguration(classes = [TestingApp::class]) 8 | class SubRepositoryTest( 9 | private val userRepository: UserRepository, 10 | private val postRepository: PostRepository, 11 | ) : DescribeSpec({ 12 | 13 | describe("user sub repository") { 14 | it("should not be null") { 15 | userRepository shouldNotBe null 16 | postRepository shouldNotBe null 17 | } 18 | } 19 | }) -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/test/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/TestingApp.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc 2 | 3 | import io.github.wickedev.graphql.spring.data.r2dbc.configuration.EnableGraphQLR2dbcRepositories 4 | import io.r2dbc.spi.ConnectionFactories 5 | import io.r2dbc.spi.ConnectionFactory 6 | import org.springframework.boot.autoconfigure.SpringBootApplication 7 | import org.springframework.context.annotation.Bean 8 | import org.springframework.core.io.ClassPathResource 9 | import org.springframework.r2dbc.connection.init.CompositeDatabasePopulator 10 | import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer 11 | import org.springframework.r2dbc.connection.init.DatabasePopulator 12 | import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator 13 | 14 | @SpringBootApplication 15 | @EnableGraphQLR2dbcRepositories 16 | class TestingApp { 17 | @Bean 18 | fun connectionFactory(): ConnectionFactory = 19 | ConnectionFactories.get("r2dbc:tc:postgresql:///test?TC_IMAGE_TAG=9.6.23") 20 | 21 | @Bean 22 | fun populator(): DatabasePopulator { 23 | val resourcePaths = listOf("db/schema.sql") 24 | return CompositeDatabasePopulator( 25 | resourcePaths.map { 26 | ResourceDatabasePopulator( 27 | ClassPathResource(it) 28 | ) 29 | } 30 | ) 31 | } 32 | 33 | @Bean 34 | fun initializer( 35 | connectionFactory: ConnectionFactory, 36 | populator: DatabasePopulator 37 | ): ConnectionFactoryInitializer { 38 | val initializer = ConnectionFactoryInitializer() 39 | initializer.setConnectionFactory(connectionFactory) 40 | initializer.setDatabasePopulator(populator) 41 | return initializer 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/test/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc 2 | 3 | import io.github.wickedev.graphql.interfases.Node 4 | import io.github.wickedev.graphql.spring.data.r2dbc.repository.interfaces.GraphQLR2dbcRepository 5 | import io.github.wickedev.graphql.types.ID 6 | import org.springframework.data.annotation.Id 7 | import org.springframework.data.relational.core.mapping.Table 8 | import org.springframework.stereotype.Repository 9 | 10 | @Table("users") 11 | data class User( 12 | @Id override val id: ID, 13 | val name: String 14 | ) : Node 15 | 16 | 17 | @Repository 18 | interface UserRepository : GraphQLR2dbcRepository 19 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/test/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/utils/DataLoader.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.utils 2 | 3 | import graphql.schema.DataFetchingEnvironment 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.future.await 7 | import kotlinx.coroutines.launch 8 | import java.util.concurrent.CompletableFuture 9 | 10 | fun DataFetchingEnvironment.dispatchAll() = CoroutineScope(Dispatchers.Unconfined).launch { 11 | dataLoaderRegistry.dataLoaders.forEach { 12 | it.dispatch() 13 | } 14 | } 15 | 16 | suspend fun CompletableFuture.dispatchThenAwait(env: DataFetchingEnvironment): T { 17 | env.dispatchAll() 18 | return this.await() 19 | } -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/test/kotlin/io/github/wickedev/graphql/spring/data/r2dbc/utils/Fixture.kt: -------------------------------------------------------------------------------- 1 | package io.github.wickedev.graphql.spring.data.r2dbc.utils 2 | 3 | import com.appmattus.kotlinfixture.decorator.fake.javafaker.javaFakerStrategy 4 | import com.appmattus.kotlinfixture.kotlinFixture 5 | import io.github.wickedev.graphql.spring.data.r2dbc.Post 6 | import io.github.wickedev.graphql.types.ID 7 | 8 | val fixture = kotlinFixture { 9 | javaFakerStrategy { 10 | factory { ID("") } 11 | property(Post::userId) { ID("1") } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/test/resources/db/schema.sql: -------------------------------------------------------------------------------- 1 | 2 | -- user 3 | CREATE TABLE IF NOT EXISTS users 4 | ( 5 | -- id 6 | id BIGSERIAL PRIMARY KEY, 7 | 8 | -- fields 9 | name VARCHAR(64) NOT NULL 10 | ); 11 | 12 | -- post 13 | CREATE TABLE IF NOT EXISTS post 14 | ( 15 | -- id 16 | id BIGSERIAL PRIMARY KEY, 17 | 18 | -- fields 19 | title VARCHAR(512) NOT NULL, 20 | content TEXT NOT NULL, 21 | 22 | -- relationship 23 | user_id BIGINT NOT NULL 24 | ); -------------------------------------------------------------------------------- /spring-data-graphql-r2dbc/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.moowork.gradle.node.npm.NpmTask 2 | 3 | plugins { 4 | id("base") 5 | id("distribution") 6 | id("com.moowork.node") version "1.3.1" 7 | } 8 | 9 | task("npmRunBuild") { 10 | group = "npm" 11 | setArgs(listOf("run", "build")) 12 | } 13 | 14 | task("npmStart") { 15 | group = "npm" 16 | setArgs(listOf("run", "start")) 17 | } 18 | 19 | tasks.getByName("npmRunBuild").dependsOn("npmInstall") 20 | tasks.getByName("npmStart").dependsOn("npmInstall") 21 | 22 | task("deploySite") { 23 | group = "npm" 24 | dependsOn("npmRunBuild") 25 | from(project.buildDir) 26 | into("${rootProject.projectDir}/docs") 27 | } 28 | 29 | tasks.getByName("clean") { 30 | group = "npm" 31 | delete = setOf("node_modules", "build", ".docusaurus") 32 | } -------------------------------------------------------------------------------- /website/docs/backup_intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | slug: /intro 4 | --- 5 | 6 | # Tutorial Intro 7 | 8 | Let's discover **Docusaurus in less than 5 minutes**. 9 | 10 | ## Getting Started 11 | 12 | Get started by **creating a new site**. 13 | 14 | Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**. 15 | 16 | ### What you'll need 17 | 18 | - [Node.js](https://nodejs.org/en/download/) version 14 or above: 19 | - When installing Node.js, you are recommended to check all checkboxes related to dependencies. 20 | 21 | ## Generate a new site 22 | 23 | Generate a new Docusaurus site using the **classic template**. 24 | 25 | The classic template will automatically be added to your project after you run the command: 26 | 27 | ```bash 28 | npm init docusaurus@latest my-website classic 29 | ``` 30 | 31 | You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor. 32 | 33 | The command also installs all necessary dependencies you need to run Docusaurus. 34 | 35 | ## Start your site 36 | 37 | Run the development server: 38 | 39 | ```bash 40 | cd my-website 41 | npm run start 42 | ``` 43 | 44 | The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there. 45 | 46 | The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/. 47 | 48 | Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes. 49 | -------------------------------------------------------------------------------- /website/docs/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/website/docs/img.png -------------------------------------------------------------------------------- /website/docs/tutorial-basics/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Tutorial - Basics", 3 | "position": 2 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/tutorial-basics/congratulations.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # Congratulations! 6 | 7 | You have just learned the **basics of Docusaurus** and made some changes to the **initial template**. 8 | 9 | Docusaurus has **much more to offer**! 10 | 11 | Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**. 12 | 13 | Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610) 14 | 15 | ## What's next? 16 | 17 | - Read the [official documentation](https://docusaurus.io/). 18 | - Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout) 19 | - Add a [search bar](https://docusaurus.io/docs/search) 20 | - Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase) 21 | - Get involved in the [Docusaurus Community](https://docusaurus.io/community/support) 22 | -------------------------------------------------------------------------------- /website/docs/tutorial-basics/create-a-blog-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Create a Blog Post 6 | 7 | Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed... 8 | 9 | ## Create your first Post 10 | 11 | Create a file at `blog/2021-02-28-greetings.md`: 12 | 13 | ```md title="blog/2021-02-28-greetings.md" 14 | --- 15 | slug: greetings 16 | title: Greetings! 17 | authors: 18 | - name: Joel Marcey 19 | title: Co-creator of Docusaurus 1 20 | url: https://github.com/JoelMarcey 21 | image_url: https://github.com/JoelMarcey.png 22 | - name: Sébastien Lorber 23 | title: Docusaurus maintainer 24 | url: https://sebastienlorber.com 25 | image_url: https://github.com/slorber.png 26 | tags: [greetings] 27 | --- 28 | 29 | Congratulations, you have made your first post! 30 | 31 | Feel free to play around and edit this post as much you like. 32 | ``` 33 | 34 | A new blog post is now available at `http://localhost:3000/blog/greetings`. 35 | -------------------------------------------------------------------------------- /website/docs/tutorial-basics/create-a-document.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Create a Document 6 | 7 | Documents are **groups of pages** connected through: 8 | 9 | - a **sidebar** 10 | - **previous/next navigation** 11 | - **versioning** 12 | 13 | ## Create your first Doc 14 | 15 | Create a markdown file at `docs/hello.md`: 16 | 17 | ```md title="docs/hello.md" 18 | # Hello 19 | 20 | This is my **first Docusaurus document**! 21 | ``` 22 | 23 | A new document is now available at `http://localhost:3000/docs/hello`. 24 | 25 | ## Configure the Sidebar 26 | 27 | Docusaurus automatically **creates a sidebar** from the `docs` folder. 28 | 29 | Add metadata to customize the sidebar label and position: 30 | 31 | ```md title="docs/hello.md" {1-4} 32 | --- 33 | sidebar_label: 'Hi!' 34 | sidebar_position: 3 35 | --- 36 | 37 | # Hello 38 | 39 | This is my **first Docusaurus document**! 40 | ``` 41 | 42 | It is also possible to create your sidebar explicitly in `sidebars.js`: 43 | 44 | ```js title="sidebars.js" 45 | module.exports = { 46 | tutorialSidebar: [ 47 | { 48 | type: 'category', 49 | label: 'Tutorial', 50 | // highlight-next-line 51 | items: ['hello'], 52 | }, 53 | ], 54 | }; 55 | ``` 56 | -------------------------------------------------------------------------------- /website/docs/tutorial-basics/create-a-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Create a Page 6 | 7 | Add **Markdown or React** files to `src/pages` to create a **standalone page**: 8 | 9 | - `src/pages/index.js` -> `localhost:3000/` 10 | - `src/pages/foo.md` -> `localhost:3000/foo` 11 | - `src/pages/foo/bar.js` -> `localhost:3000/foo/bar` 12 | 13 | ## Create your first React Page 14 | 15 | Create a file at `src/pages/my-react-page.js`: 16 | 17 | ```jsx title="src/pages/my-react-page.js" 18 | import React from 'react'; 19 | import Layout from '@theme/Layout'; 20 | 21 | export default function MyReactPage() { 22 | return ( 23 | 24 |

My React page

25 |

This is a React page

26 |
27 | ); 28 | } 29 | ``` 30 | 31 | A new page is now available at `http://localhost:3000/my-react-page`. 32 | 33 | ## Create your first Markdown Page 34 | 35 | Create a file at `src/pages/my-markdown-page.md`: 36 | 37 | ```mdx title="src/pages/my-markdown-page.md" 38 | # My Markdown page 39 | 40 | This is a Markdown page 41 | ``` 42 | 43 | A new page is now available at `http://localhost:3000/my-markdown-page`. 44 | -------------------------------------------------------------------------------- /website/docs/tutorial-basics/deploy-your-site.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Deploy your site 6 | 7 | Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**). 8 | 9 | It builds your site as simple **static HTML, JavaScript and CSS files**. 10 | 11 | ## Build your site 12 | 13 | Build your site **for production**: 14 | 15 | ```bash 16 | npm run build 17 | ``` 18 | 19 | The static files are generated in the `build` folder. 20 | 21 | ## Deploy your site 22 | 23 | Test your production build locally: 24 | 25 | ```bash 26 | npm run serve 27 | ``` 28 | 29 | The `build` folder is now served at `http://localhost:3000/`. 30 | 31 | You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**). 32 | -------------------------------------------------------------------------------- /website/docs/tutorial-extras/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Tutorial - Extras", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/tutorial-extras/manage-docs-versions.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Manage Docs Versions 6 | 7 | Docusaurus can manage multiple versions of your docs. 8 | 9 | ## Create a docs version 10 | 11 | Release a version 1.0 of your project: 12 | 13 | ```bash 14 | npm run docusaurus docs:version 1.0 15 | ``` 16 | 17 | The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created. 18 | 19 | Your docs now have 2 versions: 20 | 21 | - `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs 22 | - `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs** 23 | 24 | ## Add a Version Dropdown 25 | 26 | To navigate seamlessly across versions, add a version dropdown. 27 | 28 | Modify the `docusaurus.config.js` file: 29 | 30 | ```js title="docusaurus.config.js" 31 | module.exports = { 32 | themeConfig: { 33 | navbar: { 34 | items: [ 35 | // highlight-start 36 | { 37 | type: 'docsVersionDropdown', 38 | }, 39 | // highlight-end 40 | ], 41 | }, 42 | }, 43 | }; 44 | ``` 45 | 46 | The docs version dropdown appears in your navbar: 47 | 48 | ![Docs Version Dropdown](/img/tutorial/docsVersionDropdown.png) 49 | 50 | ## Update an existing version 51 | 52 | It is possible to edit versioned docs in their respective folder: 53 | 54 | - `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello` 55 | - `docs/hello.md` updates `http://localhost:3000/docs/next/hello` 56 | -------------------------------------------------------------------------------- /website/docs/tutorial-extras/translate-your-site.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Translate your site 6 | 7 | Let's translate `docs/intro.md` to French. 8 | 9 | ## Configure i18n 10 | 11 | Modify `docusaurus.config.js` to add support for the `fr` locale: 12 | 13 | ```js title="docusaurus.config.js" 14 | module.exports = { 15 | i18n: { 16 | defaultLocale: 'en', 17 | locales: ['en', 'fr'], 18 | }, 19 | }; 20 | ``` 21 | 22 | ## Translate a doc 23 | 24 | Copy the `docs/intro.md` file to the `i18n/fr` folder: 25 | 26 | ```bash 27 | mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/ 28 | 29 | cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md 30 | ``` 31 | 32 | Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French. 33 | 34 | ## Start your localized site 35 | 36 | Start your site on the French locale: 37 | 38 | ```bash 39 | npm run start -- --locale fr 40 | ``` 41 | 42 | Your localized site is accessible at `http://localhost:3000/fr/` and the `Getting Started` page is translated. 43 | 44 | :::caution 45 | 46 | In development, you can only use one locale at a same time. 47 | 48 | ::: 49 | 50 | ## Add a Locale Dropdown 51 | 52 | To navigate seamlessly across languages, add a locale dropdown. 53 | 54 | Modify the `docusaurus.config.js` file: 55 | 56 | ```js title="docusaurus.config.js" 57 | module.exports = { 58 | themeConfig: { 59 | navbar: { 60 | items: [ 61 | // highlight-start 62 | { 63 | type: 'localeDropdown', 64 | }, 65 | // highlight-end 66 | ], 67 | }, 68 | }, 69 | }; 70 | ``` 71 | 72 | The locale dropdown now appears in your navbar: 73 | 74 | ![Locale Dropdown](/img/tutorial/localeDropdown.png) 75 | 76 | ## Build your localized site 77 | 78 | Build your site for a specific locale: 79 | 80 | ```bash 81 | npm run build -- --locale fr 82 | ``` 83 | 84 | Or build your site to include all the locales at once: 85 | 86 | ```bash 87 | npm run build 88 | ``` 89 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "2.0.0-beta.18", 18 | "@docusaurus/preset-classic": "2.0.0-beta.18", 19 | "@mdx-js/react": "^1.6.22", 20 | "clsx": "^1.1.1", 21 | "prism-react-renderer": "^1.3.1", 22 | "react": "^17.0.2", 23 | "react-dom": "^17.0.2" 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | ">0.5%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /website/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['hello'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | module.exports = sidebars; 32 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './styles.module.css'; 4 | 5 | const FeatureList = [ 6 | { 7 | title: 'Easy to Use', 8 | Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, 9 | description: ( 10 | <> 11 | Docusaurus was designed from the ground up to be easily installed and 12 | used to get your website up and running quickly. 13 | 14 | ), 15 | }, 16 | { 17 | title: 'Focus on What Matters', 18 | Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, 19 | description: ( 20 | <> 21 | Docusaurus lets you focus on your docs, and we'll do the chores. Go 22 | ahead and move your docs into the docs directory. 23 | 24 | ), 25 | }, 26 | { 27 | title: 'Powered by React', 28 | Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, 29 | description: ( 30 | <> 31 | Extend or customize your website layout by reusing React. Docusaurus can 32 | be extended while reusing the same header and footer. 33 | 34 | ), 35 | }, 36 | ]; 37 | 38 | function Feature({Svg, title, description}) { 39 | return ( 40 |
41 |
42 | 43 |
44 |
45 |

{title}

46 |

{description}

47 |
48 |
49 | ); 50 | } 51 | 52 | export default function HomepageFeatures() { 53 | return ( 54 |
55 |
56 |
57 | {FeatureList.map((props, idx) => ( 58 | 59 | ))} 60 |
61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /website/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | } 18 | 19 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 20 | [data-theme='dark'] { 21 | --ifm-color-primary: #25c2a0; 22 | --ifm-color-primary-dark: #21af90; 23 | --ifm-color-primary-darker: #1fa588; 24 | --ifm-color-primary-darkest: #1a8870; 25 | --ifm-color-primary-light: #29d5b0; 26 | --ifm-color-primary-lighter: #32d8b4; 27 | --ifm-color-primary-lightest: #4fddbf; 28 | } 29 | 30 | .docusaurus-highlight-code-line { 31 | background-color: rgba(0, 0, 0, 0.1); 32 | display: block; 33 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 34 | padding: 0 var(--ifm-pre-padding); 35 | } 36 | 37 | [data-theme='dark'] .docusaurus-highlight-code-line { 38 | background-color: rgba(0, 0, 0, 0.3); 39 | } 40 | -------------------------------------------------------------------------------- /website/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Layout from '@theme/Layout'; 4 | import Link from '@docusaurus/Link'; 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 6 | import styles from './index.module.css'; 7 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 8 | 9 | function HomepageHeader() { 10 | const {siteConfig} = useDocusaurusContext(); 11 | return ( 12 |
13 |
14 |

{siteConfig.title}

15 |

{siteConfig.tagline}

16 |
17 | 20 | Docusaurus Tutorial - 5min ⏱️ 21 | 22 |
23 |
24 |
25 | ); 26 | } 27 | 28 | export default function Home() { 29 | const {siteConfig} = useDocusaurusContext(); 30 | return ( 31 | 34 | 35 |
36 | 37 |
38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /website/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /website/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /website/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/website/static/.nojekyll -------------------------------------------------------------------------------- /website/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/website/static/img/docusaurus.png -------------------------------------------------------------------------------- /website/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/website/static/img/favicon.ico -------------------------------------------------------------------------------- /website/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/static/img/tutorial/docsVersionDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/website/static/img/tutorial/docsVersionDropdown.png -------------------------------------------------------------------------------- /website/static/img/tutorial/localeDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wickedev/graphql-jetpack/d6913beaecf9e7ef065acdb1805794a10b07d55e/website/static/img/tutorial/localeDropdown.png --------------------------------------------------------------------------------