Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support GraphQL Fragments with Apollo Client and Fragment Registry #9140

Merged
merged 33 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1d7b303
Support GraphQL Fragments with Apollo Client with Auto Registration
dthyresson Sep 7, 2023
148c38d
Merge branch 'main' into dt-initial-gql-fragment-support
dthyresson Sep 8, 2023
df24cef
Export fragmentRegistry properly
dthyresson Sep 8, 2023
bf11925
Remove babel plugin for fragments
dthyresson Sep 11, 2023
7b2ff7d
Generate possible types as part of gql codegen
dthyresson Sep 12, 2023
d434f2f
Add types to fragment
dthyresson Sep 12, 2023
df22cb4
Test possible type generation
dthyresson Sep 12, 2023
05e752f
Adds test project for fragments with possible type generation
dthyresson Sep 13, 2023
f5d6143
fragment test project readme
dthyresson Sep 13, 2023
eb21c90
Make possible types pretty
dthyresson Sep 14, 2023
665bd7c
update `useRegisteredFragmentHook`
dthyresson Sep 14, 2023
570d7b3
Merge branch 'main' into dt-initial-gql-fragment-support
dthyresson Sep 14, 2023
cf69064
Uses fragment registry in fragment-test-project
dthyresson Sep 14, 2023
1feb474
Adds types
dthyresson Sep 14, 2023
1bb181e
Specify prettier format parser
dthyresson Sep 14, 2023
71869bc
renames FragmentIdentifier
dthyresson Sep 14, 2023
960422e
Comment the fragmentregistry
dthyresson Sep 14, 2023
e6823c5
Code comment possible types
dthyresson Sep 14, 2023
13ce79d
How to use possible types
dthyresson Sep 14, 2023
71b684d
Adds possible types to templates
dthyresson Sep 14, 2023
73db404
Merge branch 'main' into dt-initial-gql-fragment-support
dthyresson Sep 18, 2023
a03094a
Updates snapshot for new prettifying
dthyresson Sep 18, 2023
9614985
Document fragments first pass.
dthyresson Sep 18, 2023
821cc76
Merge branch 'main' into dt-initial-gql-fragment-support
dthyresson Sep 18, 2023
0c9f75f
Merge branch 'main' into dt-initial-gql-fragment-support
dthyresson Sep 18, 2023
7ae69ab
Added test fixture updates
dthyresson Sep 19, 2023
dda1055
Merge branch 'main' into dt-initial-gql-fragment-support
dthyresson Sep 19, 2023
cbf38a7
CRWA needs the stub of possibleTypes before graphql code gen happens
dthyresson Sep 19, 2023
d3b30eb
Adds a useCache hook to try out
dthyresson Sep 20, 2023
d9ca7cd
Documents useCache
dthyresson Sep 21, 2023
525e0b5
Merge branch 'main' into dt-initial-gql-fragment-support
dthyresson Sep 21, 2023
9062978
Fixes broken links in graphql docs.
dthyresson Sep 21, 2023
2050d25
Updates CRWA snapshot for test
dthyresson Sep 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions __fixtures__/fragment-test-project/.redwood/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""
Use to check whether or not a user is authenticated and is associated
with an optional set of roles.
"""
directive @requireAuth(roles: [String]) on FIELD_DEFINITION

"""Use to skip authentication checks and allow public access."""
directive @skipAuth on FIELD_DEFINITION

scalar BigInt

scalar Date

scalar DateTime

type Fruit implements Grocery {
id: ID!

"""Seedless is only for fruits"""
isSeedless: Boolean
name: String!
nutrients: String
price: Int!
quantity: Int!
region: String!

"""Ripeness is only for fruits"""
ripenessIndicators: String
stall: Stall!
}

union Groceries = Fruit | Vegetable

interface Grocery {
id: ID!
name: String!
nutrients: String
price: Int!
quantity: Int!
region: String!
stall: Stall!
}

scalar JSON

scalar JSONObject

"""About the Redwood queries."""
type Query {
fruitById(id: ID!): Fruit
fruits: [Fruit!]!
groceries: [Groceries!]!

"""Fetches the Redwood root schema."""
redwood: Redwood
stallById(id: ID!): Stall
stalls: [Stall!]!
vegetableById(id: ID!): Vegetable
vegetables: [Vegetable!]!
}

"""
The RedwoodJS Root Schema

