-
Notifications
You must be signed in to change notification settings - Fork 991
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Support GraphQL Fragments with Apollo Client and Fragment Regis…
…try (#9140) This PR adds support to register GraphQL Fragments. It: * adds a fragmentRegistry to the Apollo client cache * Sets up graphql config to recognize fragments in VSCode * Generates possible types for Apollo client union type and interface support * Exposes a useRegisteredFragment to render cached data * New test app with fragments for future CI * Adds possible types to create redwood app templates * Document the possible types code gen * Documents use of fragments and the fragment registry helpers This is a WIP and first phase for easier fragment support. See the text fixture for some examples and the docs for usage. Still to code are use in mutations and updating the fragment in the client cache directly.
- Loading branch information
1 parent
6abe206
commit 2cbdf11
Showing
74 changed files
with
2,215 additions
and
19 deletions.
There are no files selected for viewing
102 changes: 102 additions & 0 deletions
102
__fixtures__/fragment-test-project/.redwood/schema.graphql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
18 changes: 18 additions & 0 deletions
18
__fixtures__/fragment-test-project/api/src/directives/requireAuth/requireAuth.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
}) | ||
}) |
25 changes: 25 additions & 0 deletions
25
__fixtures__/fragment-test-project/api/src/directives/requireAuth/requireAuth.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
10 changes: 10 additions & 0 deletions
10
__fixtures__/fragment-test-project/api/src/directives/skipAuth/skipAuth.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | ||
}) | ||
}) |
16 changes: 16 additions & 0 deletions
16
__fixtures__/fragment-test-project/api/src/directives/skipAuth/skipAuth.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
19
__fixtures__/fragment-test-project/api/src/functions/graphql.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
49 changes: 49 additions & 0 deletions
49
__fixtures__/fragment-test-project/api/src/graphql/groceries.sdl.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
14
__fixtures__/fragment-test-project/api/src/graphql/stalls.sdl.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
` |
Oops, something went wrong.