diff --git a/next.config.js b/next.config.js index 56da077fae..3a99e7137e 100644 --- a/next.config.js +++ b/next.config.js @@ -37,7 +37,7 @@ const ALLOWED_SVG_REGEX = new RegExp(`${sep}icons${sep}.+\\.svg$`) const config = { output: undefined, // reactStrictMode: true, provoke duplicated codemirror editors - webpack(config) { + webpack(config, { isServer, dev }) { // #region MDX const mdxRule = config.module.rules.find(rule => rule.test?.test?.(".mdx")) if (mdxRule) { @@ -62,6 +62,19 @@ const config = { fileLoaderRule.exclude = /\.svg$/i config.module.rules.push( + { + test: /\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg|txt)$/i, + resourceQuery: /resource/, + type: "asset/resource", + generator: { + filename: "static/media/[name].[hash][ext]", + publicPath: "/_next/", + // Server build outputs to .next/server/, so go up to reach .next/static/ + outputPath: [isServer && "../", !dev && "../"] + .filter(Boolean) + .join(""), + }, + }, // All .svg from /icons/ and with ?svgr are going to be processed by @svgr/webpack { test: ALLOWED_SVG_REGEX, @@ -103,7 +116,7 @@ const config = { test: /\.svg$/i, exclude: ALLOWED_SVG_REGEX, resourceQuery: { - not: [...fileLoaderRule.resourceQuery.not, /svgr/], + not: [...fileLoaderRule.resourceQuery.not, /svgr|resource/], }, }, ) diff --git a/package.json b/package.json index d3b9d07d11..1d3a6faff6 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/nesting": "0.0.0-insiders.565cd3e", "@tailwindcss/typography": "^0.5.15", + "arktype": "2.1.28", "autoprefixer": "^10.4.20", "calendar-link": "^2.10.0", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0df41ae641..ba6e6906f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ importers: '@tailwindcss/typography': specifier: ^0.5.15 version: 0.5.19(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.1)) + arktype: + specifier: 2.1.28 + version: 2.1.28 autoprefixer: specifier: ^10.4.20 version: 10.4.22(postcss@8.5.6) @@ -326,7 +329,7 @@ importers: scripts/sync-working-groups: dependencies: arktype: - specifier: ^2.1.27 + specifier: 2.1.28 version: 2.1.28 packages: diff --git a/scripts/get-github-info/github-stats.json b/scripts/get-github-info/github-stats.json index 9f6b835af8..dae0f6bf92 100644 --- a/scripts/get-github-info/github-stats.json +++ b/scripts/get-github-info/github-stats.json @@ -1,15 +1,15 @@ { "altair-graphql/altair": { "hasCommitsInLast3Months": false, - "stars": 5365, + "stars": 5372, "formattedStars": "5k", "license": "MIT License", - "lastRelease": "2025-10-28T22:43:22Z", - "formattedLastRelease": "1 month ago" + "lastRelease": "2025-12-12T08:48:15Z", + "formattedLastRelease": "12 hours ago" }, "apache/apisix": { "hasCommitsInLast3Months": false, - "stars": 15907, + "stars": 15968, "formattedStars": "16k", "license": "Apache License 2.0", "lastRelease": "2025-10-16T07:54:57Z", @@ -17,27 +17,27 @@ }, "apollographql/apollo-studio-community": { "hasCommitsInLast3Months": false, - "stars": 261, - "formattedStars": "261", + "stars": 260, + "formattedStars": "260", "license": "Unknown", "lastRelease": "", "formattedLastRelease": "" }, "ChilliCream/hotchocolate": { "hasCommitsInLast3Months": false, - "stars": 5637, + "stars": 5650, "formattedStars": "6k", "license": "MIT License", - "lastRelease": "2025-11-26T11:12:44Z", - "formattedLastRelease": "3 days ago" + "lastRelease": "2025-12-10T12:55:40Z", + "formattedLastRelease": "2 days ago" }, "dgraph-io/dgraph": { "hasCommitsInLast3Months": false, - "stars": 21367, + "stars": 21392, "formattedStars": "21k", "license": "Apache License 2.0", - "lastRelease": "2025-10-07T20:50:36Z", - "formattedLastRelease": "1 month ago" + "lastRelease": "2025-12-12T00:45:30Z", + "formattedLastRelease": "20 hours ago" }, "yahoo/elide": { "hasCommitsInLast3Months": false, @@ -45,7 +45,7 @@ "formattedStars": "1k", "license": "Other", "lastRelease": "2025-09-01T03:57:54Z", - "formattedLastRelease": "2 months ago" + "formattedLastRelease": "3 months ago" }, "graphapi-io/resources": { "hasCommitsInLast3Months": false, @@ -57,7 +57,7 @@ }, "hasura/graphql-engine": { "hasCommitsInLast3Months": false, - "stars": 31827, + "stars": 31835, "formattedStars": "32k", "license": "Apache License 2.0", "lastRelease": "2025-10-14T15:20:38Z", @@ -65,23 +65,23 @@ }, "graphql-hive/platform": { "hasCommitsInLast3Months": false, - "stars": 471, - "formattedStars": "471", + "stars": 472, + "formattedStars": "472", "license": "MIT License", - "lastRelease": "2025-11-25T15:15:47Z", - "formattedLastRelease": "3 days ago" + "lastRelease": "2025-12-11T15:59:16Z", + "formattedLastRelease": "1 day ago" }, "Kong/insomnia": { "hasCommitsInLast3Months": false, - "stars": 37611, + "stars": 37667, "formattedStars": "38k", "license": "Apache License 2.0", - "lastRelease": "2025-11-21T17:29:45Z", - "formattedLastRelease": "1 week ago" + "lastRelease": "2025-12-12T19:24:57Z", + "formattedLastRelease": "1 hour ago" }, "postmanlabs/postman-app-support": { "hasCommitsInLast3Months": false, - "stars": 5979, + "stars": 5982, "formattedStars": "6k", "license": "Unknown", "lastRelease": "", @@ -97,87 +97,39 @@ }, "TykTechnologies/tyk": { "hasCommitsInLast3Months": false, - "stars": 10515, + "stars": 10532, "formattedStars": "11k", "license": "Other", "lastRelease": "2025-11-28T16:32:34Z", - "formattedLastRelease": "19 hours ago" + "formattedLastRelease": "2 weeks ago" }, "twinlogix/typetta": { "hasCommitsInLast3Months": false, - "stars": 115, - "formattedStars": "115", + "stars": 116, + "formattedStars": "116", "license": "Apache License 2.0", "lastRelease": "2023-10-16T07:50:50Z", "formattedLastRelease": "2 years ago" }, "webiny/webiny-js": { "hasCommitsInLast3Months": false, - "stars": 7894, + "stars": 7900, "formattedStars": "8k", "license": "Other", - "lastRelease": "2025-09-16T08:29:00Z", - "formattedLastRelease": "2 months ago" + "lastRelease": "2025-12-09T11:36:01Z", + "formattedLastRelease": "3 days ago" }, "ballerina-platform/module-ballerina-graphql": { "hasCommitsInLast3Months": false, "stars": 138, "formattedStars": "138", "license": "Apache License 2.0", - "lastRelease": "2025-11-06T10:54:08Z", - "formattedLastRelease": "3 weeks ago" - }, - "oliyh/re-graph": { - "hasCommitsInLast3Months": false, - "stars": 464, - "formattedStars": "464", - "license": "Unknown", - "lastRelease": "2022-07-20T09:24:02Z", - "formattedLastRelease": "3 years ago" - }, - "microsoft/cppgraphqlgen": { - "hasCommitsInLast3Months": false, - "stars": 343, - "formattedStars": "343", - "license": "MIT License", - "lastRelease": "2024-12-10T17:25:31Z", - "formattedLastRelease": "11 months ago" - }, - "graphql/libgraphqlparser": { - "hasCommitsInLast3Months": false, - "stars": 1102, - "formattedStars": "1k", - "license": "MIT License", - "lastRelease": "2017-10-16T21:47:42Z", - "formattedLastRelease": "8 years ago" - }, - "alumbra/alumbra": { - "hasCommitsInLast3Months": false, - "stars": 148, - "formattedStars": "148", - "license": "MIT License", - "lastRelease": "2017-06-12T12:14:25Z", - "formattedLastRelease": "8 years ago" - }, - "tendant/graphql-clj": { - "hasCommitsInLast3Months": false, - "stars": 285, - "formattedStars": "285", - "license": "Eclipse Public License 1.0", - "lastRelease": "", - "formattedLastRelease": "" - }, - "walmartlabs/lacinia": { - "hasCommitsInLast3Months": false, - "stars": 1843, - "formattedStars": "2k", - "license": "Other", - "lastRelease": "", - "formattedLastRelease": "" + "lastRelease": "2025-12-08T12:39:34Z", + "formattedLastRelease": "4 days ago" }, "graphql-dotnet/graphql-client": { "hasCommitsInLast3Months": false, - "stars": 644, + "stars": 645, "formattedStars": "1k", "license": "MIT License", "lastRelease": "2024-05-21T07:06:30Z", @@ -197,7 +149,7 @@ "formattedStars": "8", "license": "MIT License", "lastRelease": "2025-11-14T07:39:17Z", - "formattedLastRelease": "2 weeks ago" + "formattedLastRelease": "4 weeks ago" }, "sahb1239/SAHB.GraphQLClient": { "hasCommitsInLast3Months": false, @@ -217,19 +169,19 @@ }, "EntityGraphQL/EntityGraphQL": { "hasCommitsInLast3Months": false, - "stars": 449, - "formattedStars": "449", + "stars": 450, + "formattedStars": "450", "license": "MIT License", - "lastRelease": "2025-09-16T00:35:14Z", - "formattedLastRelease": "2 months ago" + "lastRelease": "2025-12-01T22:09:40Z", + "formattedLastRelease": "1 week ago" }, "graphql-dotnet/graphql-dotnet": { "hasCommitsInLast3Months": false, - "stars": 5975, + "stars": 5976, "formattedStars": "6k", "license": "MIT License", "lastRelease": "2025-11-17T17:57:35Z", - "formattedLastRelease": "1 week ago" + "formattedLastRelease": "3 weeks ago" }, "chkimes/graphql-net": { "hasCommitsInLast3Months": false, @@ -247,37 +199,37 @@ "lastRelease": "", "formattedLastRelease": "" }, - "burner/graphqld": { + "microsoft/cppgraphqlgen": { "hasCommitsInLast3Months": false, - "stars": 35, - "formattedStars": "35", - "license": "GNU Lesser General Public License v3.0", - "lastRelease": "2024-05-14T13:42:29Z", + "stars": 343, + "formattedStars": "343", + "license": "MIT License", + "lastRelease": "2024-12-10T17:25:31Z", "formattedLastRelease": "1 year ago" }, - "annkissam/common_graphql_client": { + "graphql/libgraphqlparser": { "hasCommitsInLast3Months": false, - "stars": 42, - "formattedStars": "42", + "stars": 1102, + "formattedStars": "1k", "license": "MIT License", - "lastRelease": "2020-05-05T16:48:50Z", - "formattedLastRelease": "5 years ago" + "lastRelease": "2017-10-16T21:47:42Z", + "formattedLastRelease": "8 years ago" }, - "uesteibar/neuron": { + "burner/graphqld": { "hasCommitsInLast3Months": false, - "stars": 333, - "formattedStars": "333", - "license": "Other", - "lastRelease": "", - "formattedLastRelease": "" + "stars": 35, + "formattedStars": "35", + "license": "GNU Lesser General Public License v3.0", + "lastRelease": "2024-05-14T13:42:29Z", + "formattedLastRelease": "1 year ago" }, "absinthe-graphql/absinthe": { "hasCommitsInLast3Months": false, - "stars": 4374, + "stars": 4379, "formattedStars": "4k", "license": "Other", "lastRelease": "2025-11-21T15:08:24Z", - "formattedLastRelease": "1 week ago" + "formattedLastRelease": "3 weeks ago" }, "graphql-elixir/graphql": { "hasCommitsInLast3Months": false, @@ -295,6 +247,14 @@ "lastRelease": "", "formattedLastRelease": "" }, + "oliyh/re-graph": { + "hasCommitsInLast3Months": false, + "stars": 464, + "formattedStars": "464", + "license": "Unknown", + "lastRelease": "2022-07-20T09:24:02Z", + "formattedLastRelease": "3 years ago" + }, "jlouis/graphql-erlang": { "hasCommitsInLast3Months": false, "stars": 314, @@ -303,9 +263,33 @@ "lastRelease": "2018-06-22T12:35:43Z", "formattedLastRelease": "7 years ago" }, + "alumbra/alumbra": { + "hasCommitsInLast3Months": false, + "stars": 148, + "formattedStars": "148", + "license": "MIT License", + "lastRelease": "2017-06-12T12:14:25Z", + "formattedLastRelease": "8 years ago" + }, + "tendant/graphql-clj": { + "hasCommitsInLast3Months": false, + "stars": 285, + "formattedStars": "285", + "license": "Eclipse Public License 1.0", + "lastRelease": "", + "formattedLastRelease": "" + }, + "walmartlabs/lacinia": { + "hasCommitsInLast3Months": false, + "stars": 1843, + "formattedStars": "2k", + "license": "Other", + "lastRelease": "", + "formattedLastRelease": "" + }, "gql-dart/ferry": { "hasCommitsInLast3Months": false, - "stars": 628, + "stars": 630, "formattedStars": "1k", "license": "MIT License", "lastRelease": "", @@ -319,9 +303,33 @@ "lastRelease": "2025-10-21T16:42:55Z", "formattedLastRelease": "1 month ago" }, + "annkissam/common_graphql_client": { + "hasCommitsInLast3Months": false, + "stars": 42, + "formattedStars": "42", + "license": "MIT License", + "lastRelease": "2020-05-05T16:48:50Z", + "formattedLastRelease": "5 years ago" + }, + "uesteibar/neuron": { + "hasCommitsInLast3Months": false, + "stars": 333, + "formattedStars": "333", + "license": "Other", + "lastRelease": "", + "formattedLastRelease": "" + }, + "dosco/graphjin": { + "hasCommitsInLast3Months": false, + "stars": 3014, + "formattedStars": "3k", + "license": "Apache License 2.0", + "lastRelease": "2025-11-05T07:51:12Z", + "formattedLastRelease": "1 month ago" + }, "Khan/genqlient": { "hasCommitsInLast3Months": false, - "stars": 1266, + "stars": 1274, "formattedStars": "1k", "license": "MIT License", "lastRelease": "2025-05-18T19:09:08Z", @@ -329,15 +337,15 @@ }, "hasura/go-graphql-client": { "hasCommitsInLast3Months": false, - "stars": 459, - "formattedStars": "459", + "stars": 461, + "formattedStars": "461", "license": "MIT License", "lastRelease": "2025-11-05T06:45:53Z", - "formattedLastRelease": "3 weeks ago" + "formattedLastRelease": "1 month ago" }, "shurcooL/graphql": { "hasCommitsInLast3Months": false, - "stars": 728, + "stars": 727, "formattedStars": "1k", "license": "MIT License", "lastRelease": "", @@ -353,11 +361,11 @@ }, "99designs/gqlgen": { "hasCommitsInLast3Months": false, - "stars": 10593, + "stars": 10607, "formattedStars": "11k", "license": "MIT License", "lastRelease": "2025-11-24T16:56:20Z", - "formattedLastRelease": "4 days ago" + "formattedLastRelease": "2 weeks ago" }, "andrewwphillips/eggql": { "hasCommitsInLast3Months": false, @@ -377,15 +385,15 @@ }, "graph-gophers/graphql-go": { "hasCommitsInLast3Months": false, - "stars": 4741, + "stars": 4743, "formattedStars": "5k", "license": "BSD 2-Clause \"Simplified\" License", "lastRelease": "2025-09-09T11:37:07Z", - "formattedLastRelease": "2 months ago" + "formattedLastRelease": "3 months ago" }, "graphql-go/graphql": { "hasCommitsInLast3Months": false, - "stars": 10132, + "stars": 10135, "formattedStars": "10k", "license": "MIT License", "lastRelease": "2023-04-10T18:20:23Z", @@ -409,43 +417,43 @@ }, "wundergraph/graphql-go-tools": { "hasCommitsInLast3Months": false, - "stars": 790, + "stars": 814, "formattedStars": "1k", "license": "MIT License", - "lastRelease": "2025-11-21T21:18:20Z", - "formattedLastRelease": "1 week ago" + "lastRelease": "2025-12-10T11:25:46Z", + "formattedLastRelease": "2 days ago" }, - "dosco/graphjin": { + "morpheusgraphql/morpheus-graphql": { "hasCommitsInLast3Months": false, - "stars": 3011, - "formattedStars": "3k", - "license": "Apache License 2.0", - "lastRelease": "2025-11-05T07:51:12Z", - "formattedLastRelease": "3 weeks ago" + "stars": 418, + "formattedStars": "418", + "license": "MIT License", + "lastRelease": "2024-06-10T08:34:35Z", + "formattedLastRelease": "1 year ago" }, - "grails/gorm-graphql": { + "apollographql/apollo-kotlin": { "hasCommitsInLast3Months": false, - "stars": 81, - "formattedStars": "81", - "license": "Unknown", - "lastRelease": "2023-12-08T10:48:05Z", - "formattedLastRelease": "1 year ago" + "stars": 3930, + "formattedStars": "4k", + "license": "MIT License", + "lastRelease": "2025-11-13T17:33:51Z", + "formattedLastRelease": "4 weeks ago" }, - "grooviter/gql": { + "ExpediaGroup/graphql-kotlin": { "hasCommitsInLast3Months": false, - "stars": 49, - "formattedStars": "49", + "stars": 1796, + "formattedStars": "2k", "license": "Apache License 2.0", - "lastRelease": "2024-11-05T10:13:23Z", - "formattedLastRelease": "1 year ago" + "lastRelease": "2025-06-16T17:02:18Z", + "formattedLastRelease": "5 months ago" }, - "morpheusgraphql/morpheus-graphql": { + "americanexpress/nodes": { "hasCommitsInLast3Months": false, - "stars": 418, - "formattedStars": "418", - "license": "MIT License", - "lastRelease": "2024-06-10T08:34:35Z", - "formattedLastRelease": "1 year ago" + "stars": 307, + "formattedStars": "307", + "license": "Apache License 2.0", + "lastRelease": "2019-07-13T22:47:01Z", + "formattedLastRelease": "6 years ago" }, "jasonsychau/graphql-w-persistent": { "hasCommitsInLast3Months": false, @@ -463,47 +471,135 @@ "lastRelease": "2021-01-11T11:19:38Z", "formattedLastRelease": "4 years ago" }, - "apollographql/apollo-client": { + "grails/gorm-graphql": { "hasCommitsInLast3Months": false, - "stars": 19673, - "formattedStars": "20k", - "license": "MIT License", - "lastRelease": "2025-11-19T01:20:25Z", - "formattedLastRelease": "1 week ago" + "stars": 81, + "formattedStars": "81", + "license": "Unknown", + "lastRelease": "2023-12-08T10:48:05Z", + "formattedLastRelease": "2 years ago" }, - "aws-amplify/amplify-js": { + "grooviter/gql": { "hasCommitsInLast3Months": false, - "stars": 9567, - "formattedStars": "10k", + "stars": 49, + "formattedStars": "49", "license": "Apache License 2.0", - "lastRelease": "2025-11-06T13:36:19Z", - "formattedLastRelease": "3 weeks ago" + "lastRelease": "2024-11-05T10:13:23Z", + "formattedLastRelease": "1 year ago" }, - "Houfeng/gq-loader": { + "graphql-java-generator/graphql-gradle-plugin-project": { "hasCommitsInLast3Months": false, - "stars": 59, - "formattedStars": "59", - "license": "Unknown", + "stars": 57, + "formattedStars": "57", + "license": "MIT License", "lastRelease": "", "formattedLastRelease": "" }, - "gqty-dev/gqty": { + "graphql-calculator/graphql-calculator": { "hasCommitsInLast3Months": false, - "stars": 1030, - "formattedStars": "1k", - "license": "MIT License", - "lastRelease": "2025-10-26T19:29:38Z", - "formattedLastRelease": "1 month ago" + "stars": 112, + "formattedStars": "112", + "license": "Apache License 2.0", + "lastRelease": "2021-09-03T01:56:25Z", + "formattedLastRelease": "4 years ago" }, - "grafoojs/grafoo": { + "graphql-java-kickstart/graphql-spring-boot": { "hasCommitsInLast3Months": false, - "stars": 274, - "formattedStars": "274", + "stars": 1513, + "formattedStars": "2k", "license": "MIT License", - "lastRelease": "2018-06-20T15:21:00Z", - "formattedLastRelease": "7 years ago" + "lastRelease": "2023-12-07T11:07:47Z", + "formattedLastRelease": "2 years ago" }, - "badbatch/graphql-box": { + "graphql-java/graphql-java": { + "hasCommitsInLast3Months": false, + "stars": 6227, + "formattedStars": "6k", + "license": "MIT License", + "lastRelease": "2025-11-10T01:21:35Z", + "formattedLastRelease": "1 month ago" + }, + "babyfish-ct/jimmer": { + "hasCommitsInLast3Months": false, + "stars": 1581, + "formattedStars": "2k", + "license": "Apache License 2.0", + "lastRelease": "2025-11-26T13:05:27Z", + "formattedLastRelease": "2 weeks ago" + }, + "aPureBase/KGraphQL": { + "hasCommitsInLast3Months": false, + "stars": 308, + "formattedStars": "308", + "license": "MIT License", + "lastRelease": "2023-01-27T10:09:55Z", + "formattedLastRelease": "2 years ago" + }, + "eclipse/microprofile-graphql": { + "hasCommitsInLast3Months": false, + "stars": 101, + "formattedStars": "101", + "license": "Apache License 2.0", + "lastRelease": "2022-03-21T18:26:51Z", + "formattedLastRelease": "3 years ago" + }, + "netflix/dgs-framework": { + "hasCommitsInLast3Months": false, + "stars": 3281, + "formattedStars": "3k", + "license": "Apache License 2.0", + "lastRelease": "2025-12-05T21:45:25Z", + "formattedLastRelease": "6 days ago" + }, + "spring-projects/spring-graphql": { + "hasCommitsInLast3Months": false, + "stars": 1580, + "formattedStars": "2k", + "license": "Apache License 2.0", + "lastRelease": "2025-11-18T10:05:26Z", + "formattedLastRelease": "3 weeks ago" + }, + "apollographql/apollo-client": { + "hasCommitsInLast3Months": false, + "stars": 19681, + "formattedStars": "20k", + "license": "MIT License", + "lastRelease": "2025-12-10T08:08:29Z", + "formattedLastRelease": "2 days ago" + }, + "aws-amplify/amplify-js": { + "hasCommitsInLast3Months": false, + "stars": 9570, + "formattedStars": "10k", + "license": "Apache License 2.0", + "lastRelease": "2025-12-10T14:00:23Z", + "formattedLastRelease": "2 days ago" + }, + "Houfeng/gq-loader": { + "hasCommitsInLast3Months": false, + "stars": 59, + "formattedStars": "59", + "license": "Unknown", + "lastRelease": "", + "formattedLastRelease": "" + }, + "gqty-dev/gqty": { + "hasCommitsInLast3Months": false, + "stars": 1031, + "formattedStars": "1k", + "license": "MIT License", + "lastRelease": "2025-10-26T19:29:38Z", + "formattedLastRelease": "1 month ago" + }, + "grafoojs/grafoo": { + "hasCommitsInLast3Months": false, + "stars": 274, + "formattedStars": "274", + "license": "MIT License", + "lastRelease": "2018-06-20T15:21:00Z", + "formattedLastRelease": "7 years ago" + }, + "badbatch/graphql-box": { "hasCommitsInLast3Months": false, "stars": 27, "formattedStars": "27", @@ -513,32 +609,32 @@ }, "nearform/graphql-hooks": { "hasCommitsInLast3Months": false, - "stars": 1889, + "stars": 1888, "formattedStars": "2k", "license": "Other", "lastRelease": "2025-01-08T18:45:52Z", - "formattedLastRelease": "10 months ago" + "formattedLastRelease": "11 months ago" }, "graphql/graphql-http": { "hasCommitsInLast3Months": false, - "stars": 357, - "formattedStars": "357", + "stars": 359, + "formattedStars": "359", "license": "MIT License", "lastRelease": "2025-01-17T14:16:52Z", "formattedLastRelease": "10 months ago" }, "jasonkuhrt/graphql-request": { "hasCommitsInLast3Months": false, - "stars": 6081, + "stars": 6086, "formattedStars": "6k", "license": "MIT License", - "lastRelease": "2025-11-25T16:55:56Z", - "formattedLastRelease": "3 days ago" + "lastRelease": "2025-12-12T15:51:04Z", + "formattedLastRelease": "4 hours ago" }, "enisdenjo/graphql-sse": { "hasCommitsInLast3Months": false, - "stars": 435, - "formattedStars": "435", + "stars": 438, + "formattedStars": "438", "license": "MIT License", "lastRelease": "2025-10-22T16:19:40Z", "formattedLastRelease": "1 month ago" @@ -553,7 +649,7 @@ }, "enisdenjo/graphql-ws": { "hasCommitsInLast3Months": false, - "stars": 1843, + "stars": 1846, "formattedStars": "2k", "license": "MIT License", "lastRelease": "2025-07-14T12:15:37Z", @@ -585,59 +681,43 @@ }, "facebook/relay": { "hasCommitsInLast3Months": false, - "stars": 18893, + "stars": 18900, "formattedStars": "19k", "license": "MIT License", "lastRelease": "2025-08-06T23:45:00Z", - "formattedLastRelease": "3 months ago" + "formattedLastRelease": "4 months ago" }, "FormidableLabs/urql": { "hasCommitsInLast3Months": false, - "stars": 8900, + "stars": 8905, "formattedStars": "9k", "license": "MIT License", "lastRelease": "2025-08-29T08:06:41Z", "formattedLastRelease": "3 months ago" }, - "apollographql/apollo-server": { - "hasCommitsInLast3Months": false, - "stars": 13926, - "formattedStars": "14k", - "license": "MIT License", - "lastRelease": "2025-11-21T23:19:03Z", - "formattedLastRelease": "1 week ago" - }, - "graphql/graphql-js": { + "neomatrixcode/Diana.jl": { "hasCommitsInLast3Months": false, - "stars": 20279, - "formattedStars": "20k", + "stars": 117, + "formattedStars": "117", "license": "MIT License", - "lastRelease": "2025-11-01T14:18:53Z", - "formattedLastRelease": "3 weeks ago" + "lastRelease": "2022-08-16T03:22:22Z", + "formattedLastRelease": "3 years ago" }, - "dotansimha/graphql-yoga": { + "DeloitteDigitalAPAC/GraphQLClient.jl": { "hasCommitsInLast3Months": false, - "stars": 8455, - "formattedStars": "8k", - "license": "MIT License", - "lastRelease": "2025-11-28T11:05:21Z", - "formattedLastRelease": "1 day ago" + "stars": 47, + "formattedStars": "47", + "license": "Other", + "lastRelease": "2022-10-26T16:48:16Z", + "formattedLastRelease": "3 years ago" }, - "mercurius-js/mercurius": { + "andreas/ocaml-graphql-server": { "hasCommitsInLast3Months": false, - "stars": 2462, - "formattedStars": "2k", + "stars": 621, + "formattedStars": "1k", "license": "MIT License", - "lastRelease": "2025-11-13T14:12:18Z", - "formattedLastRelease": "2 weeks ago" - }, - "getcronit/pylon": { - "hasCommitsInLast3Months": false, - "stars": 345, - "formattedStars": "345", - "license": "Apache License 2.0", - "lastRelease": "2025-10-01T08:35:15Z", - "formattedLastRelease": "1 month ago" + "lastRelease": "2022-07-08T16:26:45Z", + "formattedLastRelease": "3 years ago" }, "networkimprov/brangr": { "hasCommitsInLast3Months": false, @@ -649,19 +729,19 @@ }, "hayes/giraphql": { "hasCommitsInLast3Months": false, - "stars": 2553, + "stars": 2561, "formattedStars": "3k", "license": "ISC License", - "lastRelease": "2025-11-10T01:29:18Z", - "formattedLastRelease": "2 weeks ago" + "lastRelease": "2025-12-10T22:07:12Z", + "formattedLastRelease": "1 day ago" }, "graphql/graphiql": { "hasCommitsInLast3Months": false, - "stars": 16715, + "stars": 16730, "formattedStars": "17k", "license": "MIT License", - "lastRelease": "2025-11-01T22:30:04Z", - "formattedLastRelease": "3 weeks ago" + "lastRelease": "2025-11-30T09:04:01Z", + "formattedLastRelease": "1 week ago" }, "Urigo/graphql-cli": { "hasCommitsInLast3Months": false, @@ -673,11 +753,11 @@ }, "dotansimha/graphql-code-generator": { "hasCommitsInLast3Months": false, - "stars": 11179, + "stars": 11183, "formattedStars": "11k", "license": "MIT License", "lastRelease": "2025-11-29T04:07:19Z", - "formattedLastRelease": "7 hours ago" + "formattedLastRelease": "1 week ago" }, "kamilkisiela/graphql-config": { "hasCommitsInLast3Months": false, @@ -689,7 +769,7 @@ }, "dimaMachina/graphql-eslint/": { "hasCommitsInLast3Months": false, - "stars": 831, + "stars": 832, "formattedStars": "1k", "license": "MIT License", "lastRelease": "2025-03-26T14:11:23Z", @@ -700,8 +780,8 @@ "stars": 1725, "formattedStars": "2k", "license": "MIT License", - "lastRelease": "2025-11-15T02:42:13Z", - "formattedLastRelease": "2 weeks ago" + "lastRelease": "2025-12-10T22:16:19Z", + "formattedLastRelease": "1 day ago" }, "graphql/graphql-language-service": { "hasCommitsInLast3Months": false, @@ -713,18 +793,18 @@ }, "n1ru4l/graphql-live-query": { "hasCommitsInLast3Months": false, - "stars": 440, - "formattedStars": "440", + "stars": 441, + "formattedStars": "441", "license": "MIT License", "lastRelease": "2022-07-29T09:27:53Z", "formattedLastRelease": "3 years ago" }, "Urigo/graphql-mesh": { "hasCommitsInLast3Months": false, - "stars": 3463, + "stars": 3465, "formattedStars": "3k", "license": "MIT License", - "lastRelease": "2025-11-19T12:12:00Z", + "lastRelease": "2025-12-04T22:32:32Z", "formattedLastRelease": "1 week ago" }, "maticzav/graphql-middleware": { @@ -745,7 +825,7 @@ }, "Urigo/graphql-scalars": { "hasCommitsInLast3Months": false, - "stars": 1927, + "stars": 1929, "formattedStars": "2k", "license": "MIT License", "lastRelease": "2025-10-14T23:00:24Z", @@ -753,7 +833,7 @@ }, "maticzav/graphql-shield": { "hasCommitsInLast3Months": false, - "stars": 3574, + "stars": 3573, "formattedStars": "4k", "license": "MIT License", "lastRelease": "2022-11-22T19:08:37Z", @@ -761,11 +841,11 @@ }, "ardatan/graphql-tools": { "hasCommitsInLast3Months": false, - "stars": 5418, + "stars": 5416, "formattedStars": "5k", "license": "MIT License", "lastRelease": "2025-11-28T10:05:14Z", - "formattedLastRelease": "1 day ago" + "formattedLastRelease": "2 weeks ago" }, "anvilco/graphql-introspection-tools": { "hasCommitsInLast3Months": false, @@ -777,7 +857,7 @@ }, "graphile/postgraphile": { "hasCommitsInLast3Months": false, - "stars": 12858, + "stars": 12866, "formattedStars": "13k", "license": "Other", "lastRelease": "2023-10-05T16:27:00Z", @@ -785,7 +865,7 @@ }, "Urigo/SOFA": { "hasCommitsInLast3Months": false, - "stars": 1111, + "stars": 1112, "formattedStars": "1k", "license": "MIT License", "lastRelease": "2024-12-16T10:06:41Z", @@ -793,59 +873,131 @@ }, "anvilco/spectaql": { "hasCommitsInLast3Months": false, - "stars": 1205, + "stars": 1207, "formattedStars": "1k", "license": "MIT License", "lastRelease": "", "formattedLastRelease": "" }, - "neomatrixcode/Diana.jl": { + "apollographql/apollo-server": { "hasCommitsInLast3Months": false, - "stars": 117, - "formattedStars": "117", + "stars": 13930, + "formattedStars": "14k", "license": "MIT License", - "lastRelease": "2022-08-16T03:22:22Z", - "formattedLastRelease": "3 years ago" + "lastRelease": "2025-11-21T23:19:03Z", + "formattedLastRelease": "2 weeks ago" }, - "DeloitteDigitalAPAC/GraphQLClient.jl": { + "graphql/graphql-js": { "hasCommitsInLast3Months": false, - "stars": 47, - "formattedStars": "47", - "license": "Other", - "lastRelease": "2022-10-26T16:48:16Z", - "formattedLastRelease": "3 years ago" + "stars": 20290, + "formattedStars": "20k", + "license": "MIT License", + "lastRelease": "2025-11-01T14:18:53Z", + "formattedLastRelease": "1 month ago" }, - "andreas/ocaml-graphql-server": { + "dotansimha/graphql-yoga": { "hasCommitsInLast3Months": false, - "stars": 621, - "formattedStars": "1k", + "stars": 8460, + "formattedStars": "8k", "license": "MIT License", - "lastRelease": "2022-07-08T16:26:45Z", - "formattedLastRelease": "3 years ago" + "lastRelease": "2025-12-02T00:13:11Z", + "formattedLastRelease": "1 week ago" + }, + "mercurius-js/mercurius": { + "hasCommitsInLast3Months": false, + "stars": 2464, + "formattedStars": "2k", + "license": "MIT License", + "lastRelease": "2025-11-13T14:12:18Z", + "formattedLastRelease": "4 weeks ago" + }, + "getcronit/pylon": { + "hasCommitsInLast3Months": false, + "stars": 348, + "formattedStars": "348", + "license": "Apache License 2.0", + "lastRelease": "2025-10-01T08:35:15Z", + "formattedLastRelease": "2 months ago" }, "graphql-perl/graphql-perl": { "hasCommitsInLast3Months": false, - "stars": 73, - "formattedStars": "73", + "stars": 72, + "formattedStars": "72", + "license": "Unknown", + "lastRelease": "", + "formattedLastRelease": "" + }, + "mirumee/ariadne-codegen": { + "hasCommitsInLast3Months": false, + "stars": 373, + "formattedStars": "373", + "license": "BSD 3-Clause \"New\" or \"Revised\" License", + "lastRelease": "2025-12-06T19:39:02Z", + "formattedLastRelease": "6 days ago" + }, + "graphql-python/gql": { + "hasCommitsInLast3Months": false, + "stars": 1654, + "formattedStars": "2k", + "license": "MIT License", + "lastRelease": "2025-09-05T14:22:54Z", + "formattedLastRelease": "3 months ago" + }, + "denisart/graphql-query": { + "hasCommitsInLast3Months": false, + "stars": 66, + "formattedStars": "66", + "license": "MIT License", + "lastRelease": "2024-07-31T10:54:53Z", + "formattedLastRelease": "1 year ago" + }, + "prisma-labs/python-graphql-client": { + "hasCommitsInLast3Months": false, + "stars": 156, + "formattedStars": "156", + "license": "MIT License", + "lastRelease": "", + "formattedLastRelease": "" + }, + "dsal3389/ql": { + "hasCommitsInLast3Months": false, + "stars": 9, + "formattedStars": "9", "license": "Unknown", + "lastRelease": "2025-02-04T17:36:51Z", + "formattedLastRelease": "10 months ago" + }, + "qlient-org/python-qlient": { + "hasCommitsInLast3Months": false, + "stars": 46, + "formattedStars": "46", + "license": "MIT License", + "lastRelease": "2022-07-29T16:10:08Z", + "formattedLastRelease": "3 years ago" + }, + "profusion/sgqlc": { + "hasCommitsInLast3Months": false, + "stars": 547, + "formattedStars": "1k", + "license": "ISC License", "lastRelease": "", "formattedLastRelease": "" }, "api-platform/api-platform": { "hasCommitsInLast3Months": false, - "stars": 9054, + "stars": 9066, "formattedStars": "9k", "license": "MIT License", "lastRelease": "2025-03-11T16:15:41Z", - "formattedLastRelease": "8 months ago" + "formattedLastRelease": "9 months ago" }, "GatoGraphQL/GatoGraphQL": { "hasCommitsInLast3Months": false, - "stars": 376, - "formattedStars": "376", + "stars": 377, + "formattedStars": "377", "license": "GNU General Public License v2.0", "lastRelease": "2025-11-26T08:29:30Z", - "formattedLastRelease": "3 days ago" + "formattedLastRelease": "2 weeks ago" }, "infinityloop-dev/graphpinator": { "hasCommitsInLast3Months": false, @@ -861,15 +1013,15 @@ "formattedStars": "16", "license": "MIT License", "lastRelease": "2025-10-11T09:19:14Z", - "formattedLastRelease": "1 month ago" + "formattedLastRelease": "2 months ago" }, "webonyx/graphql-php": { "hasCommitsInLast3Months": false, - "stars": 4702, + "stars": 4706, "formattedStars": "5k", "license": "MIT License", - "lastRelease": "2025-11-20T11:51:16Z", - "formattedLastRelease": "1 week ago" + "lastRelease": "2025-12-09T07:31:19Z", + "formattedLastRelease": "3 days ago" }, "ivome/graphql-relay-php": { "hasCommitsInLast3Months": false, @@ -885,7 +1037,7 @@ "formattedStars": "1k", "license": "MIT License", "lastRelease": "2025-10-31T08:00:22Z", - "formattedLastRelease": "4 weeks ago" + "formattedLastRelease": "1 month ago" }, "thecodingmachine/graphqlite": { "hasCommitsInLast3Months": false, @@ -893,15 +1045,15 @@ "formattedStars": "1k", "license": "MIT License", "lastRelease": "2025-09-04T16:39:26Z", - "formattedLastRelease": "2 months ago" + "formattedLastRelease": "3 months ago" }, "nuwave/lighthouse": { "hasCommitsInLast3Months": false, - "stars": 3465, + "stars": 3471, "formattedStars": "3k", "license": "MIT License", - "lastRelease": "2025-09-11T08:07:50Z", - "formattedLastRelease": "2 months ago" + "lastRelease": "2025-12-05T08:19:37Z", + "formattedLastRelease": "1 week ago" }, "railt/railt": { "hasCommitsInLast3Months": false, @@ -929,71 +1081,63 @@ }, "wp-graphql/wp-graphql": { "hasCommitsInLast3Months": false, - "stars": 3759, + "stars": 3758, "formattedStars": "4k", "license": "GNU General Public License v3.0", "lastRelease": "2025-11-24T22:39:55Z", - "formattedLastRelease": "4 days ago" + "formattedLastRelease": "2 weeks ago" }, - "mirumee/ariadne-codegen": { + "ropensci/ghql": { "hasCommitsInLast3Months": false, - "stars": 368, - "formattedStars": "368", - "license": "BSD 3-Clause \"New\" or \"Revised\" License", - "lastRelease": "2025-10-13T06:38:02Z", - "formattedLastRelease": "1 month ago" + "stars": 149, + "formattedStars": "149", + "license": "Other", + "lastRelease": "2025-09-08T08:41:00Z", + "formattedLastRelease": "3 months ago" }, - "graphql-python/gql": { + "ghostdogpr/caliban": { "hasCommitsInLast3Months": false, - "stars": 1653, - "formattedStars": "2k", - "license": "MIT License", - "lastRelease": "2025-09-05T14:22:54Z", - "formattedLastRelease": "2 months ago" + "stars": 977, + "formattedStars": "1k", + "license": "Apache License 2.0", + "lastRelease": "2025-07-14T00:24:20Z", + "formattedLastRelease": "4 months ago" }, - "denisart/graphql-query": { + "ohler55/agoo": { "hasCommitsInLast3Months": false, - "stars": 66, - "formattedStars": "66", + "stars": 924, + "formattedStars": "1k", "license": "MIT License", - "lastRelease": "2024-07-31T10:54:53Z", - "formattedLastRelease": "1 year ago" + "lastRelease": "2025-09-24T22:20:23Z", + "formattedLastRelease": "2 months ago" }, - "prisma-labs/python-graphql-client": { + "rmosolgo/graphql-ruby": { "hasCommitsInLast3Months": false, - "stars": 156, - "formattedStars": "156", + "stars": 5427, + "formattedStars": "5k", "license": "MIT License", - "lastRelease": "", - "formattedLastRelease": "" - }, - "dsal3389/ql": { - "hasCommitsInLast3Months": false, - "stars": 9, - "formattedStars": "9", - "license": "Unknown", - "lastRelease": "2025-02-04T17:36:51Z", - "formattedLastRelease": "9 months ago" + "lastRelease": "2025-07-19T17:15:49Z", + "formattedLastRelease": "4 months ago" }, - "qlient-org/python-qlient": { + "virtualshield/rails-graphql": { "hasCommitsInLast3Months": false, - "stars": 46, - "formattedStars": "46", + "stars": 187, + "formattedStars": "187", "license": "MIT License", - "lastRelease": "2022-07-29T16:10:08Z", - "formattedLastRelease": "3 years ago" + "lastRelease": "2025-08-25T17:53:38Z", + "formattedLastRelease": "3 months ago" }, - "profusion/sgqlc": { + "sangria-graphql/sangria": { "hasCommitsInLast3Months": false, - "stars": 546, - "formattedStars": "1k", - "license": "ISC License", - "lastRelease": "", - "formattedLastRelease": "" + "stars": 1957, + "formattedStars": "2k", + "license": "Apache License 2.0", + "lastRelease": "2025-10-20T11:40:30Z", + "formattedLastRelease": "1 month ago" }, "mirumee/ariadne": { "hasCommitsInLast3Months": false, - "stars": 2309, + "stars": 2310, "formattedStars": "2k", "license": "BSD 3-Clause \"New\" or \"Revised\" License", "lastRelease": "2025-04-18T08:27:47Z", @@ -1017,7 +1161,7 @@ }, "graphql-python/graphene": { "hasCommitsInLast3Months": false, - "stars": 8239, + "stars": 8240, "formattedStars": "8k", "license": "MIT License", "lastRelease": "2024-11-09T20:43:58Z", @@ -1025,203 +1169,131 @@ }, "strawberry-graphql/strawberry": { "hasCommitsInLast3Months": false, - "stars": 4569, + "stars": 4571, "formattedStars": "5k", "license": "MIT License", - "lastRelease": "2025-11-22T13:00:06Z", - "formattedLastRelease": "6 days ago" + "lastRelease": "2025-12-12T11:49:36Z", + "formattedLastRelease": "9 hours ago" }, "tartiflette/tartiflette": { "hasCommitsInLast3Months": false, - "stars": 856, + "stars": 854, "formattedStars": "1k", "license": "MIT License", "lastRelease": "2021-11-15T11:05:03Z", "formattedLastRelease": "4 years ago" }, - "apollographql/apollo-kotlin": { - "hasCommitsInLast3Months": false, - "stars": 3929, - "formattedStars": "4k", - "license": "MIT License", - "lastRelease": "2025-11-13T17:33:51Z", - "formattedLastRelease": "2 weeks ago" - }, - "ExpediaGroup/graphql-kotlin": { - "hasCommitsInLast3Months": false, - "stars": 1795, - "formattedStars": "2k", - "license": "Apache License 2.0", - "lastRelease": "2025-06-16T17:02:18Z", - "formattedLastRelease": "5 months ago" - }, - "americanexpress/nodes": { - "hasCommitsInLast3Months": false, - "stars": 307, - "formattedStars": "307", - "license": "Apache License 2.0", - "lastRelease": "2019-07-13T22:47:01Z", - "formattedLastRelease": "6 years ago" - }, - "graphql-calculator/graphql-calculator": { - "hasCommitsInLast3Months": false, - "stars": 112, - "formattedStars": "112", - "license": "Apache License 2.0", - "lastRelease": "2021-09-03T01:56:25Z", - "formattedLastRelease": "4 years ago" - }, - "graphql-java-kickstart/graphql-spring-boot": { - "hasCommitsInLast3Months": false, - "stars": 1513, - "formattedStars": "2k", - "license": "MIT License", - "lastRelease": "2023-12-07T11:07:47Z", - "formattedLastRelease": "1 year ago" - }, - "graphql-java/graphql-java": { - "hasCommitsInLast3Months": false, - "stars": 6226, - "formattedStars": "6k", - "license": "MIT License", - "lastRelease": "2025-11-10T01:21:35Z", - "formattedLastRelease": "2 weeks ago" - }, - "babyfish-ct/jimmer": { + "obmarg/cynic": { "hasCommitsInLast3Months": false, - "stars": 1565, - "formattedStars": "2k", - "license": "Apache License 2.0", - "lastRelease": "2025-11-26T13:05:27Z", - "formattedLastRelease": "2 days ago" + "stars": 443, + "formattedStars": "443", + "license": "Mozilla Public License 2.0", + "lastRelease": "2025-08-19T19:37:22Z", + "formattedLastRelease": "3 months ago" }, - "aPureBase/KGraphQL": { + "arthurkhlghatyan/gql-client-rs": { "hasCommitsInLast3Months": false, - "stars": 308, - "formattedStars": "308", + "stars": 51, + "formattedStars": "51", "license": "MIT License", - "lastRelease": "2023-01-27T10:09:55Z", - "formattedLastRelease": "2 years ago" + "lastRelease": "2025-06-07T14:31:10Z", + "formattedLastRelease": "6 months ago" }, - "eclipse/microprofile-graphql": { + "async-graphql/async-graphql": { "hasCommitsInLast3Months": false, - "stars": 101, - "formattedStars": "101", + "stars": 3606, + "formattedStars": "4k", "license": "Apache License 2.0", - "lastRelease": "2022-03-21T18:26:51Z", - "formattedLastRelease": "3 years ago" + "lastRelease": "", + "formattedLastRelease": "" }, - "netflix/dgs-framework": { + "graphql-rust/juniper": { "hasCommitsInLast3Months": false, - "stars": 3280, - "formattedStars": "3k", - "license": "Apache License 2.0", - "lastRelease": "2025-11-24T21:04:55Z", - "formattedLastRelease": "4 days ago" + "stars": 5920, + "formattedStars": "6k", + "license": "Other", + "lastRelease": "2025-09-08T23:23:40Z", + "formattedLastRelease": "3 months ago" }, - "spring-projects/spring-graphql": { + "apollographql/router": { "hasCommitsInLast3Months": false, - "stars": 1578, - "formattedStars": "2k", - "license": "Apache License 2.0", - "lastRelease": "2025-11-18T10:05:26Z", - "formattedLastRelease": "1 week ago" + "stars": 941, + "formattedStars": "1k", + "license": "Other", + "lastRelease": "2025-12-12T12:42:12Z", + "formattedLastRelease": "8 hours ago" }, - "graphql-java-generator/graphql-gradle-plugin-project": { + "eerimoq/gqt": { "hasCommitsInLast3Months": false, - "stars": 57, - "formattedStars": "57", + "stars": 470, + "formattedStars": "470", "license": "MIT License", "lastRelease": "", "formattedLastRelease": "" }, - "ropensci/ghql": { - "hasCommitsInLast3Months": false, - "stars": 149, - "formattedStars": "149", - "license": "Other", - "lastRelease": "2025-09-08T08:41:00Z", - "formattedLastRelease": "2 months ago" - }, - "ohler55/agoo": { + "Escape-Technologies/graphql-armor": { "hasCommitsInLast3Months": false, - "stars": 924, + "stars": 561, "formattedStars": "1k", "license": "MIT License", - "lastRelease": "2025-09-24T22:20:23Z", - "formattedLastRelease": "2 months ago" + "lastRelease": "2025-08-22T13:32:40Z", + "formattedLastRelease": "3 months ago" }, - "rmosolgo/graphql-ruby": { + "ldebruijn/graphql-protect": { "hasCommitsInLast3Months": false, - "stars": 5428, - "formattedStars": "5k", + "stars": 34, + "formattedStars": "34", "license": "MIT License", - "lastRelease": "2025-07-19T17:15:49Z", - "formattedLastRelease": "4 months ago" + "lastRelease": "2025-11-25T14:27:46Z", + "formattedLastRelease": "2 weeks ago" }, - "virtualshield/rails-graphql": { + "graphql-hive/gateway": { "hasCommitsInLast3Months": false, - "stars": 187, - "formattedStars": "187", + "stars": 69, + "formattedStars": "69", "license": "MIT License", - "lastRelease": "2025-08-25T17:53:38Z", - "formattedLastRelease": "3 months ago" + "lastRelease": "2025-12-10T19:27:44Z", + "formattedLastRelease": "2 days ago" }, - "obmarg/cynic": { + "microcks/microcks": { "hasCommitsInLast3Months": false, - "stars": 442, - "formattedStars": "442", - "license": "Mozilla Public License 2.0", - "lastRelease": "2025-08-19T19:37:22Z", - "formattedLastRelease": "3 months ago" + "stars": 1762, + "formattedStars": "2k", + "license": "Apache License 2.0", + "lastRelease": "2025-12-08T15:51:55Z", + "formattedLastRelease": "4 days ago" }, - "arthurkhlghatyan/gql-client-rs": { + "schemathesis/schemathesis": { "hasCommitsInLast3Months": false, - "stars": 51, - "formattedStars": "51", + "stars": 2891, + "formattedStars": "3k", "license": "MIT License", - "lastRelease": "2025-06-07T14:31:10Z", - "formattedLastRelease": "5 months ago" + "lastRelease": "2025-12-10T19:22:47Z", + "formattedLastRelease": "2 days ago" }, - "async-graphql/async-graphql": { + "glideapps/quicktype": { "hasCommitsInLast3Months": false, - "stars": 3594, - "formattedStars": "4k", + "stars": 13505, + "formattedStars": "14k", "license": "Apache License 2.0", "lastRelease": "", "formattedLastRelease": "" }, - "graphql-rust/juniper": { - "hasCommitsInLast3Months": false, - "stars": 5911, - "formattedStars": "6k", - "license": "Other", - "lastRelease": "2025-09-08T23:23:40Z", - "formattedLastRelease": "2 months ago" - }, - "ghostdogpr/caliban": { + "wundergraph/cosmo": { "hasCommitsInLast3Months": false, - "stars": 976, + "stars": 1126, "formattedStars": "1k", "license": "Apache License 2.0", - "lastRelease": "2025-07-14T00:24:20Z", - "formattedLastRelease": "4 months ago" - }, - "sangria-graphql/sangria": { - "hasCommitsInLast3Months": false, - "stars": 1957, - "formattedStars": "2k", - "license": "Apache License 2.0", - "lastRelease": "2025-10-20T11:40:30Z", - "formattedLastRelease": "1 month ago" + "lastRelease": "2025-12-10T12:36:49Z", + "formattedLastRelease": "2 days ago" }, "apollographql/apollo-ios": { "hasCommitsInLast3Months": false, - "stars": 4011, + "stars": 4018, "formattedStars": "4k", "license": "MIT License", - "lastRelease": "2025-11-05T23:30:57Z", - "formattedLastRelease": "3 weeks ago" + "lastRelease": "2025-12-03T20:34:39Z", + "formattedLastRelease": "1 week ago" }, "nerdsupremacist/Graphaello": { "hasCommitsInLast3Months": false, @@ -1249,7 +1321,7 @@ }, "GraphQLSwift/Graphiti": { "hasCommitsInLast3Months": false, - "stars": 553, + "stars": 554, "formattedStars": "1k", "license": "MIT License", "lastRelease": "2025-08-21T19:30:20Z", @@ -1262,77 +1334,5 @@ "license": "MIT License", "lastRelease": "2021-05-17T12:51:10Z", "formattedLastRelease": "4 years ago" - }, - "apollographql/router": { - "hasCommitsInLast3Months": false, - "stars": 939, - "formattedStars": "1k", - "license": "Other", - "lastRelease": "2025-11-28T17:47:44Z", - "formattedLastRelease": "18 hours ago" - }, - "Escape-Technologies/graphql-armor": { - "hasCommitsInLast3Months": false, - "stars": 558, - "formattedStars": "1k", - "license": "MIT License", - "lastRelease": "2025-08-22T13:32:40Z", - "formattedLastRelease": "3 months ago" - }, - "eerimoq/gqt": { - "hasCommitsInLast3Months": false, - "stars": 470, - "formattedStars": "470", - "license": "MIT License", - "lastRelease": "", - "formattedLastRelease": "" - }, - "ldebruijn/graphql-protect": { - "hasCommitsInLast3Months": false, - "stars": 34, - "formattedStars": "34", - "license": "MIT License", - "lastRelease": "2025-11-25T14:27:46Z", - "formattedLastRelease": "3 days ago" - }, - "graphql-hive/gateway": { - "hasCommitsInLast3Months": false, - "stars": 69, - "formattedStars": "69", - "license": "MIT License", - "lastRelease": "2025-11-24T15:40:17Z", - "formattedLastRelease": "4 days ago" - }, - "microcks/microcks": { - "hasCommitsInLast3Months": false, - "stars": 1754, - "formattedStars": "2k", - "license": "Apache License 2.0", - "lastRelease": "2025-10-25T15:08:00Z", - "formattedLastRelease": "1 month ago" - }, - "schemathesis/schemathesis": { - "hasCommitsInLast3Months": false, - "stars": 2871, - "formattedStars": "3k", - "license": "MIT License", - "lastRelease": "2025-11-28T16:13:29Z", - "formattedLastRelease": "19 hours ago" - }, - "glideapps/quicktype": { - "hasCommitsInLast3Months": false, - "stars": 13464, - "formattedStars": "13k", - "license": "Apache License 2.0", - "lastRelease": "", - "formattedLastRelease": "" - }, - "wundergraph/cosmo": { - "hasCommitsInLast3Months": false, - "stars": 1120, - "formattedStars": "1k", - "license": "Apache License 2.0", - "lastRelease": "2025-11-27T13:47:56Z", - "formattedLastRelease": "1 day ago" } } \ No newline at end of file diff --git a/scripts/get-github-info/last-success.isodate b/scripts/get-github-info/last-success.isodate index 817c2fe4de..5d68e2023c 100644 --- a/scripts/get-github-info/last-success.isodate +++ b/scripts/get-github-info/last-success.isodate @@ -1 +1 @@ -2025-11-29T12:04:08.519Z \ No newline at end of file +2025-12-12T20:51:22.323Z \ No newline at end of file diff --git a/scripts/sync-working-groups/package.json b/scripts/sync-working-groups/package.json index b470821c56..7659901d21 100644 --- a/scripts/sync-working-groups/package.json +++ b/scripts/sync-working-groups/package.json @@ -7,6 +7,6 @@ "start": "node ./sync-working-groups.ts" }, "dependencies": { - "arktype": "^2.1.27" + "arktype": "2.1.28" } } diff --git a/scripts/sync-working-groups/working-group-events.ndjson b/scripts/sync-working-groups/working-group-events.ndjson index 012d82306e..962f16860f 100644 --- a/scripts/sync-working-groups/working-group-events.ndjson +++ b/scripts/sync-working-groups/working-group-events.ndjson @@ -30,8 +30,12 @@ {"kind":"calendar#event","etag":"\"3524923696926750\"","id":"56uko3hh68be4q73tttdicg7l2_20251225T183000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=NTZ1a28zaGg2OGJlNHE3M3R0dGRpY2c3bDJfMjAyNTEyMjVUMTgzMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2025-10-16T15:10:58.000Z","updated":"2025-11-06T20:44:08.463Z","summary":"GraphQL AI Working Group","description":"Sign up and view agenda at https://github.com/graphql/ai-wg


Zoom password: aiwg","location":"https://zoom.us/j/92302442188","creator":{"email":"benjie@graphile.com"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2025-12-11T13:30:00-05:00","end":"2025-12-11T14:30:00-05:00","recurringEventId":"56uko3hh68be4q73tttdicg7l2","originalStartTime":{"dateTime":"2025-12-25T13:30:00-05:00","timeZone":"America/New_York"},"transparency":"transparent","iCalUID":"56uko3hh68be4q73tttdicg7l2@google.com","sequence":1,"eventType":"default"} {"kind":"calendar#event","etag":"\"3516415120288286\"","id":"h9erafl4rc1jjor9i6akokm5ec_20251218T160000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=aDllcmFmbDRyYzFqam9yOWk2YWtva201ZWNfMjAyNTEyMThUMTYwMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2023-12-08T21:32:03.000Z","updated":"2025-09-18T14:59:20.144Z","summary":"GraphQL Governing Board Meeting","creator":{"email":"jburson@linuxfoundation.org"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2025-12-18T11:00:00-05:00","end":"2025-12-18T12:00:00-05:00","recurringEventId":"h9erafl4rc1jjor9i6akokm5ec","originalStartTime":{"dateTime":"2025-12-18T11:00:00-05:00","timeZone":"America/New_York"},"iCalUID":"h9erafl4rc1jjor9i6akokm5ec@google.com","sequence":3,"eventType":"default"} {"kind":"calendar#event","etag":"\"3462003372886000\"","id":"kkc5tt01ovrjv8fki1lo31g5hj_20251218T170000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=a2tjNXR0MDFvdnJqdjhma2kxbG8zMWc1aGpfMjAyNTEyMThUMTcwMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2024-01-12T09:55:37.000Z","updated":"2024-11-07T17:48:06.443Z","summary":"Composite schemas WG - Weekly 3","description":"The weekly \"secondary\" meeting of the composite schemas WG: https://github.com/graphql/composite-schemas-wg

Meeting password is \"composite\"

Live notes are at https://docs.google.com/document/d/1hJO6U7daYvcNcQ3FBKnh3v4R256ers6M8IGyqRpY_kE/edit?usp=sharing","location":"https://zoom.us/j/91078840351","creator":{"email":"benjie@graphile.com"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2025-12-18T12:00:00-05:00","end":"2025-12-18T13:00:00-05:00","recurringEventId":"kkc5tt01ovrjv8fki1lo31g5hj","originalStartTime":{"dateTime":"2025-12-18T12:00:00-05:00","timeZone":"Europe/Berlin"},"iCalUID":"kkc5tt01ovrjv8fki1lo31g5hj@google.com","sequence":1,"eventType":"default"} -{"kind":"calendar#event","etag":"\"3500694996844990\"","id":"2ffd8o32sh77kd3mtccrtg887n_20251218T183000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=MmZmZDhvMzJzaDc3a2QzbXRjY3J0Zzg4N25fMjAyNTEyMThUMTgzMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2025-05-01T19:23:48.000Z","updated":"2025-06-19T15:38:18.422Z","summary":"GraphQL WG - Secondary (EU)","description":"Zoom password: graphqlwg","location":"https://zoom.us/j/593263740","creator":{"email":"benjie@graphile.com"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2025-12-18T13:30:00-05:00","end":"2025-12-18T15:00:00-05:00","recurringEventId":"2ffd8o32sh77kd3mtccrtg887n","originalStartTime":{"dateTime":"2025-12-18T13:30:00-05:00","timeZone":"America/Los_Angeles"},"iCalUID":"2ffd8o32sh77kd3mtccrtg887n@google.com","sequence":0,"eventType":"default"} +{"kind":"calendar#event","etag":"\"3529763944050462\"","id":"2ffd8o32sh77kd3mtccrtg887n_20251218T183000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=MmZmZDhvMzJzaDc3a2QzbXRjY3J0Zzg4N25fMjAyNTEyMThUMTgzMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2025-05-01T19:23:48.000Z","updated":"2025-12-04T20:59:32.025Z","summary":"GraphQL WG - Secondary (EU)","description":"Zoom password: graphqlwg","location":"https://zoom.us/j/593263740","creator":{"email":"benjie@graphile.com"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2025-12-18T13:30:00-05:00","end":"2025-12-18T15:00:00-05:00","recurringEventId":"2ffd8o32sh77kd3mtccrtg887n","originalStartTime":{"dateTime":"2025-12-18T13:30:00-05:00","timeZone":"America/Los_Angeles"},"iCalUID":"2ffd8o32sh77kd3mtccrtg887n@google.com","sequence":0,"eventType":"default"} {"kind":"calendar#event","etag":"\"3517067971709790\"","id":"f7cvs5ala9jtt147l3mik2mlvl_20251222T160000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=ZjdjdnM1YWxhOWp0dDE0N2wzbWlrMm1sdmxfMjAyNTEyMjJUMTYwMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2024-01-29T15:14:17.000Z","updated":"2025-09-22T09:39:45.854Z","summary":"Conference & Community Committee Meeting - Fortnightly Recurring","description":"\nYou have been invited to a recurring meeting for GraphQL Foundation\n\nWeekly Sync and Coordination Meeting for Conference Committee participants. Notes Document: https://docs.google.com/document/d/19-alP5jywnXzgN_1zYLBTRWh-4CaXzGakEZdTBFwNAc/edit\n\nWays to join meeting:\n\n1. Join from PC, Mac, iPad, or Android\n\nhttps://zoom-lfx.platform.linuxfoundation.org/meeting/96286151238?password=ff267735-efbd-4be4-a89c-b927b596190a\n\n2. Join via audio\n\nOne tap mobile:\nUS: +12532158782,,96286151238# or +13462487799,,96286151238\n\nOr dial:\nUS: +1 253 215 8782 or +1 346 248 7799 or +1 669 900 6833 or +1 301 715 8592 or +1 312 626 6799 or +1 646 374 8656 or 877 369 0926 (Toll Free) or 855 880 1246 (Toll Free)\nCanada: +1 647 374 4685 or +1 647 558 0588 or +1 778 907 2071 or +1 204 272 7920 or +1 438 809 7799 or +1 587 328 1099 or 855 703 8985 (Toll Free)\n\nMeeting ID: 96286151238\n\nMeeting Passcode: 986182\n\n\nInternational numbers: https://zoom.us/u/alwnPIaVT\n","location":"https://zoom-lfx.platform.linuxfoundation.org/meeting/96286151238?password=ff267735-efbd-4be4-a89c-b927b596190a","creator":{"email":"benjie@graphile.com"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2025-12-22T11:00:00-05:00","end":"2025-12-22T12:00:00-05:00","recurringEventId":"f7cvs5ala9jtt147l3mik2mlvl","originalStartTime":{"dateTime":"2025-12-22T11:00:00-05:00","timeZone":"America/New_York"},"iCalUID":"f7cvs5ala9jtt147l3mik2mlvl@google.com","sequence":2,"guestsCanInviteOthers":false,"eventType":"default"} {"kind":"calendar#event","etag":"\"3524923598591262\"","id":"s9agipg1r702pfngano7pol2h5_20251225T170000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=czlhZ2lwZzFyNzAycGZuZ2Fubzdwb2wyaDVfMjAyNTEyMjVUMTcwMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2024-01-12T09:56:07.000Z","updated":"2025-11-06T20:43:19.295Z","summary":"Composite schemas WG - Weekly 4","description":"The weekly "secondary" meeting of the composite schemas WG: https://github.com/graphql/composite-schemas-wg

Meeting password is "composite"

Live notes are at https://docs.google.com/document/d/1hJO6U7daYvcNcQ3FBKnh3v4R256ers6M8IGyqRpY_kE/edit?usp=sharing","location":"https://zoom.us/j/91078840351","creator":{"email":"benjie@graphile.com"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2025-12-25T12:00:00-05:00","end":"2025-12-25T13:00:00-05:00","recurringEventId":"s9agipg1r702pfngano7pol2h5","originalStartTime":{"dateTime":"2025-12-25T12:00:00-05:00","timeZone":"Europe/Berlin"},"iCalUID":"s9agipg1r702pfngano7pol2h5@google.com","sequence":1,"eventType":"default"} {"kind":"calendar#event","etag":"\"3524923550687710\"","id":"4igp67o2j2nkso49c1d6nbv040_20251225T180000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=NGlncDY3bzJqMm5rc280OWMxZDZuYnYwNDBfMjAyNTEyMjVUMTgwMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2025-04-15T10:29:33.000Z","updated":"2025-11-06T20:42:55.343Z","summary":"GraphQL OTel WG","description":"Zoom password: otel
 
https://github.com/graphql/otel-wg","location":"https://zoom.us/j/93594710848?pwd=meEB8rd5g69r5DF8zFaL8VIWO2Il1v.1","creator":{"email":"benjie@graphile.com"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2025-12-25T13:00:00-05:00","end":"2025-12-25T14:00:00-05:00","recurringEventId":"4igp67o2j2nkso49c1d6nbv040","originalStartTime":{"dateTime":"2025-12-25T13:00:00-05:00","timeZone":"America/Los_Angeles"},"iCalUID":"4igp67o2j2nkso49c1d6nbv040@google.com","sequence":0,"eventType":"default"} {"kind":"calendar#event","etag":"\"3524923616454910\"","id":"pag44b4o3k87r90laj5vf5t67v_20251225T190000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=cGFnNDRiNG8zazg3cjkwbGFqNXZmNXQ2N3ZfMjAyNTEyMjVUMTkwMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2023-12-04T10:48:14.000Z","updated":"2025-11-06T20:43:28.227Z","summary":"GraphQL-over-HTTP WG","description":"Zoom password: httpwg","location":"https://zoom.us/j/92781382543","creator":{"email":"benjie@graphile.com"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2025-12-25T14:00:00-05:00","end":"2025-12-25T15:00:00-05:00","recurringEventId":"pag44b4o3k87r90laj5vf5t67v","originalStartTime":{"dateTime":"2025-12-25T14:00:00-05:00","timeZone":"America/Los_Angeles"},"iCalUID":"pag44b4o3k87r90laj5vf5t67v@google.com","sequence":3,"eventType":"default"} +{"kind":"calendar#event","etag":"\"3530925154728702\"","id":"f7cvs5ala9jtt147l3mik2mlvl_20260105T160000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=ZjdjdnM1YWxhOWp0dDE0N2wzbWlrMm1sdmxfMjAyNjAxMDVUMTYwMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2024-01-29T15:14:17.000Z","updated":"2025-12-11T14:16:17.364Z","summary":"Conference & Community Committee Meeting - Fortnightly Recurring","description":"\nYou have been invited to a recurring meeting for GraphQL Foundation\n\nWeekly Sync and Coordination Meeting for Conference Committee participants. Notes Document: https://docs.google.com/document/d/19-alP5jywnXzgN_1zYLBTRWh-4CaXzGakEZdTBFwNAc/edit\n\nWays to join meeting:\n\n1. Join from PC, Mac, iPad, or Android\n\nhttps://zoom-lfx.platform.linuxfoundation.org/meeting/96286151238?password=ff267735-efbd-4be4-a89c-b927b596190a\n\n2. Join via audio\n\nOne tap mobile:\nUS: +12532158782,,96286151238# or +13462487799,,96286151238\n\nOr dial:\nUS: +1 253 215 8782 or +1 346 248 7799 or +1 669 900 6833 or +1 301 715 8592 or +1 312 626 6799 or +1 646 374 8656 or 877 369 0926 (Toll Free) or 855 880 1246 (Toll Free)\nCanada: +1 647 374 4685 or +1 647 558 0588 or +1 778 907 2071 or +1 204 272 7920 or +1 438 809 7799 or +1 587 328 1099 or 855 703 8985 (Toll Free)\n\nMeeting ID: 96286151238\n\nMeeting Passcode: 986182\n\n\nInternational numbers: https://zoom.us/u/alwnPIaVT\n","location":"https://zoom-lfx.platform.linuxfoundation.org/meeting/96286151238?password=ff267735-efbd-4be4-a89c-b927b596190a","creator":{"email":"benjie@graphile.com"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2026-01-05T11:00:00-05:00","end":"2026-01-05T12:00:00-05:00","recurringEventId":"f7cvs5ala9jtt147l3mik2mlvl","originalStartTime":{"dateTime":"2026-01-05T11:00:00-05:00","timeZone":"America/New_York"},"iCalUID":"f7cvs5ala9jtt147l3mik2mlvl@google.com","sequence":2,"guestsCanInviteOthers":false,"eventType":"default"} +{"kind":"calendar#event","etag":"\"3526826931784574\"","id":"q3qul35gpekign7gc8cvr6bap1_20260108T140000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=cTNxdWwzNWdwZWtpZ243Z2M4Y3ZyNmJhcDFfMjAyNjAxMDhUMTQwMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2023-12-08T21:20:13.000Z","updated":"2025-11-17T21:04:25.892Z","summary":"Marketing & Content Subcommittee Meeting","location":"https://zoom-lfx.platform.linuxfoundation.org/meeting/91228653788?password=0745533d-9a7a-42bb-8c72-3b823f679384","creator":{"email":"benjie@graphile.com"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2026-01-08T09:00:00-05:00","end":"2026-01-08T10:00:00-05:00","recurringEventId":"q3qul35gpekign7gc8cvr6bap1_R20251030T130000","originalStartTime":{"dateTime":"2026-01-08T09:00:00-05:00","timeZone":"America/New_York"},"iCalUID":"q3qul35gpekign7gc8cvr6bap1_R20251030T130000@google.com","sequence":1,"eventType":"default"} +{"kind":"calendar#event","etag":"\"3512629578532638\"","id":"1ae8m39lvqtigc4ao1p670g8il_20260108T160000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=MWFlOG0zOWx2cXRpZ2M0YW8xcDY3MGc4aWxfMjAyNjAxMDhUMTYwMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2025-02-04T14:14:16.000Z","updated":"2025-08-27T17:13:09.266Z","summary":"GraphQL Community WG","description":"Meeting password: community

https://github.com/graphql/community-wg/tree/main/agendas

Please be aware that meetings are recorded and/or live-streamed.","location":"https://zoom.us/j/93104287544","creator":{"email":"benjie@graphile.com"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2026-01-08T11:00:00-05:00","end":"2026-01-08T12:00:00-05:00","recurringEventId":"1ae8m39lvqtigc4ao1p670g8il_R20250313T150000","originalStartTime":{"dateTime":"2026-01-08T11:00:00-05:00","timeZone":"America/Los_Angeles"},"iCalUID":"1ae8m39lvqtigc4ao1p670g8il_R20250313T150000@google.com","sequence":1,"eventType":"default"} +{"kind":"calendar#event","etag":"\"3462003293658000\"","id":"lvqspfdh491rrdmvl7k1mruqd8_20260108T170000Z","status":"confirmed","htmlLink":"https://www.google.com/calendar/event?eid=bHZxc3BmZGg0OTFycmRtdmw3azFtcnVxZDhfMjAyNjAxMDhUMTcwMDAwWiBsaW51eGZvdW5kYXRpb24ub3JnX2lrNzl0OXV1ajJwMzJpM3IyMDNkZ3Y1bW84QGc","created":"2024-01-12T09:56:35.000Z","updated":"2024-11-07T17:47:26.829Z","summary":"Composite schemas WG - Weekly 2","description":"The weekly "secondary" meeting of the composite schemas WG: https://github.com/graphql/composite-schemas-wg

Meeting password is "composite"

Live notes are at https://docs.google.com/document/d/1hJO6U7daYvcNcQ3FBKnh3v4R256ers6M8IGyqRpY_kE/edit?usp=sharing","location":"https://zoom.us/j/91078840351","creator":{"email":"benjie@graphile.com"},"organizer":{"email":"linuxfoundation.org_ik79t9uuj2p32i3r203dgv5mo8@group.calendar.google.com","displayName":"GraphQL Foundation - Public","self":true},"start":"2026-01-08T12:00:00-05:00","end":"2026-01-08T13:00:00-05:00","recurringEventId":"lvqspfdh491rrdmvl7k1mruqd8","originalStartTime":{"dateTime":"2026-01-08T12:00:00-05:00","timeZone":"Europe/Berlin"},"iCalUID":"lvqspfdh491rrdmvl7k1mruqd8@google.com","sequence":1,"eventType":"default"} diff --git a/src/_design-system/breadcrumbs.tsx b/src/_design-system/breadcrumbs.tsx index 4b42a7554a..a394874b38 100644 --- a/src/_design-system/breadcrumbs.tsx +++ b/src/_design-system/breadcrumbs.tsx @@ -26,9 +26,10 @@ export const Breadcrumbs = ({ const title = extractStringsFromReactNode(item.title) const className = clsx( - "text-neu-700 dark:text-neu-400 min-w-6 last:text-neu-800 dark:last:text-neu-800 leading-none", + "text-neu-700 dark:text-neu-400 min-w-6 last:text-neu-800 dark:last:text-neu-800 leading-none whitespace-pre", href && "gql-focus-visible ring-inset hover:text-neu-900 hover:underline underline-offset-2", + item.title.length > 8 ? "overflow-hidden truncate" : "shrink-0", ) return ( diff --git a/src/app/(main)/resources/[category]/blog-posts-section.tsx b/src/app/(main)/resources/[category]/blog-posts-section.tsx new file mode 100644 index 0000000000..db715520c1 --- /dev/null +++ b/src/app/(main)/resources/[category]/blog-posts-section.tsx @@ -0,0 +1,65 @@ +"use client" + +import { Button } from "@/app/conf/_design-system/button" +import { Eyebrow } from "@/_design-system/eyebrow" +import { BlogCard } from "@/components/blog-page/blog-card" +import { sectionIds } from "./texts" + +export interface BlogPost { + href: string + title: string + author: string + date?: Date + tags: string[] +} + +export interface BlogPostsSectionProps { + title: string + description: string + posts: BlogPost[] + readAllHref?: string + readAllLabel?: string +} + +export function BlogPostsSection({ + title, + description, + posts, + readAllHref = "/blog", + readAllLabel = "Read all GraphQL stories", +}: BlogPostsSectionProps) { + return ( +
+
+
+ Blog posts +

{title}

+

+ {description} +

+
+ +
+ +
+ {posts.map(post => ( + + ))} +
+
+ ) +} diff --git a/src/app/(main)/resources/[category]/blur-corner.webp b/src/app/(main)/resources/[category]/blur-corner.webp new file mode 100644 index 0000000000..50e58292e7 Binary files /dev/null and b/src/app/(main)/resources/[category]/blur-corner.webp differ diff --git a/src/app/(main)/resources/[category]/cards-section.tsx b/src/app/(main)/resources/[category]/cards-section.tsx new file mode 100644 index 0000000000..b440598b59 --- /dev/null +++ b/src/app/(main)/resources/[category]/cards-section.tsx @@ -0,0 +1,64 @@ +import { Kind, ResourceMetadata, Topic } from "@/resources/types" +import { Eyebrow } from "@/_design-system/eyebrow" +import { Button } from "@/app/conf/_design-system/button" + +import { ResourceHubCard } from "../resource-hub-card" + +import { texts, sectionKindNames, sectionIds } from "./texts" + +function sectionLabel(kind: Kind) { + return sectionKindNames[kind] ?? `${kind[0].toUpperCase()}${kind.slice(1)}` +} + +export function CardsSection({ + section, + category, +}: { + section: { kind: Kind; resources: ResourceMetadata[] } + category: Topic +}) { + const sectionData = texts[category].sections[section.kind] + const heading = sectionData?.heading ?? sectionLabel(section.kind) + const text = sectionData?.text + + return ( +
+
+
+ {sectionKindNames[section.kind]} +

{heading}

+ {text && ( +

+ {text} +

+ )} +
+ {section.kind === "video" ? ( + + ) : ( + + {section.resources.length} resources + + )} +
+ + +
+ ) +} diff --git a/src/app/(main)/resources/[category]/category-tools-libraries-section.tsx b/src/app/(main)/resources/[category]/category-tools-libraries-section.tsx new file mode 100644 index 0000000000..00302436fb --- /dev/null +++ b/src/app/(main)/resources/[category]/category-tools-libraries-section.tsx @@ -0,0 +1,265 @@ +import path from "node:path" +import { glob } from "node:fs/promises" +import { readFile } from "node:fs/promises" +import matter from "gray-matter" +import type { CSSProperties } from "react" + +import { Button } from "@/app/conf/_design-system/button" +import blurCorner from "./blur-corner.webp" +import { Eyebrow } from "@/_design-system/eyebrow" +import slugMap from "@/code/slug-map.json" +import { type Topic } from "@/resources/types" +import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration" + +import { IconSpritesheet, IconName } from "./spritesheet" +import CaretDown from "@/app/conf/_design-system/pixelarticons/caret-down.svg?svgr" + +interface LibraryEntry { + name: string + href?: string + group: string + tags: string[] +} + +const librariesPromise = loadLibraries() + +async function loadLibraries(): Promise { + const entries: LibraryEntry[] = [] + + for await (const file of glob("src/code/**/*.md")) { + const relative = path.relative("src/code", file) + const segments = relative.split(path.sep) + const top = segments[0] + const group = + top === "language-support" ? (segments[1] ?? "language-support") : top + if (!group) continue + + const raw = await readFile(file, "utf8") + const { data } = matter(raw) + const tags: string[] = Array.isArray(data.tags) ? data.tags : [] + if (!tags.includes("tools-and-libraries")) continue + + const name: string | undefined = data.name + if (!name) continue + + const href: string | undefined = + data.url ?? + (data.github ? `https://github.com/${data.github}` : undefined) ?? + (data.npm ? `https://npmjs.com/package/${data.npm}` : undefined) + + entries.push({ name, href, group, tags }) + } + + const deduped = entries.filter( + (item, index, self) => + index === + self.findIndex(t => t.name.toLowerCase() === item.name.toLowerCase()), + ) + + return deduped +} + +function displayName(id: string) { + const key = id as keyof typeof slugMap + return slugMap[key] ?? id +} + +export async function CategoryToolsLibrariesSection({ + category, +}: { + category: Topic +}) { + const libraries = await librariesPromise + const filtered = libraries.filter(item => item.tags.includes(category)) + + const sortedGroups = Array.from( + filtered.reduce>((acc, item) => { + const list = acc.get(item.group) ?? [] + list.push(item) + acc.set(item.group, list) + return acc + }, new Map()), + ) + .map(([group, items]) => ({ + id: group, + name: displayName(group), + items: items + .sort((a, b) => + a.name.localeCompare(b.name, "en", { sensitivity: "base" }), + ) + .slice(0, 20), + })) + .sort((a, b) => b.items.length - a.items.length) + + const grouped: GroupData[] = sortedGroups.map((group, index) => { + const nextLength = sortedGroups[index + 1]?.items.length ?? 0 + const columns = + nextLength > 0 && group.items.length >= nextLength * 1.9 ? 2 : 1 + const breakIndex = columns === 2 ? Math.ceil(group.items.length / 2) : 0 + return { ...group, columns, breakIndex } + }) + + if (grouped.length === 0) { + return null + } + + return ( +
+ +
+
+
+ + key tools & libraries + +

