Repository: prismagraphql/graphql-middleware
Branch: master
Commit: ed63d0da5e45
Files: 39
Total size: 75.0 KB
Directory structure:
gitextract_ihhyes_m/
├── .github/
│ ├── FUNDING.yml
│ ├── stale.yml
│ └── workflows/
│ ├── publish.yml
│ └── test.yml
├── .gitignore
├── .huskyrc.json
├── .vscode/
│ ├── launch.json
│ └── settings.json
├── LICENSE
├── README.md
├── bob-esbuild.config.ts
├── examples/
│ ├── logging/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── package.json
│ └── permissions/
│ ├── README.md
│ ├── index.js
│ └── package.json
├── jest.config.js
├── package.json
├── prettier.config.js
├── renovate.json
├── src/
│ ├── applicator.ts
│ ├── constructors.ts
│ ├── fragments.ts
│ ├── generator.ts
│ ├── index.ts
│ ├── middleware.ts
│ ├── types.ts
│ ├── utils.ts
│ └── validation.ts
├── tests/
│ ├── core.test.ts
│ ├── execution.test.ts
│ ├── fragments.test.ts
│ ├── generator.test.ts
│ ├── immutability.test.ts
│ ├── integration.test.ts
│ └── validation.test.ts
├── tsconfig.json
└── tsconfig.test.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: maticzav
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/stale.yml
================================================
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 45
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 10
# Issues with these labels will never be considered stale
exemptLabels:
- kind/feature
- reproduction-available
- help-wanted
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
# Limit to only `issues`
only: issues
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish
on:
push:
branches:
- master
jobs:
build:
name: release
runs-on: ubuntu-latest
steps:
# Setup
- uses: actions/checkout@master
- uses: actions/setup-node@v1
with:
node-version: '14.x'
- run: npm i -g pnpm@7
- run: pnpm i
# Publish
- run: pnpm compile
- run: pnpm test
env:
GH_TOKEN: ${{ secrets.GH_SPONSORS_TOKEN }}
- run: pnpm coverage
# This alias makes semantic-release to use pnpm when publishing
# Which adds support for "publishConfig.directory"
- run: alias npm=pnpm
- run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on: [push, pull_request]
jobs:
build:
name: release
runs-on: ubuntu-latest
steps:
# Setup
- uses: actions/checkout@master
- uses: actions/setup-node@v1
with:
node-version: '14.x'
- run: npm i -g pnpm@7
- run: pnpm install
# Test
- run: pnpm compile
- run: pnpm test
env:
GH_TOKEN: ${{ secrets.GH_SPONSORS_TOKEN }}
- run: pnpm coverage
================================================
FILE: .gitignore
================================================
node_modules
dist
.idea
.DS_Store
*.log*
package-lock.json
coverage
================================================
FILE: .huskyrc.json
================================================
{
"hooks": {
"pre-commit": "pretty-quick --staged"
}
}
================================================
FILE: .vscode/launch.json
================================================
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "JS examples debug",
"program": "${workspaceFolder}/examples/authorization/index.js",
"skipFiles": ["<node_internals>/**/*.js"]
},
{
"type": "node",
"request": "launch",
"name": "AVA",
"program": "${workspaceRoot}/node_modules/ava/profile.js",
"args": ["--serial", "${file}"],
"skipFiles": ["<node_internals>/**/*.js"]
},
{
"type": "node",
"request": "launch",
"name": "Current TS File",
"args": ["${relativeFile}"],
"runtimeArgs": ["-r", "ts-node/register"],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
}
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"eslint.enable": false,
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/node_modules/**": true
},
"typescript.tsdk": "./node_modules/typescript/lib",
"spellright.language": ["en"],
"spellright.documentTypes": ["plaintext"],
"editor.formatOnSave": true
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Matic Zavadlal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<p align="center"><img src="media/logo.png" width="150" /></p>
# graphql-middleware
[](https://github.com/maticzav/graphql-middleware/actions)
[](https://codecov.io/gh/maticzav/graphql-middleware)
[](https://badge.fury.io/js/graphql-middleware)
Split up your GraphQL resolvers in middleware functions.
## Overview
GraphQL Middleware is a schema wrapper which allows you to manage additional functionality across multiple resolvers efficiently.
- 💡 **Easy to use:** An intuitive, yet familiar API that you will pick up in a second.
- 💪 **Powerful:** Allows complete control over your resolvers (Before, After).
- 🌈 **Compatible:** Works with any GraphQL Schema.
> **NOTE:** As of 3.0.0 `graphql-middleware` no longer wraps introspection queries.
> **NOTE:** As of 5.0.0 `graphql-middleware` no longer supports GraphQL Yoga out of the box. We might bring back the support if the library becomes maintained again. We are keeping the docs as the reference for older versions.
## Install
```sh
yarn add graphql-middleware
```
## How does it work
GraphQL Middleware lets you run arbitrary code before or after a resolver is invoked. It improves your code structure by enabling code reuse and a clear separation of concerns.
```ts
const { ApolloServer } = require('apollo-server')
const { makeExecutableSchema } = require('@graphql-tools/schema')
const typeDefs = `
type Query {
hello(name: String): String
bye(name: String): String
}
`
const resolvers = {
Query: {
hello: (root, args, context, info) => {
console.log(`3. resolver: hello`)
return `Hello ${args.name ? args.name : 'world'}!`
},
bye: (root, args, context, info) => {
console.log(`3. resolver: bye`)
return `Bye ${args.name ? args.name : 'world'}!`
},
},
}
const logInput = async (resolve, root, args, context, info) => {
console.log(`1. logInput: ${JSON.stringify(args)}`)
const result = await resolve(root, args, context, info)
console.log(`5. logInput`)
return result
}
const logResult = async (resolve, root, args, context, info) => {
console.log(`2. logResult`)
const result = await resolve(root, args, context, info)
console.log(`4. logResult: ${JSON.stringify(result)}`)
return result
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
const schemaWithMiddleware = applyMiddleware(schema, logInput, logResult)
const server = new ApolloServer({
schema: schemaWithMiddleware,
})
await server.listen({ port: 8008 })
```
Execution of the middleware and resolver functions follow the "onion"-principle, meaning each middleware function adds a layer before and after the actual resolver invocation.
<p align="center"><img src="media/idea.png" /></p>
> The order of the middleware functions in the middlewares array is important. The first resolver is the "most-outer" layer, so it gets executed first and last. The second resolver is the "second-outer" layer, so it gets executed second and second to last... And so forth.
> You can read more about GraphQL Middleware in this fantastic [article](https://www.prisma.io/blog/graphql-middleware-zie3iphithxy/).
## Standalone usage
```ts
const { ApolloServer } = require('apollo-server')
const { makeExecutableSchema } = require('@graphql-tools/schema')
// Minimal example middleware (before & after)
const beepMiddleware = {
Query: {
hello: async (resolve, parent, args, context, info) => {
// You can use middleware to override arguments
const argsWithDefault = { name: 'Bob', ...args }
const result = await resolve(parent, argsWithDefault, context, info)
// Or change the returned values of resolvers
return result.replace(/Trump/g, 'beep')
},
},
}
const typeDefs = `
type Query {
hello(name: String): String
}
`
const resolvers = {
Query: {
hello: (parent, { name }, context) => `Hello ${name ? name : 'world'}!`,
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
const schemaWithMiddleware = applyMiddleware(
schema,
metricsMiddleware,
authMiddleware,
beepMiddleware,
)
const server = new ApolloServer({
schema: schemaWithMiddleware,
})
await server.listen({ port: 8008 })
```
### Usage with `graphql-yoga`
> `graphql-yoga` has built-in support for `graphql-middleware`!
```ts
import { GraphQLServer } from 'graphql-yoga'
import { authMiddleware, metricsMiddleware } from './middleware'
const typeDefs = `
type Query {
hello(name: String): String
}
`
const resolvers = {
Query: {
hello: (parent, { name }, context) => `Hello ${name ? name : 'world'}!`,
},
}
const server = new GraphQLServer({
typeDefs,
resolvers,
middlewares: [authMiddleware, metricsMiddleware],
documentMiddleware: [],
})
server.start(() => console.log('Server is running on localhost:4000'))
```
## Awesome Middlewares [](https://awesome.re)
- [graphql-shield](https://github.com/maticzav/graphql-shield) - Permissions as another layer of abstraction.
- [graphql-middleware-apollo-upload-server](http://github.com/homeroom-live/graphql-middleware-apollo-upload-server) - Uploading files is hard, that's why this package manages it for you!
- [graphql-middleware-sentry](https://github.com/maticzav/graphql-middleware-sentry) - Report your server errors to Sentry.
- [graphql-middleware-forward-binding](https://github.com/maticzav/graphql-middleware-forward-binding) - GraphQL Binding forwardTo plugin for GraphQL Middleware.
- [graphql-yup-middleware](https://github.com/JCMais/graphql-yup-middleware) - Use yup to validate mutation arguments
- [graphql-pino-middleware](https://github.com/addityasingh/graphql-pino-middleware) - GraphQL middleware to augment resolvers with pino logger
- [graphql-lightstep-middleware](https://github.com/addityasingh/graphql-lightstep-middleware) - GraphQL middleware to instrument resolvers with `lightstep` traces
- [graphql-filter](https://github.com/hata6502/graphql-filter) - A GraphQL middleware to filter output data.
## API
A middleware is a resolver function that wraps another resolver function.
```ts
export declare type IMiddlewareResolver<
TSource = any,
TContext = any,
TArgs = any
> = (
resolve: Function,
parent: TSource,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo,
) => Promise<any>
export interface IMiddlewareWithOptions<
TSource = any,
TContext = any,
TArgs = any
> {
fragment?: IMiddlewareFragment
fragments?: IMiddlewareFragment[]
resolve?: IMiddlewareResolver<TSource, TContext, TArgs>
}
export type IMiddlewareFunction<TSource = any, TContext = any, TArgs = any> =
| IMiddlewareWithOptions<TSource, TContext, TArgs>
| IMiddlewareResolver<TSource, TContext, TArgs>
interface IMiddlewareTypeMap {
[key: string]: IMiddlewareFunction | IMiddlewareFieldMap
}
interface IMiddlewareFieldMap {
[key: string]: IMiddlewareFunction
}
type IMiddleware = IMiddlewareFunction | IMiddlewareTypeMap
function middleware(
generator: (schema: GraphQLSchema) => IMiddleware,
): MiddlewareGenerator
function applyMiddleware(
schema: GraphQLSchema,
...middleware: (IMiddleware | MiddlewareGenerator)[]
): GraphQLSchema & {
schema?: GraphQLSchema
fragmentReplacements?: FragmentReplacement[]
}
/**
* Applies middleware to a schema like `applyMiddleware` but only applies the
* middleware to fields that have non-default resolvers. This method can be
* useful if you want to report performance of only non-trivial methods.
*/
function applyMiddlewareToDeclaredResolvers(
schema: GraphQLSchema,
...middleware: (IMiddleware | MiddlewareGenerator)[]
): GraphQLSchema & {
schema?: GraphQLSchema
fragmentReplacements?: FragmentReplacement[]
}
```
### Middleware Generator
In some cases, your middleware could depend on how your schema looks. In such situations, you can turn your middleware into a middleware generator. Middleware generators are denoted with function `middleware` and receive `schema` as the first argument.
```ts
const schemaDependentMiddleware = middleware((schema) => {
return generateMiddlewareFromSchema(schema)
})
const schemaWithMiddleware = applyMiddleware(
schema,
schemaDependentMiddleware,
someOtherOptionalMiddleware,
etc,
)
```
### Middleware Fragments
Fragments are a way of expressing what information your resolver requires to make sure it can execute correctly. They are primarily used in schema forwarding when the client might not always request all the fields your resolver demands. Because of that, we need to provide a way of telling what other information we need from a remote schema and that's why we use fragments.
You can read more about fragments in the [`graphql-binding`](https://github.com/graphql-binding/graphql-binding) repository and on [`graphql-tools`](https://www.apollographql.com/docs/graphql-tools/schema-transforms.html#Other) documentation website.
GraphQL Middleware provides a convenient way to quickly and easily add fragments to your middleware. This might turn out particularly useful when your middleware depends on resolver data.
We've made fragments extremely flexible by using the general API which, if you have ever run over fragments, you probably already know.
```ts
// Schema wide - gets applied to every field.
const middlewareWithFragments = {
fragment: `fragment NodeID on Node { id }`,
resolve: (resolve, { id }, args, ctx, info) => {
const foo = doSomethingWithID(id)
return resolve(foo)
},
}
// Type wide - gets applied to every field of certain type.
const middlewareWithFragments = {
Query: {
fragment: `fragment NodeID on Node { id }`,
resolve: (resolve, { id }, args, ctx, info) => {
const foo = doSomethingWithID(id)
return resolve(foo)
},
},
Mutation: {
fragments: [
`fragment NodeID on Node { id }`,
`fragment NodeSecret on Node { secret }`,
],
resolve: (resolve, parent, args, ctx, info) => {
return resolve(parent, customArgs)
},
},
}
// Field scoped - gets applied to particular field.
const middlewareWithFragments = {
Query: {
node: {
fragment: `fragment NodeID on Node { id }`,
resolve: (resolve, { id }, args, ctx, info) => {
const foo = doSomethingWithID(id)
return resolve(foo)
},
},
books: (resolve, parent, args, ctx, info) => {
return resolve(parent, customArgs)
},
},
}
const { schema, fragmentReplacements } = applyMiddleware(
schema,
middlewareWithFragments,
someOtherMiddleware,
)
```
> `graphql-middleware` automatically merges fragments from multiple middlewares if possible. Otherwise, validation function throws an error.
## GraphQL Middleware Use Cases
- Logging
- Metrics
- Input sanitisation
- Performance measurement
- Authorization
- Caching
- Tracing
## FAQ
### Can I use GraphQL Middleware without GraphQL Yoga?
Yes. Nevertheless, we encourage you to use it in combination with Yoga. Combining the power of `middlewares` that GraphQL Middleware offers, with `documentMiddleware` which Yoga exposes, gives you unparalleled control over the execution of your queries.
### How does GraphQL Middleware compare to `directives`?
GraphQL Middleware and `directives` tackle the same problem in a completely different way. GraphQL Middleware allows you to implement all your middleware logic in your code, whereas directives encourage you to mix schema with your functionality.
### Should I modify the context using GraphQL Middleware?
GraphQL Middleware allows you to modify the context of your resolvers, but we encourage you to use GraphQL Yoga's `documentMiddleware` for this functionality instead.
## Thank you
Thanks to everyone who supported the development of this project. It's an honor to lead a project that helps so many people.
- [Prisma](http://github.com/prisma) - for sponsoring the project,
- Johannes Schickling - for guiding the project development, and
- everyone else who personally contributed to the project in one way or another.
Thank you! :heart:
================================================
FILE: bob-esbuild.config.ts
================================================
export const config: import('bob-esbuild').BobConfig = {
tsc: {
dirs: ['.'],
},
distDir: 'dist',
verbose: true,
singleBuild: true,
}
================================================
FILE: examples/logging/README.md
================================================
# GraphQL Middleware - Logging example
This example illustrates basic usage of GraphQL Middleware. The idea is to log every field that has been requested by user.
## Code
> Mind the following parts
### Import
This is where we import `graphql-middleware`.
```js
const { applyMiddleware } = require('graphql-middleware')
```
### Middleware
Because we want every field of our schema to make a log once it's requested, we use `schema` wide middleware definition.
```js
const logMiddleware = async (resolve, parent, args, ctx, info) => {
console.log(args, info)
return resolve()
}
```
### Applying middleware
This is the part where we modify the schema to reflect the changed middleware introduce.
```js
const analysedSchema = applyMiddleware(schema, logMiddleware)
```
## License
MIT
================================================
FILE: examples/logging/index.js
================================================
const { GraphQLServer } = require('graphql-yoga')
// Schema
const typeDefs = `
type Query {
hello(name: String): String!
bye(name: String): String!
}
`
const resolvers = {
Query: {
hello: (_, { name }) => {
throw new Error('No hello!')
},
bye: (_, { name }) => `Bye ${name || 'World'}`,
},
}
// Middleware
const logMiddleware = async (resolve, parent, args, ctx, info) => {
try {
const res = await resolve()
return res
} catch (e) {
console.log(e)
}
}
const server = new GraphQLServer({
typeDefs: typeDefs,
resolvers: resolvers,
middlewares: [logMiddleware],
context: req => ({ ...req }),
})
server.start(() => console.log('Server is running on http://localhost:4000'))
================================================
FILE: examples/logging/package.json
================================================
{
"name": "logging",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"graphql-yoga": "latest"
}
}
================================================
FILE: examples/permissions/README.md
================================================
# GraphQL Middleware - Permissions example
This example illustrates how to use GraphQL Middleware to handle user permissions. Do take into consideration that this is a low level implementation with no optimizations. We recommend using `graphql-shield` for production usage.
## Code
> Mind the following parts
### Import
This is where we import `graphql-middleware`.
```js
const { applyMiddleware } = require('graphql-middleware')
```
### Permission function
This is where we decide whether the user should or shouldn't access the information. The following implementation preassumes that the secret is passed as the query header using `Authorization: <token>` format.
```js
const isLoggedIn = async (resolve, parent, args, ctx, info) => {
// Include your agent code as Authorization: <token> header.
const permit = ctx.request.get('Authorization') === code
if (!permit) {
throw new Error(`Not authorised!`)
}
return resolve()
}
```
### Middleware
The following middleware implements one field-scoped and one type-scoped middleware. We use `field` scoped middleware with `secured` field to ensure only `secured` field of `Query` requires authorisation. Furthermore, we also use `type` middleware to make sure every field of `Me` type requires authorisation.
There is no need to apply permissions to `me` field of `Query` as requesting any of type `Me` fields already requires authentication.
```js
const permissions = {
Query: {
secured: isLoggedIn,
},
Me: isLoggedIn,
}
```
### Applying middleware
This is the part where we modify the schema to reflect the changed middleware introduce.
```js
const protectedSchema = applyMiddleware(schema, permissions)
```
## License
MIT
================================================
FILE: examples/permissions/index.js
================================================
const { GraphQLServer } = require('graphql-yoga')
// Schema
const code = 'supersecret'
const typeDefs = `
type Query {
open: String!
secured: String!
me: Me!
}
type Me {
name: String!
surname: String!
age: Int!
}
`
const resolvers = {
Query: {
open: () => `Open data, everyone's welcome!`,
secured: () => `Personal diary - this is for my eyes only!`,
me: () => ({}),
},
Me: {
name: () => 'Ben',
surname: () => 'Cool',
age: () => 18,
},
}
// Middleware - Permissions
const isLoggedIn = async (resolve, parent, args, ctx, info) => {
// Include your agent code as Authorization: <token> header.
const permit = ctx.request.get('Authorization') === code
if (!permit) {
throw new Error(`Not authorised!`)
}
return resolve()
}
const permissions = {
Query: {
secured: isLoggedIn,
},
Me: isLoggedIn,
}
// Server
const server = new GraphQLServer({
typeDefs: typeDefs,
resolvers: resolvers,
middlewares: [permissions],
context: req => ({ ...req }),
})
server.start(() => console.log('Server is running on http://localhost:4000'))
================================================
FILE: examples/permissions/package.json
================================================
{
"name": "permissions",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"graphql-yoga": "latest"
}
}
================================================
FILE: jest.config.js
================================================
module.exports = {
roots: ['<rootDir>/tests', '<rootDir>/src'],
testEnvironment: 'node',
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
testPathIgnorePatterns: ['/node_modules/', '/__fixtures__/'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
collectCoverage: true,
collectCoverageFrom: [
'**/*.{ts,tsx}',
'!**/node_modules/**',
'!**/vendor/**',
'!**/generated/**',
],
verbose: true,
coverageDirectory: './coverage',
coverageReporters: ['json', 'lcov', 'text', 'clover', 'html'],
globals: {
'ts-jest': {
tsconfig: 'tsconfig.test.json',
},
},
}
================================================
FILE: package.json
================================================
{
"name": "graphql-middleware",
"description": "GraphQL Middleware done right!",
"version": "0.0.0-semantic-release",
"main": "dist/index.js",
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"typescript": {
"definition": "dist/index.d.ts"
},
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
},
"./*": {
"require": "./dist/*.js",
"import": "./dist/*.mjs",
"types": "./dist/*.d.ts"
}
},
"publishConfig": {
"directory": "dist"
},
"author": "Matic Zavadlal <matic.zavadlal@gmail.com>",
"dependencies": {
"@graphql-tools/delegate": "^8.8.1",
"@graphql-tools/schema": "^8.5.1"
},
"devDependencies": {
"@types/jest": "^26.0.24",
"@types/lodash": "^4.14.182",
"@types/node":"^18.8.3",
"apollo-server": "^3.10.2",
"axios": "^0.27.2",
"bob-esbuild": "^1.3.0",
"bob-esbuild-cli": "^1.0.1",
"codecov": "^3.8.3",
"graphql": "^16.6.0",
"husky": "^6.0.0",
"iterall": "^1.3.0",
"jest": "^26.6.3",
"prettier": "^2.7.1",
"pretty-quick": "^3.1.3",
"rimraf": "^3.0.2",
"semantic-release": "^17.4.7",
"ts-jest": "^26.5.6",
"ts-node": "^9.1.1",
"typescript": "^4.7.4"
},
"peerDependencies": {
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
},
"scripts": {
"clean": "rimraf dist",
"compile": "bob-esbuild build",
"coverage": "codecov",
"test": "jest",
"prepublishOnly": "npm run compile"
},
"files": [
"dist"
],
"release": {
"branch": "master"
},
"homepage": "https://github.com/maticzav/graphql-middleware",
"repository": {
"type": "git",
"url": "https://github.com/maticzav/graphql-middleware.git"
},
"bugs": {
"url": "https://github.com/maticzav/graphql-middleware/issues"
},
"keywords": [
"graphql",
"middleware",
"schema",
"resolvers",
"server",
"yoga"
],
"license": "MIT"
}
================================================
FILE: prettier.config.js
================================================
module.exports = {
semi: false,
trailingComma: 'all',
singleQuote: true,
}
================================================
FILE: renovate.json
================================================
{
"extends": ["config:base", "docker:disable", ":automergeMinor"],
"rangeStrategy": "bump",
"pathRules": [
{
"paths": ["examples/**"],
"extends": [":semanticCommitTypeAll(chore)", ":automergeMinor"],
"branchName": "{{branchPrefix}}examples-{{depNameSanitized}}-{{newVersionMajor}}.x"
}
]
}
================================================
FILE: src/applicator.ts
================================================
import {
GraphQLObjectType,
GraphQLFieldResolver,
GraphQLField,
GraphQLSchema,
defaultFieldResolver,
isIntrospectionType,
GraphQLArgument,
} from 'graphql'
import {
IMiddlewareFunction,
IMiddlewareResolver,
IMiddlewareFieldMap,
IApplyOptions,
IMiddleware,
IResolvers,
IResolverOptions,
} from './types'
import {
isMiddlewareFunction,
isGraphQLObjectType,
isMiddlewareResolver,
isMiddlewareWithFragment,
} from './utils'
// Applicator
function wrapResolverInMiddleware<TSource, TContext, TArgs>(
resolver: GraphQLFieldResolver<any, any, any>,
middleware: IMiddlewareResolver<TSource, TContext, TArgs>,
): GraphQLFieldResolver<any, any, any> {
return (parent, args, ctx, info) =>
middleware(
(_parent = parent, _args = args, _ctx = ctx, _info = info) =>
resolver(_parent, _args, _ctx, _info),
parent,
args,
ctx,
info,
)
}
function parseField(
field: GraphQLField<any, any, any> & { isDeprecated?: boolean },
) {
const { isDeprecated, ...restData } = field
const argsMap = field.args.reduce(
(acc, cur) => {
acc[cur.name] = cur;
return acc;
}, {} as Record<string, GraphQLArgument>,
)
return {
...restData,
args: argsMap,
}
}
function applyMiddlewareToField<TSource, TContext, TArgs>(
field: GraphQLField<any, any, any>,
options: IApplyOptions,
middleware: IMiddlewareFunction<TSource, TContext, TArgs>,
): IResolverOptions {
const parsedField = parseField(field)
if (
isMiddlewareWithFragment(middleware) &&
parsedField.resolve &&
parsedField.resolve !== defaultFieldResolver
) {
return {
...parsedField,
fragment: middleware.fragment,
fragments: middleware.fragments,
resolve: wrapResolverInMiddleware(
parsedField.resolve,
middleware.resolve,
),
}
} else if (isMiddlewareWithFragment(middleware) && parsedField.subscribe) {
return {
...parsedField,
fragment: middleware.fragment,
fragments: middleware.fragments,
subscribe: wrapResolverInMiddleware(
parsedField.subscribe,
middleware.resolve,
),
}
} else if (
isMiddlewareResolver(middleware) &&
parsedField.resolve &&
parsedField.resolve !== defaultFieldResolver
) {
return {
...parsedField,
resolve: wrapResolverInMiddleware(parsedField.resolve, middleware),
}
} else if (isMiddlewareResolver(middleware) && parsedField.subscribe) {
return {
...parsedField,
subscribe: wrapResolverInMiddleware(parsedField.subscribe, middleware),
}
} else if (
isMiddlewareWithFragment(middleware) &&
!options.onlyDeclaredResolvers
) {
return {
...parsedField,
fragment: middleware.fragment,
fragments: middleware.fragments,
resolve: wrapResolverInMiddleware(
defaultFieldResolver,
middleware.resolve,
),
}
} else if (
isMiddlewareResolver(middleware) &&
!options.onlyDeclaredResolvers
) {
return {
...parsedField,
resolve: wrapResolverInMiddleware(defaultFieldResolver, middleware),
}
} else {
return { ...parsedField, resolve: defaultFieldResolver }
}
}
function applyMiddlewareToType<TSource, TContext, TArgs>(
type: GraphQLObjectType,
options: IApplyOptions,
middleware:
| IMiddlewareFunction<TSource, TContext, TArgs>
| IMiddlewareFieldMap<TSource, TContext, TArgs>,
): IResolvers {
const fieldMap = type.getFields()
if (isMiddlewareFunction(middleware)) {
const resolvers = Object.keys(fieldMap).reduce(
(resolvers, fieldName) => {
resolvers[fieldName] = applyMiddlewareToField(
fieldMap[fieldName],
options,
middleware as IMiddlewareFunction<TSource, TContext, TArgs>,
);
return resolvers;
},
{},
)
return resolvers
} else {
const resolvers = Object.keys(middleware).reduce(
(resolvers, fieldName) => {
resolvers[fieldName] = applyMiddlewareToField(
fieldMap[fieldName],
options,
middleware[fieldName],
);
return resolvers;
},
{},
)
return resolvers
}
}
function applyMiddlewareToSchema<TSource, TContext, TArgs>(
schema: GraphQLSchema,
options: IApplyOptions,
middleware: IMiddlewareFunction<TSource, TContext, TArgs>,
): IResolvers {
const typeMap = schema.getTypeMap()
const resolvers = Object.keys(typeMap)
.filter(
(type) =>
isGraphQLObjectType(typeMap[type]) &&
!isIntrospectionType(typeMap[type]),
)
.reduce(
(resolvers, type) => {
resolvers[type] = applyMiddlewareToType(
typeMap[type] as GraphQLObjectType,
options,
middleware,
);
return resolvers;
},
{},
)
return resolvers
}
// Generator
export function generateResolverFromSchemaAndMiddleware<
TSource,
TContext,
TArgs,
>(
schema: GraphQLSchema,
options: IApplyOptions,
middleware: IMiddleware<TSource, TContext, TArgs>,
): IResolvers {
if (isMiddlewareFunction(middleware)) {
return applyMiddlewareToSchema(
schema,
options,
middleware as IMiddlewareFunction<TSource, TContext, TArgs>,
)
} else {
const typeMap = schema.getTypeMap()
const resolvers = Object.keys(middleware).reduce(
(resolvers, type) => {
resolvers[type] = applyMiddlewareToType(
typeMap[type] as GraphQLObjectType,
options,
middleware[type],
);
return resolvers;
},
{},
)
return resolvers
}
}
================================================
FILE: src/constructors.ts
================================================
import { MiddlewareGenerator } from './generator'
import { IMiddlewareGenerator, IMiddlewareGeneratorConstructor } from './types'
export function middleware<TSource = any, TContext = any, TArgs = any>(
generator: IMiddlewareGeneratorConstructor<TSource, TContext, TArgs>,
): IMiddlewareGenerator<TSource, TContext, TArgs> {
return new MiddlewareGenerator(generator)
}
================================================
FILE: src/fragments.ts
================================================
import {
Kind,
parse,
InlineFragmentNode,
OperationDefinitionNode,
print,
} from 'graphql'
import { FragmentReplacement, IResolvers } from './types'
export function extractFragmentReplacements(
resolvers: IResolvers,
): FragmentReplacement[] {
const allFragmentReplacements: FragmentReplacement[] = []
/* Collect fragments. */
for (const typeName in resolvers) {
const fieldResolvers = resolvers[typeName]
for (const fieldName in fieldResolvers) {
const fieldResolver = fieldResolvers[fieldName]
if (typeof fieldResolver === 'object' && fieldResolver.fragment) {
allFragmentReplacements.push({
field: fieldName,
fragment: fieldResolver.fragment,
})
}
if (typeof fieldResolver === 'object' && fieldResolver.fragments) {
for (const fragment of fieldResolver.fragments) {
allFragmentReplacements.push({
field: fieldName,
fragment: fragment,
})
}
}
}
}
/* Filter and map circular dependencies. */
const fragmentReplacements = allFragmentReplacements
.filter(fragment => Boolean(fragment))
.map(fragmentReplacement => {
const fragment = parseFragmentToInlineFragment(
fragmentReplacement.fragment,
)
const newSelections = fragment.selectionSet.selections.filter(node => {
switch (node.kind) {
case Kind.FIELD: {
return node.name.value !== fragmentReplacement.field
}
default: {
return true
}
}
})
if (newSelections.length === 0) {
return null
}
const newFragment: InlineFragmentNode = {
...fragment,
selectionSet: {
kind: fragment.selectionSet.kind,
loc: fragment.selectionSet.loc,
selections: newSelections,
},
}
const parsedFragment = print(newFragment)
return {
field: fragmentReplacement.field,
fragment: parsedFragment,
}
})
.filter(fr => fr !== null)
return fragmentReplacements
/* Helper functions */
function parseFragmentToInlineFragment(
definitions: string,
): InlineFragmentNode {
if (definitions.trim().startsWith('fragment')) {
const document = parse(definitions)
for (const definition of document.definitions) {
if (definition.kind === Kind.FRAGMENT_DEFINITION) {
return {
kind: Kind.INLINE_FRAGMENT,
typeCondition: definition.typeCondition,
selectionSet: definition.selectionSet,
}
}
}
}
const query = parse(`{${definitions}}`)
.definitions[0] as OperationDefinitionNode
for (const selection of query.selectionSet.selections) {
if (selection.kind === Kind.INLINE_FRAGMENT) {
return selection
}
}
throw new Error('Could not parse fragment')
}
}
================================================
FILE: src/generator.ts
================================================
import {
IMiddlewareGeneratorConstructor,
IMiddleware,
IMiddlewareGenerator,
} from './types'
import { GraphQLSchema } from 'graphql'
export class MiddlewareGenerator<TSource, TContext, TArgs>
implements IMiddlewareGenerator<TSource, TContext, TArgs> {
private generator: IMiddlewareGeneratorConstructor<TSource, TContext, TArgs>
constructor(
generator: IMiddlewareGeneratorConstructor<TSource, TContext, TArgs>,
) {
this.generator = generator
}
generate(schema: GraphQLSchema): IMiddleware<TSource, TContext, TArgs> {
return this.generator(schema)
}
}
================================================
FILE: src/index.ts
================================================
export type {
FragmentReplacement,
IMiddleware,
IMiddlewareTypeMap,
IMiddlewareFieldMap,
IMiddlewareFunction,
IMiddlewareGenerator,
IMiddlewareGeneratorConstructor,
} from './types'
export {
applyMiddleware,
applyMiddlewareToDeclaredResolvers,
} from './middleware'
export { middleware } from './constructors'
export { MiddlewareError } from './validation'
================================================
FILE: src/middleware.ts
================================================
import { GraphQLSchema } from 'graphql'
import { addResolversToSchema } from '@graphql-tools/schema'
import {
IApplyOptions,
IMiddleware,
FragmentReplacement,
IMiddlewareGenerator,
GraphQLSchemaWithFragmentReplacements,
} from './types'
import { generateResolverFromSchemaAndMiddleware } from './applicator'
import { validateMiddleware } from './validation'
import { extractFragmentReplacements } from './fragments'
import { isMiddlewareGenerator } from './utils'
/**
*
* @param schema
* @param options
* @param middleware
*
* Validates middleware and generates resolvers map for provided middleware.
* Applies middleware to the current schema and returns the modified one.
*
*/
export function addMiddlewareToSchema<TSource, TContext, TArgs>(
schema: GraphQLSchema,
options: IApplyOptions,
middleware: IMiddleware<TSource, TContext, TArgs>,
): {
schema: GraphQLSchema
fragmentReplacements: FragmentReplacement[]
} {
const validMiddleware = validateMiddleware(schema, middleware)
const resolvers = generateResolverFromSchemaAndMiddleware(
schema,
options,
validMiddleware,
)
const fragmentReplacements = extractFragmentReplacements(resolvers)
const newSchema = addResolversToSchema({
schema,
resolvers,
updateResolversInPlace: false,
resolverValidationOptions: {
requireResolversForResolveType: 'ignore',
},
})
return { schema: newSchema, fragmentReplacements }
}
/**
*
* @param schema
* @param options
* @param middlewares
*
* Generates middleware from middleware generators and applies middleware to
* resolvers. Returns generated schema with all provided middleware.
*
*/
function applyMiddlewareWithOptions<TSource = any, TContext = any, TArgs = any>(
schema: GraphQLSchema,
options: IApplyOptions,
...middlewares: (
| IMiddleware<TSource, TContext, TArgs>
| IMiddlewareGenerator<TSource, TContext, TArgs>
)[]
): GraphQLSchemaWithFragmentReplacements {
const normalisedMiddlewares = middlewares.map((middleware) => {
if (isMiddlewareGenerator(middleware)) {
return middleware.generate(schema)
} else {
return middleware
}
})
const schemaWithMiddlewareAndFragmentReplacements = normalisedMiddlewares.reduceRight(
(
{
schema: currentSchema,
fragmentReplacements: currentFragmentReplacements,
},
middleware,
) => {
const {
schema: newSchema,
fragmentReplacements: newFragmentReplacements,
} = addMiddlewareToSchema(currentSchema, options, middleware)
return {
schema: newSchema,
fragmentReplacements: [
...currentFragmentReplacements,
...newFragmentReplacements,
],
}
},
{ schema, fragmentReplacements: [] },
)
const schemaWithMiddleware: GraphQLSchemaWithFragmentReplacements =
schemaWithMiddlewareAndFragmentReplacements.schema
schemaWithMiddleware.schema =
schemaWithMiddlewareAndFragmentReplacements.schema
schemaWithMiddleware.fragmentReplacements =
schemaWithMiddlewareAndFragmentReplacements.fragmentReplacements
return schemaWithMiddleware
}
// Exposed functions
/**
*
* @param schema
* @param middlewares
*
* Apply middleware to resolvers and return generated schema.
*
*/
export function applyMiddleware<TSource = any, TContext = any, TArgs = any>(
schema: GraphQLSchema,
...middlewares: (
| IMiddleware<TSource, TContext, TArgs>
| IMiddlewareGenerator<TSource, TContext, TArgs>
)[]
): GraphQLSchemaWithFragmentReplacements {
return applyMiddlewareWithOptions(
schema,
{ onlyDeclaredResolvers: false },
...middlewares,
)
}
/**
*
* @param schema
* @param middlewares
*
* Apply middleware to declared resolvers and return new schema.
*
*/
export function applyMiddlewareToDeclaredResolvers<
TSource = any,
TContext = any,
TArgs = any
>(
schema: GraphQLSchema,
...middlewares: (
| IMiddleware<TSource, TContext, TArgs>
| IMiddlewareGenerator<TSource, TContext, TArgs>
)[]
): GraphQLSchemaWithFragmentReplacements {
return applyMiddlewareWithOptions(
schema,
{ onlyDeclaredResolvers: true },
...middlewares,
)
}
================================================
FILE: src/types.ts
================================================
import {
GraphQLResolveInfo,
GraphQLSchema,
GraphQLTypeResolver,
GraphQLIsTypeOfFn,
} from 'graphql'
import { StitchingInfo } from '@graphql-tools/delegate'
// Middleware Tree
export declare type IMiddlewareFragment = string
export declare type IMiddlewareResolver<
TSource = any,
TContext = any,
TArgs = any
> = (
resolve: (
source?: TSource,
args?: TArgs,
context?: TContext,
info?: GraphQLResolveInfo & { stitchingInfo?: StitchingInfo },
) => any,
parent: TSource,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo,
) => Promise<any>
export interface IMiddlewareWithOptions<
TSource = any,
TContext = any,
TArgs = any
> {
fragment?: IMiddlewareFragment
fragments?: IMiddlewareFragment[]
resolve?: IMiddlewareResolver<TSource, TContext, TArgs>
}
export type IMiddlewareFunction<TSource = any, TContext = any, TArgs = any> =
| IMiddlewareWithOptions<TSource, TContext, TArgs>
| IMiddlewareResolver<TSource, TContext, TArgs>
export interface IMiddlewareTypeMap<
TSource = any,
TContext = any,
TArgs = any
> {
[key: string]:
| IMiddlewareFunction<TSource, TContext, TArgs>
| IMiddlewareFieldMap<TSource, TContext, TArgs>
}
export interface IMiddlewareFieldMap<
TSource = any,
TContext = any,
TArgs = any
> {
[key: string]: IMiddlewareFunction<TSource, TContext, TArgs>
}
// Middleware Generator
export declare class IMiddlewareGenerator<TSource, TContext, TArgs> {
constructor(
generator: IMiddlewareGeneratorConstructor<TSource, TContext, TArgs>,
)
generate(schema: GraphQLSchema): IMiddleware<TSource, TContext, TArgs>
}
export declare type IMiddlewareGeneratorConstructor<
TSource = any,
TContext = any,
TArgs = any
> = (schema: GraphQLSchema) => IMiddleware<TSource, TContext, TArgs>
export declare type IMiddleware<TSource = any, TContext = any, TArgs = any> =
| IMiddlewareFunction<TSource, TContext, TArgs>
| IMiddlewareTypeMap<TSource, TContext, TArgs>
// Middleware
export declare type IApplyOptions = {
onlyDeclaredResolvers: boolean
}
export declare type GraphQLSchemaWithFragmentReplacements = GraphQLSchema & {
schema?: GraphQLSchema
fragmentReplacements?: FragmentReplacement[]
}
// Fragments (inspired by graphql-tools)
export interface FragmentReplacement {
field: string
fragment: string
}
export interface IResolvers<TSource = any, TContext = any> {
[key: string]: IResolverObject<TSource, TContext>
}
export interface IResolverObject<TSource = any, TContext = any> {
[key: string]:
| IFieldResolver<TSource, TContext>
| IResolverOptions<TSource, TContext>
}
export interface IResolverOptions<TSource = any, TContext = any> {
fragment?: string
fragments?: string[]
resolve?: IFieldResolver<TSource, TContext>
subscribe?: IFieldResolver<TSource, TContext>
__resolveType?: GraphQLTypeResolver<TSource, TContext>
__isTypeOf?: GraphQLIsTypeOfFn<TSource, TContext>
}
export type IFieldResolver<TSource, TContext> = (
source: TSource,
args: { [argument: string]: any },
context: TContext,
info: GraphQLResolveInfo & { stitchingInfo?: StitchingInfo },
) => any
================================================
FILE: src/utils.ts
================================================
import { GraphQLObjectType, GraphQLInterfaceType } from 'graphql'
import {
IMiddlewareResolver,
IMiddlewareWithOptions,
IMiddlewareFunction,
IMiddlewareGenerator,
} from './types'
import { MiddlewareGenerator } from './generator'
// Type checkers
export function isMiddlewareResolver<TSource, TContext, TArgs>(
obj: any,
): obj is IMiddlewareResolver<TSource, TContext, TArgs> {
return (
typeof obj === 'function' ||
(typeof obj === 'object' && obj.then !== undefined)
)
}
export function isMiddlewareWithFragment<TSource, TContext, TArgs>(
obj: any,
): obj is IMiddlewareWithOptions<TSource, TContext, TArgs> {
return (
(typeof obj.fragment === 'string' || typeof obj.fragments === 'object') &&
isMiddlewareResolver(obj.resolve)
)
}
export function isMiddlewareFunction<TSource, TContext, TArgs>(
obj: any,
): obj is IMiddlewareFunction<TSource, TContext, TArgs> {
return isMiddlewareWithFragment(obj) || isMiddlewareResolver(obj)
}
export function isMiddlewareGenerator<TSource, TContext, TArgs>(
x: any,
): x is IMiddlewareGenerator<TSource, TContext, TArgs> {
return x instanceof MiddlewareGenerator
}
export function isGraphQLObjectType(
obj: any,
): obj is GraphQLObjectType | GraphQLInterfaceType {
return obj instanceof GraphQLObjectType || obj instanceof GraphQLInterfaceType
}
================================================
FILE: src/validation.ts
================================================
import { GraphQLSchema, GraphQLObjectType, GraphQLInterfaceType } from 'graphql'
import { IMiddleware } from './types'
import { isMiddlewareFunction } from './utils'
export function validateMiddleware<TSource, TContext, TArgs>(
schema: GraphQLSchema,
middleware: IMiddleware<TSource, TContext, TArgs>,
): IMiddleware<TSource, TContext, TArgs> {
if (isMiddlewareFunction(middleware)) {
return middleware
}
const types = schema.getTypeMap()
Object.keys(middleware).forEach(type => {
if (!Object.keys(types).includes(type)) {
throw new MiddlewareError(
`Type ${type} exists in middleware but is missing in Schema.`,
)
}
if (!isMiddlewareFunction(middleware[type])) {
const fields = (types[type] as
| GraphQLObjectType
| GraphQLInterfaceType).getFields()
Object.keys(middleware[type]).forEach(field => {
if (!Object.keys(fields).includes(field)) {
throw new MiddlewareError(
`Field ${type}.${field} exists in middleware but is missing in Schema.`,
)
}
if (!isMiddlewareFunction(middleware[type][field])) {
throw new MiddlewareError(
`Expected ${type}.${field} to be a function but found ` +
typeof middleware[type][field],
)
}
})
}
})
return middleware
}
export class MiddlewareError extends Error {}
================================================
FILE: tests/core.test.ts
================================================
import { makeExecutableSchema } from '@graphql-tools/schema'
import { ExecutionResult, graphql, subscribe, parse } from 'graphql'
import { $$asyncIterator } from 'iterall'
import { applyMiddleware } from '../src'
import {
IResolvers,
IMiddlewareTypeMap,
IMiddlewareFunction,
} from '../src/types'
describe('core:', () => {
/* Schema. */
const typeDefs = `
type Query {
before(arg: String!): String!
beforeNothing(arg: String!): String!
after: String!
afterNothing: String!
null: String
nested: Nothing!
resolverless: Resolverless!
}
type Subscription {
sub: String
}
type Nothing {
nothing: String!
}
type Resolverless {
someData: String!
}
schema {
query: Query,
subscription: Subscription
}
`
const resolvers: IResolvers = {
Query: {
before: (parent, { arg }, ctx, info) => arg,
beforeNothing: (parent, { arg }, ctx, info) => arg,
after: () => 'after',
afterNothing: () => 'after',
null: () => null,
nested: () => ({}),
resolverless: () => ({ someData: 'data' }),
},
Subscription: {
sub: {
subscribe: async (parent, { arg }, ctx, info) => {
const iterator = {
next: () => Promise.resolve({ done: false, value: { sub: arg } }),
return: () => {
return
},
throw: () => {
return
},
[$$asyncIterator]: () => iterator,
}
return iterator
},
},
},
Nothing: {
nothing: () => 'nothing',
},
}
const getSchema = () => makeExecutableSchema({ typeDefs, resolvers })
// Field Middleware
// Type Middleware
const typeMiddlewareBefore: IMiddlewareTypeMap = {
Query: async (resolve, parent, args, context, info) => {
const _args = { arg: 'changed' }
return resolve(parent, _args)
},
Subscription: async (resolve, parent, args, context, info) => {
const _args = { arg: 'changed' }
return resolve(parent, _args)
},
}
const typeMiddlewareAfter: IMiddlewareTypeMap = {
Query: async (resolve, parent, args, context, info) => {
const res = resolve()
return 'changed'
},
}
// Schema Middleware
const schemaMiddlewareBefore: IMiddlewareFunction = async (
resolve,
parent,
args,
context,
info,
) => {
const _args = { arg: 'changed' }
return resolve(parent, _args, context, info)
}
const schemaMiddlewareAfter: IMiddlewareFunction = async (
resolve,
parent,
args,
context,
info,
) => {
const res = resolve()
return 'changed'
}
const emptyStringMiddleware: IMiddlewareFunction = async (
resolve,
parent,
args,
context,
info,
) => {
if (/^String!?$/.test(String(info.returnType))) {
return ''
} else {
return resolve()
}
}
test('field middleware', async () => {
const schema = getSchema()
const fieldMiddleware: IMiddlewareTypeMap = {
Query: {
before: async (resolve, parent) => {
const _args = { arg: 'changed' }
return resolve(parent, _args)
},
after: async (resolve) => {
return 'changed'
},
},
}
const schemaWithMiddleware = applyMiddleware(schema, fieldMiddleware)
const query = `
query {
before(arg: "before")
beforeNothing(arg: "before")
after
afterNothing
null
nested { nothing }
}
`
const res = await graphql({ schema: schemaWithMiddleware, source: query })
/* Tests. */
expect(res).toEqual({
data: {
before: 'changed',
beforeNothing: 'before',
after: 'changed',
afterNothing: 'after',
null: null,
nested: { nothing: 'nothing' },
},
})
})
test('field middleware subscriptions', async () => {
const schema = getSchema()
const fieldMiddleware: IMiddlewareTypeMap = {
Subscription: {
sub: async (resolve, parent, args, context, info) => {
const _args = { arg: 'changed' }
return resolve(parent, _args)
},
},
}
const schemaWithMiddleware = applyMiddleware(schema, fieldMiddleware)
const query = `
subscription {
sub
}
`
const iterator = await subscribe({
schema: schemaWithMiddleware,
document: parse(query),
})
const res = await (iterator as AsyncIterator<ExecutionResult>).next()
/* Tests. */
expect(res).toEqual({
done: false,
value: {
data: {
sub: 'changed',
},
},
})
})
test('type middleware before', async () => {
const schema = getSchema()
const schemaWithMiddleware = applyMiddleware(schema, typeMiddlewareBefore)
const query = `
query {
before(arg: "before")
beforeNothing(arg: "before")
after
afterNothing
null
nested { nothing }
}
`
const res = await graphql({ schema: schemaWithMiddleware, source: query })
/* Tests. */
expect(res).toEqual({
data: {
before: 'changed',
beforeNothing: 'changed',
after: 'after',
afterNothing: 'after',
null: null,
nested: { nothing: 'nothing' },
},
})
})
test('type middleware after', async () => {
const schema = getSchema()
const schemaWithMiddleware = applyMiddleware(schema, typeMiddlewareAfter)
const query = `
query {
before(arg: "before")
beforeNothing(arg: "before")
after
afterNothing
null
nested { nothing }
}
`
const res = await graphql({ schema: schemaWithMiddleware, source: query })
/* Tests. */
expect(res).toEqual({
data: {
before: 'changed',
beforeNothing: 'changed',
after: 'changed',
afterNothing: 'changed',
null: 'changed',
nested: { nothing: 'nothing' },
},
})
})
test('type middleware subscriptions', async () => {
const schema = getSchema()
const schemaWithMiddleware = applyMiddleware(schema, typeMiddlewareBefore)
const query = `
subscription {
sub
}
`
const iterator = await subscribe({
schema: schemaWithMiddleware,
document: parse(query),
})
const res = await (iterator as AsyncIterator<ExecutionResult>).next()
expect(res).toEqual({
done: false,
value: {
data: {
sub: 'changed',
},
},
})
})
test('schema middleware before', async () => {
const schema = getSchema()
const schemaWithMiddleware = applyMiddleware(schema, schemaMiddlewareBefore)
const query = `
query {
before(arg: "before")
beforeNothing(arg: "before")
after
afterNothing
null
nested { nothing }
}
`
const res = await graphql({ schema: schemaWithMiddleware, source: query })
/* Tests. */
expect(res).toEqual({
data: {
before: 'changed',
beforeNothing: 'changed',
after: 'after',
afterNothing: 'after',
null: null,
nested: { nothing: 'nothing' },
},
})
})
test('schema middleware after', async () => {
const schema = getSchema()
const schemaWithMiddleware = applyMiddleware(schema, schemaMiddlewareAfter)
const query = `
query {
before(arg: "before")
beforeNothing(arg: "before")
after
afterNothing
null
nested { nothing }
}
`
const res = await graphql({ schema: schemaWithMiddleware, source: query })
/* Tests. */
expect(res).toEqual({
data: {
before: 'changed',
beforeNothing: 'changed',
after: 'changed',
afterNothing: 'changed',
null: 'changed',
nested: { nothing: 'changed' },
},
})
})
test('schema middleware before', async () => {
const schema = getSchema()
const schemaWithMiddleware = applyMiddleware(schema, schemaMiddlewareBefore)
const query = `
query {
before(arg: "before")
beforeNothing(arg: "before")
after
afterNothing
null
nested { nothing }
}
`
const res = await graphql({ schema: schemaWithMiddleware, source: query })
/* Tests. */
expect(res).toEqual({
data: {
before: 'changed',
beforeNothing: 'changed',
after: 'after',
afterNothing: 'after',
null: null,
nested: { nothing: 'nothing' },
},
})
})
test('schema middleware subscription', async () => {
const schema = getSchema()
const schemaWithMiddleware = applyMiddleware(schema, schemaMiddlewareBefore)
const query = `
subscription {
sub
}
`
const iterator = await subscribe({
schema: schemaWithMiddleware,
document: parse(query),
})
const res = await (iterator as AsyncIterator<ExecutionResult>).next()
expect(res).toEqual({
done: false,
value: {
data: {
sub: 'changed',
},
},
})
})
test('schema middleware uses default field resolver', async () => {
const schema = getSchema()
const schemaWithMiddleware = applyMiddleware(schema, schemaMiddlewareBefore)
const query = `
query {
resolverless {
someData
}
}
`
const res = await graphql({ schema: schemaWithMiddleware, source: query })
expect(res).toEqual({
data: {
resolverless: {
someData: 'data',
},
},
})
})
})
================================================
FILE: tests/execution.test.ts
================================================
import { makeExecutableSchema } from '@graphql-tools/schema'
import { graphql } from 'graphql'
import {
applyMiddleware,
applyMiddlewareToDeclaredResolvers,
IMiddlewareFunction,
} from '../src'
import { IResolvers } from '../src/types'
describe('execution:', () => {
test('follows correct order', async () => {
// Schema
const typeDefs = `
type Query {
test: String!
}
`
const resolvers = {
Query: {
test: () => 'pass',
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
/* Middleware tests. */
let sequence: string[] = []
const firstMiddleware: IMiddlewareFunction = async (resolve) => {
sequence.push('first')
return resolve()
}
const secondMiddleware: IMiddlewareFunction = async (resolve) => {
sequence.push('second')
return resolve()
}
const schemaWithMiddleware = applyMiddleware(
schema,
firstMiddleware,
secondMiddleware,
)
const query = `
query {
test
}
`
await graphql({
schema: schemaWithMiddleware,
source: query,
rootValue: null,
contextValue: {},
})
/* Tests */
expect(JSON.stringify(sequence)).toBe(JSON.stringify(['first', 'second']))
})
test('forwards arguments correctly', async () => {
/* Schema. */
const typeDefs = `
type Query {
test(arg: String): String!
}
`
const resolvers: IResolvers = {
Query: {
test: (parent, { arg }) => arg,
},
}
const schema = makeExecutableSchema({ resolvers, typeDefs })
/* Middleware. */
const randomTestString = Math.random().toString()
const middleware: IMiddlewareFunction = (resolve, parent) => {
return resolve(parent, { arg: randomTestString })
}
const schemaWithMiddleware = applyMiddleware(schema, middleware)
const query = `
query {
test(arg: "none")
}
`
const res = await graphql({ schema: schemaWithMiddleware, source: query })
/* Tests. */
expect(res).toEqual({
data: {
test: randomTestString,
},
})
})
test('applies multiple middlwares only to declared resolvers', async () => {
/* Schema. */
const typeDefs = `
type Object {
id: String,
name: String
}
type Query {
test(arg: String): Object!
}
`
const resolvers: IResolvers = {
Query: {
test: (parent, { arg }) => ({ id: arg, name: 'name' }),
},
}
const schema = makeExecutableSchema({ resolvers, typeDefs })
/* Middleware. */
const randomTestString = Math.random().toString()
const firstMiddleware: IMiddlewareFunction = jest.fn((resolve, parent) => {
return resolve(parent, { arg: randomTestString })
})
const secondMiddleware: IMiddlewareFunction = jest.fn((resolve, parent) => {
return resolve(parent, { arg: randomTestString })
})
const schemaWithMiddleware = applyMiddlewareToDeclaredResolvers(
schema,
firstMiddleware,
secondMiddleware,
)
const query = `
query {
test(arg: "id") {
id
name
}
}
`
await graphql({ schema: schemaWithMiddleware, source: query })
/* Tests. */
expect(firstMiddleware).toHaveBeenCalledTimes(1)
expect(secondMiddleware).toHaveBeenCalledTimes(1)
})
})
================================================
FILE: tests/fragments.test.ts
================================================
import { makeExecutableSchema } from '@graphql-tools/schema'
import { $$asyncIterator } from 'iterall'
import {
applyMiddleware,
applyMiddlewareToDeclaredResolvers,
IMiddlewareFunction,
IMiddlewareTypeMap,
} from '../src'
import { IResolvers } from '../src/types'
/**
* Tests whether graphql-middleware-tool correctly applies middleware to fields it
* ought to impact based on the width of the middleware specification.
*/
describe('fragments:', () => {
test('schema-wide middleware', async () => {
/* Schema. */
const typeDefs = `
type Query {
book: Book!
}
type Book {
id: ID!
name: String!
content: String!
author: String!
}
`
const resolvers = {
Query: {
book() {
return {
id: 'id',
name: 'name',
content: 'content',
author: 'author',
}
},
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
/* Middleware. */
const schemaMiddlewareWithFragment: IMiddlewareFunction = {
fragment: `fragment NodeID on Node { id }`,
resolve: (resolve) => resolve(),
}
const { fragmentReplacements } = applyMiddleware(
schema,
schemaMiddlewareWithFragment,
)
/* Tests. */
expect(fragmentReplacements).toEqual([
{ field: 'book', fragment: '... on Node {\n id\n}' },
{ field: 'name', fragment: '... on Node {\n id\n}' },
{ field: 'content', fragment: '... on Node {\n id\n}' },
{ field: 'author', fragment: '... on Node {\n id\n}' },
])
})
test('type-wide middleware', async () => {
/* Schema. */
const typeDefs = `
type Query {
book: Book!
author: Author!
}
type Book {
id: ID!
content: String!
author: String!
}
type Author {
id: ID!
name: String!
}
`
const resolvers = {
Query: {
book() {
return {
id: 'id',
content: 'content',
author: 'author',
}
},
author() {
return {
name: 'name',
}
},
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
// Middleware
const typeMiddlewareWithFragment: IMiddlewareTypeMap = {
Book: {
fragment: `fragment BookId on Book { id }`,
resolve: (resolve) => resolve(),
},
Author: {
fragments: [`... on Author { id }`, `... on Author { name }`],
resolve: (resolve) => resolve(),
},
}
const { fragmentReplacements } = applyMiddleware(
schema,
typeMiddlewareWithFragment,
)
/* Tests. */
expect(fragmentReplacements).toEqual([
{
field: 'content',
fragment: '... on Book {\n id\n}',
},
{
field: 'author',
fragment: '... on Book {\n id\n}',
},
{
field: 'id',
fragment: '... on Author {\n name\n}',
},
{
field: 'name',
fragment: '... on Author {\n id\n}',
},
])
})
test('field-specific middleware', async () => {
const typeDefs = `
type Query {
book: Book!
}
type Book {
id: ID!
name: String!
content: String!
author: String!
}
`
const resolvers = {
Query: {
book() {
return {
id: 'id',
name: 'name',
content: 'content',
author: 'author',
}
},
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
// Middleware
const fieldMiddlewareWithFragment: IMiddlewareTypeMap = {
Book: {
content: {
fragment: `fragment BookId on Book { id ... on Book { name } }`,
resolve: (resolve) => resolve(),
},
author: {
fragments: [
`fragment BookId on Book { id }`,
`fragment BookContent on Book { content }`,
],
resolve: (resolve) => resolve(),
},
},
}
const { fragmentReplacements } = applyMiddleware(
schema,
fieldMiddlewareWithFragment,
)
/* Tests. */
expect(fragmentReplacements).toEqual([
{
field: 'content',
fragment: '... on Book {\n id\n ... on Book {\n name\n }\n}',
},
{
field: 'author',
fragment: '... on Book {\n id\n}',
},
{
field: 'author',
fragment: '... on Book {\n content\n}',
},
])
})
test('subscription fragment', async () => {
/* Schema. */
const typeDefs = `
type Query {
book(id: ID!): Book!
}
type Subscription {
book(id: ID!): Book!
}
type Book {
id: ID!
name: String!
}
schema {
query: Query,
subscription: Subscription
}
`
const resolvers: IResolvers = {
Query: {
book() {
return {
id: 'id',
name: 'name',
}
},
},
Subscription: {
book: {
subscribe: async (parent, { id }) => {
const iterator = {
next: () => Promise.resolve({ done: false, value: { sub: id } }),
return: () => {
return
},
throw: () => {
return
},
[$$asyncIterator]: () => iterator,
}
return iterator
},
},
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
/* Middleware. */
const fieldMiddlewareWithFragment: IMiddlewareTypeMap = {
Subscription: {
book: {
fragment: `fragment Ignored on Book { ignore }`,
resolve: (resolve) => resolve(),
},
},
}
const { fragmentReplacements } = applyMiddlewareToDeclaredResolvers(
schema,
fieldMiddlewareWithFragment,
)
/* Tests. */
expect(fragmentReplacements).toEqual([
{
field: 'book',
fragment: '... on Book {\n ignore\n}',
},
])
})
})
describe('fragments on declared resolvers:', () => {
test('schema-wide middleware', async () => {
/* Schema. */
const typeDefs = `
type Query {
book: Book!
}
type Book {
id: ID!
name: String!
content: String!
author: String!
}
`
const resolvers = {
Query: {
book() {
return {
id: 'id',
name: 'name',
content: 'content',
author: 'author',
}
},
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
/* Middleware. */
const schemaMiddlewareWithFragment: IMiddlewareFunction = {
fragment: `fragment NodeId on Node { id }`,
resolve: (resolve) => resolve(),
}
const { fragmentReplacements } = applyMiddlewareToDeclaredResolvers(
schema,
schemaMiddlewareWithFragment,
)
/* Tests. */
expect(fragmentReplacements).toEqual([
{ field: 'book', fragment: '... on Node {\n id\n}' },
])
})
test('type-wide middleware', async () => {
/* Schema. */
const typeDefs = `
type Query {
book: Book!
}
type Book {
id: ID!
name: String!
content: String!
author: String!
}
`
const resolvers = {
Query: {
book() {
return {
id: 'id',
name: 'name',
content: 'content',
author: 'author',
}
},
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
/* Middleware. */
const typeMiddlewareWithFragment: IMiddlewareTypeMap = {
Query: {
fragments: [`fragment QueryViewer on Query { viewer }`],
resolve: (resolve) => resolve(),
},
Book: {
fragment: `... on Book { id }`,
resolve: (resolve) => resolve(),
},
}
const { fragmentReplacements } = applyMiddlewareToDeclaredResolvers(
schema,
typeMiddlewareWithFragment,
)
/* Tests. */
expect(fragmentReplacements).toEqual([
{
field: 'book',
fragment: '... on Query {\n viewer\n}',
},
])
})
test('field-specific middleware', async () => {
/* Schema. */
const typeDefs = `
type Query {
book: Book!
}
type Book {
id: ID!
name: String!
content: String!
author: String!
}
`
const resolvers = {
Query: {
book() {
return {}
},
},
Book: {
id: () => 'id',
name: () => 'name',
content: () => 'content',
author: () => 'author',
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
/* Middleware. */
const fieldMiddlewareWithFragment: IMiddlewareTypeMap = {
Book: {
id: {
fragment: `fragment Ignored on Book { ignore }`,
resolve: (resolve) => resolve(),
},
content: {
fragment: `fragment BookId on Book { id }`,
resolve: (resolve) => resolve(),
},
author: {
fragments: [
`fragment AuthorId on Author { id }`,
`fragment AuthorName on Author { name }`,
],
resolve: (resolve) => resolve(),
},
},
}
const { fragmentReplacements } = applyMiddlewareToDeclaredResolvers(
schema,
fieldMiddlewareWithFragment,
)
/* Tests. */
expect(fragmentReplacements).toEqual([
{
field: 'id',
fragment: '... on Book {\n ignore\n}',
},
{
field: 'content',
fragment: '... on Book {\n id\n}',
},
{
field: 'author',
fragment: '... on Author {\n id\n}',
},
{
field: 'author',
fragment: '... on Author {\n name\n}',
},
])
})
})
test('imparsable fragment', async () => {
/* Schema. */
const typeDefs = `
type Query {
book: String!
}
`
const resolvers = {
Query: {
book() {
return 'book'
},
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
/* Middleware. */
const fieldMiddlewareWithFragment: IMiddlewareFunction = {
fragment: 'foo',
resolve: (resolve) => resolve(),
}
/* Tests. */
expect(() => {
applyMiddlewareToDeclaredResolvers(schema, fieldMiddlewareWithFragment)
}).toThrow('Could not parse fragment')
})
================================================
FILE: tests/generator.test.ts
================================================
import { makeExecutableSchema } from '@graphql-tools/schema'
import { GraphQLSchema } from 'graphql'
import { applyMiddleware, middleware, IMiddlewareGenerator } from '../src'
test('middleware generator integration', async () => {
/* Schema. */
const typeDefs = `
type Query {
test: String!
}
`
const resolvers = {
Query: {
test: () => 'fail',
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
let generatorSchema: GraphQLSchema
/* Middleware. */
const testMiddleware: IMiddlewareGenerator<any, any, any> = middleware(
(_schema) => {
generatorSchema = _schema
return async () => 'pass'
},
)
applyMiddleware(schema, testMiddleware)
/* Tests. */
expect(generatorSchema).toEqual(schema)
})
================================================
FILE: tests/immutability.test.ts
================================================
import { makeExecutableSchema } from '@graphql-tools/schema'
import { graphql } from 'graphql'
import { applyMiddleware, IMiddleware } from '../src'
type Context = {
middlewareCalled: boolean
}
test('immutable', async () => {
const typeDefs = `
type Query {
test: Boolean!
}
`
const resolvers = {
Query: {
test: (_: unknown, __: unknown, ctx: Context) => ctx.middlewareCalled,
},
}
const middleware: IMiddleware = (
resolve,
parent,
args,
ctx: Context,
info,
) => {
ctx.middlewareCalled = true
return resolve(parent, args, { ...ctx, middlewareCalled: true }, info)
}
const middlewares = {
Query: {
test: middleware,
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
const schemaWithMiddlewares = applyMiddleware(schema, middlewares)
const query = `
query TestQuery {
test
}
`
const responseWithMiddleware = await graphql({
schema: schemaWithMiddlewares,
source: query,
contextValue: { middlewareCalled: false },
})
expect(responseWithMiddleware.errors).toBeUndefined()
expect(responseWithMiddleware.data!.test).toEqual(true)
const responseWihoutMiddleware = await graphql({
schema,
source: query,
rootValue: {},
contextValue: { middlewareCalled: false },
variableValues: {},
})
expect(responseWihoutMiddleware.errors).toBeUndefined()
expect(responseWihoutMiddleware.data!.test).toEqual(false)
})
================================================
FILE: tests/integration.test.ts
================================================
import { makeExecutableSchema } from '@graphql-tools/schema'
// import { GraphQLServer as YogaServer } from 'graphql-yoga'
import { ApolloServer } from 'apollo-server'
import Axios from 'axios'
// import { AddressInfo } from 'ws'
import { applyMiddleware } from '../src'
describe('integrations', () => {
/* GraphQL Yoga */
// https://github.com/prisma-labs/graphql-yoga/issues/449
// We might bring back support for GraphQL Yoga if they start
// supporting new GraphQL versions.
//
// test('GraphQL Yoga', async () => {
// const typeDefs = `
// type Query {
// test: String!
// }
// `
// const resolvers = {
// Query: {
// test: () => 'test',
// },
// }
// const schema = makeExecutableSchema({ typeDefs, resolvers })
// const schemaWithMiddleware = applyMiddleware(schema, async (resolve) => {
// const res = await resolve()
// return `pass-${res}`
// })
// const server = new YogaServer({
// schema: schemaWithMiddleware,
// })
// const http = await server.start({ port: 0 })
// try {
// const { port } = http.address() as AddressInfo
// const uri = `http://localhost:${port}/`
// /* Tests */
// const query = `
// query {
// test
// }
// `
// const body = await Axios.post(uri, {
// query,
// })
// /* Tests. */
// expect(body.data).toEqual({
// data: {
// test: 'pass-test',
// },
// })
// } finally {
// http.close()
// }
// })
/* Apollo Server */
test('ApolloServer', async () => {
/* Schema. */
const typeDefs = `
type Query {
test: String!
}
`
const resolvers = {
Query: {
test: () => 'test',
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
const schemaWithMiddleware = applyMiddleware(schema, async (resolve) => {
const res = await resolve()
return `pass-${res}`
})
const server = new ApolloServer({
schema: schemaWithMiddleware,
})
await server.listen({ port: 8008 })
const uri = `http://localhost:8008/`
/* Tests */
const query = `
query {
test
}
`
try {
const body = await Axios.post(uri, { query })
/* Tests. */
expect(body.data).toEqual({
data: {
test: 'pass-test',
},
})
} finally {
await server.stop()
}
})
})
================================================
FILE: tests/validation.test.ts
================================================
import { makeExecutableSchema } from '@graphql-tools/schema'
import { validateMiddleware, MiddlewareError } from '../src/validation'
import { IMiddlewareFieldMap, IMiddlewareTypeMap } from '../src'
describe('validation:', () => {
test('warns about the unknown type', async () => {
/* Schema. */
const typeDefs = `
type Query {
pass: String!
}
`
const resolvers = {
Query: {
pass: () => 'pass',
},
}
const schema = makeExecutableSchema({ resolvers, typeDefs })
/* Middleware. */
const middlewareWithUndefinedType: IMiddlewareFieldMap = {
Test: async () => ({}),
}
/* Tests. */
expect(() => {
validateMiddleware(schema, middlewareWithUndefinedType)
}).toThrow(
new MiddlewareError(
`Type Test exists in middleware but is missing in Schema.`,
),
)
})
test('warns about the unknown field', async () => {
/* Schema. */
const typeDefs = `
type Query {
pass: String!
}
`
const resolvers = {
Query: {
pass: () => 'pass',
},
}
const schema = makeExecutableSchema({ resolvers, typeDefs })
/* Middleware. */
const middlewareWithUndefinedField: IMiddlewareTypeMap = {
Query: {
test: async () => ({}),
},
}
/* Tests. */
expect(() => {
validateMiddleware(schema, middlewareWithUndefinedField)
}).toThrow(
new MiddlewareError(
`Field Query.test exists in middleware but is missing in Schema.`,
),
)
})
test('warns that middleware leafs are not functions', async () => {
/* Schema. */
const typeDefs = `
type Query {
test: String!
}
`
const resolvers = {
Query: {
test: () => 'pass',
},
}
const schema = makeExecutableSchema({ resolvers, typeDefs })
/* Middleware. */
const middlewareWithObjectField: any = {
Query: {
test: false,
},
}
/* Tests. */
expect(() => {
validateMiddleware(schema, middlewareWithObjectField as any)
}).toThrow(
new MiddlewareError(
`Expected Query.test to be a function but found boolean`,
),
)
})
})
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"target": "es2017",
"moduleResolution": "node",
"module": "commonjs",
"sourceMap": true,
"rootDir": "src",
"outDir": "dist",
"lib": ["esnext"],
"skipLibCheck": true,
"declaration": true,
"emitDeclarationOnly": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
================================================
FILE: tsconfig.test.json
================================================
{
"compilerOptions": {
"target": "es5",
"moduleResolution": "node",
"module": "commonjs",
"sourceMap": true,
"rootDir": "src",
"outDir": "dist",
"lib": ["esnext"],
"skipLibCheck": true
},
"include": ["tests/**/*"],
"exclude": ["node_modules", "dist"]
}
gitextract_ihhyes_m/ ├── .github/ │ ├── FUNDING.yml │ ├── stale.yml │ └── workflows/ │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .huskyrc.json ├── .vscode/ │ ├── launch.json │ └── settings.json ├── LICENSE ├── README.md ├── bob-esbuild.config.ts ├── examples/ │ ├── logging/ │ │ ├── README.md │ │ ├── index.js │ │ └── package.json │ └── permissions/ │ ├── README.md │ ├── index.js │ └── package.json ├── jest.config.js ├── package.json ├── prettier.config.js ├── renovate.json ├── src/ │ ├── applicator.ts │ ├── constructors.ts │ ├── fragments.ts │ ├── generator.ts │ ├── index.ts │ ├── middleware.ts │ ├── types.ts │ ├── utils.ts │ └── validation.ts ├── tests/ │ ├── core.test.ts │ ├── execution.test.ts │ ├── fragments.test.ts │ ├── generator.test.ts │ ├── immutability.test.ts │ ├── integration.test.ts │ └── validation.test.ts ├── tsconfig.json └── tsconfig.test.json
SYMBOL INDEX (48 symbols across 10 files)
FILE: src/applicator.ts
function wrapResolverInMiddleware (line 28) | function wrapResolverInMiddleware<TSource, TContext, TArgs>(
function parseField (line 43) | function parseField(
function applyMiddlewareToField (line 59) | function applyMiddlewareToField<TSource, TContext, TArgs>(
function applyMiddlewareToType (line 129) | function applyMiddlewareToType<TSource, TContext, TArgs>(
function applyMiddlewareToSchema (line 169) | function applyMiddlewareToSchema<TSource, TContext, TArgs>(
function generateResolverFromSchemaAndMiddleware (line 199) | function generateResolverFromSchemaAndMiddleware<
FILE: src/constructors.ts
function middleware (line 4) | function middleware<TSource = any, TContext = any, TArgs = any>(
FILE: src/fragments.ts
function extractFragmentReplacements (line 10) | function extractFragmentReplacements(
FILE: src/generator.ts
class MiddlewareGenerator (line 8) | class MiddlewareGenerator<TSource, TContext, TArgs>
method constructor (line 12) | constructor(
method generate (line 18) | generate(schema: GraphQLSchema): IMiddleware<TSource, TContext, TArgs> {
FILE: src/middleware.ts
function addMiddlewareToSchema (line 25) | function addMiddlewareToSchema<TSource, TContext, TArgs>(
function applyMiddlewareWithOptions (line 64) | function applyMiddlewareWithOptions<TSource = any, TContext = any, TArgs...
function applyMiddleware (line 125) | function applyMiddleware<TSource = any, TContext = any, TArgs = any>(
function applyMiddlewareToDeclaredResolvers (line 147) | function applyMiddlewareToDeclaredResolvers<
FILE: src/types.ts
type IMiddlewareFragment (line 11) | type IMiddlewareFragment = string
type IMiddlewareResolver (line 13) | type IMiddlewareResolver<
type IMiddlewareWithOptions (line 30) | interface IMiddlewareWithOptions<
type IMiddlewareFunction (line 40) | type IMiddlewareFunction<TSource = any, TContext = any, TArgs = any> =
type IMiddlewareTypeMap (line 44) | interface IMiddlewareTypeMap<
type IMiddlewareFieldMap (line 54) | interface IMiddlewareFieldMap<
class IMiddlewareGenerator (line 64) | class IMiddlewareGenerator<TSource, TContext, TArgs> {
type IMiddlewareGeneratorConstructor (line 71) | type IMiddlewareGeneratorConstructor<
type IMiddleware (line 77) | type IMiddleware<TSource = any, TContext = any, TArgs = any> =
type IApplyOptions (line 83) | type IApplyOptions = {
type GraphQLSchemaWithFragmentReplacements (line 87) | type GraphQLSchemaWithFragmentReplacements = GraphQLSchema & {
type FragmentReplacement (line 94) | interface FragmentReplacement {
type IResolvers (line 99) | interface IResolvers<TSource = any, TContext = any> {
type IResolverObject (line 103) | interface IResolverObject<TSource = any, TContext = any> {
type IResolverOptions (line 109) | interface IResolverOptions<TSource = any, TContext = any> {
type IFieldResolver (line 118) | type IFieldResolver<TSource, TContext> = (
FILE: src/utils.ts
function isMiddlewareResolver (line 12) | function isMiddlewareResolver<TSource, TContext, TArgs>(
function isMiddlewareWithFragment (line 21) | function isMiddlewareWithFragment<TSource, TContext, TArgs>(
function isMiddlewareFunction (line 30) | function isMiddlewareFunction<TSource, TContext, TArgs>(
function isMiddlewareGenerator (line 36) | function isMiddlewareGenerator<TSource, TContext, TArgs>(
function isGraphQLObjectType (line 42) | function isGraphQLObjectType(
FILE: src/validation.ts
function validateMiddleware (line 5) | function validateMiddleware<TSource, TContext, TArgs>(
class MiddlewareError (line 47) | class MiddlewareError extends Error {}
FILE: tests/fragments.test.ts
method book (line 33) | book() {
method book (line 90) | book() {
method author (line 97) | author() {
method book (line 163) | book() {
method book (line 241) | book() {
method book (line 314) | book() {
method book (line 363) | book() {
method book (line 421) | book() {
method book (line 495) | book() {
FILE: tests/immutability.test.ts
type Context (line 5) | type Context = {
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (82K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 642,
"preview": "# These are supported funding model platforms\n\ngithub: maticzav\npatreon: # Replace with a single Patreon username\nopen_c"
},
{
"path": ".github/stale.yml",
"chars": 757,
"preview": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 45\n# Number of days of inactivity before a "
},
{
"path": ".github/workflows/publish.yml",
"chars": 768,
"preview": "name: Publish\n\non:\n push:\n branches:\n - master\n\njobs:\n build:\n name: release\n runs-on: ubuntu-latest\n "
},
{
"path": ".github/workflows/test.yml",
"chars": 452,
"preview": "name: Test\n\non: [push, pull_request]\n\njobs:\n build:\n name: release\n runs-on: ubuntu-latest\n steps:\n # Set"
},
{
"path": ".gitignore",
"chars": 67,
"preview": "node_modules\ndist\n.idea\n.DS_Store\n*.log*\npackage-lock.json\ncoverage"
},
{
"path": ".huskyrc.json",
"chars": 63,
"preview": "{\n \"hooks\": {\n \"pre-commit\": \"pretty-quick --staged\"\n }\n}\n"
},
{
"path": ".vscode/launch.json",
"chars": 798,
"preview": "{\n \"version\": \"0.2.0\",\n \"configurations\": [\n {\n \"type\": \"node\",\n \"request\": \"launch\",\n \"name\": \"JS e"
},
{
"path": ".vscode/settings.json",
"chars": 326,
"preview": "{\n \"eslint.enable\": false,\n \"files.watcherExclude\": {\n \"**/.git/objects/**\": true,\n \"**/.git/subtree-cache/**\": "
},
{
"path": "LICENSE",
"chars": 1071,
"preview": "MIT License\n\nCopyright (c) 2020 Matic Zavadlal\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "README.md",
"chars": 12279,
"preview": "<p align=\"center\"><img src=\"media/logo.png\" width=\"150\" /></p>\n\n# graphql-middleware\n\n[.BobConfig = {\r\n tsc: {\r\n dirs: ['.'],\r\n },\r\n distDir: 'dist',\r\n verbos"
},
{
"path": "examples/logging/README.md",
"chars": 798,
"preview": "# GraphQL Middleware - Logging example\n\nThis example illustrates basic usage of GraphQL Middleware. The idea is to log e"
},
{
"path": "examples/logging/index.js",
"chars": 737,
"preview": "const { GraphQLServer } = require('graphql-yoga')\n\n// Schema\n\nconst typeDefs = `\n type Query {\n hello(name: String):"
},
{
"path": "examples/logging/package.json",
"chars": 142,
"preview": "{\n \"name\": \"logging\",\n \"version\": \"1.0.0\",\n \"main\": \"index.js\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"graphql-"
},
{
"path": "examples/permissions/README.md",
"chars": 1717,
"preview": "# GraphQL Middleware - Permissions example\n\nThis example illustrates how to use GraphQL Middleware to handle user permis"
},
{
"path": "examples/permissions/index.js",
"chars": 1128,
"preview": "const { GraphQLServer } = require('graphql-yoga')\n\n// Schema\n\nconst code = 'supersecret'\n\nconst typeDefs = `\n type Quer"
},
{
"path": "examples/permissions/package.json",
"chars": 146,
"preview": "{\n \"name\": \"permissions\",\n \"version\": \"1.0.0\",\n \"main\": \"index.js\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"grap"
},
{
"path": "jest.config.js",
"chars": 681,
"preview": "module.exports = {\n roots: ['<rootDir>/tests', '<rootDir>/src'],\n testEnvironment: 'node',\n transform: {\n '^.+\\\\.t"
},
{
"path": "package.json",
"chars": 2023,
"preview": "{\n \"name\": \"graphql-middleware\",\n \"description\": \"GraphQL Middleware done right!\",\n \"version\": \"0.0.0-semantic-releas"
},
{
"path": "prettier.config.js",
"chars": 81,
"preview": "module.exports = {\n semi: false,\n trailingComma: 'all',\n singleQuote: true,\n}\n"
},
{
"path": "renovate.json",
"chars": 324,
"preview": "{\n \"extends\": [\"config:base\", \"docker:disable\", \":automergeMinor\"],\n \"rangeStrategy\": \"bump\",\n \"pathRules\": [\n {\n "
},
{
"path": "src/applicator.ts",
"chars": 5651,
"preview": "import {\n GraphQLObjectType,\n GraphQLFieldResolver,\n GraphQLField,\n GraphQLSchema,\n defaultFieldResolver,\n isIntro"
},
{
"path": "src/constructors.ts",
"chars": 373,
"preview": "import { MiddlewareGenerator } from './generator'\nimport { IMiddlewareGenerator, IMiddlewareGeneratorConstructor } from "
},
{
"path": "src/fragments.ts",
"chars": 2934,
"preview": "import {\n Kind,\n parse,\n InlineFragmentNode,\n OperationDefinitionNode,\n print,\n} from 'graphql'\nimport { FragmentRe"
},
{
"path": "src/generator.ts",
"chars": 588,
"preview": "import {\n IMiddlewareGeneratorConstructor,\n IMiddleware,\n IMiddlewareGenerator,\n} from './types'\nimport { GraphQLSche"
},
{
"path": "src/index.ts",
"chars": 375,
"preview": "export type {\n FragmentReplacement,\n IMiddleware,\n IMiddlewareTypeMap,\n IMiddlewareFieldMap,\n IMiddlewareFunction,\n"
},
{
"path": "src/middleware.ts",
"chars": 4204,
"preview": "import { GraphQLSchema } from 'graphql'\nimport { addResolversToSchema } from '@graphql-tools/schema'\nimport {\n IApplyOp"
},
{
"path": "src/types.ts",
"chars": 3142,
"preview": "import {\n GraphQLResolveInfo,\n GraphQLSchema,\n GraphQLTypeResolver,\n GraphQLIsTypeOfFn,\n} from 'graphql'\nimport { St"
},
{
"path": "src/utils.ts",
"chars": 1341,
"preview": "import { GraphQLObjectType, GraphQLInterfaceType } from 'graphql'\nimport {\n IMiddlewareResolver,\n IMiddlewareWithOptio"
},
{
"path": "src/validation.ts",
"chars": 1405,
"preview": "import { GraphQLSchema, GraphQLObjectType, GraphQLInterfaceType } from 'graphql'\nimport { IMiddleware } from './types'\ni"
},
{
"path": "tests/core.test.ts",
"chars": 9792,
"preview": "import { makeExecutableSchema } from '@graphql-tools/schema'\nimport { ExecutionResult, graphql, subscribe, parse } from "
},
{
"path": "tests/execution.test.ts",
"chars": 3452,
"preview": "import { makeExecutableSchema } from '@graphql-tools/schema'\nimport { graphql } from 'graphql'\nimport {\n applyMiddlewar"
},
{
"path": "tests/fragments.test.ts",
"chars": 10762,
"preview": "import { makeExecutableSchema } from '@graphql-tools/schema'\nimport { $$asyncIterator } from 'iterall'\nimport {\n applyM"
},
{
"path": "tests/generator.test.ts",
"chars": 786,
"preview": "import { makeExecutableSchema } from '@graphql-tools/schema'\nimport { GraphQLSchema } from 'graphql'\nimport { applyMiddl"
},
{
"path": "tests/immutability.test.ts",
"chars": 1479,
"preview": "import { makeExecutableSchema } from '@graphql-tools/schema'\nimport { graphql } from 'graphql'\nimport { applyMiddleware,"
},
{
"path": "tests/integration.test.ts",
"chars": 2528,
"preview": "import { makeExecutableSchema } from '@graphql-tools/schema'\n// import { GraphQLServer as YogaServer } from 'graphql-yog"
},
{
"path": "tests/validation.test.ts",
"chars": 2236,
"preview": "import { makeExecutableSchema } from '@graphql-tools/schema'\nimport { validateMiddleware, MiddlewareError } from '../src"
},
{
"path": "tsconfig.json",
"chars": 352,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es2017\",\n \"moduleResolution\": \"node\",\n \"module\": \"commonjs\",\n \"sourceMa"
},
{
"path": "tsconfig.test.json",
"chars": 293,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es5\",\n \"moduleResolution\": \"node\",\n \"module\": \"commonjs\",\n \"sourceMap\":"
}
]
About this extraction
This page contains the full source code of the prismagraphql/graphql-middleware GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (75.0 KB), approximately 19.2k tokens, and a symbol index with 48 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.