Defines details about RedwoodJS such as the current user and version information.
"""
type Redwood {
"""The current user."""
currentUser: JSON

"""The version of Prisma."""
prismaVersion: String

"""The version of Redwood."""
version: String
}

type Stall {
fruits: [Fruit]
id: ID!
name: String!
stallNumber: String!
vegetables: [Vegetable]
}

scalar Time

type Vegetable implements Grocery {
id: ID!

"""Pickled is only for vegetables"""
isPickled: Boolean
name: String!
nutrients: String
price: Int!
quantity: Int!
region: String!
stall: Stall!

"""Veggie Family is only for vegetables"""
vegetableFamily: String
}
4 changes: 4 additions & 0 deletions __fixtures__/fragment-test-project/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Project to Test GraphQL Fragments

* Has unions and interfaces to test: https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces
* Generates "possible types" for Apollo Client, see: https://www.apollographql.com/docs/react/data/fragments/#defining-possibletypes-manually
36 changes: 36 additions & 0 deletions __fixtures__/fragment-test-project/api/db/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}

generator client {
provider = "prisma-client-js"
binaryTargets = "native"
}

model Stall {
id String @id @default(cuid())
name String
stallNumber String @unique
produce Produce[]
}

model Produce {
id String @id @default(cuid())
name String @unique
quantity Int
price Int
nutrients String?
region String
/// Available only for fruits
isSeedless Boolean?
/// Available only for fruits
ripenessIndicators String?
/// Available only for vegetables
vegetableFamily String?
/// Available only for vegetables
isPickled Boolean?
stall Stall @relation(fields: [stallId], references: [id], onDelete: Cascade)
stallId String
}
8 changes: 8 additions & 0 deletions __fixtures__/fragment-test-project/api/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// More info at https://redwoodjs.com/docs/project-configuration-dev-test-build

const config = {
rootDir: '../',
preset: '@redwoodjs/testing/config/jest/api',
}

module.exports = config
9 changes: 9 additions & 0 deletions __fixtures__/fragment-test-project/api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "api",
"version": "0.0.0",
"private": true,
"dependencies": {
"@redwoodjs/api": "6.2.0",
"@redwoodjs/graphql-server": "6.2.0"
}
}
52 changes: 52 additions & 0 deletions __fixtures__/fragment-test-project/api/server.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* This file allows you to configure the Fastify Server settings
* used by the RedwoodJS dev server.
*
* It also applies when running RedwoodJS with `yarn rw serve`.
*
* For the Fastify server options that you can set, see:
* https://www.fastify.io/docs/latest/Reference/Server/#factory
*
* Examples include: logger settings, timeouts, maximum payload limits, and more.
*
* Note: This configuration does not apply in a serverless deploy.
*/

/** @type {import('fastify').FastifyServerOptions} */
const config = {
requestTimeout: 15_000,
logger: {
// Note: If running locally using `yarn rw serve` you may want to adjust
// the default non-development level to `info`
level: process.env.NODE_ENV === 'development' ? 'debug' : 'warn',
},
}

/**
* You can also register Fastify plugins and additional routes for the API and Web sides
* in the configureFastify function.
*
* This function has access to the Fastify instance and options, such as the side
* (web, api, or proxy) that is being configured and other settings like the apiRootPath
* of the functions endpoint.
*
* Note: This configuration does not apply in a serverless deploy.
*/

/** @type {import('@redwoodjs/api-server/dist/types').FastifySideConfigFn} */
const configureFastify = async (fastify, options) => {
if (options.side === 'api') {
fastify.log.trace({ custom: { options } }, 'Configuring api side')
}

if (options.side === 'web') {
fastify.log.trace({ custom: { options } }, 'Configuring web side')
}

return fastify
}

module.exports = {
config,
configureFastify,
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { mockRedwoodDirective, getDirectiveName } from '@redwoodjs/testing/api'

import requireAuth from './requireAuth'

describe('requireAuth directive', () => {
it('declares the directive sdl as schema, with the correct name', () => {
expect(requireAuth.schema).toBeTruthy()
expect(getDirectiveName(requireAuth.schema)).toBe('requireAuth')
})

it('requireAuth has stub implementation. Should not throw when current user', () => {
// If you want to set values in context, pass it through e.g.
// mockRedwoodDirective(requireAuth, { context: { currentUser: { id: 1, name: 'Lebron McGretzky' } }})
const mockExecution = mockRedwoodDirective(requireAuth, { context: {} })

expect(mockExecution).not.toThrowError()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import gql from 'graphql-tag'

import type { ValidatorDirectiveFunc } from '@redwoodjs/graphql-server'
import { createValidatorDirective } from '@redwoodjs/graphql-server'

import { requireAuth as applicationRequireAuth } from 'src/lib/auth'

export const schema = gql`
"""
Use to check whether or not a user is authenticated and is associated
with an optional set of roles.
"""
directive @requireAuth(roles: [String]) on FIELD_DEFINITION
`

type RequireAuthValidate = ValidatorDirectiveFunc<{ roles?: string[] }>

const validate: RequireAuthValidate = ({ directiveArgs }) => {
const { roles } = directiveArgs
applicationRequireAuth({ roles })
}

const requireAuth = createValidatorDirective(schema, validate)

export default requireAuth
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { getDirectiveName } from '@redwoodjs/testing/api'

import skipAuth from './skipAuth'

describe('skipAuth directive', () => {
it('declares the directive sdl as schema, with the correct name', () => {
expect(skipAuth.schema).toBeTruthy()
expect(getDirectiveName(skipAuth.schema)).toBe('skipAuth')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import gql from 'graphql-tag'

import { createValidatorDirective } from '@redwoodjs/graphql-server'

export const schema = gql`
"""
Use to skip authentication checks and allow public access.
"""
directive @skipAuth on FIELD_DEFINITION
`

const skipAuth = createValidatorDirective(schema, () => {
return
})

export default skipAuth
19 changes: 19 additions & 0 deletions __fixtures__/fragment-test-project/api/src/functions/graphql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { createGraphQLHandler } from '@redwoodjs/graphql-server'

import directives from 'src/directives/**/*.{js,ts}'
import sdls from 'src/graphql/**/*.sdl.{js,ts}'
import services from 'src/services/**/*.{js,ts}'

import { db } from 'src/lib/db'
import { logger } from 'src/lib/logger'

export const handler = createGraphQLHandler({
loggerConfig: { logger, options: {} },
directives,
sdls,
services,
onException: () => {
// Disconnect from your database with an unhandled exception.
db.$disconnect()
},
})
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export const schema = gql`
interface Grocery {
id: ID!
name: String!
quantity: Int!
price: Int!
nutrients: String
stall: Stall!
region: String!
}

type Fruit implements Grocery {
id: ID!
name: String!
quantity: Int!
price: Int!
nutrients: String
stall: Stall!
region: String!
"Seedless is only for fruits"
isSeedless: Boolean
"Ripeness is only for fruits"
ripenessIndicators: String
}

type Vegetable implements Grocery {
id: ID!
name: String!
quantity: Int!
price: Int!
nutrients: String
stall: Stall!
region: String!
"Veggie Family is only for vegetables"
vegetableFamily: String
"Pickled is only for vegetables"
isPickled: Boolean
}

union Groceries = Fruit | Vegetable

type Query {
groceries: [Groceries!]! @skipAuth
fruits: [Fruit!]! @skipAuth
fruitById(id: ID!): Fruit @skipAuth
vegetables: [Vegetable!]! @skipAuth
vegetableById(id: ID!): Vegetable @skipAuth
}
`
14 changes: 14 additions & 0 deletions __fixtures__/fragment-test-project/api/src/graphql/stalls.sdl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const schema = gql`
type Stall {
id: ID!
stallNumber: String!
name: String!
fruits: [Fruit]
vegetables: [Vegetable]
}

type Query {
stalls: [Stall!]! @skipAuth
stallById(id: ID!): Stall @skipAuth
}
`
Loading
Loading