+ Build GraphQL with tools and libraries +

+

+ Explore language and platform tooling to ship production-ready + graphs. +

+
+ +
+ +
+ {distributeToColumns(grouped).map((column, colIndex) => ( +
+ {column.map(group => ( + + ))} +
+ ))} +
+
+
+ ) +} + +interface GroupData { + id: string + name: string + items: LibraryEntry[] + columns: 1 | 2 + breakIndex: number +} + +function distributeToColumns(groups: GroupData[]): [GroupData[], GroupData[]] { + const left: GroupData[] = [] + const right: GroupData[] = [] + + let leftHeight = 0 + let rightHeight = 0 + + for (const group of groups) { + const itemRows = + group.columns === 2 + ? Math.ceil(group.items.length / 2) + : group.items.length + const height = itemRows + 1 + if (leftHeight <= rightHeight) { + left.push(group) + leftHeight += height + } else { + right.push(group) + rightHeight += height + } + } + + return [left, right] +} + +function Group({ group }: { group: GroupData }) { + return ( +
+ + +
    + {group.items.map((item, i) => ( +
  • = group.breakIndex ? "1px" : "", + } + : {} + } + > + {item.href ? ( + + {item.name} + + ) : ( + + {item.name} + + )} +
  • + ))} +
+
+ ) +} + +function Stripes() { + return ( +
+ +
+ ) +} diff --git a/src/app/(main)/resources/[category]/page.tsx b/src/app/(main)/resources/[category]/page.tsx new file mode 100644 index 0000000000..894de2c57a --- /dev/null +++ b/src/app/(main)/resources/[category]/page.tsx @@ -0,0 +1,163 @@ +import { Metadata } from "next" +import { notFound } from "next/navigation" + +import { NavbarFixed } from "@/components/navbar/navbar-fixed" +import { getResourcesByTag } from "@/resources/data" +import { + Kind, + kinds, + topics, + type ResourceMetadata, + type Topic, +} from "@/resources/types" + +import { ResourcesHero } from "../resources-hero" +import { TocHeroContents } from "@/components/toc-hero" +import { BlogPostsSection } from "./blog-posts-section" +import { CategoryToolsLibrariesSection } from "./category-tools-libraries-section" +import { Breadcrumbs } from "@/_design-system/breadcrumbs" + +import { sectionKindNames, sectionId, texts } from "./texts" +import { CardsSection } from "./cards-section" + +interface PageParams { + category: string +} + +export async function generateStaticParams() { + return topics.map(category => ({ category })) +} + +export async function generateMetadata({ + params, +}: { + params: PageParams +}): Promise { + const category = params.category as Topic + if (!topics.includes(category)) return {} + + const title = `${texts[category].heading} Resources` + const description = texts[category].subtitle + + return { title, description } +} + +export default async function CategoryPage({ params }: { params: PageParams }) { + const category = params.category as Topic + if (!topics.includes(category)) return notFound() + + const resources = await getResourcesByTag(category) + const deduped = uniqueByTitle(resources) + const grouped = groupByKind(deduped) + + const activePath = [ + { + name: "Home", + route: "/", + }, + { + name: "Resource Hub", + route: "/resources", + }, + { + name: texts[category].heading, + route: `/resources/${category}`, + }, + ].map(item => ({ + ...item, + title: item.name, + type: "page", + children: [], + frontMatter: {}, + })) + + return ( +
+ + + + sectionLabel(section.kind))} + className="max-w-[528px]" + /> + + +
+ +
+ + {grouped.map(section => ( + + ))} +
+ ) +} + +function uniqueByTitle(resources: ResourceMetadata[]) { + const seen = new Set() + return resources.filter(resource => { + const key = resource.title.trim().toLowerCase() + if (seen.has(key)) return false + seen.add(key) + return true + }) +} + +function groupByKind(resources: ResourceMetadata[]) { + return kinds + .map(kind => ({ + kind, + resources: resources.filter( + resource => (resource.kind ?? getKindFromTags(resource)) === kind, + ), + })) + .filter(section => section.resources.length > 0) +} + +function getKindFromTags(resource: ResourceMetadata) { + return kinds.find(kind => resource.tags.includes(kind)) +} + +function sectionLabel(kind: Kind) { + return sectionKindNames[kind] ?? `${kind[0].toUpperCase()}${kind.slice(1)}` +} + +function CategorySection({ + section, + category, +}: { + section: { kind: Kind; resources: ResourceMetadata[] } + category: Topic +}) { + if (section.kind === "tools-and-libraries") { + return + } + + if (section.kind === "blog") { + const blogSection = texts[category].sections["blog-or-newsletter"] + return ( + ({ + href: resource.url, + title: resource.title, + author: resource.author ?? "GraphQL Community", + tags: resource.tags.filter(tag => tag !== "blog" && tag !== category), + }))} + /> + ) + } + + return +} diff --git a/src/app/(main)/resources/[category]/spritesheet/index.tsx b/src/app/(main)/resources/[category]/spritesheet/index.tsx new file mode 100644 index 0000000000..47eef9a0d1 --- /dev/null +++ b/src/app/(main)/resources/[category]/spritesheet/index.tsx @@ -0,0 +1,40 @@ +import type { SVGProps } from "react" + +import sheet from "./sheet.svg?resource" + +export type IconName = + | "ballerina" + | "c-net" + | "clojure" + | "elixir" + | "elm" + | "flutter" + | "go" + | "haskell" + | "java" + | "javascript" + | "julia" + | "multiplatform" + | "php" + | "python" + | "ruby" + | "rust" + | "scala" + | "swift" + +interface IconSpritesheetProps extends SVGProps { + sprite: IconName +} + +export function IconSpritesheet({ sprite, ...props }: IconSpritesheetProps) { + return ( + <> + + + + + ) +} diff --git a/src/app/(main)/resources/[category]/spritesheet/sheet.svg b/src/app/(main)/resources/[category]/spritesheet/sheet.svg new file mode 100644 index 0000000000..8c708ee752 --- /dev/null +++ b/src/app/(main)/resources/[category]/spritesheet/sheet.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/(main)/resources/[category]/texts.ts b/src/app/(main)/resources/[category]/texts.ts new file mode 100644 index 0000000000..b8ad42f0ed --- /dev/null +++ b/src/app/(main)/resources/[category]/texts.ts @@ -0,0 +1,155 @@ +import { Kind, Topic } from "@/resources/types" + +export const texts: { + [key in Topic]: { + heading: string + subtitle: string + sections: { + [key in Kind]?: { + heading: string + text: string + } + } + } +} = { + frontend: { + heading: "Frontend", + subtitle: "Learn how to use GraphQL on the frontend.", + sections: { + video: { + heading: "Master GraphQL on the frontend", + text: "Watch talks and tutorials from GraphQL Conf and community experts. See how teams integrate GraphQL on the frontend and learn from real-world case studies.", + }, + "tools-and-libraries": { + heading: "Frontend tools & libraries", + text: "Explore the most popular GraphQL client libraries and frameworks for frontend. These tools help you fetch and manage data with GraphQL.", + }, + "blog-or-newsletter": { + heading: "Insights for frontend devs", + text: "Stay up to date with insights from the GraphQL community.", + }, + }, + }, + backend: { + heading: "Backend", + subtitle: + "Build powerful GraphQL backends with the right tools, libraries and expert insights.", + sections: { + video: { + heading: "Master GraphQL on the backend", + text: "Discover videos and tutorials to help you build, deploy and scale your GraphQL backend.", + }, + "tools-and-libraries": { + heading: "Backend tools & libraries", + text: "Find the right GraphQL backend stack — from JavaScript to Rust and beyond.", + }, + "blog-or-newsletter": { + heading: "Build better GraphQL infrastructure", + text: "Dive into articles on server architecture, schema design and best practices for running GraphQL at scale.", + }, + }, + }, + federation: { + heading: "Federation", + subtitle: "Learn how to build and compose GraphQL graphs with federation.", + sections: { + video: { + heading: "Master GraphQL federation", + text: "Watch talks and tutorials from GraphQL Conf and community experts. See how teams build and compose GraphQL graphs with federation.", + }, + "tools-and-libraries": { + heading: "Tools & libraries for federated graphs", + text: "Run federated GraphQL graphs at scale with the right tools — from open-source routers to managed platforms.", + }, + "blog-or-newsletter": { + heading: "Latest updates on federation & composition", + text: "Read the latest announcements and technical deep dives.", + }, + }, + }, + ai: { + heading: "Artificial Intelligence", + subtitle: "Explore how to use GraphQL for AI systems.", + sections: { + "tools-and-libraries": { + heading: "GraphQL tools for AI", + text: "Discover the best tools for building AI systems with GraphQL.", + }, + "blog-or-newsletter": { + heading: "Latest insights on AI & GraphQL", + text: "Read the latest announcements and technical deep dives.", + }, + }, + }, + security: { + heading: "Security", + subtitle: "Learn how to secure your GraphQL APIs.", + sections: { + "tools-and-libraries": { + heading: "GraphQL security tools", + text: "Find resources to help secure GraphQL APIs across various languages and frameworks.", + }, + docs: { + heading: "Security in practice", + text: "Follow proven patterns to delegate authorization correctly and protect your GraphQL APIs from malicious operations.", + }, + }, + }, + monitoring: { + heading: "Monitoring", + subtitle: + "Stay ahead of issues by monitoring queries and watching error trends.", + sections: { + "tools-and-libraries": { + heading: "GraphQL monitoring tools", + text: "Connect GraphQL tracing and alerting systems to reduce blind spots in production.", + }, + docs: { + heading: "Monitoring in practice", + text: "Learn how to track the right signals in your GraphQL ecosystem — from latency and error rates to resolver-level performance.", + }, + "blog-or-newsletter": { + heading: "Latest insights on monitoring", + text: "Read the latest announcements and technical deep dives.", + }, + }, + }, + "schema-design": { + heading: "Schema Design", + subtitle: "Learn how to design and maintain GraphQL schemas.", + sections: {}, + }, + "api-platform-and-gateways": { + heading: "API Platform and Gateways", + subtitle: "Learn how to build and deploy API Gateways and Supergraphs.", + sections: {}, + }, + "developer-experience": { + heading: "Developer Experience", + subtitle: "Learn how to improve your developer experience.", + sections: {}, + }, + tools: { + heading: "Tools", + subtitle: "Discover the best tools for GraphQL development.", + sections: {}, + }, +} + +export const sectionKindNames: Record = { + video: "Featured videos", + blog: "Blog posts", + "tools-and-libraries": "Tools & Libraries", + guide: "Guides", + book: "Books", + "blog-or-newsletter": "Blogs & Newsletters", + docs: "Documentation", +} + +export function slugify(name: string): string { + return name.toLowerCase().replace(/ & /g, "-and-").replace(/ /g, "-") +} + +export const sectionIds: Record = Object.fromEntries( + Object.entries(sectionKindNames).map(([kind, name]) => [kind, slugify(name)]), +) as Record diff --git a/src/app/(main)/resources/assets/ai.svg b/src/app/(main)/resources/assets/ai.svg new file mode 100644 index 0000000000..1a74a15a0e --- /dev/null +++ b/src/app/(main)/resources/assets/ai.svg @@ -0,0 +1,18 @@ + + + + + diff --git a/src/app/(main)/resources/assets/archive.svg b/src/app/(main)/resources/assets/archive.svg new file mode 100644 index 0000000000..bb98c0c8e5 --- /dev/null +++ b/src/app/(main)/resources/assets/archive.svg @@ -0,0 +1,14 @@ + + + + diff --git a/src/app/(main)/resources/assets/backend.svg b/src/app/(main)/resources/assets/backend.svg new file mode 100644 index 0000000000..2477c25f58 --- /dev/null +++ b/src/app/(main)/resources/assets/backend.svg @@ -0,0 +1,21 @@ + + + + + + + diff --git a/src/app/(main)/resources/assets/bookmark.svg b/src/app/(main)/resources/assets/bookmark.svg new file mode 100644 index 0000000000..422dc51a2a --- /dev/null +++ b/src/app/(main)/resources/assets/bookmark.svg @@ -0,0 +1,15 @@ + diff --git a/src/app/(main)/resources/assets/federation.svg b/src/app/(main)/resources/assets/federation.svg new file mode 100644 index 0000000000..e16b30d1a2 --- /dev/null +++ b/src/app/(main)/resources/assets/federation.svg @@ -0,0 +1,11 @@ + + + diff --git a/src/app/(main)/resources/assets/frontend.svg b/src/app/(main)/resources/assets/frontend.svg new file mode 100644 index 0000000000..135bf7899a --- /dev/null +++ b/src/app/(main)/resources/assets/frontend.svg @@ -0,0 +1,14 @@ + + + + diff --git a/src/app/(main)/resources/assets/monitoring.svg b/src/app/(main)/resources/assets/monitoring.svg new file mode 100644 index 0000000000..aa9ce67345 --- /dev/null +++ b/src/app/(main)/resources/assets/monitoring.svg @@ -0,0 +1,14 @@ + + + + diff --git a/src/app/(main)/resources/assets/newspaper.svg b/src/app/(main)/resources/assets/newspaper.svg new file mode 100644 index 0000000000..0bc736695f --- /dev/null +++ b/src/app/(main)/resources/assets/newspaper.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/src/app/(main)/resources/assets/security.svg b/src/app/(main)/resources/assets/security.svg new file mode 100644 index 0000000000..c999cc8256 --- /dev/null +++ b/src/app/(main)/resources/assets/security.svg @@ -0,0 +1,20 @@ + + + + + + diff --git a/src/app/(main)/resources/assets/tools.svg b/src/app/(main)/resources/assets/tools.svg new file mode 100644 index 0000000000..e22e782c3d --- /dev/null +++ b/src/app/(main)/resources/assets/tools.svg @@ -0,0 +1,20 @@ + + + + + + diff --git a/src/app/(main)/resources/assets/video-player.svg b/src/app/(main)/resources/assets/video-player.svg new file mode 100644 index 0000000000..6a01b6f929 --- /dev/null +++ b/src/app/(main)/resources/assets/video-player.svg @@ -0,0 +1,6 @@ + diff --git a/src/app/(main)/resources/assets/write-note.svg b/src/app/(main)/resources/assets/write-note.svg new file mode 100644 index 0000000000..6c75219572 --- /dev/null +++ b/src/app/(main)/resources/assets/write-note.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/src/app/(main)/resources/blog-category-links.tsx b/src/app/(main)/resources/blog-category-links.tsx new file mode 100644 index 0000000000..4ce389ef9c --- /dev/null +++ b/src/app/(main)/resources/blog-category-links.tsx @@ -0,0 +1,46 @@ +"use client" + +import Link from "next/link" +import { Collapsible } from "@base-ui-components/react/collapsible" + +import CaretDownIcon from "@/app/conf/_design-system/pixelarticons/caret-down.svg?svgr" +import { BlogTags } from "@/components/blog-page/blog-tags" +import { blogTagColors } from "@/components/blog-page/blog-tag-colors" + +const categories = Object.keys(blogTagColors) + +/** + * Shows tags on desktop and a collapsible on mobile. + */ +export function BlogCategoryLinks() { + return ( + <> + + + categories + + + + {categories.map(category => ( + + {category.replace(/-/g, " ")} + + ))} + + + +
+ + categories + +
+ +
+
+ + ) +} diff --git a/src/app/(main)/resources/blog-post-list-item.tsx b/src/app/(main)/resources/blog-post-list-item.tsx new file mode 100644 index 0000000000..9dcf236926 --- /dev/null +++ b/src/app/(main)/resources/blog-post-list-item.tsx @@ -0,0 +1,53 @@ +import Link from "next/link" + +import ArrowRightIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr" +import { BlogTags } from "@/components/blog-page/blog-tags" + +export interface BlogPostRowProps { + date: string + category: string + title: string + href: string + author: string +} + +export function BlogPostListItem({ + date, + category, + title, + href, + author, +}: BlogPostRowProps) { + const formattedDate = new Date(date) + .toLocaleDateString("en-GB", { + day: "2-digit", + month: "2-digit", + year: "numeric", + }) + .replaceAll("/", "-") + + return ( + +
+ +
+

