From fbf6bfa04f46f521a833279814caba9446678436 Mon Sep 17 00:00:00 2001 From: Jamie Barton Date: Tue, 15 Nov 2022 18:18:31 +0000 Subject: [PATCH] feat(examples): with-grafbase (#42898) --- examples/with-grafbase/.env.local.example | 2 + examples/with-grafbase/.gitignore | 38 ++ examples/with-grafbase/.vscode/settings.json | 4 + examples/with-grafbase/README.md | 68 +++ examples/with-grafbase/app/globals.css | 3 + examples/with-grafbase/app/head.tsx | 7 + examples/with-grafbase/app/layout.tsx | 80 ++++ examples/with-grafbase/app/page.tsx | 13 + .../with-grafbase/app/posts/[slug]/page.tsx | 34 ++ examples/with-grafbase/codegen.ts | 26 + .../with-grafbase/gql/fragment-masking.ts | 44 ++ examples/with-grafbase/gql/gql.ts | 25 + examples/with-grafbase/gql/graphql.ts | 444 ++++++++++++++++++ examples/with-grafbase/gql/index.ts | 2 + .../with-grafbase/grafbase/schema.graphql | 12 + examples/with-grafbase/lib/grafbase.ts | 11 + examples/with-grafbase/next.config.js | 10 + examples/with-grafbase/package.json | 29 ++ examples/with-grafbase/postcss.config.js | 6 + examples/with-grafbase/tailwind.config.js | 8 + examples/with-grafbase/tsconfig.json | 25 + 21 files changed, 891 insertions(+) create mode 100644 examples/with-grafbase/.env.local.example create mode 100644 examples/with-grafbase/.gitignore create mode 100644 examples/with-grafbase/.vscode/settings.json create mode 100644 examples/with-grafbase/README.md create mode 100644 examples/with-grafbase/app/globals.css create mode 100644 examples/with-grafbase/app/head.tsx create mode 100644 examples/with-grafbase/app/layout.tsx create mode 100644 examples/with-grafbase/app/page.tsx create mode 100644 examples/with-grafbase/app/posts/[slug]/page.tsx create mode 100644 examples/with-grafbase/codegen.ts create mode 100644 examples/with-grafbase/gql/fragment-masking.ts create mode 100644 examples/with-grafbase/gql/gql.ts create mode 100644 examples/with-grafbase/gql/graphql.ts create mode 100644 examples/with-grafbase/gql/index.ts create mode 100644 examples/with-grafbase/grafbase/schema.graphql create mode 100644 examples/with-grafbase/lib/grafbase.ts create mode 100644 examples/with-grafbase/next.config.js create mode 100644 examples/with-grafbase/package.json create mode 100644 examples/with-grafbase/postcss.config.js create mode 100644 examples/with-grafbase/tailwind.config.js create mode 100644 examples/with-grafbase/tsconfig.json diff --git a/examples/with-grafbase/.env.local.example b/examples/with-grafbase/.env.local.example new file mode 100644 index 000000000000..b1de6720459f --- /dev/null +++ b/examples/with-grafbase/.env.local.example @@ -0,0 +1,2 @@ +GRAFBASE_API_URL=http://localhost:4000/graphql +GRAFBASE_API_KEY= diff --git a/examples/with-grafbase/.gitignore b/examples/with-grafbase/.gitignore new file mode 100644 index 000000000000..315d86ad9b20 --- /dev/null +++ b/examples/with-grafbase/.gitignore @@ -0,0 +1,38 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +.grafbase \ No newline at end of file diff --git a/examples/with-grafbase/.vscode/settings.json b/examples/with-grafbase/.vscode/settings.json new file mode 100644 index 000000000000..fae8e3d8a976 --- /dev/null +++ b/examples/with-grafbase/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true +} diff --git a/examples/with-grafbase/README.md b/examples/with-grafbase/README.md new file mode 100644 index 000000000000..e5e1ae94b314 --- /dev/null +++ b/examples/with-grafbase/README.md @@ -0,0 +1,68 @@ +# Next.js with Grafbase + +This example shows to use [Grafbase](https://grafbase.com) with Next.js. This example features fetching from a local GraphQL backend powered by the Grafbase CLI, and GraphQL Code Generator for making type-safe queries. + +## Demo + +You can see a demo of this online at [https://grafbase-with-nextjs-rsc.grafbase-vercel.dev](https://grafbase-with-nextjs-rsc.grafbase-vercel.dev). + +## Deploy + +First deploy this to Grafbase to get your backend API URL and Key: + +[![Deploy to Grafbase](https://grafbase.com/button)](https://grafbase.com/new/configure?template=NextExample&source=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-grafbase) + +Then deploy this example using [Vercel](https://vercel.com): + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-grafbase&env=GRAFBASE_API_URL,GRAFBASE_API_KEY) + +## How to use + +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example: + +```bash +npx create-next-app --example with-grafbase with-grafbase-app +``` + +```bash +yarn create next-app --example with-grafbase with-grafbase-app +``` + +```bash +pnpm create next-app --example with-grafbase with-grafbase-app +``` + +To run the example locally you need to: + +1. Copy the `.env.local.example` to `.env.local` and provide your API URL and API Key: `cp .env.local.example .env.local` — the defaults will be fine for development mode. + +2. Run the [Grafbase CLI](https://grafbase.com/cli) using `npx grafbase@latest dev` + +3. Populate the backend with some `Post` entries using a GraphQL mutation: + +```graphql +mutation { + postCreate( + input: { + title: "I love Next.js!" + slug: "i-love-nextjs" + comments: [{ create: { message: "me too!" } }] + } + ) { + post { + id + slug + } + } +} +``` + +4. Run the app locally and go to [http://localhost:3000](http://localhost:3000) to navigate to each post page! This data is fetched from the local backend. + +5. Optionally run `npm run codegen` to watch for any changes to queries inside of the app and automatically generate types. + +## Learn more + +- [Grafbase Quickstart](https://grafbase.com/docs/quickstart/get-started) — get started with Grafbase, quickly! +- [Create an account](https://grafbase.com/sign-up) — deploy to the edge with Grafbase! +- [Next.js Documentation](https://nextjs.org/docs) — learn more about Next.js diff --git a/examples/with-grafbase/app/globals.css b/examples/with-grafbase/app/globals.css new file mode 100644 index 000000000000..b5c61c956711 --- /dev/null +++ b/examples/with-grafbase/app/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/examples/with-grafbase/app/head.tsx b/examples/with-grafbase/app/head.tsx new file mode 100644 index 000000000000..5b847ecacdb2 --- /dev/null +++ b/examples/with-grafbase/app/head.tsx @@ -0,0 +1,7 @@ +const Head = () => ( + <> + Grafbase + Next.js + +) + +export default Head diff --git a/examples/with-grafbase/app/layout.tsx b/examples/with-grafbase/app/layout.tsx new file mode 100644 index 000000000000..45c191591861 --- /dev/null +++ b/examples/with-grafbase/app/layout.tsx @@ -0,0 +1,80 @@ +import './globals.css' + +import Link from 'next/link' + +import { graphql } from '../gql' +import { grafbase } from '../lib/grafbase' + +const GetAllPostsDocument = graphql(/* GraphQL */ ` + query GetAllPosts($first: Int!) { + postCollection(first: $first) { + edges { + node { + id + title + slug + } + } + } + } +`) + +const RootLayout = async ({ children }: { children: React.ReactNode }) => { + const { postCollection } = await grafbase.request(GetAllPostsDocument, { + first: 50, + }) + + return ( + + + Grafbase + Next.js 13 + + +
+ +
+
+
{children}
+
+
+
+ + + ) +} + +export default RootLayout diff --git a/examples/with-grafbase/app/page.tsx b/examples/with-grafbase/app/page.tsx new file mode 100644 index 000000000000..fb61c3b6b626 --- /dev/null +++ b/examples/with-grafbase/app/page.tsx @@ -0,0 +1,13 @@ +const Page = async () => { + return ( + <> +

Next.js 13 + Grafbase

+

+ Once you've added some posts using the GraphQL Playground, you can + explore each post by clicking the link in the nav. +

+ + ) +} + +export default Page diff --git a/examples/with-grafbase/app/posts/[slug]/page.tsx b/examples/with-grafbase/app/posts/[slug]/page.tsx new file mode 100644 index 000000000000..2443b0f1ef55 --- /dev/null +++ b/examples/with-grafbase/app/posts/[slug]/page.tsx @@ -0,0 +1,34 @@ +import { graphql } from '../../../gql' +import { grafbase } from '../../../lib/grafbase' + +export const revalidate = 3600 + +const GetPostBySlugDocument = graphql(/* GraphQL */ ` + query GetPostBySlug($slug: String!) { + post(by: { slug: $slug }) { + id + title + slug + } + } +`) + +const Page = async ({ params }: { params: { slug: string } }) => { + const { post } = await grafbase.request(GetPostBySlugDocument, { + slug: params.slug, + }) + + if (!post) { + // optionally import notFound from next/navigation + return

404: Not Found

+ } + + return ( + <> +

{post.title}

+
{JSON.stringify(post, null, 2)}
+ + ) +} + +export default Page diff --git a/examples/with-grafbase/codegen.ts b/examples/with-grafbase/codegen.ts new file mode 100644 index 000000000000..28164af1fc11 --- /dev/null +++ b/examples/with-grafbase/codegen.ts @@ -0,0 +1,26 @@ +import { CodegenConfig } from '@graphql-codegen/cli' + +const url = process.env.GRAFBASE_API_URL as string +const xApiKey = process.env.GRAFBASE_API_KEY as string + +const config: CodegenConfig = { + schema: [ + { + [url]: { + headers: { + 'x-api-key': xApiKey, + }, + }, + }, + ], + documents: ['app/**/*.tsx', 'app/**/*.ts'], + ignoreNoDocuments: true, + generates: { + './gql/': { + preset: 'client', + plugins: [], + }, + }, +} + +export default config diff --git a/examples/with-grafbase/gql/fragment-masking.ts b/examples/with-grafbase/gql/fragment-masking.ts new file mode 100644 index 000000000000..4de725491dd3 --- /dev/null +++ b/examples/with-grafbase/gql/fragment-masking.ts @@ -0,0 +1,44 @@ +import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core' + +export type FragmentType> = + TDocumentType extends DocumentNode + ? 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( + _documentNode: DocumentNode, + fragmentType: FragmentType> +): TType +// return nullable if `fragmentType` is nullable +export function useFragment( + _documentNode: DocumentNode, + fragmentType: FragmentType> | null | undefined +): TType | null | undefined +// return array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentNode, + fragmentType: ReadonlyArray>> +): ReadonlyArray +// return array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentNode, + fragmentType: + | ReadonlyArray>> + | null + | undefined +): ReadonlyArray | null | undefined +export function useFragment( + _documentNode: DocumentNode, + fragmentType: + | FragmentType> + | ReadonlyArray>> + | null + | undefined +): TType | ReadonlyArray | null | undefined { + return fragmentType as any +} diff --git a/examples/with-grafbase/gql/gql.ts b/examples/with-grafbase/gql/gql.ts new file mode 100644 index 000000000000..6bf7a46bc9f8 --- /dev/null +++ b/examples/with-grafbase/gql/gql.ts @@ -0,0 +1,25 @@ +/* eslint-disable */ +import * as types from './graphql' +import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core' + +const documents = { + '\n query GetAllPosts($first: Int!) {\n postCollection(first: $first) {\n edges {\n node {\n id\n title\n slug\n }\n }\n }\n }\n': + types.GetAllPostsDocument, + '\n query GetPostBySlug($slug: String!) {\n post(by: { slug: $slug }) {\n id\n title\n slug\n }\n }\n': + types.GetPostBySlugDocument, +} + +export function graphql( + source: '\n query GetAllPosts($first: Int!) {\n postCollection(first: $first) {\n edges {\n node {\n id\n title\n slug\n }\n }\n }\n }\n' +): typeof documents['\n query GetAllPosts($first: Int!) {\n postCollection(first: $first) {\n edges {\n node {\n id\n title\n slug\n }\n }\n }\n }\n'] +export function graphql( + source: '\n query GetPostBySlug($slug: String!) {\n post(by: { slug: $slug }) {\n id\n title\n slug\n }\n }\n' +): typeof documents['\n query GetPostBySlug($slug: String!) {\n post(by: { slug: $slug }) {\n id\n title\n slug\n }\n }\n'] + +export function graphql(source: string): unknown +export function graphql(source: string) { + return (documents as any)[source] ?? {} +} + +export type DocumentType> = + TDocumentNode extends DocumentNode ? TType : never diff --git a/examples/with-grafbase/gql/graphql.ts b/examples/with-grafbase/gql/graphql.ts new file mode 100644 index 000000000000..97ba10d05a02 --- /dev/null +++ b/examples/with-grafbase/gql/graphql.ts @@ -0,0 +1,444 @@ +/* eslint-disable */ +import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core' +export type Maybe = T | null +export type InputMaybe = Maybe +export type Exact = { + [K in keyof T]: T[K] +} +export type MakeOptional = Omit & { + [SubKey in K]?: Maybe +} +export type MakeMaybe = Omit & { + [SubKey in K]: Maybe +} +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: string + String: string + Boolean: boolean + Int: number + Float: number + /** + * A date-time string at UTC, such as 2007-12-03T10:15:30Z, is compliant with the date-time format outlined in section 5.6 of the RFC 3339 + * profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. + * + * This scalar is a description of an exact instant on the timeline such as the instant that a user account was created. + * + * # Input Coercion + * + * When expected as an input type, only RFC 3339 compliant date-time strings are accepted. All other input values raise a query error indicating an incorrect type. + * + * # Result Coercion + * + * Where an RFC 3339 compliant date-time string has a time-zone other than UTC, it is shifted to UTC. + * For example, the date-time string 2016-01-01T14:10:20+01:00 is shifted to 2016-01-01T13:10:20Z. + */ + DateTime: any +} + +export type Comment = { + __typename?: 'Comment' + /** when the model was created */ + createdAt: Scalars['DateTime'] + id: Scalars['ID'] + message: Scalars['String'] + post?: Maybe + /** when the model was updated */ + updatedAt: Scalars['DateTime'] +} + +export type CommentByInput = { + id?: InputMaybe +} + +/** Input to create a new CommentCommentRelatePostPost */ +export type CommentCommentRelatePostPostCreateInput = { + slug: Scalars['String'] + title: Scalars['String'] +} + +/** Input to create a new CommentCommentRelatePostPost relation */ +export type CommentCommentRelatePostPostCreateRelationInput = { + create?: InputMaybe + link?: InputMaybe +} + +/** Input to update a CommentCommentRelatePostPost relation */ +export type CommentCommentRelatePostPostUpdateRelationInput = { + create?: InputMaybe + link?: InputMaybe + unlink?: InputMaybe +} + +export type CommentConnection = { + __typename?: 'CommentConnection' + edges?: Maybe>> + /** Information to aid in pagination */ + pageInfo: PageInfo +} + +/** Input to create a new Comment */ +export type CommentCreateInput = { + message: Scalars['String'] + post?: InputMaybe +} + +export type CommentCreatePayload = { + __typename?: 'CommentCreatePayload' + comment?: Maybe +} + +export type CommentDeletePayload = { + __typename?: 'CommentDeletePayload' + deletedId: Scalars['ID'] +} + +export type CommentEdge = { + __typename?: 'CommentEdge' + cursor: Scalars['String'] + node: Comment +} + +/** Input to create a new Comment */ +export type CommentUpdateInput = { + message?: InputMaybe + post?: InputMaybe +} + +export type CommentUpdatePayload = { + __typename?: 'CommentUpdatePayload' + comment?: Maybe +} + +export type Mutation = { + __typename?: 'Mutation' + /** Create a Comment */ + commentCreate?: Maybe + /** Delete a Comment by ID or unique field */ + commentDelete?: Maybe + /** Update a Comment */ + commentUpdate?: Maybe + /** Create a Post */ + postCreate?: Maybe + /** Delete a Post by ID or unique field */ + postDelete?: Maybe + /** Update a Post */ + postUpdate?: Maybe +} + +export type MutationCommentCreateArgs = { + input: CommentCreateInput +} + +export type MutationCommentDeleteArgs = { + by: CommentByInput +} + +export type MutationCommentUpdateArgs = { + by: CommentByInput + input: CommentUpdateInput +} + +export type MutationPostCreateArgs = { + input: PostCreateInput +} + +export type MutationPostDeleteArgs = { + by: PostByInput +} + +export type MutationPostUpdateArgs = { + by: PostByInput + input: PostUpdateInput +} + +export type PageInfo = { + __typename?: 'PageInfo' + endCursor?: Maybe + hasNextPage: Scalars['Boolean'] + hasPreviousPage: Scalars['Boolean'] + startCursor?: Maybe +} + +export type Post = { + __typename?: 'Post' + comments?: Maybe + /** when the model was created */ + createdAt: Scalars['DateTime'] + id: Scalars['ID'] + slug: Scalars['String'] + title: Scalars['String'] + /** when the model was updated */ + updatedAt: Scalars['DateTime'] +} + +export type PostCommentsArgs = { + after?: InputMaybe + before?: InputMaybe + first?: InputMaybe + last?: InputMaybe +} + +export type PostByInput = { + id?: InputMaybe + slug?: InputMaybe +} + +/** Input to create a new PostCommentRelatePostComment */ +export type PostCommentRelatePostCommentCreateInput = { + message: Scalars['String'] +} + +/** Input to create a new PostCommentRelatePostComment relation */ +export type PostCommentRelatePostCommentCreateRelationInput = { + create?: InputMaybe + link?: InputMaybe +} + +/** Input to update a PostCommentRelatePostComment relation */ +export type PostCommentRelatePostCommentUpdateRelationInput = { + create?: InputMaybe + link?: InputMaybe + unlink?: InputMaybe +} + +export type PostConnection = { + __typename?: 'PostConnection' + edges?: Maybe>> + /** Information to aid in pagination */ + pageInfo: PageInfo +} + +/** Input to create a new Post */ +export type PostCreateInput = { + comments?: InputMaybe< + Array> + > + slug: Scalars['String'] + title: Scalars['String'] +} + +export type PostCreatePayload = { + __typename?: 'PostCreatePayload' + post?: Maybe +} + +export type PostDeletePayload = { + __typename?: 'PostDeletePayload' + deletedId: Scalars['ID'] +} + +export type PostEdge = { + __typename?: 'PostEdge' + cursor: Scalars['String'] + node: Post +} + +/** Input to create a new Post */ +export type PostUpdateInput = { + comments?: InputMaybe< + Array> + > + slug?: InputMaybe + title?: InputMaybe +} + +export type PostUpdatePayload = { + __typename?: 'PostUpdatePayload' + post?: Maybe +} + +export type Query = { + __typename?: 'Query' + /** Query a single Comment by an ID or a unique field */ + comment?: Maybe + /** Paginated query to fetch the whole list of `Comment`. */ + commentCollection?: Maybe + /** Query a single Post by an ID or a unique field */ + post?: Maybe + /** Paginated query to fetch the whole list of `Post`. */ + postCollection?: Maybe +} + +export type QueryCommentArgs = { + by: CommentByInput +} + +export type QueryCommentCollectionArgs = { + after?: InputMaybe + before?: InputMaybe + first?: InputMaybe + last?: InputMaybe +} + +export type QueryPostArgs = { + by: PostByInput +} + +export type QueryPostCollectionArgs = { + after?: InputMaybe + before?: InputMaybe + first?: InputMaybe + last?: InputMaybe +} + +export type GetAllPostsQueryVariables = Exact<{ + first: Scalars['Int'] +}> + +export type GetAllPostsQuery = { + __typename?: 'Query' + postCollection?: { + __typename?: 'PostConnection' + edges?: Array<{ + __typename?: 'PostEdge' + node: { __typename?: 'Post'; id: string; title: string; slug: string } + } | null> | null + } | null +} + +export type GetPostBySlugQueryVariables = Exact<{ + slug: Scalars['String'] +}> + +export type GetPostBySlugQuery = { + __typename?: 'Query' + post?: { __typename?: 'Post'; id: string; title: string; slug: string } | null +} + +export const GetAllPostsDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'query', + name: { kind: 'Name', value: 'GetAllPosts' }, + variableDefinitions: [ + { + kind: 'VariableDefinition', + variable: { + kind: 'Variable', + name: { kind: 'Name', value: 'first' }, + }, + type: { + kind: 'NonNullType', + type: { kind: 'NamedType', name: { kind: 'Name', value: 'Int' } }, + }, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'postCollection' }, + arguments: [ + { + kind: 'Argument', + name: { kind: 'Name', value: 'first' }, + value: { + kind: 'Variable', + name: { kind: 'Name', value: 'first' }, + }, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'edges' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'node' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'id' }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'title' }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'slug' }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode +export const GetPostBySlugDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'query', + name: { kind: 'Name', value: 'GetPostBySlug' }, + variableDefinitions: [ + { + kind: 'VariableDefinition', + variable: { kind: 'Variable', name: { kind: 'Name', value: 'slug' } }, + type: { + kind: 'NonNullType', + type: { + kind: 'NamedType', + name: { kind: 'Name', value: 'String' }, + }, + }, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'post' }, + arguments: [ + { + kind: 'Argument', + name: { kind: 'Name', value: 'by' }, + value: { + kind: 'ObjectValue', + fields: [ + { + kind: 'ObjectField', + name: { kind: 'Name', value: 'slug' }, + value: { + kind: 'Variable', + name: { kind: 'Name', value: 'slug' }, + }, + }, + ], + }, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'id' } }, + { kind: 'Field', name: { kind: 'Name', value: 'title' } }, + { kind: 'Field', name: { kind: 'Name', value: 'slug' } }, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode diff --git a/examples/with-grafbase/gql/index.ts b/examples/with-grafbase/gql/index.ts new file mode 100644 index 000000000000..99775ae23cb0 --- /dev/null +++ b/examples/with-grafbase/gql/index.ts @@ -0,0 +1,2 @@ +export * from './gql' +export * from './fragment-masking' diff --git a/examples/with-grafbase/grafbase/schema.graphql b/examples/with-grafbase/grafbase/schema.graphql new file mode 100644 index 000000000000..4acbcc7e29fe --- /dev/null +++ b/examples/with-grafbase/grafbase/schema.graphql @@ -0,0 +1,12 @@ +type Post @model { + id: ID! + title: String! + slug: String! @unique + comments: [Comment] +} + +type Comment @model { + id: ID! + message: String! + post: Post +} diff --git a/examples/with-grafbase/lib/grafbase.ts b/examples/with-grafbase/lib/grafbase.ts new file mode 100644 index 000000000000..d2cf5dc2f1ab --- /dev/null +++ b/examples/with-grafbase/lib/grafbase.ts @@ -0,0 +1,11 @@ +import { GraphQLClient } from 'graphql-request' +export { gql } from 'graphql-request' + +export const grafbase = new GraphQLClient( + process.env.GRAFBASE_API_URL as string, + { + headers: { + 'x-api-key': process.env.GRAFBASE_API_KEY as string, + }, + } +) diff --git a/examples/with-grafbase/next.config.js b/examples/with-grafbase/next.config.js new file mode 100644 index 000000000000..c73b884cee31 --- /dev/null +++ b/examples/with-grafbase/next.config.js @@ -0,0 +1,10 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + swcMinify: true, + experimental: { + appDir: true, + }, +} + +module.exports = nextConfig diff --git a/examples/with-grafbase/package.json b/examples/with-grafbase/package.json new file mode 100644 index 000000000000..4ada5b1a9baf --- /dev/null +++ b/examples/with-grafbase/package.json @@ -0,0 +1,29 @@ +{ + "private": true, + "scripts": { + "dev": "next dev", + "backend": "npx grafbase@latest dev & yarn codegen", + "build": "next build", + "start": "next start", + "codegen": "graphql-codegen --watch -r dotenv/config" + }, + "dependencies": { + "@types/node": "18.11.9", + "@types/react": "18.0.25", + "@types/react-dom": "18.0.8", + "graphql": "16.6.0", + "graphql-request": "5.0.0", + "next": "latest", + "react": "18.2.0", + "react-dom": "18.2.0", + "typescript": "4.8.4" + }, + "devDependencies": { + "@graphql-codegen/cli": "2.13.12", + "@graphql-codegen/client-preset": "1.1.3", + "@tailwindcss/typography": "0.5.8", + "autoprefixer": "10.4.13", + "postcss": "8.4.19", + "tailwindcss": "3.2.4" + } +} diff --git a/examples/with-grafbase/postcss.config.js b/examples/with-grafbase/postcss.config.js new file mode 100644 index 000000000000..33ad091d26d8 --- /dev/null +++ b/examples/with-grafbase/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/examples/with-grafbase/tailwind.config.js b/examples/with-grafbase/tailwind.config.js new file mode 100644 index 000000000000..d4b5c6122d0a --- /dev/null +++ b/examples/with-grafbase/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./app/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: {}, + }, + plugins: [require('@tailwindcss/typography')], +} diff --git a/examples/with-grafbase/tsconfig.json b/examples/with-grafbase/tsconfig.json new file mode 100644 index 000000000000..6ef5cd577f6e --- /dev/null +++ b/examples/with-grafbase/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}