diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68af50dc..eec220e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,8 +4,9 @@ jobs: test: runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: - node-version: [16.x, 18.x, 20.x] + node-version: [20.x, 22.x] os: [ubuntu-latest, windows-latest, macOS-latest] steps: - uses: actions/checkout@v4 diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..43c97e71 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/README.md b/README.md index 6974f483..e05b09b4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ # mercurius -![CI workflow](https://github.com/mercurius-js/mercurius/workflows/CI%20workflow/badge.svg) +[![CI workflow](https://github.com/mercurius-js/mercurius/actions/workflows/ci.yml/badge.svg)](https://github.com/mercurius-js/mercurius/actions/workflows/ci.yml) +[![NPM version](https://img.shields.io/npm/v/mercurius.svg?style=flat)](https://www.npmjs.com/package/mercurius) +[![NPM downloads](https://img.shields.io/npm/dm/mercurius.svg?style=flat)](https://www.npmjs.com/package/mercurius) +[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard) Mercurius is a [**GraphQL**](https://graphql.org/) adapter for [**Fastify**](https://www.fastify.io) diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..6b8c5cf5 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,5 @@ +'use strict' + +module.exports = require('neostandard')({ + ts: true, +}) diff --git a/index.d.ts b/index.d.ts index 55ded720..a64fc2f8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -4,7 +4,7 @@ import { FastifyRequest, FastifyInstance, RouteOptions -} from "fastify"; +} from 'fastify' import { DocumentNode, ExecutionResult, @@ -15,10 +15,10 @@ import { ValidationRule, FormattedExecutionResult, ParseOptions, -} from "graphql"; -import type { WebSocket } from "ws"; -import { IncomingMessage, OutgoingHttpHeaders } from "http"; -import { Readable } from "stream"; +} from 'graphql' +import type { WebSocket } from 'ws' +import { IncomingMessage, OutgoingHttpHeaders } from 'http' +import { Readable } from 'stream' type Mercurius = typeof mercurius @@ -27,7 +27,7 @@ declare namespace mercurius { subscribe(topics: string | string[]): Promise>; publish(event: { topic: string; payload: TResult }, callback?: () => void): void; } - + export interface MercuriusContext { app: FastifyInstance; reply: FastifyReply; @@ -39,11 +39,11 @@ declare namespace mercurius { */ pubsub: PubSub; } - + export interface MercuriusError extends FastifyError { errors?: TError[] } - + export interface Loader< TObj extends Record = any, TParams extends Record = any, @@ -59,24 +59,24 @@ declare namespace mercurius { } ): any; } - + export interface MercuriusLoaders = MercuriusContext> { [root: string]: { [field: string]: | Loader | { - loader: Loader; - opts?: { - cache?: boolean; - }; + loader: Loader; + opts?: { + cache?: boolean; }; + }; }; } - + // ------------------------ // Request Lifecycle hooks // ------------------------ - + /** * `preParsing` is the first hook to be executed in the GraphQL request lifecycle. The next hook will be `preValidation`. */ @@ -87,7 +87,7 @@ declare namespace mercurius { context: TContext, ): Promise | void; } - + /** * `preValidation` is the second hook to be executed in the GraphQL request lifecycle. The previous hook was `preParsing`, the next hook will be `preExecution`. */ @@ -98,7 +98,7 @@ declare namespace mercurius { context: TContext, ): Promise | void; } - + /** * `preExecution` is the third hook to be executed in the GraphQL request lifecycle. The previous hook was `preValidation`. * Notice: in the `preExecution` hook, you can modify the following items by returning them in the hook definition: @@ -115,7 +115,7 @@ declare namespace mercurius { variables: Record, ): Promise | void> | PreExecutionHookResponse | void; } - + /** * `onResolution` is the fifth and final hook to be executed in the GraphQL request lifecycle. The previous hook was `preExecution`. */ @@ -125,11 +125,11 @@ declare namespace mercurius { context: TContext, ): Promise | void; } - + // ----------------------------- // Subscription Lifecycle hooks // ----------------------------- - + /** * `preSubscriptionParsing` is the first hook to be executed in the GraphQL subscription lifecycle. The next hook will be `preSubscriptionExecution`. * This hook will only be triggered when subscriptions are enabled. @@ -141,7 +141,7 @@ declare namespace mercurius { context: TContext, ): Promise | void; } - + /** * `preSubscriptionExecution` is the second hook to be executed in the GraphQL subscription lifecycle. The previous hook was `preSubscriptionParsing`. * This hook will only be triggered when subscriptions are enabled. @@ -153,7 +153,7 @@ declare namespace mercurius { context: TContext, ): Promise | void; } - + /** * `onSubscriptionResolution` is the fourth hook to be executed in the GraphQL subscription lifecycle. The next hook will be `onSubscriptionEnd`. * This hook will only be triggered when subscriptions are enabled. @@ -164,7 +164,7 @@ declare namespace mercurius { context: TContext, ): Promise | void; } - + /** * `onSubscriptionEnd` is the fifth and final hook to be executed in the GraphQL subscription lifecycle. The previous hook was `onSubscriptionResolution`. * This hook will only be triggered when subscriptions are enabled. @@ -175,22 +175,22 @@ declare namespace mercurius { id: string | number, ): Promise | void; } - + // ---------------------------- // Application Lifecycle hooks // ---------------------------- - + export interface onExtendSchemaHandler { ( schema: GraphQLSchema, context: TContext, ): Promise | void; } - + interface ServiceConfig { setSchema: (schema: string) => ServiceConfig; } - + export interface MercuriusPlugin { < TData extends Record = Record, @@ -237,21 +237,21 @@ declare namespace mercurius { * Managed GraphQL schema object for doing custom execution with. Will reflect changes made via `extendSchema`, `defineResolvers`, etc. */ schema: GraphQLSchema; - + // addHook: overloads - + // Request lifecycle addHooks - + /** * `preParsing` is the first hook to be executed in the GraphQL request lifecycle. The next hook will be `preValidation`. */ addHook(name: 'preParsing', hook: preParsingHookHandler): void; - + /** * `preValidation` is the second hook to be executed in the GraphQL request lifecycle. The previous hook was `preParsing`, the next hook will be `preExecution`. */ addHook(name: 'preValidation', hook: preValidationHookHandler): void; - + /** * `preExecution` is the third hook to be executed in the GraphQL request lifecycle. The previous hook was `preValidation`. * Notice: in the `preExecution` hook, you can modify the following items by returning them in the hook definition: @@ -259,49 +259,49 @@ declare namespace mercurius { * - `errors` */ addHook(name: 'preExecution', hook: preExecutionHookHandler): void; - + /** * `onResolution` is the fifth and final hook to be executed in the GraphQL request lifecycle. The previous hook was `preExecution`. */ addHook = Record, TContext = MercuriusContext>(name: 'onResolution', hook: onResolutionHookHandler): void; - + // Subscription lifecycle addHooks - + /** * `preSubscriptionParsing` is the first hook to be executed in the GraphQL subscription lifecycle. The next hook will be `preSubscriptionExecution`. * This hook will only be triggered when subscriptions are enabled. */ addHook(name: 'preSubscriptionParsing', hook: preSubscriptionParsingHookHandler): void; - + /** * `preSubscriptionExecution` is the second hook to be executed in the GraphQL subscription lifecycle. The previous hook was `preSubscriptionParsing`. * This hook will only be triggered when subscriptions are enabled. */ addHook(name: 'preSubscriptionExecution', hook: preSubscriptionExecutionHookHandler): void; - + /** * `onSubscriptionResolution` is the fourth and final hook to be executed in the GraphQL subscription lifecycle. * This hook will only be triggered when subscriptions are enabled. */ addHook = Record, TContext = MercuriusContext>(name: 'onSubscriptionResolution', hook: onSubscriptionResolutionHookHandler): void; - + /** * `onSubscriptionEnd` is the fifth and final hook to be executed in the GraphQL subscription lifecycle. The previous hook was `onSubscriptionResolution`. * This hook will only be triggered when subscriptions are enabled. */ addHook(name: 'onSubscriptionEnd', hook: onSubscriptionEndHookHandler): void; - + // Application lifecycle addHooks addHook(name: 'onExtendSchema', hook: onExtendSchemaHandler): void; } - + interface QueryRequest { operationName?: string; query: string; variables?: object; extensions?: object; } - + interface WsConnectionParams { connectionInitPayload?: | (() => Record | Promise>) @@ -311,9 +311,9 @@ declare namespace mercurius { connectionCallback?: () => void; failedConnectionCallback?: (err: { message: string }) => void | Promise; failedReconnectCallback?: () => void; - rewriteConnectionInitPayload?: (payload: Record | undefined, context: TContext) => Record; + rewriteConnectionInitPayload?: (payload: Record | undefined, context: TContext) => Record; } - + export interface MercuriusSchemaOptions { /** * The GraphQL schema. String schema will be parsed @@ -332,7 +332,7 @@ declare namespace mercurius { */ schemaTransforms?: ((originalSchema: GraphQLSchema) => GraphQLSchema) | Array<(originalSchema: GraphQLSchema) => GraphQLSchema>; } - + export interface MercuriusGraphiQLOptions { /** * Expose the graphiql app, default `true` @@ -360,16 +360,16 @@ declare namespace mercurius { fetcherWrapper?: string; }> } - + export interface GrapQLValidateOptions { maxErrors?: number; } - + export interface MercuriusGraphQLOptions { parseOptions?: ParseOptions, validateOptions?: GrapQLValidateOptions } - + export interface MercuriusCommonOptions { /** * Serve GraphiQL on /graphiql if true or 'graphiql' and if routes is true @@ -446,33 +446,33 @@ declare namespace mercurius { subscription?: | boolean | { - emitter?: object; - pubsub?: any; // FIXME: Technically this should be the PubSub type. But PubSub is now typed as SubscriptionContext. - verifyClient?: ( - info: { origin: string; secure: boolean; req: IncomingMessage }, - next: ( - result: boolean, - code?: number, - message?: string, - headers?: OutgoingHttpHeaders - ) => void - ) => void; - context?: ( - socket: WebSocket, - request: FastifyRequest - ) => Record | Promise>; - onConnect?: (data: { - type: 'connection_init'; - payload: any; - }) => any; - onDisconnect?: (context: MercuriusContext) => void | Promise; - keepAlive?: number, - fullWsTransport?: boolean, - }; + emitter?: object; + pubsub?: any; // FIXME: Technically this should be the PubSub type. But PubSub is now typed as SubscriptionContext. + verifyClient?: ( + info: { origin: string; secure: boolean; req: IncomingMessage }, + next: ( + result: boolean, + code?: number, + message?: string, + headers?: OutgoingHttpHeaders + ) => void + ) => void; + context?: ( + socket: WebSocket, + request: FastifyRequest + ) => Record | Promise>; + onConnect?: (data: { + type: 'connection_init'; + payload: any; + }) => any; + onDisconnect?: (context: MercuriusContext) => void | Promise; + keepAlive?: number, + fullWsTransport?: boolean, + }; /** * Persisted queries, overrides persistedQueryProvider. */ - persistedQueries?: Record; + persistedQueries?: Record; /** * Only allow persisted queries. Required persistedQueries, overrides persistedQueryProvider. */ @@ -481,7 +481,7 @@ declare namespace mercurius { * Settings for enabling persisted queries. */ persistedQueryProvider?: PersistedQueryProvider; - + /** * Enable support for batched queries (POST requests only). * Batched query support allows clients to send an array of queries and @@ -490,18 +490,18 @@ declare namespace mercurius { allowBatchedQueries?: boolean; /** - * Customize the graphql routers initialized by mercurius with - * more fastify route options. - * + * Customize the graphql routers initialized by mercurius with + * more fastify route options. + * * Usecases: - * - Add more validation - * - change the schema structure + * - Add more validation + * - change the schema structure * - Hook on the request's life cycle * - Increase body size limit for larger queries */ - additionalRouteOptions?:Omit + additionalRouteOptions?: Omit } - + export type MercuriusOptions = MercuriusCommonOptions & (MercuriusSchemaOptions) export interface IResolvers { @@ -513,7 +513,7 @@ declare namespace mercurius { | IEnumResolver | undefined; } - + export type IResolverObject = { [key: string]: | IFieldResolver @@ -521,17 +521,17 @@ declare namespace mercurius { | IResolverObject | undefined; } - + export interface IResolverOptions { fragment?: string; resolve?: IFieldResolver; subscribe?: IFieldResolver; } - + type IEnumResolver = { [key: string]: string | number; - }; - + } + export interface IFieldResolver> { ( source: TSource, @@ -542,10 +542,10 @@ declare namespace mercurius { } ): any; } - + type MergeInfo = { delegate: ( - type: "query" | "mutation" | "subscription", + type: 'query' | 'mutation' | 'subscription', fieldName: string, args: { [key: string]: any; @@ -561,14 +561,14 @@ declare namespace mercurius { field: string; fragment: string; }>; - }; - + } + type Transform = { transformSchema?: (schema: GraphQLSchema) => GraphQLSchema; transformRequest?: (originalRequest: Request) => Request; transformResult?: (result: Result) => Result; - }; - + } + interface IDelegateToSchemaOptions< TContext = { [key: string]: any; @@ -585,31 +585,31 @@ declare namespace mercurius { transforms?: Array; skipValidation?: boolean; } - - type Operation = "query" | "mutation" | "subscription"; - + + type Operation = 'query' | 'mutation' | 'subscription' + type Result = ExecutionResult & { extensions?: Record; - }; - + } + interface IGraphQLToolsResolveInfo extends GraphQLResolveInfo { mergeInfo?: MergeInfo; } - + type Request = { document: DocumentNode; variables: Record; extensions?: Record; - }; - + } + type ValidationRules = | ValidationRule[] | ((params: { - source: string; - variables?: Record; - operationName?: string; - }) => ValidationRule[]); - + source: string; + variables?: Record; + operationName?: string; + }) => ValidationRule[]) + export interface PreExecutionHookResponse { schema?: GraphQLSchema document?: DocumentNode @@ -657,12 +657,12 @@ declare namespace mercurius { * Extended errors for adding additional information in error responses */ export class ErrorWithProps extends Error { - constructor(message: string, extensions?: object, statusCode?: number); + constructor (message: string, extensions?: object, statusCode?: number) /** * Custom additional properties of this error */ - extensions?: object; - statusCode?: number; + extensions?: object + statusCode?: number } /** @@ -672,7 +672,7 @@ declare namespace mercurius { prepared: (persistedQueries: object) => PersistedQueryProvider; preparedOnly: (persistedQueries: object) => PersistedQueryProvider; automatic: (maxSize?: number) => PersistedQueryProvider; - }; + } /** * Default error formatter. @@ -680,7 +680,7 @@ declare namespace mercurius { export const defaultErrorFormatter: ( execution: ExecutionResult & Required>, context: MercuriusContext - ) => { statusCode: number; response: FormattedExecutionResult }; + ) => { statusCode: number; response: FormattedExecutionResult } /** * Subscriptions with filter functionality @@ -718,7 +718,7 @@ declare namespace mercurius { export { mercurius as default } } -declare module "fastify" { +declare module 'fastify' { interface FastifyInstance { /** * GraphQL plugin @@ -752,12 +752,11 @@ declare module "fastify" { * @param opts Mercurius options */ declare function mercurius - ( - instance: FastifyInstance, - opts: mercurius.MercuriusOptions - ): void; - +( + instance: FastifyInstance, + opts: mercurius.MercuriusOptions +): void // CJS export // const mercurius = require('mercurius') -export = mercurius; +export = mercurius diff --git a/package.json b/package.json index 2c2007e7..00171a9b 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,8 @@ "docs": "docsify serve", "unit": "tap test/*.js test/internals/*.js", "cov": "tap --coverage-report=html -J test/*.js test/internals/*.js", - "lint": "npm run lint:standard && npm run lint:typescript", - "lint:fix": "standard --fix", - "lint:standard": "standard | snazzy", - "lint:typescript": "standard --parser @typescript-eslint/parser --plugin @typescript-eslint/eslint-plugin test/types/*.ts", + "lint": "eslint", + "lint:fix": "eslint --fix", "typescript": "tsd", "test": "npm run lint && npm run unit && npm run typescript", "test2": "npm run unit" @@ -37,21 +35,19 @@ "@types/isomorphic-form-data": "^2.0.0", "@types/node": "^20.1.4", "@types/ws": "^8.2.0", - "@typescript-eslint/eslint-plugin": "^5.21.0", - "@typescript-eslint/parser": "^5.21.0", "autocannon": "^7.3.0", "concurrently": "^8.0.1", "docsify-cli": "^4.4.3", + "eslint": "^9.9.1", "fastify": "^4.17.0", "graphql": "^16.0.0", "graphql-ws": "^5.11.2", + "neostandard": "^0.11.4", "pre-commit": "^1.2.2", "proxyquire": "^2.1.3", "semver": "^7.5.0", "sinon": "^17.0.0", - "snazzy": "^9.0.0", "split2": "^4.0.0", - "standard": "^17.0.0", "tap": "^16.3.0", "tsd": "^0.30.0", "typescript": "^5.0.2", @@ -77,6 +73,6 @@ "directory": "test/types" }, "engines": { - "node": ">=14.19.3" + "node": "^20.9.0 || >=22.0.0" } } diff --git a/test/types/index.ts b/test/types/index.ts index b941f5ba..4e863989 100644 --- a/test/types/index.ts +++ b/test/types/index.ts @@ -1,11 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ + import { expectAssignable, expectError, expectType } from 'tsd' -/* eslint-disable no-unused-expressions */ import { EventEmitter } from 'events' -// eslint-disable-next-line no-unused-vars import Fastify, { FastifyRequest } from 'fastify' -// eslint-disable-next-line no-unused-vars import { Readable } from 'stream' -// eslint-disable-next-line no-unused-vars import * as mercuriusNamespaceImport from '../..' import mercurius, { mercurius as mercuriusNamedImport, @@ -19,7 +17,6 @@ import mercurius, { MercuriusPlugin, MercuriusLoaders } from '../..' -// eslint-disable-next-line no-unused-vars import { DocumentNode, ExecutionResult, GraphQLSchema, ValidationContext, ValidationRule } from 'graphql' import { mapSchema } from '@graphql-tools/utils' import mq from 'mqemitter' @@ -62,7 +59,7 @@ const resolvers: IResolvers = { // declare module 'mercurius' { declare module '../../' { - interface MercuriusContext { // eslint-disable-line + interface MercuriusContext { request: FastifyRequest } } @@ -139,7 +136,7 @@ app.register(mercurius, { } }, Mutation: { - addDog (_root, { name, breed }: { name: string; breed?:string }, ctx) { + addDog (_root, { name, breed }: { name: string; breed?: string }, ctx) { ctx.pubsub.publish({ topic: 'new_dog', payload: { @@ -208,7 +205,7 @@ app.register(async function (app) { }) app.graphql.defineLoaders({ Dog: { - owner: async (queries: Array<{ obj: { name: keyof typeof owners }, params: {a: string} }>, _ctx) => { + owner: async (queries: Array<{ obj: { name: keyof typeof owners }, params: { a: string } }>, _ctx) => { return queries.map(({ obj }) => owners[obj.name]) } } @@ -580,7 +577,7 @@ expectError(() => { }) declare module 'fastify' { -// eslint-disable-next-line no-unused-vars + interface FastifyInstance { graphql: MercuriusPlugin }