+ {title} +

+
+
+ + + {author} + +
+ +
+ + ) +} diff --git a/src/app/(main)/resources/blog-section.tsx b/src/app/(main)/resources/blog-section.tsx new file mode 100644 index 0000000000..91a6546f9a --- /dev/null +++ b/src/app/(main)/resources/blog-section.tsx @@ -0,0 +1,103 @@ +import fs from "node:fs/promises" +import path from "node:path" +import Link from "next/link" +import grayMatter from "gray-matter" + +import { Button } from "@/app/conf/_design-system/button" +import PlayIcon from "@/app/conf/_design-system/pixelarticons/play.svg?svgr" + +import { blogTagColors } from "@/components/blog-page/blog-tag-colors" +import { BlogCategoryLinks } from "./blog-category-links" +import { BlogPostListItem } from "./blog-post-list-item" + +interface BlogFrontMatter { + title: string + tags?: string[] + byline: string + date: string | number | Date +} + +type BlogFrontMatterWithFile = BlogFrontMatter & { + fileName: string + date: Date +} + +let cachedBlogFrontMatters: BlogFrontMatterWithFile[] | null = null + +async function getBlogFrontMatters() { + if (cachedBlogFrontMatters) return cachedBlogFrontMatters + + const files = await fs.readdir("./src/pages/blog") + + const blogs = await Promise.all( + files + .filter(filename => /\.mdx?$/.test(filename)) + .map(async (filename: string) => { + const filePath = path.join("./src/pages/blog", filename) + const content = await fs.readFile(filePath, "utf8") + const { data } = grayMatter(content) + const frontMatter = data as BlogFrontMatter + + return { + ...frontMatter, + fileName: path.parse(filePath).name, + date: new Date(frontMatter.date), + } + }), + ) + + cachedBlogFrontMatters = blogs + .filter((blog): blog is BlogFrontMatterWithFile => + Boolean(blog.fileName && blog.title && blog.date), + ) + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) + + return cachedBlogFrontMatters +} + +export async function BlogSection() { + const blogs = await getBlogFrontMatters() + const blogPosts = Object.keys(blogTagColors) + .flatMap(tag => blogs.filter(blog => blog.tags?.includes(tag)).slice(0, 5)) + .reduce((unique, blog) => { + if (!unique.some(item => item.fileName === blog.fileName)) { + unique.push(blog) + } + return unique + }, []) + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) + .slice(0, 5) + + return ( +
+
+
+ + + Blog + +
+

The GraphQL Blog

+
+ + + +
    + {blogPosts.map(post => ( + + ))} +
+ +
+ +
+
+ ) +} diff --git a/src/app/(main)/resources/categories-section.tsx b/src/app/(main)/resources/categories-section.tsx new file mode 100644 index 0000000000..29ff4b4350 --- /dev/null +++ b/src/app/(main)/resources/categories-section.tsx @@ -0,0 +1,120 @@ +import Link from "next/link" + +import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr" + +import { Eyebrow } from "@/_design-system/eyebrow" +import { type Topic } from "@/resources/types" + +import FrontendIcon from "./assets/frontend.svg?svgr" +import BackendIcon from "./assets/backend.svg?svgr" +import FederationIcon from "./assets/federation.svg?svgr" +import SecurityIcon from "./assets/security.svg?svgr" +import AIIcon from "./assets/ai.svg?svgr" +import MonitoringIcon from "./assets/monitoring.svg?svgr" + +interface Category { + id: Topic + name: string + description: string + icon: React.ReactNode +} + +const categories: Category[] = [ + { + id: "frontend", + name: "Frontend", + description: + "Build better queries and optimize UI performance with the right client tools.", + icon: ( + + ), + }, + { + id: "backend", + name: "Backend", + description: + "From resolvers to execution — everything you need to run a GraphQL server in production.", + icon: ( + + ), + }, + { + id: "federation", + name: "Federation", + description: + "Design and manage distributed graphs that scale across teams and services.", + icon: ( + + ), + }, + { + id: "security", + name: "Security", + description: + "Secure your GraphQL API with query limits and schema protection.", + icon: ( + + ), + }, + { + id: "ai", + name: "AI", + description: + "Use GraphQL to power AI systems — patterns, tools and implementations.", + icon: ( + + ), + }, + { + id: "monitoring", + name: "Monitoring", + description: + "Track performance, usage and schema changes to keep your graph healthy.", + icon: ( + + ), + }, +] + +export function CategoriesSection() { + return ( +
+
+ Explore GraphQL by topic +
+

