@studio-fes/layer-strapi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.nuxtrc ADDED
@@ -0,0 +1 @@
1
+ typescript.includeWorkspace = true
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @studio-fes/layer-strapi
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - first beta release for base, craft and strapi layers
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # Nuxt Layer Starter
2
+
3
+ Create Nuxt extendable layer with this GitHub template.
4
+
5
+ ## Setup
6
+
7
+ Make sure to install the dependencies:
8
+
9
+ ```bash
10
+ pnpm install
11
+ ```
12
+
13
+ ## Working on your layer
14
+
15
+ Your layer is at the root of this repository, it is exactly like a regular Nuxt project, except you can publish it on NPM.
16
+
17
+ ## Distributing your layer
18
+
19
+ Your Nuxt layer is shaped exactly the same as any other Nuxt project, except you can publish it on NPM.
20
+
21
+ To do so, you only have to check if `files` in `package.json` are valid, then run:
22
+
23
+ ```bash
24
+ npm publish --access public
25
+ ```
26
+
27
+ Once done, your users will only have to run:
28
+
29
+ ```bash
30
+ npm install --save your-layer
31
+ ```
32
+
33
+ Then add the dependency to their `extends` in `nuxt.config`:
34
+
35
+ ```ts
36
+ defineNuxtConfig({
37
+ extends: 'your-layer'
38
+ })
39
+ ```
40
+
41
+ ## Development Server
42
+
43
+ Start the development server on http://localhost:3000
44
+
45
+ ```bash
46
+ pnpm dev
47
+ ```
48
+
49
+ ## Production
50
+
51
+ Build the application for production:
52
+
53
+ ```bash
54
+ pnpm build
55
+ ```
56
+
57
+ Or statically generate it with:
58
+
59
+ ```bash
60
+ pnpm generate
61
+ ```
62
+
63
+ Locally preview production build:
64
+
65
+ ```bash
66
+ pnpm preview
67
+ ```
68
+
69
+ Checkout the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ import type { NuxtImgProps } from '@studio-fes/layer-base/app/components/Image.vue'
3
+ import type { StrapiFileFragment } from '~/types/graphql-operations'
4
+
5
+ interface CraftImageProps {
6
+ image?: StrapiFileFragment
7
+ }
8
+
9
+ const props = defineProps<NuxtImgProps & CraftImageProps>()
10
+ const imageProps = computed(() => ({
11
+ ...props,
12
+ ...parseStrapiImage(props.image),
13
+ }))
14
+ </script>
15
+
16
+ <template>
17
+ <Image v-if="imageProps" v-bind="imageProps" />
18
+ </template>
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import type { PropType } from 'vue'
3
+
4
+ import type * as StrapiRichText from '~/types/StrapiRichText'
5
+
6
+ export type StrapiRichTextBlocks = StrapiRichText.BlockNode[]
7
+
8
+ export default defineComponent({
9
+ props: {
10
+ blocks: { type: Array as PropType<StrapiRichTextBlocks>, default: () => [] },
11
+ inline: { type: Boolean, default: false },
12
+ tag: { type: String, default: undefined },
13
+ },
14
+ setup(props) {
15
+ return () => {
16
+ const tag = props.tag || (props.inline ? 'p' : 'div')
17
+ const children = renderStrapiRichText(props.blocks, props.inline)
18
+ const selector = props.inline ? 'inline-richtext' : 'richtext'
19
+ return h(tag, {
20
+ class: selector,
21
+ }, children)
22
+ }
23
+ },
24
+ })
25
+ </script>
26
+
27
+ <style scoped>
28
+ [class*='richtext'] {
29
+ &:not(.inline-richtext) {
30
+ display: block;
31
+
32
+ :deep(br) {
33
+ display: block;
34
+ height: 1em;
35
+ line-height: 1;
36
+
37
+ content: '';
38
+ }
39
+ }
40
+
41
+ &.inline-richtext {
42
+ :deep(span) {
43
+ display: block;
44
+ }
45
+ }
46
+ }
47
+ </style>
@@ -0,0 +1,18 @@
1
+ export async function useStrapiPage<T>(queryName: never, variables?: Record<string, unknown>): Promise<T> {
2
+ const nuxtApp = useNuxtApp()
3
+
4
+ // @ts-expect-error i18n is not used yet
5
+ const locale = '$i18n' in nuxtApp ? [nuxtApp.$i18n?.locale?.value] : undefined
6
+
7
+ // @ts-expect-error queryName is not used yet
8
+ const response = await useAsyncGraphqlQuery(queryName, {
9
+ locale,
10
+ status: 'PUBLISHED',
11
+ graphqlCaching: { client: true },
12
+ ...variables,
13
+ })
14
+
15
+ checkPageData(response)
16
+
17
+ return response.data.value?.data as T
18
+ }
@@ -0,0 +1,49 @@
1
+ import type { MetaSocialFragment, SeoMetaFragment } from '~/types/graphql-operations'
2
+
3
+ export function useStrapiSEO(seoData?: MaybeRefOrGetter<SeoMetaFragment>) {
4
+ const nuxtApp = useNuxtApp()
5
+ const runtimeConfig = useRuntimeConfig()
6
+
7
+ const seo = computed(() => {
8
+ if (!seoData)
9
+ return {}
10
+
11
+ const { metaTitle, metaDescription, metaImage, metaSocial, metaRobots, keywords } = toValue(seoData)
12
+
13
+ const metaSocials = (metaSocial as MetaSocialFragment[]).map((social: MetaSocialFragment) => {
14
+ let key = null
15
+
16
+ switch (social.socialNetwork) {
17
+ case 'Facebook':
18
+ key = 'og'
19
+ break
20
+ case 'Twitter':
21
+ key = 'twitter'
22
+ break
23
+ }
24
+
25
+ if (!key)
26
+ return []
27
+
28
+ return [
29
+ { property: `${key}:title`, content: social.title || metaTitle },
30
+ { property: `${key}:description`, content: social.description || metaDescription },
31
+ { property: `${key}:image`, content: runtimeConfig.public.imageDomain + (social.image || metaImage)?.url },
32
+ { property: `${key}:image:alt`, content: (social.image || metaImage)?.alternativeText || metaTitle },
33
+ ]
34
+ })
35
+
36
+ return {
37
+ title: metaTitle,
38
+ meta: [
39
+ { name: 'description', content: metaDescription },
40
+ { name: 'robots', content: metaRobots },
41
+ { name: 'keywords', content: keywords },
42
+ { property: 'twitter:card', content: 'summary_large_image' },
43
+ ...metaSocials.flat(),
44
+ ],
45
+ }
46
+ })
47
+
48
+ nuxtApp.runWithContext(() => useHead(seo.value))
49
+ }
@@ -0,0 +1,52 @@
1
+ // import type { StrapiFileFragment } from '#graphql-operations'
2
+ import type { StrapiFileFragment } from '~/types/graphql-operations'
3
+
4
+ export interface HeadingNode {
5
+ type: 'heading'
6
+ level: 1 | 2 | 3 | 4 | 5 | 6
7
+ children: (TextNode | LinkNode)[]
8
+ }
9
+
10
+ export interface ParagraphNode {
11
+ type: 'paragraph'
12
+ children: (TextNode | LinkNode)[]
13
+ }
14
+
15
+ export interface TextNode {
16
+ text: string
17
+ type: 'text'
18
+ bold?: boolean
19
+ underline?: boolean
20
+ italic?: boolean
21
+ strikethrough?: boolean
22
+ code?: boolean
23
+ }
24
+
25
+ export interface LinkNode {
26
+ url: string
27
+ type: 'link'
28
+ children: TextNode[]
29
+ }
30
+
31
+ export interface ListNode {
32
+ type: 'list'
33
+ format: 'unordered' | 'ordered'
34
+ children: ListItemNode[]
35
+ }
36
+
37
+ export interface ListItemNode {
38
+ type: 'list-item'
39
+ children: (TextNode | LinkNode | ListNode)[]
40
+ }
41
+
42
+ export interface QuoteNode {
43
+ type: 'quote'
44
+ children: (TextNode | LinkNode)[]
45
+ }
46
+
47
+ export interface ImageNode {
48
+ type: 'image'
49
+ image: StrapiFileFragment
50
+ }
51
+
52
+ export type BlockNode = HeadingNode | ParagraphNode | TextNode | LinkNode | ListNode | ListItemNode | QuoteNode | ImageNode
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @example
3
+ * ```graphql
4
+ * enum ENUM_COMPONENTSHAREDMETASOCIAL_SOCIALNETWORK {
5
+ * Facebook
6
+ * Twitter
7
+ * }
8
+ * ```
9
+ */
10
+ export type EnumComponentsharedmetasocialSocialnetwork = 'Facebook' | 'Twitter'
11
+
12
+ /**
13
+ *
14
+ * @example
15
+ * ```graphql
16
+ * fragment EntryInterfaceType {
17
+ * id
18
+ * __typename
19
+ * }
20
+ * ```
21
+ */
22
+ export interface EntryInterfaceTypeFragment {
23
+ __typename: string
24
+ /** The ID of the entity */
25
+ documentId?: string | number
26
+ }
27
+
28
+ /**
29
+ *
30
+ * @example
31
+ * ```graphql
32
+ * fragment SeoMeta on ComponentSharedSeo {
33
+ * id
34
+ * metaTitle
35
+ * metaDescription
36
+ * metaImage {
37
+ * ...StrapiFile
38
+ * }
39
+ * metaSocial {
40
+ * ...MetaSocial
41
+ * }
42
+ * keywords
43
+ * metaRobots
44
+ * structuredData
45
+ * metaViewport
46
+ * canonicalURL
47
+ * }
48
+ * ```
49
+ */
50
+ export interface SeoMetaFragment {
51
+ canonicalURL?: string
52
+ id: string | number
53
+ keywords?: string
54
+ metaDescription: string
55
+ metaImage: StrapiFileFragment
56
+ metaRobots?: string
57
+ metaSocial?: (MetaSocialFragment | null)[]
58
+ metaTitle: string
59
+ metaViewport?: string
60
+ structuredData?: any
61
+ }
62
+
63
+ /**
64
+ *
65
+ * @example
66
+ * ```graphql
67
+ * fragment StrapiFile on UploadFile {
68
+ * alternativeText
69
+ * height
70
+ * width
71
+ * url
72
+ * mime
73
+ * ext
74
+ * }
75
+ * ```
76
+ */
77
+ export interface StrapiFileFragment {
78
+ alternativeText?: string
79
+ ext?: string
80
+ height?: number
81
+ mime: string
82
+ url: string
83
+ width?: number
84
+ }
85
+
86
+ /**
87
+ *
88
+ * @example
89
+ * ```graphql
90
+ * fragment MetaSocial on ComponentSharedMetaSocial {
91
+ * description
92
+ * id
93
+ * socialNetwork
94
+ * title
95
+ * image {
96
+ * ...StrapiFile
97
+ * }
98
+ * }
99
+ * ```
100
+ */
101
+ export interface MetaSocialFragment {
102
+ description: string
103
+ id: string | number
104
+ image?: StrapiFileFragment
105
+ socialNetwork: EnumComponentsharedmetasocialSocialnetwork
106
+ title: string
107
+ }
@@ -0,0 +1,76 @@
1
+ // import type { EntryQuery } from '#graphql-operations'
2
+ import type { GraphqlResponse } from '#nuxt-graphql-middleware/response'
3
+ import type { NuxtError } from 'nuxt/app'
4
+ import type { EntryInterfaceTypeFragment } from '~/types/graphql-operations'
5
+
6
+ import { createError } from 'nuxt/app'
7
+
8
+ /**
9
+ * Throws a 404 error with a specified message.
10
+ *
11
+ * @param {string} [message] - The error message to be displayed.
12
+ * @throws {Error} Throws an error with a status code of 404.
13
+ * @returns {never} This function does not return a value; it always throws an error.
14
+ */
15
+ export function throw404(
16
+ message: string = 'Page Not Found.',
17
+ ): never {
18
+ throw createError({
19
+ statusCode: 404,
20
+ statusMessage: message,
21
+ fatal: true,
22
+ })
23
+ }
24
+
25
+ /**
26
+ * Throws a 404 error with a specified message.
27
+ *
28
+ * @param {string} [message] - The error message to be displayed.
29
+ * @throws {Error} Throws an error with a status code of 404.
30
+ * @returns {never} This function does not return a value; it always throws an error.
31
+ */
32
+ export function throw500(
33
+ message: string = 'Server Error',
34
+ ): never {
35
+ throw createError({
36
+ statusCode: 500,
37
+ statusMessage: message,
38
+ fatal: true,
39
+ })
40
+ }
41
+
42
+ /**
43
+ * Checks for errors in an AsyncData response from a GraphQL query and throws appropriate HTTP errors.
44
+ *
45
+ * @remarks
46
+ * This function performs validation on the response of an asynchronous GraphQL query and throws
47
+ * specific errors based on the validation outcome. It checks for missing data, GraphQL errors,
48
+ * and server errors in a specific order.
49
+ *
50
+ * @param response - The AsyncData object containing either GraphQL response data or a NuxtError
51
+ *
52
+ * @throws {Error} Throws a 500 error if:
53
+ * - No response is provided
54
+ * - The response contains an error value
55
+ * - The GraphQL response contains errors in the errors array
56
+ *
57
+ * @throws {Error} Throws a 404 error if the response.data.value is undefined or null
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const response = await useAsyncData('query', () => fetchGraphQL());
62
+ * checkPageData(response);
63
+ * ```
64
+ */
65
+ export function checkPageData(response: Awaited<ReturnType<typeof useAsyncData<GraphqlResponse<EntryInterfaceTypeFragment> | undefined, NuxtError | undefined>>>): void {
66
+ if (!response) {
67
+ throw500('No data returned from the server.')
68
+ }
69
+ if (response.error.value || response.data.value?.errors?.length) {
70
+ throw500(response.error.value?.statusMessage || response.error.value?.message || response.data.value?.errors?.map((e: any) => e.message).join(', ') || 'Unknown error occurred.')
71
+ }
72
+
73
+ if (!response.data.value?.data) {
74
+ throw404()
75
+ }
76
+ }
@@ -0,0 +1,25 @@
1
+ // import type { StrapiFileFragment } from '#graphql-operations'
2
+ import type { NuxtImgProps } from '@studio-fes/layer-base/app/components/Image.vue'
3
+ import type { StrapiFileFragment } from '~/types/graphql-operations'
4
+
5
+ export function parseStrapiImage(image: StrapiFileFragment | undefined): NuxtImgProps | null {
6
+ if (!image)
7
+ return null
8
+
9
+ const runtimeConfig = useRuntimeConfig()
10
+ const { width, height, url: src, alternativeText: alt } = image
11
+
12
+ return {
13
+ width,
14
+ height,
15
+ src: runtimeConfig.public.imageDomain + src,
16
+ alt,
17
+ format: 'webp',
18
+ } as {
19
+ width: number
20
+ height: number
21
+ src: string
22
+ alt: string
23
+ format?: string
24
+ }
25
+ }
@@ -0,0 +1,97 @@
1
+ import type * as RichText from '~/types/StrapiRichText'
2
+
3
+ import { Comment, Fragment } from 'vue'
4
+
5
+ function renderParagraphBlock(block: RichText.ParagraphNode, inline?: boolean) {
6
+ const children = renderStrapiRichText(block.children, inline)
7
+ const tag = inline ? 'fragment' : 'p'
8
+ return children.length ? h(tag, children) : h('br')
9
+ }
10
+
11
+ function renderTextBlock(block: RichText.TextNode) {
12
+ const lines = block.text.split('\n')
13
+ const children = lines.reduce((acc, line, i) => {
14
+ if (i < lines.length - 1) {
15
+ acc.push(`${line} `.replace(/\s+/g, ' '), h('br'))
16
+ }
17
+ else {
18
+ acc.push(line)
19
+ }
20
+ return acc
21
+ }, [] as (string | VNode)[])
22
+
23
+ if (block.bold)
24
+ return h('strong', children)
25
+ if (block.code)
26
+ return h('code', children)
27
+ if (block.italic)
28
+ return h('em', children)
29
+ if (block.strikethrough)
30
+ return h('span', { class: 'line-through' }, children)
31
+ if (block.underline)
32
+ return h('span', { class: 'underline' }, children)
33
+ return h(Fragment, children)
34
+ }
35
+
36
+ export function renderInlineBlock(block: RichText.BlockNode): VNode {
37
+ switch (block.type) {
38
+ case 'heading':
39
+ case 'list-item':
40
+ case 'quote':
41
+ return h('span', renderStrapiRichText(block.children, true))
42
+ case 'image':
43
+ return h(Fragment)
44
+ case 'link':
45
+ return h('a', { href: block.url, target: '_blank', rel: 'noopener noreferrer' }, renderStrapiRichText(block.children, true))
46
+ case 'list':
47
+ return h(Fragment, renderStrapiRichText(block.children, true))
48
+ case 'paragraph':
49
+ return renderParagraphBlock(block, true)
50
+ case 'text':
51
+ return renderTextBlock(block)
52
+ default:
53
+ return h(Comment, 'Unknown block type')
54
+ }
55
+ }
56
+
57
+ export function renderBlock(block: RichText.BlockNode): VNode {
58
+ switch (block.type) {
59
+ case 'heading':
60
+ return h(`h${block.level}`, renderStrapiRichText(block.children))
61
+ case 'image':
62
+ return h('img', { src: block.image.url, width: block.image.width, height: block.image.height, alt: block.image.alternativeText })
63
+ case 'link':
64
+ return h('a', { title: getPlainText(block.children), href: block.url, target: '_blank', rel: 'noopener noreferrer' }, h('span', renderStrapiRichText(block.children)))
65
+ case 'list':
66
+ return h(block.format === 'ordered' ? 'ol' : 'ul', renderStrapiRichText(block.children))
67
+ case 'list-item':
68
+ return h('li', renderStrapiRichText(block.children))
69
+ case 'paragraph':
70
+ return renderParagraphBlock(block)
71
+ case 'quote':
72
+ return h('blockquote', renderStrapiRichText(block.children))
73
+ case 'text':
74
+ return renderTextBlock(block)
75
+ default:
76
+ return h(Comment, 'Unknown block type')
77
+ }
78
+ }
79
+
80
+ export function renderStrapiRichText(blocks: RichText.BlockNode[], inline?: boolean): VNode[] {
81
+ const render = inline ? renderInlineBlock : renderBlock
82
+ return blocks.map(render)
83
+ }
84
+
85
+ export function getPlainText(block: RichText.BlockNode[]) {
86
+ if (!block)
87
+ return ''
88
+
89
+ const text: string = block.reduce((acc, node) => {
90
+ if (node.type === 'text') {
91
+ return acc + node.text
92
+ }
93
+ return acc + getPlainText(node.children)
94
+ }, '')
95
+
96
+ return text
97
+ }
@@ -0,0 +1,74 @@
1
+ // @ts-check
2
+ import antfu from '@antfu/eslint-config'
3
+ import graphqlPlugin from '@graphql-eslint/eslint-plugin'
4
+ import { globalIgnores } from 'eslint/config'
5
+ import withNuxt from './.nuxt/eslint.config.mjs'
6
+
7
+ // export default withNuxt(
8
+ export default withNuxt(
9
+ // Your custom configs here
10
+ antfu(
11
+ {
12
+ formatters: true,
13
+ vue: true,
14
+ pnpm: false,
15
+ unocss: true,
16
+ },
17
+ ),
18
+ globalIgnores([
19
+ 'dist',
20
+ 'node_modules',
21
+ '.output',
22
+ '.nuxt',
23
+ '.storybook',
24
+ 'storybook-static',
25
+ '.github',
26
+ 'coverage',
27
+ '*.log',
28
+ 'nuxt.d.ts',
29
+ '.output',
30
+ '.DS_Store',
31
+ '.vscode',
32
+ '*.md',
33
+ 'package.json',
34
+ 'package-lock.json',
35
+ 'babel.config.js',
36
+ 'graphql',
37
+ 'types.ts',
38
+ 'generated',
39
+ 'components.d.ts',
40
+ 'icons.d.ts',
41
+ 'auto.d.ts',
42
+ 'src-tauri',
43
+ 'auto-imports.d.ts',
44
+ 'schema.graphql',
45
+ ]),
46
+ {
47
+ rules: {
48
+ 'unocss/order': 'error', // or "error",
49
+ },
50
+ },
51
+ // @ts-expect-error
52
+ {
53
+ files: ['**/*.graphql', '**/*.gql'],
54
+ languageOptions: {
55
+ parser: graphqlPlugin.parser,
56
+ },
57
+ plugins: {
58
+ '@graphql-eslint': graphqlPlugin,
59
+ },
60
+ rules: {
61
+ '@graphql-eslint/no-anonymous-operations': 'error',
62
+ '@graphql-eslint/naming-convention': [
63
+ 'error',
64
+ {
65
+ OperationDefinition: {
66
+ style: 'PascalCase',
67
+ forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
68
+ forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
69
+ },
70
+ },
71
+ ],
72
+ },
73
+ },
74
+ )
@@ -0,0 +1,4 @@
1
+ export default {
2
+ schema: 'schema.graphql',
3
+ documents: '**/*.{graphql,gql}',
4
+ }
package/nuxt.config.ts ADDED
@@ -0,0 +1,23 @@
1
+ // https://nuxt.com/docs/api/configuration/nuxt-config
2
+ export default defineNuxtConfig({
3
+ extends: ['@studio-fes/layer-base'],
4
+
5
+ modules: [
6
+ 'nuxt-graphql-middleware',
7
+ 'nuxt-multi-cache',
8
+ ],
9
+
10
+ runtimeConfig: {
11
+ multiCacheEnabled: import.meta.env.NUXT_CACHE_ENABLED === 'true',
12
+ },
13
+
14
+ graphqlMiddleware: {
15
+ downloadSchema: false,
16
+ graphqlEndpoint: 'https://example.com/graphql',
17
+ serverApiPrefix: '/gql',
18
+ clientCache: {
19
+ enabled: true,
20
+ },
21
+ autoImportPatterns: ['./queries/**/*.gql'],
22
+ },
23
+ })
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@studio-fes/layer-strapi",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "main": "./nuxt.config.ts",
6
+ "scripts": {
7
+ "dev": "nuxi dev",
8
+ "build": "nuxi build",
9
+ "start": "nuxi preview",
10
+ "prepare": "nuxi prepare",
11
+ "generate": "nuxt generate",
12
+ "preview": "nuxt preview",
13
+ "lint": "eslint .",
14
+ "lint:fix": "eslint . --fix"
15
+ },
16
+ "devDependencies": {
17
+ "@nuxt/eslint": "latest",
18
+ "@studio-fes/layer-base": "workspace:*",
19
+ "@types/node": "^24.10.4",
20
+ "eslint": "^9.39.2",
21
+ "nuxt": "^4.2.2",
22
+ "nuxt-graphql-middleware": "^5.3.2",
23
+ "nuxt-multi-cache": "^4.0.3",
24
+ "typescript": "^5.9.3",
25
+ "vue": "latest"
26
+ },
27
+ "description": "Create Nuxt extendable layer with this GitHub template.",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/rondodevs/nuxt-layers.git"
31
+ },
32
+ "author": "studio fes",
33
+ "license": "MIT",
34
+ "bugs": {
35
+ "url": "https://github.com/rondodevs/nuxt-layers/issues"
36
+ },
37
+ "homepage": "https://github.com/rondodevs/nuxt-layers#readme"
38
+ }
File without changes
package/schema.graphql ADDED
File without changes
@@ -0,0 +1,32 @@
1
+ import type { FetchResponse } from 'ofetch'
2
+ import { defineGraphqlServerOptions } from 'nuxt-graphql-middleware/server-options'
3
+
4
+ export default defineGraphqlServerOptions({
5
+ async doGraphqlRequest(context) {
6
+ const { operationName, variables, operationDocument } = context
7
+
8
+ const config = useRuntimeConfig()
9
+
10
+ function doRequest(): Promise<FetchResponse<{ data: any, errors: any[] }>> {
11
+ return $fetch.raw(config.graphqlMiddleware.graphqlEndpoint, {
12
+ method: 'POST',
13
+ ignoreResponseError: true,
14
+ body: {
15
+ query: operationDocument,
16
+ variables,
17
+ operationName,
18
+ }
19
+ })
20
+ }
21
+
22
+ const res = await doRequest()
23
+
24
+ return {
25
+ data: res._data?.data,
26
+ errors: res._data?.errors?.map(e => ({
27
+ ...e,
28
+ path: Array.isArray(e.path) ? e.path : [],
29
+ })) || [],
30
+ }
31
+ },
32
+ })
@@ -0,0 +1,22 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ import { defineMultiCacheOptions } from 'nuxt-multi-cache/server-options'
4
+ import fsDriver from 'unstorage/drivers/fs'
5
+ import nullDriver from 'unstorage/drivers/null'
6
+
7
+ export default defineMultiCacheOptions(() => {
8
+ const config = useRuntimeConfig()
9
+ const { multiCacheEnabled } = config
10
+
11
+ const driver = multiCacheEnabled
12
+ ? fsDriver({ base: '.nuxt-multi-cache' })
13
+ : nullDriver()
14
+
15
+ return {
16
+ data: {
17
+ storage: {
18
+ driver,
19
+ },
20
+ },
21
+ }
22
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "./.nuxt/tsconfig.json"
3
+ }