mirror of
https://github.com/sergeyyarkov/yarkov.tech.git
synced 2026-10-11 02:19:08 +03:00
graphql codegen
This commit is contained in:
parent
a4576db438
commit
e03cd19a78
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -1,3 +1,5 @@
|
||||
{
|
||||
"githubPullRequests.ignoredPullRequestBranches": ["develop"]
|
||||
"githubPullRequests.ignoredPullRequestBranches": ["develop"],
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
||||
|
||||
31
codegen.ts
Normal file
31
codegen.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import type { CodegenConfig } from "@graphql-codegen/cli";
|
||||
|
||||
const config: CodegenConfig = {
|
||||
schema: [
|
||||
{
|
||||
[`${process.env.DIRECTUS_URL}/graphql`]: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.DIRECTUS_TOKEN}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
documents: ["src/**/*.ts"],
|
||||
ignoreNoDocuments: true,
|
||||
generates: {
|
||||
"./src/graphql/": {
|
||||
preset: "client",
|
||||
config: {
|
||||
documentMode: "string",
|
||||
},
|
||||
},
|
||||
"./schema.graphql": {
|
||||
plugins: ["schema-ast"],
|
||||
config: {
|
||||
includeDirectives: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
6648
package-lock.json
generated
6648
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@ -16,7 +16,8 @@
|
||||
"start": "node --env-file=.env ./dist/server/entry.mjs",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"directus:gen-types": "node --env-file=.env ./scripts/gen-types.js"
|
||||
"directus:gen-types": "node --env-file=.env ./scripts/gen-types.js",
|
||||
"codegen": "graphql-codegen --require dotenv/config --config codegen.ts --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.5",
|
||||
@ -28,6 +29,7 @@
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"astro": "^5.15.8",
|
||||
"astro-icon": "^1.1.5",
|
||||
"graphql": "^16.12.0",
|
||||
"hast-util-select": "^6.0.4",
|
||||
"hast-util-to-string": "^3.0.0",
|
||||
"hastscript": "^8.0.0",
|
||||
@ -43,14 +45,19 @@
|
||||
"remark-toc": "^9.0.0",
|
||||
"sharp": "^0.32.6",
|
||||
"solid-js": "^1.7.12",
|
||||
"typescript": "^5.8.3",
|
||||
"unified": "^11.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0no-co/graphqlsp": "^1.15.1",
|
||||
"@graphql-codegen/cli": "^6.1.0",
|
||||
"@graphql-codegen/schema-ast": "^5.0.0",
|
||||
"@parcel/watcher": "^2.5.1",
|
||||
"@types/html-escaper": "^3.0.4",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"@types/remark-prism": "^1.3.7",
|
||||
"directus-sdk-typegen": "^0.2.1",
|
||||
"sass": "^1.68.0"
|
||||
"dotenv": "^17.2.3",
|
||||
"sass": "^1.68.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
2394
schema.graphql
Normal file
2394
schema.graphql
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,10 +6,11 @@ import TagsList from "./TagsList";
|
||||
import ArticleItem from "@components/ArticleItem";
|
||||
import "./ArticlesList.scss";
|
||||
import type { ArticleTranslation } from "@/directus-schema";
|
||||
import { ArticleQuery } from "@/src/graphql/graphql";
|
||||
|
||||
type UiStringsType = { "articles.empty": string; "input.search": string };
|
||||
|
||||
type ArticlesBlockType = Record<string, ArticleTranslation[]>;
|
||||
type ArticlesBlockType = Record<string, ArticleQuery["article"][0]["translations"]>;
|
||||
|
||||
type ArticlesListProps = {
|
||||
articles: ArticlesBlockType;
|
||||
|
||||
27
src/graphql/execute.ts
Normal file
27
src/graphql/execute.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type { ExecutionResult } from "graphql";
|
||||
import type { TypedDocumentString } from "./graphql";
|
||||
import { DIRECTUS_URL, DIRECTUS_TOKEN } from "astro:env/server";
|
||||
|
||||
export default async function execute<TResult, TVariables>(
|
||||
query: TypedDocumentString<TResult, TVariables>,
|
||||
...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
|
||||
) {
|
||||
const response = await fetch(DIRECTUS_URL + "/graphql", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/graphql-response+json",
|
||||
Authorization: `Bearer ${DIRECTUS_TOKEN}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Network response was not ok. ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json() as ExecutionResult<TResult>;
|
||||
}
|
||||
83
src/graphql/fragment-masking.ts
Normal file
83
src/graphql/fragment-masking.ts
Normal file
@ -0,0 +1,83 @@
|
||||
/* eslint-disable */
|
||||
import { ResultOf, DocumentTypeDecoration } from '@graphql-typed-document-node/core';
|
||||
import { Incremental, TypedDocumentString } from './graphql';
|
||||
|
||||
|
||||
export type FragmentType<TDocumentType extends DocumentTypeDecoration<any, any>> = TDocumentType extends DocumentTypeDecoration<
|
||||
infer TType,
|
||||
any
|
||||
>
|
||||
? [TType] extends [{ ' $fragmentName'?: infer TKey }]
|
||||
? TKey extends string
|
||||
? { ' $fragmentRefs'?: { [key in TKey]: TType } }
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
|
||||
// return non-nullable if `fragmentType` is non-nullable
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>>
|
||||
): TType;
|
||||
// return nullable if `fragmentType` is undefined
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | undefined
|
||||
): TType | undefined;
|
||||
// return nullable if `fragmentType` is nullable
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | null
|
||||
): TType | null;
|
||||
// return nullable if `fragmentType` is nullable or undefined
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | null | undefined
|
||||
): TType | null | undefined;
|
||||
// return array of non-nullable if `fragmentType` is array of non-nullable
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: Array<FragmentType<DocumentTypeDecoration<TType, any>>>
|
||||
): Array<TType>;
|
||||
// return array of nullable if `fragmentType` is array of nullable
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: Array<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
|
||||
): Array<TType> | null | undefined;
|
||||
// return readonly array of non-nullable if `fragmentType` is array of non-nullable
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
|
||||
): ReadonlyArray<TType>;
|
||||
// return readonly array of nullable if `fragmentType` is array of nullable
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
|
||||
): ReadonlyArray<TType> | null | undefined;
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | Array<FragmentType<DocumentTypeDecoration<TType, any>>> | ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
|
||||
): TType | Array<TType> | ReadonlyArray<TType> | null | undefined {
|
||||
return fragmentType as any;
|
||||
}
|
||||
|
||||
|
||||
export function makeFragmentData<
|
||||
F extends DocumentTypeDecoration<any, any>,
|
||||
FT extends ResultOf<F>
|
||||
>(data: FT, _fragment: F): FragmentType<F> {
|
||||
return data as FragmentType<F>;
|
||||
}
|
||||
export function isFragmentReady<TQuery, TFrag>(
|
||||
queryNode: TypedDocumentString<TQuery, any>,
|
||||
fragmentNode: TypedDocumentString<TFrag, any>,
|
||||
data: FragmentType<TypedDocumentString<Incremental<TFrag>, any>> | null | undefined
|
||||
): data is FragmentType<typeof fragmentNode> {
|
||||
const deferredFields = queryNode.__meta__?.deferredFields as Record<string, (keyof TFrag)[]>;
|
||||
const fragName = fragmentNode.__meta__?.fragmentName as string | undefined;
|
||||
|
||||
if (!deferredFields || !fragName) return true;
|
||||
|
||||
const fields = deferredFields[fragName] ?? [];
|
||||
return fields.length > 0 && fields.every(field => data && field in data);
|
||||
}
|
||||
38
src/graphql/gql.ts
Normal file
38
src/graphql/gql.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/* eslint-disable */
|
||||
import * as types from './graphql';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Map of all GraphQL operations in the project.
|
||||
*
|
||||
* This map has several performance disadvantages:
|
||||
* 1. It is not tree-shakeable, so it will include all operations in the project.
|
||||
* 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle.
|
||||
* 3. It does not support dead code elimination, so it will add unused operations.
|
||||
*
|
||||
* Therefore it is highly recommended to use the babel or swc plugin for production.
|
||||
* Learn more about it here: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#reducing-bundle-size
|
||||
*/
|
||||
type Documents = {
|
||||
"\n\t\tquery Article {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle\n\t\t\t\t\ttags {\n\t\t\t\t\t\ttag_id {\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdescription\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.ArticleDocument,
|
||||
"\n\t\tquery Tag {\n\t\t\ttag {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t}\n\t\t}\n\t": typeof types.TagDocument,
|
||||
};
|
||||
const documents: Documents = {
|
||||
"\n\t\tquery Article {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle\n\t\t\t\t\ttags {\n\t\t\t\t\t\ttag_id {\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdescription\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.ArticleDocument,
|
||||
"\n\t\tquery Tag {\n\t\t\ttag {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t}\n\t\t}\n\t": types.TagDocument,
|
||||
};
|
||||
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n\t\tquery Article {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle\n\t\t\t\t\ttags {\n\t\t\t\t\t\ttag_id {\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdescription\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"): typeof import('./graphql').ArticleDocument;
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n\t\tquery Tag {\n\t\t\ttag {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t}\n\t\t}\n\t"): typeof import('./graphql').TagDocument;
|
||||
|
||||
|
||||
export function graphql(source: string) {
|
||||
return (documents as any)[source] ?? {};
|
||||
}
|
||||
4358
src/graphql/graphql.ts
Normal file
4358
src/graphql/graphql.ts
Normal file
File diff suppressed because it is too large
Load Diff
2
src/graphql/index.ts
Normal file
2
src/graphql/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./fragment-masking";
|
||||
export * from "./gql";
|
||||
33
src/queries/article.ts
Normal file
33
src/queries/article.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { graphql } from "../graphql";
|
||||
import execute from "../graphql/execute";
|
||||
|
||||
export async function getArticleList() {
|
||||
const ArticleQuery = graphql(`
|
||||
query Article {
|
||||
article {
|
||||
translations(filter: { status: { _eq: "published" } }) {
|
||||
id
|
||||
title
|
||||
tags {
|
||||
tag_id {
|
||||
title
|
||||
}
|
||||
}
|
||||
description
|
||||
slug
|
||||
pub_date
|
||||
languages_code {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const result = await execute(ArticleQuery);
|
||||
|
||||
if (!result.data) return [];
|
||||
|
||||
return result.data.article;
|
||||
}
|
||||
2
src/queries/index.ts
Normal file
2
src/queries/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./tag";
|
||||
export * from "./article";
|
||||
19
src/queries/tag.ts
Normal file
19
src/queries/tag.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { graphql } from "../graphql";
|
||||
import execute from "../graphql/execute";
|
||||
|
||||
export async function getTagList() {
|
||||
const TagsQuery = graphql(`
|
||||
query Tag {
|
||||
tag {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const result = await execute(TagsQuery);
|
||||
|
||||
if (!result.data) return [];
|
||||
|
||||
return result.data.tag;
|
||||
}
|
||||
@ -1,19 +1,20 @@
|
||||
---
|
||||
import { useTranslation, getLanguageFromURL } from "@i18n/utils";
|
||||
import * as utils from "@utils/index";
|
||||
import { removeArticleDuplicates, formatToArticleBlocks } from "@utils/article";
|
||||
import PageLayout from "@layouts/default.astro";
|
||||
import PageHeading from "@components/PageHeading.astro";
|
||||
import ArticlesList from "@root/components/ArticlesList";
|
||||
import { fetchTags, fetchArticles } from "@/directus";
|
||||
import { getTagList } from "@root/queries/tag";
|
||||
import { getArticleList } from "@root/queries/article";
|
||||
|
||||
const t = useTranslation(Astro);
|
||||
const pageLang = getLanguageFromURL(Astro.url.pathname);
|
||||
const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
|
||||
const tags = (await fetchTags()).map((t) => t.title);
|
||||
const articles = (await fetchArticles()).flatMap((a) =>
|
||||
utils.removeArticleDuplicates(a.translations, pageLang)
|
||||
const tags = (await getTagList()).map((t) => t.title);
|
||||
const articles = (await getArticleList()).flatMap((a) =>
|
||||
removeArticleDuplicates(a.translations, pageLang)
|
||||
);
|
||||
const articleBlocks = utils.formatToArticleBlocks(articles);
|
||||
const articleBlocks = formatToArticleBlocks(articles);
|
||||
---
|
||||
|
||||
<PageLayout
|
||||
|
||||
@ -1,26 +1,25 @@
|
||||
import type { MarkdownHeading } from "astro";
|
||||
import { DEFAULT_LANGUAGE } from "@root/constants";
|
||||
import type { ArticleTranslation } from "@/directus-schema";
|
||||
import { ArticleQuery } from "../graphql/graphql";
|
||||
|
||||
/**
|
||||
* If there is a translation of an article in the current language of the page,
|
||||
* the remaining articles with other translations should be removed.
|
||||
*/
|
||||
export function removeArticleDuplicates(
|
||||
articleTranslations: ArticleTranslation[] | null,
|
||||
articles: ArticleQuery["article"][0]["translations"],
|
||||
pageLang: LanguageKeys
|
||||
): ArticleTranslation[] {
|
||||
if (!articleTranslations) return [];
|
||||
if (articleTranslations && articleTranslations.length >= 2) {
|
||||
const pageLangArticles = articleTranslations.filter((a) => {
|
||||
return (
|
||||
typeof a.languages_code === "object" && a.languages_code.code.split("-")[0] === pageLang
|
||||
);
|
||||
});
|
||||
) {
|
||||
if (!articles) return [];
|
||||
if (articles && articles.length >= 2) {
|
||||
const pageLangArticles = articles.filter(
|
||||
(a) => a?.languages_code?.code.split("-")[0] === pageLang
|
||||
);
|
||||
if (pageLangArticles.length !== 0) return pageLangArticles;
|
||||
return articleTranslations;
|
||||
return articles;
|
||||
}
|
||||
return articleTranslations;
|
||||
return articles;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,20 +36,17 @@ export function createRelativeArticleUrl(
|
||||
return `${lang}/${prefix}/${date}/${slug}`;
|
||||
}
|
||||
|
||||
export function formatToArticleBlocks(articles: ArticleTranslation[]) {
|
||||
const blocks: Record<string, ArticleTranslation[]> = {};
|
||||
export function formatToArticleBlocks(articles: ArticleQuery["article"][0]["translations"]) {
|
||||
const blocks: Record<string, ArticleQuery["article"][0]["translations"]> = {};
|
||||
|
||||
if (!articles) return {};
|
||||
|
||||
articles.forEach((a) => {
|
||||
const year = new Date(a.pub_date).getFullYear();
|
||||
const year = new Date(a?.pub_date).getFullYear();
|
||||
if (!blocks[year]) blocks[year] = [];
|
||||
blocks[year].push({
|
||||
id: a.id,
|
||||
title: a.title,
|
||||
tags: a.tags,
|
||||
slug: a.slug,
|
||||
pub_date: a.pub_date,
|
||||
});
|
||||
blocks[year].push(a);
|
||||
});
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
"exclude": ["dist"],
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"target": "esnext",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
@ -17,6 +18,13 @@
|
||||
"@hooks/*": ["./src/hooks/*"],
|
||||
"@i18n/*": ["./src/i18n/*"]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "@0no-co/graphqlsp",
|
||||
"schema": "./schema.graphql"
|
||||
}
|
||||
],
|
||||
"verbatimModuleSyntax": false,
|
||||
"typeRoots": ["./node_modules/@types", "./src/types"],
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user