Choose a resource category

+

+ Dive into the topics most relevant to your work with GraphQL. Find + selected tools, videos and other resources. +

+
+
+ +
+ {categories.map(category => ( + + ))} +
+
+ ) +} + +function CategoryCard({ category }: { category: Category }) { + return ( + +
+ {category.icon} + +
+
+

{category.name}

+

+ {category.description} +

+
+ + ) +} diff --git a/src/app/(main)/resources/keep-learning.tsx b/src/app/(main)/resources/keep-learning.tsx new file mode 100644 index 0000000000..956c04edc2 --- /dev/null +++ b/src/app/(main)/resources/keep-learning.tsx @@ -0,0 +1,44 @@ +import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr" +import blurCorner from "./[category]/blur-corner.webp" +import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration" +import { Anchor } from "@/app/conf/_design-system/anchor" + +// "bg-[linear-gradient(90deg,hsl(var(--color-sec-lighter))_0_12px,hsl(var(--color-sec-light))_12px_24px)] dark:bg-[linear-gradient(90deg,hsl(var(--color-sec-dark)/0.22)_0_12px,hsl(var(--color-sec-base)/0.22)_12px_24px)]" + +export function KeepLearning({ + title, + href, + stripes, +}: { + title: string + href: string + stripes: string +}) { + return ( +
+

Keep Learning

+ + + Next + + +

{title}

+ +
+
+ ) +} + +function Stripes({ stripes }: { stripes: string }) { + return ( +
+ +
+ ) +} diff --git a/src/app/(main)/resources/page.tsx b/src/app/(main)/resources/page.tsx new file mode 100644 index 0000000000..8cb5aa3652 --- /dev/null +++ b/src/app/(main)/resources/page.tsx @@ -0,0 +1,47 @@ +import { NavbarFixed } from "@/components/navbar/navbar-fixed" +import { LookingForMore } from "@/components/looking-for-more" + +import { ResourcesHero } from "./resources-hero" +import { CategoriesSection } from "./categories-section" +import { ToolsLibrariesSection } from "./tools-libraries-section" +import { SpecificationSection } from "./specification-section" +import { BlogSection } from "./blog-section" +import { VideoResourcesSection } from "./video-resources-section" +import { ReadingResourcesSection } from "./reading-resources-section" + +export const metadata = { + title: "Resource Hub", + description: + "Explore curated GraphQL resources by topic. Find tools, videos, blog posts, and more to help you build with GraphQL.", +} + +export default function ResourcesPage() { + return ( +
+ + +
+ + + + +
+
+ +
+ +
+ +
+ ) +} diff --git a/src/app/(main)/resources/reading-resources-section.tsx b/src/app/(main)/resources/reading-resources-section.tsx new file mode 100644 index 0000000000..daf6d6227c --- /dev/null +++ b/src/app/(main)/resources/reading-resources-section.tsx @@ -0,0 +1,71 @@ +import Link from "next/link" + +import { Button } from "@/app/conf/_design-system/button" +import { Eyebrow } from "@/_design-system/eyebrow" +import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr" + +import NewspaperIcon from "./assets/newspaper.svg?svgr" +import WriteIcon from "./assets/write-note.svg?svgr" +import BookIcon from "./assets/bookmark.svg?svgr" + +export function ReadingResourcesSection() { + return ( +
+
+
+ reading resources library + +

+ Dive into GraphQL content +

+

+ Browse reading materials to learn best practices and stay up to date + with the ecosystem. +

+ +
+ +
+ } + label="Blogs and newsletters" + /> + } + label="Individual posts" + /> + } + label="Books" + /> +
+
+
+ ) +} + +function ReadingLink({ + href, + icon, + label, +}: { + href: string + icon: React.ReactNode + label: string +}) { + return ( + + {icon} + {label} + + + ) +} diff --git a/src/app/(main)/resources/reading/[subcategory]/page.tsx b/src/app/(main)/resources/reading/[subcategory]/page.tsx new file mode 100644 index 0000000000..eea54355fd --- /dev/null +++ b/src/app/(main)/resources/reading/[subcategory]/page.tsx @@ -0,0 +1,36 @@ +import { notFound } from "next/navigation" + +import { + ReadingLibraryPage, + readingMetadata, + subcategories, + type Subcategory, +} from "../reading-page" + +interface PageParams { + subcategory: string +} + +function isSubcategory(value: string): value is Subcategory { + return subcategories.includes(value as Subcategory) +} + +export function generateStaticParams() { + return subcategories.map(subcategory => ({ subcategory })) +} + +export function generateMetadata({ params }: { params: PageParams }) { + const subcategory = params.subcategory + if (!isSubcategory(subcategory)) return {} + return readingMetadata(subcategory) +} + +export default function ReadingSubcategoryPage({ + params, +}: { + params: PageParams +}) { + const subcategory = params.subcategory + if (!isSubcategory(subcategory)) return notFound() + return +} diff --git a/src/app/(main)/resources/reading/page.tsx b/src/app/(main)/resources/reading/page.tsx new file mode 100644 index 0000000000..a997ace6c3 --- /dev/null +++ b/src/app/(main)/resources/reading/page.tsx @@ -0,0 +1,7 @@ +import { ReadingLibraryPage, readingMetadata } from "./reading-page" + +export const metadata = readingMetadata("all") + +export default function ReadingPage() { + return +} diff --git a/src/app/(main)/resources/reading/reading-page.tsx b/src/app/(main)/resources/reading/reading-page.tsx new file mode 100644 index 0000000000..1618ed946b --- /dev/null +++ b/src/app/(main)/resources/reading/reading-page.tsx @@ -0,0 +1,200 @@ +import Link from "next/link" +import { NavbarFixed } from "@/components/navbar/navbar-fixed" +import { notFound } from "next/navigation" +import { Breadcrumbs } from "@/_design-system/breadcrumbs" +import { clsx } from "clsx" + +import { ResourcesHero } from "../resources-hero" +import { Eyebrow } from "@/_design-system/eyebrow" +import { ReadingResourcesCard } from "./reading-resources-card" +import { readResources } from "@/resources/data" +import { topics, type ResourceMetadata, type Topic } from "@/resources/types" + +export const subcategories = [ + "blogs-and-newsletters", + "individual-posts", + "books", +] as const + +export type Subcategory = (typeof subcategories)[number] + +type Variant = Subcategory | "all" + +const topicSet = new Set(topics) + +const tabs: { + label: string + href: string + variant: Variant + color: string +}[] = [ + { + label: "Blogs & newsletters", + href: "/resources/reading/blogs-and-newsletters", + variant: "blogs-and-newsletters", + color: "hsl(var(--color-pri-base))", + }, + { + label: "Individual posts", + href: "/resources/reading/individual-posts", + variant: "individual-posts", + color: "#FF8800", + }, + { + label: "Books", + href: "/resources/reading/books", + variant: "books", + color: "#00C6AC", + }, +] + +const variants: Record< + Variant, + { + title: string + description: string + eyebrow: string + filter: (resource: ResourceMetadata) => boolean + } +> = { + all: { + title: "Reading Resources Library", + description: + "Browse reading materials to learn best practices and stay up to date with the ecosystem.", + eyebrow: "Reading resources", + filter: resource => + resource.tags.some( + tag => + tag === "blog-or-newsletter" || tag === "guide" || tag === "book", + ), + }, + "blogs-and-newsletters": { + title: "Blogs & Newsletters", + description: + "Popular sources to learn and keep track of the GraphQL ecosystem.", + eyebrow: "Stay informed", + filter: resource => resource.tags.includes("blog-or-newsletter"), + }, + "individual-posts": { + title: "Individual Posts", + description: "Notable posts from the community.", + eyebrow: "Deep dives", + filter: resource => + resource.tags.some( + tag => + tag === "guide" || + (tag === "blog" && !resource.url.startsWith("/blog")), + ), + }, + books: { + title: "Books", + description: + "Books to help you level up your GraphQL knowledge and practice.", + eyebrow: "Read and learn", + filter: resource => resource.tags.includes("book"), + }, +} + +export function readingMetadata(variant: Variant) { + const config = variants[variant] + if (!config) return {} + return { + title: config.title, + description: config.description, + } +} + +function hasTopicTag(resource: ResourceMetadata) { + return resource.tags.some(tag => topicSet.has(tag as Topic)) +} + +function uniqueByTitle(resources: ResourceMetadata[]) { + const seen = new Set() + return resources.filter(resource => { + const key = resource.title.trim().toLowerCase() + if (seen.has(key)) return false + seen.add(key) + return true + }) +} + +export async function ReadingLibraryPage({ variant }: { variant: Variant }) { + const config = variants[variant] + if (!config) return notFound() + + const resources = await readResources() + const filtered = uniqueByTitle(resources) + .filter(config.filter) + .sort((a, b) => + a.title.localeCompare(b.title, "en", { sensitivity: "base" }), + ) + + const activePath = [ + { + name: "Home", + route: "/", + }, + { + name: "Resource Hub", + route: "/resources", + }, + { + name: "Reading Resources Library", + route: "/resources/reading", + }, + ].map(item => ({ + ...item, + title: item.name, + type: "page", + children: [], + frontMatter: {}, + })) + + return ( +
+ + +
+ + +
    + {filtered.map(resource => ( +
  • + +
  • + ))} +
+
+
+ ) +} diff --git a/src/app/(main)/resources/reading/reading-resources-card.tsx b/src/app/(main)/resources/reading/reading-resources-card.tsx new file mode 100644 index 0000000000..4c5d5a1daf --- /dev/null +++ b/src/app/(main)/resources/reading/reading-resources-card.tsx @@ -0,0 +1,66 @@ +import type { ComponentType, SVGProps } from "react" + +import BookmarkIcon from "../assets/bookmark.svg?svgr" +import InfoIcon from "@/app/conf/_design-system/pixelarticons/info.svg?svgr" +import NotesIcon from "@/app/conf/_design-system/pixelarticons/notes.svg?svgr" +import { ResourceHubCard } from "../resource-hub-card" +import type { ResourceMetadata } from "@/resources/types" + +type CornerIcon = ComponentType> + +type ReadingKind = "book" | "blog-or-newsletter" | "blog" | "guide" + +const readingKindConfig: Record< + ReadingKind, + { label: string; color: string; Icon: CornerIcon } +> = { + book: { label: "books", color: "#00C6AC", Icon: BookmarkIcon }, + "blog-or-newsletter": { + label: "blogs & newsletters", + color: "hsl(var(--color-pri-base))", + Icon: NotesIcon, + }, + blog: { label: "blog posts", color: "#FF8800", Icon: NotesIcon }, + guide: { label: "guides", color: "#FF8800", Icon: InfoIcon }, +} + +function pickReadingKind(resource: ResourceMetadata): ReadingKind | undefined { + const candidates: ReadingKind[] = [ + "book", + "blog-or-newsletter", + "guide", + "blog", + ] + return candidates.find((candidate): candidate is ReadingKind => + resource.tags.includes(candidate), + ) +} + +export function ReadingResourcesCard({ + resource, +}: { + resource: ResourceMetadata +}) { + const kind = pickReadingKind(resource) + const config = kind ? readingKindConfig[kind] : undefined + + return ( + + + + ) : null + } + /> + ) +} diff --git a/src/app/(main)/resources/resource-hub-card.tsx b/src/app/(main)/resources/resource-hub-card.tsx new file mode 100644 index 0000000000..d6eb536e9c --- /dev/null +++ b/src/app/(main)/resources/resource-hub-card.tsx @@ -0,0 +1,118 @@ +import Link from "next/link" +import type { ReactNode } from "react" +import { clsx } from "clsx" + +import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr" +import ClockIcon from "@/app/conf/_design-system/pixelarticons/clock.svg?svgr" +import { Tag } from "@/app/conf/_design-system/tag" +import { Topic } from "@/resources/types" +import { blogTagColors } from "@/components/blog-page/blog-tag-colors" + +export const tagColors: Record = { + ...blogTagColors, + backend: "#36C1A0", + "defies-categorization": "#894545", + "developer-experience": "#6fc9af", + "federation-and-composite-schemas": "#cbc749", + "graphql-clients": "#ca78fc", + "graphql-in-production": "#e4981f", + "graphql-security": "#CC6BB0", + "graphql-spec": "#6B73CC", + scaling: "#8D8D8D", + frontend: "violet", + documentation: "salmon", + "schema-evolution": "thistle", + security: "cornflowerblue", + "case-studies": "#B36B00", + "federation-and-distributed-systems": "#FF8F70", + federation: "#5C7CFA", + tools: "#0FA3B1", + "api-platform-and-gateways": "#F4B400", + "schema-design": "#7E57C2", + ai: "#FF5FA2", + monitoring: "#2D9CDB", + "blog-or-newsletter": "#FF8800", + book: "#00C6AC", + guide: "#FF8800", +} + +interface ResourceHubCardProps { + href: string + title: string + author?: string + duration?: string + authorPlacement?: "body" | "footer" + tags?: string[] + className?: string + icon?: ReactNode +} + +export function ResourceHubCard({ + href, + title, + author, + duration, + authorPlacement = "footer", + tags, + className, + icon, +}: ResourceHubCardProps) { + return ( + +
+
+ {tags?.length ? ( +
+ {tags.map(tag => ( + + {formatTag(tag)} + + ))} +
+ ) : null} +
+ {authorPlacement === "body" && author ? ( + {author} + ) : null} +

+ {title} +

+
+
+ {icon ? ( +
{icon}
+ ) : null} +
+
+
+ {authorPlacement === "footer" && author ? ( + {author} + ) : null} + {duration ? ( + + + {duration} + + ) : null} +
+
+ +
+
+ + ) +} + +function formatTag(tag: string) { + if (tag === "blog-or-newsletter") return "Blogs & Newsletters" + if (tag === "book") return "Books" + if (tag === "guide") return "Individual posts" + + return tag.replaceAll("-", " ") +} diff --git a/src/app/(main)/resources/resources-hero.tsx b/src/app/(main)/resources/resources-hero.tsx new file mode 100644 index 0000000000..29ac61e94c --- /dev/null +++ b/src/app/(main)/resources/resources-hero.tsx @@ -0,0 +1,28 @@ +import { LearnHeroStripes } from "@/components/learn-aggregator/learn-hero-stripes" + +export function ResourcesHero({ + heading, + text, + children, +}: { + heading: string + text: string + children?: React.ReactNode +}) { + return ( +
+ +
+

{heading}

+

{text}

+ {children} +
+
+ ) +} diff --git a/src/app/(main)/resources/specification-section.tsx b/src/app/(main)/resources/specification-section.tsx new file mode 100644 index 0000000000..45fc32edfe --- /dev/null +++ b/src/app/(main)/resources/specification-section.tsx @@ -0,0 +1,54 @@ +import { Button } from "@/app/conf/_design-system/button" +import ArchiveIcon from "./assets/archive.svg?svgr" +import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration" +import { Eyebrow } from "@/_design-system/eyebrow" + +export function SpecificationSection() { + return ( +
+
+ + specification + +
+
+ +
+ +
+

+ Read the GraphQL Specification +

+

+ The specification defines the core structure of GraphQL. It's the + foundation for building consistent servers, clients, and tools. + Read the spec to better understand how GraphQL works. +

+ +
+
+
+
+ ) +} + +function Stripes() { + return ( +
+ +
+ ) +} diff --git a/src/app/(main)/resources/subtitles.ts b/src/app/(main)/resources/subtitles.ts new file mode 100644 index 0000000000..f546c51b99 --- /dev/null +++ b/src/app/(main)/resources/subtitles.ts @@ -0,0 +1,30 @@ +import { type Topic } from "@/resources/types" + +export const categoryNames: Record = { + frontend: "Frontend", + backend: "Backend", + federation: "Federation", + security: "Security", + ai: "AI", + monitoring: "Monitoring", + "api-platform-and-gateways": "API Platform and Gateways", + "developer-experience": "Developer Experience", + "schema-design": "Schema Design", + tools: "Tools", +} + +export const categorySubtitles: Record = { + frontend: "Learn how to integrate GraphQL on the frontend.", + backend: + "Build powerful GraphQL backends with the right tools, libraries and expert insights.", + federation: "Learn how to build and compose GraphQL graphs with federation.", + ai: "Explore how to use GraphQL for AI systems.", + security: "Learn how to secure your GraphQL APIs.", + monitoring: + "Stay ahead of performance issues by monitoring queries and watching error trends.", + "api-platform-and-gateways": + "Learn how to build and deploy API Gateways and Supergraphs.", + "developer-experience": "Learn how to improve your developer experience.", + "schema-design": "Learn how to design and maintain GraphQL schemas.", + tools: "Discover the best tools for GraphQL development.", +} diff --git a/src/app/(main)/resources/tools-libraries-section.tsx b/src/app/(main)/resources/tools-libraries-section.tsx new file mode 100644 index 0000000000..4c69c86096 --- /dev/null +++ b/src/app/(main)/resources/tools-libraries-section.tsx @@ -0,0 +1,34 @@ +import { Button } from "@/app/conf/_design-system/button" +import ToolsIcon from "./assets/tools.svg?svgr" +import { Eyebrow } from "@/_design-system/eyebrow" + +export function ToolsLibrariesSection() { + return ( +
+
+ + tools and libraries + + +
+
+

+ Build GraphQL with Tools & Libraries +

+

+ Explore solutions and docs for building with GraphQL — across + languages, frameworks, and platforms. +

+ +
+ +
+ +
+
+
+
+ ) +} diff --git a/src/app/(main)/resources/video-resources-section.tsx b/src/app/(main)/resources/video-resources-section.tsx new file mode 100644 index 0000000000..51122b0a5a --- /dev/null +++ b/src/app/(main)/resources/video-resources-section.tsx @@ -0,0 +1,31 @@ +import { Button } from "@/app/conf/_design-system/button" +import { Eyebrow } from "@/_design-system/eyebrow" +import VideoPlayerIcon from "./assets/video-player.svg?svgr" + +export function VideoResourcesSection() { + return ( +
+ video resources library + +
+
+ +
+ +
+

+ Watch and learn GraphQL +

+

+ Build your skills with featured videos from GraphQL Conf, global + meetups, and expert engineers — keeping you up to date in a + fast-moving ecosystem. +

+ +
+
+
+ ) +} diff --git a/src/app/(main)/resources/video/page.tsx b/src/app/(main)/resources/video/page.tsx new file mode 100644 index 0000000000..2274aaedde --- /dev/null +++ b/src/app/(main)/resources/video/page.tsx @@ -0,0 +1,64 @@ +import { NavbarFixed } from "@/components/navbar/navbar-fixed" +import { getResourcesByTag } from "@/resources/data" +import VideoPlayerIcon from "../assets/video-player.svg?svgr" +import { ResourcesHero } from "../resources-hero" +import { Eyebrow } from "@/_design-system/eyebrow" +import { VideoLibrary } from "./video-library" + +export const metadata = { + title: "Video Resources Library", + description: + "Expand your expertise with curated videos to help you master GraphQL and stay up to date with its evolving ecosystem.", +} + +export default async function VideoResourcesPage() { + const resources = await getResourcesByTag("video") + const seen = new Set() + const unique = resources.filter(resource => { + const key = resource.title.trim().toLowerCase() + if (seen.has(key)) return false + seen.add(key) + return true + }) + unique.sort((a, b) => + a.title.localeCompare(b.title, "en", { sensitivity: "base" }), + ) + + return ( +
+ + + +
+
+
+
+ +
+
+ Watch and learn +

+ Curated GraphQL talks +

+

+ Browse recorded sessions from GraphQL Conf alongside community + talks and workshops. +

+
+
+ + + {unique.length} videos + +
+ +
+ +
+
+
+ ) +} diff --git a/src/app/(main)/resources/video/video-library.tsx b/src/app/(main)/resources/video/video-library.tsx new file mode 100644 index 0000000000..4fc1bc8b5d --- /dev/null +++ b/src/app/(main)/resources/video/video-library.tsx @@ -0,0 +1,236 @@ +"use client" + +import { useMemo, useState } from "react" +import { + Combobox, + ComboboxButton, + ComboboxInput, + ComboboxOption, + ComboboxOptions, +} from "@headlessui/react" + +import { Button } from "@/app/conf/_design-system/button" +import { Tag } from "@/app/conf/_design-system/tag" +import CaretDownIcon from "@/app/conf/_design-system/pixelarticons/caret-down.svg?svgr" +import { CheckboxIcon } from "@/app/conf/_design-system/pixelarticons/checkbox-icon" +import { ResourceHubCard } from "../resource-hub-card" +import { type ResourceMetadata, topics, type Topic } from "@/resources/types" + +interface VideoLibraryProps { + resources: ResourceMetadata[] +} + +type SortOrder = "az" | "za" + +export function VideoLibrary({ resources }: VideoLibraryProps) { + const [selectedTopics, setSelectedTopics] = useState([]) + const [sortOrder, setSortOrder] = useState("az") + + const topicOptions = useMemo(() => { + const allowed = new Set(topics) + const found = new Set() + resources.forEach(resource => { + resource.tags.forEach(tag => { + if (allowed.has(tag as Topic)) { + found.add(tag) + } + }) + }) + return Array.from(found).sort((a, b) => + a.localeCompare(b, "en", { sensitivity: "base" }), + ) + }, [resources]) + + const filtered = useMemo(() => { + const filteredByTopic = + selectedTopics.length === 0 + ? resources + : resources.filter(resource => + resource.tags.some(tag => selectedTopics.includes(tag)), + ) + + const sorted = [...filteredByTopic].sort((a, b) => + sortOrder === "az" + ? a.title.localeCompare(b.title, "en", { sensitivity: "base" }) + : b.title.localeCompare(a.title, "en", { sensitivity: "base" }), + ) + + return sorted + }, [resources, selectedTopics, sortOrder]) + + return ( +
+
+
+ + +
+ + Sort + +
+ + +
+
+
+ +
+ + {filtered.length} videos + + {selectedTopics.length > 0 && ( + + )} +
+
+ +
    + {filtered.map(resource => { + return ( +
  • + +
  • + ) + })} +
+
+ ) +} + +function TopicsCombobox({ + label, + options, + value, + onChange, +}: { + label: string + options: string[] + value: string[] + onChange: (next: string[]) => void +}) { + const [query, setQuery] = useState("") + + const filteredOptions = + query === "" + ? options + : options.filter(option => + option.toLowerCase().includes(query.toLowerCase()), + ) + + return ( + +
+ + {label} + + + +
+ + {filteredOptions.map(option => ( + + {({ active, selected }) => ( + + )} + + ))} + +
+
+
+ ) +} + +function TopicOption({ + active, + selected, + option, +}: { + active: boolean + selected: boolean + option: string +}) { + return ( +
+ +
+ + {option} + +
+
+ ) +} diff --git a/src/app/conf/_design-system/pixelarticons/caret-down.svg b/src/app/conf/_design-system/pixelarticons/caret-down.svg index 428cdbbaaf..deb68afbea 100644 --- a/src/app/conf/_design-system/pixelarticons/caret-down.svg +++ b/src/app/conf/_design-system/pixelarticons/caret-down.svg @@ -3,12 +3,11 @@ width="25" height="24" viewBox="0 0 25 24" - fill="none" + fill="#A0A88A" > diff --git a/src/app/conf/_design-system/stripes-decoration.tsx b/src/app/conf/_design-system/stripes-decoration.tsx index 8d03825612..f895baeada 100644 --- a/src/app/conf/_design-system/stripes-decoration.tsx +++ b/src/app/conf/_design-system/stripes-decoration.tsx @@ -1,21 +1,27 @@ import clsx from "clsx" const maskEven = - "repeating-linear-gradient(to right, transparent, transparent var(--stripe-width), black var(--stripe-width), black calc(var(--stripe-width) * 2))" + "repeating-linear-gradient(var(--angle), transparent, transparent var(--stripe-width), black var(--stripe-width), black calc(var(--stripe-width) * 2))" const maskOdd = - "repeating-linear-gradient(to right, black, black var(--stripe-width), transparent var(--stripe-width), transparent calc(var(--stripe-width) * 2))" + "repeating-linear-gradient(var(--angle), black, black var(--stripe-width), transparent var(--stripe-width), transparent calc(var(--stripe-width) * 2))" export interface StripesDecorationProps { evenClassName?: string oddClassName?: string stripeWidth?: string + /** + * @default "90deg" to right, + * use "-90deg" to align with right side of the container + */ + angle?: string } export function StripesDecoration({ stripeWidth = "12px", evenClassName, oddClassName, + angle = "90deg", }: StripesDecorationProps) { return ( <> @@ -23,7 +29,10 @@ export function StripesDecoration({
= { spec: "#00C6AC", grants: "#84BD01", "in-the-news": "#3F3A3D", + "developer-experience": "#6fc9af", } diff --git a/src/components/blog-page/blog-tags.tsx b/src/components/blog-page/blog-tags.tsx index 6c1a509535..618aa8282f 100644 --- a/src/components/blog-page/blog-tags.tsx +++ b/src/components/blog-page/blog-tags.tsx @@ -35,7 +35,7 @@ export function BlogTags({ key={tag} // yes, the page lives under /tags, not /blog/tags href={`/tags/${tag}`} - className="-m-1 flex p-1 ring-inset ring-neu-400 transition-opacity duration-75 hover:ring focus:!outline-offset-0 dark:ring-neu-50 [:has(>:hover)>&:not(:hover)]:opacity-70" + className="gql-focus-visible -m-1 flex p-1 ring-inset ring-neu-400 transition-opacity duration-75 hover:ring focus:!outline-offset-0 dark:ring-neu-50 [:has(>:hover)>&:not(:hover)]:opacity-70" > {tagElement} diff --git a/src/components/blog-page/featured-blog-posts.tsx b/src/components/blog-page/featured-blog-posts.tsx index fc6c7f08cc..d017dfd39b 100644 --- a/src/components/blog-page/featured-blog-posts.tsx +++ b/src/components/blog-page/featured-blog-posts.tsx @@ -47,7 +47,7 @@ export function FeaturedBlogPosts({ byline={firstFeatured.frontMatter.byline} date={firstFeatured.frontMatter.date} /> - +
diff --git a/src/components/blog-page/index.tsx b/src/components/blog-page/index.tsx index 0d597d3b7e..4f11d56fe2 100644 --- a/src/components/blog-page/index.tsx +++ b/src/components/blog-page/index.tsx @@ -5,9 +5,10 @@ import { Tag } from "@/app/conf/_design-system/tag" import { arrowsMoveSideways } from "@/app/conf/_design-system/utils/arrows-move-sideways" import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration" +import { LookingForMore } from "@/components/looking-for-more" + import { blogTagColors } from "./blog-tag-colors" import { BlogCard } from "./blog-card" -import { LookingForMore } from "./looking-for-more" import { BlogMdxContent } from "./mdx-types" import { FeaturedBlogPosts } from "./featured-blog-posts" @@ -51,14 +52,14 @@ export function BlogPage({
-
+

{currentTag || "All Posts"}

Categories

-
    +
      {Object.entries(tags) .sort((a, b) => b[1] - a[1]) .map(([tag, count], i) => ( @@ -78,7 +79,7 @@ export function BlogPage({
-
+
{blogs.map( page => (!currentTag || page.frontMatter.tags.includes(currentTag)) && ( @@ -87,7 +88,13 @@ export function BlogPage({ )}
- +
) diff --git a/src/components/blog-page/looking-for-more.tsx b/src/components/blog-page/looking-for-more.tsx deleted file mode 100644 index 2441ade4e7..0000000000 --- a/src/components/blog-page/looking-for-more.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Anchor } from "@/app/conf/_design-system/anchor" - -import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr" - -export function LookingForMore() { - return ( -
-
-
-

Looking for more?

-

- Explore learning guides and best practices — or browse for tools, - libraries and other resources. -

-
-
- - Learn - - - - Resources - - -
-
-
- ) -} diff --git a/src/components/learn-aggregator/learn-hero-stripes.tsx b/src/components/learn-aggregator/learn-hero-stripes.tsx index 29d2a4e37f..b7692117a3 100644 --- a/src/components/learn-aggregator/learn-hero-stripes.tsx +++ b/src/components/learn-aggregator/learn-hero-stripes.tsx @@ -1,13 +1,24 @@ +import { clsx } from "clsx" import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration" import blurBean from "./learn-blur-bean.webp" -export function LearnHeroStripes() { +export function LearnHeroStripes({ + className, + style, + ...rest +}: { + className?: string + style?: React.CSSProperties +}) { return (
) { - return ( -
-
-
-

Looking for more?

-

- Learning is just the beginning. Discover tools and other resources — - or connect with the GraphQL community around the world. -

-
- -
-
- ) -} diff --git a/src/components/looking-for-more.tsx b/src/components/looking-for-more.tsx new file mode 100644 index 0000000000..db174b300d --- /dev/null +++ b/src/components/looking-for-more.tsx @@ -0,0 +1,46 @@ +import { clsx } from "clsx" +import { Anchor } from "@/app/conf/_design-system/anchor" +import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr" + +type LinkItem = { href: string; label: string } + +interface LookingForMoreProps extends React.HTMLAttributes { + description: string + links: [LinkItem, LinkItem] +} + +export function LookingForMore({ + description, + links, + ...props +}: LookingForMoreProps) { + return ( +
+
+
+

Looking for more?

+

{description}

+
+
+ + {links[0].label} + + + + {links[1].label} + + +
+
+
+ ) +} diff --git a/src/components/navbar/navbar.tsx b/src/components/navbar/navbar.tsx index d0e9d8fcbd..fd4ebb7cf0 100644 --- a/src/components/navbar/navbar.tsx +++ b/src/components/navbar/navbar.tsx @@ -49,7 +49,7 @@ function NavbarMenu({ key={key} href={item.href || routes[key]?.route} target={item.newWindow ? "_blank" : undefined} - className="block py-3.5 pl-2 pr-9" + className="block py-3.5 pl-2 pr-9 underline-offset-2 hover:underline focus-visible:underline" closeOnClick render={( props: React.ComponentPropsWithoutRef<"a">, @@ -58,7 +58,7 @@ function NavbarMenu({ diff --git a/src/components/toc-hero/toc-hero-contents.tsx b/src/components/toc-hero/toc-hero-contents.tsx index 84f060efb3..bbdd207a85 100644 --- a/src/components/toc-hero/toc-hero-contents.tsx +++ b/src/components/toc-hero/toc-hero-contents.tsx @@ -1,6 +1,7 @@ import { clsx } from "clsx" import { ChevronRight } from "@/app/conf/_design-system/pixelarticons/chevron-right" +import { slugify } from "@/app/(main)/resources/[category]/texts" export interface TocHeroContentsProps extends React.HTMLAttributes { @@ -34,7 +35,7 @@ export function TocHeroContents({ typeof section === "string" ? { name: section, - href: `#${section.toLowerCase().replace(/ /g, "-")}`, + href: `#${slugify(section)}`, } : section diff --git a/src/env.d.ts b/src/env.d.ts index a5309effaf..dced5d24c3 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -17,6 +17,11 @@ declare module "*?raw" { export default content } +declare module "*.svg?resource" { + const url: string + export default url +} + // We're importing a transitive dependency to avoid a bug. declare module "next-themes" { export function ThemeProvider(props: { diff --git a/src/pages/community/resources/blogs-and-newsletters.mdx b/src/pages/community/resources/blogs-and-newsletters.mdx index 46331f19e2..1927c4b4ff 100644 --- a/src/pages/community/resources/blogs-and-newsletters.mdx +++ b/src/pages/community/resources/blogs-and-newsletters.mdx @@ -35,7 +35,6 @@ Here are a list of notable blog posts to help you better understand GraphQL: - [Your First GraphQL Server](https://medium.com/the-graphqlhub/your-first-graphql-server-3c766ab4f0a2#.ovn0y19k4) - Clay Allsopp - [Tutorial: Kick start a JS API with Apollo-server, Dataloader and Knex](https://bamtech.gitbook.io/dev-standards/backend/graphql-js/getting-started-with-apollo-server-dataloader-knex.mo) - Thomas Pucci - [Tutorial: How to Build a GraphQL Server](https://medium.com/apollo-stack/tutorial-building-a-graphql-server-cddaa023c035#.bu6sdnst4) - Jonas Helfer -- [Designing Powerful APIs with GraphQL Query Parameters](https://www.graph.cool/docs/tutorials/designing-powerful-apis-with-graphql-query-parameters-aing7uech3/) - Johannes Schickling - [GraphQL and the amazing Apollo Client](https://medium.com/google-developer-experts/graphql-and-the-amazing-apollo-client-fe57e162a70c) - Gerard Sans - [GraphQL Server Basics (Part I): The Schema](https://www.prisma.io/blog/graphql-server-basics-the-schema-ac5e2950214e) - Nikolas Burk - [GraphQL Server Basics (Part II): The Network Layer](https://www.prisma.io/blog/graphql-server-basics-the-network-layer-51d97d21861) - Nikolas Burk diff --git a/src/pages/learn/index.mdx b/src/pages/learn/index.mdx index c26ab6d58e..a121f17c81 100644 --- a/src/pages/learn/index.mdx +++ b/src/pages/learn/index.mdx @@ -11,7 +11,7 @@ import { LearnHeroStripes } from '../../components/learn-aggregator/learn-hero-s import { pagesBySection } from '../../components/learn-aggregator/learn-pages' import { CommonQuestionsSection } from '../../components/learn-aggregator/common-questions' import { TrainingCoursesSection } from '../../components/learn-aggregator/training-courses' -import { LookingForMore } from "../../components/learn-aggregator/looking-for-more" +import { LookingForMore } from "../../components/looking-for-more" - + + diff --git a/src/resources/data.ts b/src/resources/data.ts new file mode 100644 index 0000000000..db37f4d1b8 --- /dev/null +++ b/src/resources/data.ts @@ -0,0 +1,127 @@ +import path from "node:path" +import { glob } from "node:fs/promises" +import { readFile } from "node:fs/promises" +import { cache } from "react" +import matter from "gray-matter" + +import { ResourceMetadata, type ResourceTag, topics } from "./types" + +const dataGlob = "src/resources/data/*.json" +const codeGlob = "src/code/**/*.md" +const blogGlob = "src/pages/blog/**/*.mdx" + +export const readResources = cache(async () => { + const resources: ResourceMetadata[] = [] + + for await (const file of glob(dataGlob)) { + const raw = await readFile(file, "utf8") + const parsed = JSON.parse(raw) + resources.push(ResourceMetadata.assert(parsed)) + } + + for await (const file of glob(blogGlob)) { + const raw = await readFile(file, "utf8") + const { data, content } = matter(raw) + + const title: string | undefined = data.title + if (!title) continue + + const slug = blogSlug(file) + + const bodyLines = content + .split(/\r?\n/) + .map(line => line.trim()) + .map(line => + line + .replace(/!\[[^\]]*\]\([^)]+\)/g, "") + .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") + .replace(/`+/g, "") + .replace(/[*_~]+/g, "") + .replace(/^#+\s*/, "") + .replace(/<\/?[^>]+>/g, "") + .trim(), + ) + .filter(line => line.length > 0) + + const excerpt = bodyLines.slice(0, 2).join(" ") + + const description = + typeof data.description === "string" && data.description.length > 0 + ? data.description + : excerpt || undefined + + const topicsFromFrontmatter: ResourceTag[] = Array.isArray(data.topics) + ? data.topics.filter((tag): tag is ResourceTag => + topics.includes(tag as (typeof topics)[number]), + ) + : [] + + const topicTagsFromTags: ResourceTag[] = Array.isArray(data.tags) + ? data.tags.filter((tag): tag is ResourceTag => + topics.includes(tag as (typeof topics)[number]), + ) + : [] + + const tags: ResourceTag[] = [ + "blog", + ...topicsFromFrontmatter, + ...topicTagsFromTags, + ] + + resources.push( + ResourceMetadata.assert({ + title, + url: slug, + author: data.byline, + description, + kind: "blog", + tags, + }), + ) + } + + for await (const file of glob(codeGlob)) { + const raw = await readFile(file, "utf8") + const { data } = matter(raw) + const tags: ResourceMetadata["tags"] = Array.isArray(data.tags) + ? data.tags + : [] + + if (!tags.includes("tools-and-libraries")) { + tags.push("tools-and-libraries") + } + + const url: string | undefined = + data.url ?? + (data.github ? `https://github.com/${data.github}` : undefined) ?? + (data.npm ? `https://npmjs.com/package/${data.npm}` : undefined) + + const title = data.name ?? path.parse(file).name + + resources.push( + ResourceMetadata.assert({ + title, + url, + description: data.description, + tags, + }), + ) + } + + return resources +}) + +export async function getResourcesByTag(tag: ResourceTag) { + const resources = await readResources() + return resources.filter(resource => resource.tags.includes(tag)) +} + +function blogSlug(file: string) { + const relative = path.relative("src/pages", file) + const withoutExt = relative.replace(/\.mdx$/, "") + const normalized = withoutExt.split(path.sep).join("/") + const clean = normalized.endsWith("/index") + ? normalized.slice(0, -"index".length - 1) + : normalized + return `/${clean}` +} diff --git a/src/resources/data/a-beginner-s-guide-to-graphql.json b/src/resources/data/a-beginner-s-guide-to-graphql.json new file mode 100644 index 0000000000..2552c02efd --- /dev/null +++ b/src/resources/data/a-beginner-s-guide-to-graphql.json @@ -0,0 +1,6 @@ +{ + "title": "A Beginner’s Guide to GraphQL", + "url": "https://www.freecodecamp.org/news/a-beginners-guide-to-graphql-86f849ce1bec/", + "author": "Leonardo Maldonado", + "tags": ["blog"] +} diff --git a/src/resources/data/a-graphql-framework-for-non-js-servers-syrus-akbary.json b/src/resources/data/a-graphql-framework-for-non-js-servers-syrus-akbary.json new file mode 100644 index 0000000000..f5eeb1b82b --- /dev/null +++ b/src/resources/data/a-graphql-framework-for-non-js-servers-syrus-akbary.json @@ -0,0 +1,6 @@ +{ + "title": "A GraphQL Framework for Non-JS Servers", + "author": "Syrus Akbary", + "url": "https://www.youtube.com/watch?v=RNoyPSrQyPs", + "tags": ["video", "backend"] +} diff --git a/src/resources/data/a-graphql-framework-for-non-js-servers.json b/src/resources/data/a-graphql-framework-for-non-js-servers.json new file mode 100644 index 0000000000..b6f39fbb01 --- /dev/null +++ b/src/resources/data/a-graphql-framework-for-non-js-servers.json @@ -0,0 +1,6 @@ +{ + "title": "A GraphQL Framework for Non-JS Servers", + "author": "Syrus Akbary", + "url": "https://www.youtube.com/watch?v=RNoyPSrQyPs", + "tags": ["video"] +} diff --git a/src/resources/data/a-postgresql-backed-graphql-baas.json b/src/resources/data/a-postgresql-backed-graphql-baas.json new file mode 100644 index 0000000000..8db0be4cd4 --- /dev/null +++ b/src/resources/data/a-postgresql-backed-graphql-baas.json @@ -0,0 +1,6 @@ +{ + "title": "A PostgreSQL backed GraphQL BaaS", + "author": "Tanmai Gopal", + "url": "https://www.youtube.com/watch?v=neIZcc8y3B0", + "tags": ["video", "backend"] +} diff --git a/src/resources/data/all-talks-from-graphql-europe.json b/src/resources/data/all-talks-from-graphql-europe.json new file mode 100644 index 0000000000..fbadd36641 --- /dev/null +++ b/src/resources/data/all-talks-from-graphql-europe.json @@ -0,0 +1,5 @@ +{ + "title": "All Talks from GraphQL Europe", + "url": "https://www.youtube.com/playlist?list=PLn2e1F9Rfr6n_WFm9fPE-_wYPrYvSTySt", + "tags": ["video"] +} diff --git a/src/resources/data/apollo-client-put-graphql-data-in-your-ui.json b/src/resources/data/apollo-client-put-graphql-data-in-your-ui.json new file mode 100644 index 0000000000..080bf02325 --- /dev/null +++ b/src/resources/data/apollo-client-put-graphql-data-in-your-ui.json @@ -0,0 +1,6 @@ +{ + "title": "Apollo Client: Put GraphQL Data in Your UI", + "author": "Sashko Stubailo", + "url": "https://www.youtube.com/watch?v=u1E0CbGeICo", + "tags": ["video", "frontend"] +} diff --git a/src/resources/data/apollo-odyssey.json b/src/resources/data/apollo-odyssey.json new file mode 100644 index 0000000000..aa11a294bd --- /dev/null +++ b/src/resources/data/apollo-odyssey.json @@ -0,0 +1,5 @@ +{ + "title": "Apollo Odyssey", + "url": "https://apollographql.com/tutorials", + "tags": ["guide"] +} diff --git a/src/resources/data/apollo-s-blog.json b/src/resources/data/apollo-s-blog.json new file mode 100644 index 0000000000..e2b5979181 --- /dev/null +++ b/src/resources/data/apollo-s-blog.json @@ -0,0 +1,5 @@ +{ + "title": "Apollo's Blog", + "url": "https://apollographql.com/blog", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/architecture-of-a-high-performance-graphql-to-sql-engine.json b/src/resources/data/architecture-of-a-high-performance-graphql-to-sql-engine.json new file mode 100644 index 0000000000..f107cf7911 --- /dev/null +++ b/src/resources/data/architecture-of-a-high-performance-graphql-to-sql-engine.json @@ -0,0 +1,6 @@ +{ + "title": "Architecture of a high performance GraphQL to SQL engine", + "url": "https://blog.hasura.io/architecture-of-a-high-performance-graphql-to-sql-server-58d9944b8a87", + "author": "Sandip Devarkonda", + "tags": ["blog"] +} diff --git a/src/resources/data/awesome-graphql.json b/src/resources/data/awesome-graphql.json new file mode 100644 index 0000000000..0befab9e45 --- /dev/null +++ b/src/resources/data/awesome-graphql.json @@ -0,0 +1,5 @@ +{ + "title": "awesome-graphql", + "url": "https://github.com/chentsulin/awesome-graphql", + "tags": ["guide"] +} diff --git a/src/resources/data/brand-guidelines.json b/src/resources/data/brand-guidelines.json new file mode 100644 index 0000000000..f32f7804f8 --- /dev/null +++ b/src/resources/data/brand-guidelines.json @@ -0,0 +1,5 @@ +{ + "title": "brand guidelines", + "url": "/brand", + "tags": ["guide"] +} diff --git a/src/resources/data/build-a-full-graphql-backend-in-under-5-minutes-michael-paris.json b/src/resources/data/build-a-full-graphql-backend-in-under-5-minutes-michael-paris.json new file mode 100644 index 0000000000..4fc31c17c3 --- /dev/null +++ b/src/resources/data/build-a-full-graphql-backend-in-under-5-minutes-michael-paris.json @@ -0,0 +1,6 @@ +{ + "title": "Build a Full GraphQL Backend in Under 5 Minutes", + "author": "Michael Paris", + "url": "https://www.youtube.com/watch?v=bJ8pnYd6jPQ", + "tags": ["video", "backend"] +} diff --git a/src/resources/data/build-a-full-graphql-backend-in-under-5-minutes.json b/src/resources/data/build-a-full-graphql-backend-in-under-5-minutes.json new file mode 100644 index 0000000000..ce02a20361 --- /dev/null +++ b/src/resources/data/build-a-full-graphql-backend-in-under-5-minutes.json @@ -0,0 +1,6 @@ +{ + "title": "Build a Full GraphQL Backend in Under 5 Minutes", + "author": "Michael Paris", + "url": "https://www.youtube.com/watch?v=bJ8pnYd6jPQ", + "tags": ["video"] +} diff --git a/src/resources/data/build-a-graphql-backend-with-the-serverless-framework-ryan-brown.json b/src/resources/data/build-a-graphql-backend-with-the-serverless-framework-ryan-brown.json new file mode 100644 index 0000000000..d744bed512 --- /dev/null +++ b/src/resources/data/build-a-graphql-backend-with-the-serverless-framework-ryan-brown.json @@ -0,0 +1,6 @@ +{ + "title": "Build a GraphQL Backend with the Serverless Framework", + "author": "Ryan Brown", + "url": "https://acloud.guru/learn/serverless-with-graphql", + "tags": ["video", "backend"] +} diff --git a/src/resources/data/build-a-graphql-backend-with-the-serverless-framework.json b/src/resources/data/build-a-graphql-backend-with-the-serverless-framework.json new file mode 100644 index 0000000000..cb2b1a0b88 --- /dev/null +++ b/src/resources/data/build-a-graphql-backend-with-the-serverless-framework.json @@ -0,0 +1,6 @@ +{ + "title": "Build a GraphQL Backend with the Serverless Framework", + "author": "Ryan Brown", + "url": "https://acloud.guru/learn/serverless-with-graphql", + "tags": ["video"] +} diff --git a/src/resources/data/build-a-graphql-server-for-node-js-using-postgresql-mysql-lee-benson.json b/src/resources/data/build-a-graphql-server-for-node-js-using-postgresql-mysql-lee-benson.json new file mode 100644 index 0000000000..499b02f370 --- /dev/null +++ b/src/resources/data/build-a-graphql-server-for-node-js-using-postgresql-mysql-lee-benson.json @@ -0,0 +1,6 @@ +{ + "title": "Build a GraphQL server for Node.js, using PostgreSQL/MySQL", + "author": "Lee Benson", + "url": "https://www.youtube.com/watch?v=DNPVqK_woRQ", + "tags": ["video", "backend"] +} diff --git a/src/resources/data/build-a-graphql-server-for-node-js-using-postgresql-mysql.json b/src/resources/data/build-a-graphql-server-for-node-js-using-postgresql-mysql.json new file mode 100644 index 0000000000..86b3417360 --- /dev/null +++ b/src/resources/data/build-a-graphql-server-for-node-js-using-postgresql-mysql.json @@ -0,0 +1,6 @@ +{ + "title": "Build a GraphQL server for Node.js, using PostgreSQL/MySQL", + "author": "Lee Benson", + "url": "https://www.youtube.com/watch?v=DNPVqK_woRQ", + "tags": ["video"] +} diff --git a/src/resources/data/building-native-mobile-apps-with-graphql-martjin-walraven-react-europe-2016.json b/src/resources/data/building-native-mobile-apps-with-graphql-martjin-walraven-react-europe-2016.json new file mode 100644 index 0000000000..4c2f0ea1e0 --- /dev/null +++ b/src/resources/data/building-native-mobile-apps-with-graphql-martjin-walraven-react-europe-2016.json @@ -0,0 +1,6 @@ +{ + "title": "Building Native Mobile Apps with GraphQL", + "author": "Martjin Walraven", + "url": "https://www.youtube.com/watch?v=z5rz3saDPJ8", + "tags": ["video", "frontend"] +} diff --git a/src/resources/data/building-native-mobile-apps-with-graphql.json b/src/resources/data/building-native-mobile-apps-with-graphql.json new file mode 100644 index 0000000000..810cb2a13b --- /dev/null +++ b/src/resources/data/building-native-mobile-apps-with-graphql.json @@ -0,0 +1,6 @@ +{ + "title": "Building Native Mobile Apps with GraphQL", + "author": "Martjin Walraven", + "url": "https://www.youtube.com/watch?v=z5rz3saDPJ8", + "tags": ["video"] +} diff --git a/src/resources/data/building-the-f8-app-using-graphql-relay.json b/src/resources/data/building-the-f8-app-using-graphql-relay.json new file mode 100644 index 0000000000..3c36df9045 --- /dev/null +++ b/src/resources/data/building-the-f8-app-using-graphql-relay.json @@ -0,0 +1,5 @@ +{ + "title": "Building the f8 App: Using GraphQL & Relay", + "url": "http://makeitopen.com/docs/en/1-A2-relay.html", + "tags": ["blog"] +} diff --git a/src/resources/data/chillicream-s-blog.json b/src/resources/data/chillicream-s-blog.json new file mode 100644 index 0000000000..68a2657d38 --- /dev/null +++ b/src/resources/data/chillicream-s-blog.json @@ -0,0 +1,5 @@ +{ + "title": "ChilliCream's Blog", + "url": "https://chillicream.com/blog", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/community-events-section.json b/src/resources/data/community-events-section.json new file mode 100644 index 0000000000..2c3af619b8 --- /dev/null +++ b/src/resources/data/community-events-section.json @@ -0,0 +1,5 @@ +{ + "title": "community events section", + "url": "/community/upcoming-events/#meetups", + "tags": ["guide"] +} diff --git a/src/resources/data/craft-graphql-apis-in-elixir-with-absinthe.json b/src/resources/data/craft-graphql-apis-in-elixir-with-absinthe.json new file mode 100644 index 0000000000..b8c5cf6344 --- /dev/null +++ b/src/resources/data/craft-graphql-apis-in-elixir-with-absinthe.json @@ -0,0 +1,6 @@ +{ + "title": "Craft GraphQL APIs in Elixir with Absinthe", + "url": "https://pragprog.com/titles/wwgraphql/craft-graphql-apis-in-elixir-with-absinthe/", + "author": "Bruce Williams & Ben Wilson", + "tags": ["book"] +} diff --git a/src/resources/data/designing-powerful-apis-with-graphql-query-parameters.json b/src/resources/data/designing-powerful-apis-with-graphql-query-parameters.json new file mode 100644 index 0000000000..d3f99c4a56 --- /dev/null +++ b/src/resources/data/designing-powerful-apis-with-graphql-query-parameters.json @@ -0,0 +1,5 @@ +{ + "title": "Designing Powerful APIs with GraphQL Query Parameters", + "url": "https://www.graph.cool/docs/tutorials/designing-powerful-apis-with-graphql-query-parameters-aing7uech3/", + "tags": ["blog"] +} diff --git a/src/resources/data/dev-to-graphql-tag.json b/src/resources/data/dev-to-graphql-tag.json new file mode 100644 index 0000000000..7ce2241769 --- /dev/null +++ b/src/resources/data/dev-to-graphql-tag.json @@ -0,0 +1,5 @@ +{ + "title": "DEV.to GraphQL tag", + "url": "https://dev.to/t/graphql", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/development-of-real-time-apps-with-graphql-node-js-vince-ning-michael-paris-sf-node-meetup-february-2017.json b/src/resources/data/development-of-real-time-apps-with-graphql-node-js-vince-ning-michael-paris-sf-node-meetup-february-2017.json new file mode 100644 index 0000000000..94c31bff6d --- /dev/null +++ b/src/resources/data/development-of-real-time-apps-with-graphql-node-js-vince-ning-michael-paris-sf-node-meetup-february-2017.json @@ -0,0 +1,6 @@ +{ + "title": "Development of real-time apps with GraphQL Node.js", + "author": "Vince Ning & Michael Paris", + "url": "https://youtu.be/yh_A6CEqsSM", + "tags": ["video", "backend"] +} diff --git a/src/resources/data/development-of-real-time-apps-with-graphql-node-js.json b/src/resources/data/development-of-real-time-apps-with-graphql-node-js.json new file mode 100644 index 0000000000..2d94848143 --- /dev/null +++ b/src/resources/data/development-of-real-time-apps-with-graphql-node-js.json @@ -0,0 +1,6 @@ +{ + "title": "Development of real-time apps with GraphQL Node.js", + "author": "Vince Ning & Michael Paris", + "url": "https://youtu.be/yh_A6CEqsSM", + "tags": ["video"] +} diff --git a/src/resources/data/escape-security-blog.json b/src/resources/data/escape-security-blog.json new file mode 100644 index 0000000000..a0190d21a3 --- /dev/null +++ b/src/resources/data/escape-security-blog.json @@ -0,0 +1,5 @@ +{ + "title": "Escape Security Blog", + "url": "https://escape.tech/blog", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/exploring-graphql.json b/src/resources/data/exploring-graphql.json new file mode 100644 index 0000000000..611d7a37dc --- /dev/null +++ b/src/resources/data/exploring-graphql.json @@ -0,0 +1,6 @@ +{ + "title": "Exploring GraphQL", + "url": "https://youtube.com/watch?v=WQLzZf34FJ8", + "author": "Lee Byron", + "tags": ["video"] +} diff --git a/src/resources/data/from-rest-to-graphql.json b/src/resources/data/from-rest-to-graphql.json new file mode 100644 index 0000000000..b672e423aa --- /dev/null +++ b/src/resources/data/from-rest-to-graphql.json @@ -0,0 +1,6 @@ +{ + "title": "From REST to GraphQL", + "url": "https://0x2a.sh/from-rest-to-graphql-b4e95e94c26b#.tag7nzkrb", + "author": "Garen J. Torikian", + "tags": ["blog"] +} diff --git a/src/resources/data/from-zero-to-graphql-in-30-minutes.json b/src/resources/data/from-zero-to-graphql-in-30-minutes.json new file mode 100644 index 0000000000..b7897ec52b --- /dev/null +++ b/src/resources/data/from-zero-to-graphql-in-30-minutes.json @@ -0,0 +1,6 @@ +{ + "title": "From Zero to GraphQL in 30 Minutes", + "url": "https://youtube.com/watch?v=UBGzsb2UkeY", + "author": "Steven Luscher", + "tags": ["video"] +} diff --git a/src/resources/data/fullstack-graphql.json b/src/resources/data/fullstack-graphql.json new file mode 100644 index 0000000000..0f163bf5ec --- /dev/null +++ b/src/resources/data/fullstack-graphql.json @@ -0,0 +1,6 @@ +{ + "title": "Fullstack GraphQL", + "url": "https://www.graphqladmin.com/books/fullstack-graphql", + "author": "Julian Mayorga", + "tags": ["book"] +} diff --git a/src/resources/data/graphql-and-the-amazing-apollo-client.json b/src/resources/data/graphql-and-the-amazing-apollo-client.json new file mode 100644 index 0000000000..e74ff64ec1 --- /dev/null +++ b/src/resources/data/graphql-and-the-amazing-apollo-client.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL and the amazing Apollo Client", + "url": "https://medium.com/google-developer-experts/graphql-and-the-amazing-apollo-client-fe57e162a70c", + "author": "Gerard Sans", + "tags": ["blog"] +} diff --git a/src/resources/data/graphql-apis.json b/src/resources/data/graphql-apis.json new file mode 100644 index 0000000000..6c7365fdae --- /dev/null +++ b/src/resources/data/graphql-apis.json @@ -0,0 +1,5 @@ +{ + "title": "graphql-apis", + "url": "https://github.com/APIs-guru/graphql-apis", + "tags": ["guide"] +} diff --git a/src/resources/data/graphql-at-facebook.json b/src/resources/data/graphql-at-facebook.json new file mode 100644 index 0000000000..8f5a73d96d --- /dev/null +++ b/src/resources/data/graphql-at-facebook.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL at Facebook", + "url": "https://youtube.com/watch?v=etax3aEe2dA", + "author": "Dan Schafer", + "tags": ["video"] +} diff --git a/src/resources/data/graphql-best-practices-hands-on-experience-with-schema-design-security-and-error-handling-for-developers.json b/src/resources/data/graphql-best-practices-hands-on-experience-with-schema-design-security-and-error-handling-for-developers.json new file mode 100644 index 0000000000..c5fba326c9 --- /dev/null +++ b/src/resources/data/graphql-best-practices-hands-on-experience-with-schema-design-security-and-error-handling-for-developers.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL Best Practices: Hands-on experience with schema design, security, and error handling for developers", + "url": "https://www.amazon.com/dp/B0D9H7MJQV", + "author": "Marc-André Giroux & Apoorva Pandey", + "tags": ["book"] +} diff --git a/src/resources/data/graphql-code-of-conduct.json b/src/resources/data/graphql-code-of-conduct.json new file mode 100644 index 0000000000..64d708b1d2 --- /dev/null +++ b/src/resources/data/graphql-code-of-conduct.json @@ -0,0 +1,5 @@ +{ + "title": "GraphQL Code of Conduct", + "url": "/codeofconduct/", + "tags": ["guide"] +} diff --git a/src/resources/data/graphql-concepts-visualized.json b/src/resources/data/graphql-concepts-visualized.json new file mode 100644 index 0000000000..0e8e235f89 --- /dev/null +++ b/src/resources/data/graphql-concepts-visualized.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL Concepts Visualized", + "url": "https://medium.com/apollo-stack/the-concepts-of-graphql-bc68bd819be3#.hfczgtdsj", + "author": "Dhaivat Pandya", + "tags": ["blog"] +} diff --git a/src/resources/data/graphql-editor-blog.json b/src/resources/data/graphql-editor-blog.json new file mode 100644 index 0000000000..e178be9bcc --- /dev/null +++ b/src/resources/data/graphql-editor-blog.json @@ -0,0 +1,5 @@ +{ + "title": "GraphQL Editor Blog", + "url": "https://blog.graphqleditor.com", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/graphql-explained.json b/src/resources/data/graphql-explained.json new file mode 100644 index 0000000000..8f969374d4 --- /dev/null +++ b/src/resources/data/graphql-explained.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL Explained", + "url": "https://medium.com/apollo-stack/graphql-explained-5844742f195e#.zdykxos6i", + "author": "JH", + "tags": ["blog"] +} diff --git a/src/resources/data/graphql-from-zero-to-scala.json b/src/resources/data/graphql-from-zero-to-scala.json new file mode 100644 index 0000000000..f3a10cfedc --- /dev/null +++ b/src/resources/data/graphql-from-zero-to-scala.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL: From Zero to Scala", + "author": "Jérémie Astori", + "url": "https://www.youtube.com/watch?v=6ttypoLyRaU", + "tags": ["video", "backend"] +} diff --git a/src/resources/data/graphql-future.json b/src/resources/data/graphql-future.json new file mode 100644 index 0000000000..7b789c35be --- /dev/null +++ b/src/resources/data/graphql-future.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL Future", + "url": "https://youtube.com/watch?v=ViXL0YQnioU", + "author": "Lee Byron", + "tags": ["video"] +} diff --git a/src/resources/data/graphql-in-native-applications.json b/src/resources/data/graphql-in-native-applications.json new file mode 100644 index 0000000000..8da2e8cc2c --- /dev/null +++ b/src/resources/data/graphql-in-native-applications.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL in native applications", + "author": "Igor Canadi & Alex Langenfeld", + "url": "https://atscaleconference.com/videos/graphql-in-native-applications-at-scale/", + "tags": ["video", "frontend"] +} diff --git a/src/resources/data/graphql-in-production-backend-as-a-service-michael-paris-vince-ning-graphql-in-production-meetup-sf-august-2016.json b/src/resources/data/graphql-in-production-backend-as-a-service-michael-paris-vince-ning-graphql-in-production-meetup-sf-august-2016.json new file mode 100644 index 0000000000..8d1469e85e --- /dev/null +++ b/src/resources/data/graphql-in-production-backend-as-a-service-michael-paris-vince-ning-graphql-in-production-meetup-sf-august-2016.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL in Production: Backend as a Service", + "author": "Michael Paris & Vince Ning", + "url": "https://www.youtube.com/watch?v=U2NKoStGBvE", + "tags": ["video", "backend"] +} diff --git a/src/resources/data/graphql-in-production-backend-as-a-service.json b/src/resources/data/graphql-in-production-backend-as-a-service.json new file mode 100644 index 0000000000..05dbe33642 --- /dev/null +++ b/src/resources/data/graphql-in-production-backend-as-a-service.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL in Production: Backend as a Service", + "author": "Michael Paris & Vince Ning", + "url": "https://www.youtube.com/watch?v=U2NKoStGBvE", + "tags": ["video"] +} diff --git a/src/resources/data/graphql-js-tutorial.json b/src/resources/data/graphql-js-tutorial.json new file mode 100644 index 0000000000..8ad1d6af05 --- /dev/null +++ b/src/resources/data/graphql-js-tutorial.json @@ -0,0 +1,5 @@ +{ + "title": "GraphQL-JS tutorial", + "url": "/graphql-js", + "tags": ["guide"] +} diff --git a/src/resources/data/graphql-screencasts.json b/src/resources/data/graphql-screencasts.json new file mode 100644 index 0000000000..7f21b01ad3 --- /dev/null +++ b/src/resources/data/graphql-screencasts.json @@ -0,0 +1,5 @@ +{ + "title": "GraphQL Screencasts", + "url": "https://graphql.wtf", + "tags": ["guide"] +} diff --git a/src/resources/data/graphql-server-tutorial-for-node-js-with-sql-mongodb-and-rest-jonas-helfer.json b/src/resources/data/graphql-server-tutorial-for-node-js-with-sql-mongodb-and-rest-jonas-helfer.json new file mode 100644 index 0000000000..6a120eaf18 --- /dev/null +++ b/src/resources/data/graphql-server-tutorial-for-node-js-with-sql-mongodb-and-rest-jonas-helfer.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL server tutorial for Node.js with SQL, MongoDB and REST", + "author": "Jonas Helfer", + "url": "https://www.youtube.com/watch?v=PHabPhgRUuU", + "tags": ["video", "backend"] +} diff --git a/src/resources/data/graphql-server-tutorial-for-node-js-with-sql-mongodb-and-rest.json b/src/resources/data/graphql-server-tutorial-for-node-js-with-sql-mongodb-and-rest.json new file mode 100644 index 0000000000..c67dd937a2 --- /dev/null +++ b/src/resources/data/graphql-server-tutorial-for-node-js-with-sql-mongodb-and-rest.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL server tutorial for Node.js with SQL, MongoDB and REST", + "author": "Jonas Helfer", + "url": "https://www.youtube.com/watch?v=PHabPhgRUuU", + "tags": ["video"] +} diff --git a/src/resources/data/graphql-servers.json b/src/resources/data/graphql-servers.json new file mode 100644 index 0000000000..c5f8fa7806 --- /dev/null +++ b/src/resources/data/graphql-servers.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL Servers", + "author": "Nick Schrock", + "url": "https://www.youtube.com/watch?v=KOudxKJXsjc", + "tags": ["video", "backend"] +} diff --git a/src/resources/data/graphql-source-code-overview.json b/src/resources/data/graphql-source-code-overview.json new file mode 100644 index 0000000000..fdc3c0a830 --- /dev/null +++ b/src/resources/data/graphql-source-code-overview.json @@ -0,0 +1,6 @@ +{ + "title": "GraphQL Source Code Overview", + "author": "Lee Byron", + "url": "https://youtube.com/watch?v=IqtYr6RX32Q", + "tags": ["video", "backend"] +} diff --git a/src/resources/data/graphql-tutorials.json b/src/resources/data/graphql-tutorials.json new file mode 100644 index 0000000000..994d2ff091 --- /dev/null +++ b/src/resources/data/graphql-tutorials.json @@ -0,0 +1,5 @@ +{ + "title": "GraphQL Tutorials", + "url": "https://hasura.io/learn", + "tags": ["guide"] +} diff --git a/src/resources/data/graphql-weekly.json b/src/resources/data/graphql-weekly.json new file mode 100644 index 0000000000..2d90509692 --- /dev/null +++ b/src/resources/data/graphql-weekly.json @@ -0,0 +1,5 @@ +{ + "title": "GraphQL Weekly", + "url": "https://graphqlweekly.com", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/graphql-wtf-episodes-feed.json b/src/resources/data/graphql-wtf-episodes-feed.json new file mode 100644 index 0000000000..f59eceabdb --- /dev/null +++ b/src/resources/data/graphql-wtf-episodes-feed.json @@ -0,0 +1,5 @@ +{ + "title": "GraphQL WTF Episodes Feed", + "url": "https://graphql.wtf", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/hands-on-full-stack-web-development-with-graphql-and-react.json b/src/resources/data/hands-on-full-stack-web-development-with-graphql-and-react.json new file mode 100644 index 0000000000..57c9172b6b --- /dev/null +++ b/src/resources/data/hands-on-full-stack-web-development-with-graphql-and-react.json @@ -0,0 +1,6 @@ +{ + "title": "Hands-on Full-Stack Web Development with GraphQL and React", + "url": "https://www.packtpub.com/en-us/product/hands-on-full-stack-web-development-with-graphql-and-react-9781789135763", + "author": "Sebastian Grebe", + "tags": ["guide"] +} diff --git a/src/resources/data/hands-on-graphql-for-better-restful-web-services-video-by-ashwin-hegde.json b/src/resources/data/hands-on-graphql-for-better-restful-web-services-video-by-ashwin-hegde.json new file mode 100644 index 0000000000..99619acd0f --- /dev/null +++ b/src/resources/data/hands-on-graphql-for-better-restful-web-services-video-by-ashwin-hegde.json @@ -0,0 +1,6 @@ +{ + "title": "Hands-on GraphQL for Better RESTful Web Services (Video)", + "author": "Ashwin Hegde", + "url": "https://www.packtpub.com/application-development/hands-graphql-better-restful-web-services-video", + "tags": ["video", "federation", "backend"] +} diff --git a/src/resources/data/hands-on-graphql-for-better-restful-web-services-video.json b/src/resources/data/hands-on-graphql-for-better-restful-web-services-video.json new file mode 100644 index 0000000000..4cd82b919f --- /dev/null +++ b/src/resources/data/hands-on-graphql-for-better-restful-web-services-video.json @@ -0,0 +1,6 @@ +{ + "title": "Hands-on GraphQL for Better RESTful Web Services (Video)", + "author": "Ashwin Hegde", + "url": "https://www.packtpub.com/application-development/hands-graphql-better-restful-web-services-video", + "tags": ["video"] +} diff --git a/src/resources/data/hasura-s-blog.json b/src/resources/data/hasura-s-blog.json new file mode 100644 index 0000000000..7addc43662 --- /dev/null +++ b/src/resources/data/hasura-s-blog.json @@ -0,0 +1,5 @@ +{ + "title": "Hasura's Blog", + "url": "https://hasura.io/blog", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/inigo-s-security-blog.json b/src/resources/data/inigo-s-security-blog.json new file mode 100644 index 0000000000..ef9f0f1c82 --- /dev/null +++ b/src/resources/data/inigo-s-security-blog.json @@ -0,0 +1,5 @@ +{ + "title": "Inigo's Security Blog", + "url": "https://inigo.io/blog", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/learning-graphql-and-relay.json b/src/resources/data/learning-graphql-and-relay.json new file mode 100644 index 0000000000..a1f831ede2 --- /dev/null +++ b/src/resources/data/learning-graphql-and-relay.json @@ -0,0 +1,6 @@ +{ + "title": "Learning GraphQL and Relay", + "url": "https://www.amazon.com/Learning-GraphQL-Relay-Samer-Buna/dp/1786465752", + "author": "Samer Buna", + "tags": ["guide"] +} diff --git a/src/resources/data/learning-graphql-with-react-and-relay.json b/src/resources/data/learning-graphql-with-react-and-relay.json new file mode 100644 index 0000000000..103f4f0cdd --- /dev/null +++ b/src/resources/data/learning-graphql-with-react-and-relay.json @@ -0,0 +1,6 @@ +{ + "title": "Learning GraphQL with React and Relay", + "author": "Divyendu Singh", + "url": "https://www.packtpub.com/application-development/learning-graphql-react-and-relay-video", + "tags": ["video", "frontend"] +} diff --git a/src/resources/data/learning-graphql.json b/src/resources/data/learning-graphql.json new file mode 100644 index 0000000000..4287e2bdc7 --- /dev/null +++ b/src/resources/data/learning-graphql.json @@ -0,0 +1,6 @@ +{ + "title": "Learning GraphQL", + "url": "https://www.amazon.com/Learning-GraphQL-Declarative-Fetching-Modern/dp/1492030716/", + "author": "Eve Porcello & Alex Banks", + "tags": ["guide"] +} diff --git a/src/resources/data/modernize-your-angular-app-with-graphql.json b/src/resources/data/modernize-your-angular-app-with-graphql.json new file mode 100644 index 0000000000..7f6364cb55 --- /dev/null +++ b/src/resources/data/modernize-your-angular-app-with-graphql.json @@ -0,0 +1,6 @@ +{ + "title": "Modernize Your Angular App with GraphQL", + "author": "Uri Goldshtein", + "url": "https://www.youtube.com/watch?v=E8feZBidZcs", + "tags": ["video", "frontend"] +} diff --git a/src/resources/data/official-graphql-blog.json b/src/resources/data/official-graphql-blog.json new file mode 100644 index 0000000000..63976c18d7 --- /dev/null +++ b/src/resources/data/official-graphql-blog.json @@ -0,0 +1,5 @@ +{ + "title": "Official GraphQL Blog", + "url": "https://graphql.org/blog", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/production-ready-graphql.json b/src/resources/data/production-ready-graphql.json new file mode 100644 index 0000000000..48022997b1 --- /dev/null +++ b/src/resources/data/production-ready-graphql.json @@ -0,0 +1,6 @@ +{ + "title": "Production Ready GraphQL", + "url": "https://book.productionreadygraphql.com/", + "author": "Marc-Andre Giroux", + "tags": ["guide"] +} diff --git a/src/resources/data/relay-2-simpler-faster-and-more-predictable-greg-hurrell.json b/src/resources/data/relay-2-simpler-faster-and-more-predictable-greg-hurrell.json new file mode 100644 index 0000000000..5af112c51d --- /dev/null +++ b/src/resources/data/relay-2-simpler-faster-and-more-predictable-greg-hurrell.json @@ -0,0 +1,6 @@ +{ + "title": "Relay 2 - simpler, faster, and more predictable", + "author": "Greg Hurrell", + "url": "https://www.youtube.com/watch?v=OEfUBN9dAI8", + "tags": ["video", "frontend"] +} diff --git a/src/resources/data/relay-2-simpler-faster-and-more-predictable.json b/src/resources/data/relay-2-simpler-faster-and-more-predictable.json new file mode 100644 index 0000000000..7bfb22a4b4 --- /dev/null +++ b/src/resources/data/relay-2-simpler-faster-and-more-predictable.json @@ -0,0 +1,6 @@ +{ + "title": "Relay 2 - simpler, faster, and more predictable", + "author": "Greg Hurrell", + "url": "https://www.youtube.com/watch?v=OEfUBN9dAI8", + "tags": ["video"] +} diff --git a/src/resources/data/relicensing-the-graphql-specification.json b/src/resources/data/relicensing-the-graphql-specification.json new file mode 100644 index 0000000000..334cfdf4c0 --- /dev/null +++ b/src/resources/data/relicensing-the-graphql-specification.json @@ -0,0 +1,6 @@ +{ + "title": "Relicensing the GraphQL specification", + "url": "https://medium.com/@leeb/relicensing-the-graphql-specification-e7d07a52301b", + "author": "Lee Byron", + "tags": ["blog"] +} diff --git a/src/resources/data/stepzen-s-blog.json b/src/resources/data/stepzen-s-blog.json new file mode 100644 index 0000000000..208aca0ebb --- /dev/null +++ b/src/resources/data/stepzen-s-blog.json @@ -0,0 +1,5 @@ +{ + "title": "StepZen's Blog", + "url": "https://stepzen.com/blog", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/the-community-discord-channel.json b/src/resources/data/the-community-discord-channel.json new file mode 100644 index 0000000000..e694c5020e --- /dev/null +++ b/src/resources/data/the-community-discord-channel.json @@ -0,0 +1,5 @@ +{ + "title": "the community Discord channel", + "url": "/community/#official-channels", + "tags": ["guide"] +} diff --git a/src/resources/data/the-graphql-guide.json b/src/resources/data/the-graphql-guide.json new file mode 100644 index 0000000000..b34d66f420 --- /dev/null +++ b/src/resources/data/the-graphql-guide.json @@ -0,0 +1,6 @@ +{ + "title": "The GraphQL Guide", + "url": "https://graphql.guide", + "author": "Loren Sands-Ramshaw", + "tags": ["guide"] +} diff --git a/src/resources/data/the-guild-s-blog.json b/src/resources/data/the-guild-s-blog.json new file mode 100644 index 0000000000..461761c85b --- /dev/null +++ b/src/resources/data/the-guild-s-blog.json @@ -0,0 +1,5 @@ +{ + "title": "The Guild's Blog", + "url": "https://the-guild.dev/blog", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/the-guild-s-newsletter.json b/src/resources/data/the-guild-s-newsletter.json new file mode 100644 index 0000000000..c34b24e874 --- /dev/null +++ b/src/resources/data/the-guild-s-newsletter.json @@ -0,0 +1,5 @@ +{ + "title": "The Guild's Newsletter", + "url": "https://getrevue.co/profile/TheGuild", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/the-road-to-graphql.json b/src/resources/data/the-road-to-graphql.json new file mode 100644 index 0000000000..a9a97f091f --- /dev/null +++ b/src/resources/data/the-road-to-graphql.json @@ -0,0 +1,6 @@ +{ + "title": "The Road to GraphQL", + "url": "https://www.robinwieruch.de/the-road-to-graphql-book/", + "author": "Robin Wieruch", + "tags": ["guide"] +} diff --git a/src/resources/data/trademark-policy.json b/src/resources/data/trademark-policy.json new file mode 100644 index 0000000000..415fe86826 --- /dev/null +++ b/src/resources/data/trademark-policy.json @@ -0,0 +1,5 @@ +{ + "title": "trademark policy", + "url": "https://lfprojects.org/policies/trademark-policy/", + "tags": ["guide"] +} diff --git a/src/resources/data/tutorial-how-to-build-a-graphql-server.json b/src/resources/data/tutorial-how-to-build-a-graphql-server.json new file mode 100644 index 0000000000..270598c7f5 --- /dev/null +++ b/src/resources/data/tutorial-how-to-build-a-graphql-server.json @@ -0,0 +1,6 @@ +{ + "title": "Tutorial: How to Build a GraphQL Server", + "url": "https://medium.com/apollo-stack/tutorial-building-a-graphql-server-cddaa023c035#.bu6sdnst4", + "author": "Jonas Helfer & Johanna Griffin", + "tags": ["blog"] +} diff --git a/src/resources/data/tutorial-kick-start-a-js-api-with-apollo-server-dataloader-and-knex.json b/src/resources/data/tutorial-kick-start-a-js-api-with-apollo-server-dataloader-and-knex.json new file mode 100644 index 0000000000..48eaef09bb --- /dev/null +++ b/src/resources/data/tutorial-kick-start-a-js-api-with-apollo-server-dataloader-and-knex.json @@ -0,0 +1,6 @@ +{ + "title": "Tutorial: Kick start a JS API with Apollo-server, Dataloader and Knex", + "url": "https://bamtech.gitbook.io/dev-standards/backend/graphql-js/getting-started-with-apollo-server-dataloader-knex.mo", + "author": "Thomas Pucci", + "tags": ["blog"] +} diff --git a/src/resources/data/unleashing-the-power-of-graphql-using-angular-2-gerard-sans-ng-be-2016.json b/src/resources/data/unleashing-the-power-of-graphql-using-angular-2-gerard-sans-ng-be-2016.json new file mode 100644 index 0000000000..af39944189 --- /dev/null +++ b/src/resources/data/unleashing-the-power-of-graphql-using-angular-2-gerard-sans-ng-be-2016.json @@ -0,0 +1,6 @@ +{ + "title": "Unleashing the power of GraphQL using Angular 2", + "author": "Gerard Sans", + "url": "https://www.youtube.com/watch?v=VYpJ9pfugM8", + "tags": ["video", "frontend"] +} diff --git a/src/resources/data/webinar-series-graphql-around-the-world.json b/src/resources/data/webinar-series-graphql-around-the-world.json new file mode 100644 index 0000000000..b723cb2ccc --- /dev/null +++ b/src/resources/data/webinar-series-graphql-around-the-world.json @@ -0,0 +1,5 @@ +{ + "title": "Webinar Series: GraphQL Around The World", + "url": "https://graphql-world.com/webinar", + "tags": ["video"] +} diff --git a/src/resources/data/wundergraph-s-blog.json b/src/resources/data/wundergraph-s-blog.json new file mode 100644 index 0000000000..645953db5d --- /dev/null +++ b/src/resources/data/wundergraph-s-blog.json @@ -0,0 +1,5 @@ +{ + "title": "WunderGraph's Blog", + "url": "https://wundergraph.com/blog", + "tags": ["blog-or-newsletter"] +} diff --git a/src/resources/data/yoga-graphql-server-tutorial.json b/src/resources/data/yoga-graphql-server-tutorial.json new file mode 100644 index 0000000000..c1bf2f20ae --- /dev/null +++ b/src/resources/data/yoga-graphql-server-tutorial.json @@ -0,0 +1,5 @@ +{ + "title": "Yoga GraphQL Server Tutorial", + "url": "https://the-guild.dev/graphql/yoga-server/tutorial", + "tags": ["guide"] +} diff --git a/src/resources/data/your-first-graphql-server.json b/src/resources/data/your-first-graphql-server.json new file mode 100644 index 0000000000..0af9082ceb --- /dev/null +++ b/src/resources/data/your-first-graphql-server.json @@ -0,0 +1,6 @@ +{ + "title": "Your First GraphQL Server", + "url": "https://medium.com/the-graphqlhub/your-first-graphql-server-3c766ab4f0a2#.ovn0y19k4", + "author": "Clay Allsopp", + "tags": ["blog"] +} diff --git a/src/resources/types.ts b/src/resources/types.ts new file mode 100644 index 0000000000..9816c3fba8 --- /dev/null +++ b/src/resources/types.ts @@ -0,0 +1,40 @@ +import { type } from "arktype" + +export const topics = [ + "frontend", + "backend", + "federation", + "schema-design", + "api-platform-and-gateways", + "developer-experience", + "security", + "ai", + "monitoring", + "tools", +] as const +export type Topic = (typeof topics)[number] + +export const kinds = [ + "video", + "blog", + "tools-and-libraries", + "guide", + "book", + "blog-or-newsletter", + "docs", +] as const +export type Kind = (typeof kinds)[number] + +export type ResourceTag = Topic | Kind + +export const ResourceMetadata = type({ + title: "string>0", + url: type("string.url").or("/^\\/.+$/"), + "author?": "string", + "kind?": type.enumerated(...kinds), + "topics?": type.enumerated(...topics).array(), + "description?": "string>0", + tags: type.enumerated(...topics, ...kinds).array(), +}) + +export type ResourceMetadata = typeof ResourceMetadata.inferOut diff --git a/tailwind.config.ts b/tailwind.config.ts index 16649f5949..1dbf34a0fe 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -128,9 +128,10 @@ const config: Config = { plugin(({ addBase }) => { // heading styles addBase({ - ".typography-d1, .typography-h1, .typography-h2, .typography-h3": { - lineHeight: "1.2", - }, + ".typography-d1, .typography-h1, .typography-h2, .typography-h3, .typography-h4": + { + lineHeight: "1.2", + }, ".typography-d1": { fontSize: "48px", "@screen lg": { @@ -155,6 +156,12 @@ const config: Config = { fontSize: "32px", }, }, + ".typography-h4": { + fontSize: "20px", + "@screen md": { + fontSize: "28px", + }, + }, }) // paragraph styles diff --git a/test/e2e/resources-hub.spec.ts b/test/e2e/resources-hub.spec.ts new file mode 100644 index 0000000000..bc1816f630 --- /dev/null +++ b/test/e2e/resources-hub.spec.ts @@ -0,0 +1,24 @@ +import { expect, test } from "@playwright/test" + +const pages = [ + "/resources", + "/resources/frontend", + "/resources/backend", + "/resources/federation", + "/resources/ai", + "/resources/security", + "/resources/monitoring", + "/code", + "/conf", + "/resources/reading", + "/resources/video", +] + +test.describe("Resource hub pages exist", () => { + for (const path of pages) { + test(`renders ${path}`, async ({ page }) => { + const response = await page.goto(path) + expect(response?.ok()).toBeTruthy() + }) + } +}) diff --git a/vercel.json b/vercel.json index 244e7c6f2c..87a8d6f90a 100644 --- a/vercel.json +++ b/vercel.json @@ -400,6 +400,46 @@ "destination": "/blog/2020-10-15-newsletter-september-2020", "permanent": true }, + { + "source": "/community/resources/official-channels", + "destination": "/resources/official-channels", + "permanent": true + }, + { + "source": "/community/resources/training-courses", + "destination": "/resources/training-courses", + "permanent": true + }, + { + "source": "/community/resources/community-channels", + "destination": "/resources/community-channels", + "permanent": true + }, + { + "source": "/community/resources/blogs-and-newsletters", + "destination": "/resources/blogs-and-newsletters", + "permanent": true + }, + { + "source": "/community/resources/videos", + "destination": "/resources/videos", + "permanent": true + }, + { + "source": "/community/resources/vendor-channels", + "destination": "/resources/vendor-channels", + "permanent": true + }, + { + "source": "/community/resources/books", + "destination": "/resources/books", + "permanent": true + }, + { + "source": "/community/resources/more-resources", + "destination": "/resources", + "permanent": true + }, { "source": "/blog/2020-10-15-graphql-foundation-monthly-newsletter-september-2020/", "destination": "/blog/2020-10-15-newsletter-september-2020",