feat(lint): enable lint (#11)
parent
7166554bf0
commit
ce086cba19
|
@ -0,0 +1 @@
|
||||||
|
dist
|
|
@ -0,0 +1,60 @@
|
||||||
|
const { defineConfig } = require('eslint-define-config')
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
root: true,
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:node/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'prettier',
|
||||||
|
],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 2021,
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint', 'import'],
|
||||||
|
globals: {
|
||||||
|
fetch: false,
|
||||||
|
Response: false,
|
||||||
|
Request: false,
|
||||||
|
addEventListener: false,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
quotes: ['error', 'single'],
|
||||||
|
semi: ['error', 'never'],
|
||||||
|
'no-debugger': ['error'],
|
||||||
|
'no-empty': ['warn', { allowEmptyCatch: true }],
|
||||||
|
'no-process-exit': 'off',
|
||||||
|
'no-useless-escape': 'off',
|
||||||
|
'prefer-const': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
destructuring: 'all',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/ban-types': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
types: {
|
||||||
|
Function: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'sort-imports': 0,
|
||||||
|
'import/order': [2, { alphabetize: { order: 'asc' } }],
|
||||||
|
|
||||||
|
'node/no-missing-import': 'off',
|
||||||
|
'node/no-missing-require': 'off',
|
||||||
|
'node/no-deprecated-api': 'off',
|
||||||
|
'node/no-unpublished-import': 'off',
|
||||||
|
'node/no-unpublished-require': 'off',
|
||||||
|
'node/no-unsupported-features/es-syntax': 'off',
|
||||||
|
|
||||||
|
'@typescript-eslint/no-empty-function': ['error', { allow: ['arrowFunctions'] }],
|
||||||
|
'@typescript-eslint/no-empty-interface': 'off',
|
||||||
|
'@typescript-eslint/no-inferrable-types': 'off',
|
||||||
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
|
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
|
||||||
|
},
|
||||||
|
})
|
|
@ -14,6 +14,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
node-version: 16.x
|
node-version: 16.x
|
||||||
- run: yarn
|
- run: yarn
|
||||||
|
- run: yarn lint
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
- run: yarn test
|
- run: yarn test
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"jsxSingleQuote": true,
|
||||||
|
"endOfLine": "lf"
|
||||||
|
}
|
15
package.json
15
package.json
|
@ -21,6 +21,8 @@
|
||||||
"test:all": "yarn test && yarn test:deno && yarn test:bun",
|
"test:all": "yarn test && yarn test:deno && yarn test:bun",
|
||||||
"denoify": "rimraf deno_dist && denoify && rimraf 'deno_dist/**/*.test.ts'",
|
"denoify": "rimraf deno_dist && denoify && rimraf 'deno_dist/**/*.test.ts'",
|
||||||
"build": "rimraf dist && tsc --project tsconfig.build.json",
|
"build": "rimraf dist && tsc --project tsconfig.build.json",
|
||||||
|
"lint": "eslint --ext js,ts src .eslintrc.js",
|
||||||
|
"lint:fix": "eslint --ext js,ts src .eslintrc.js --fix",
|
||||||
"release": "npm publish"
|
"release": "npm publish"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -33,9 +35,22 @@
|
||||||
"denoify": "^0.11.1",
|
"denoify": "^0.11.1",
|
||||||
"jest": "^28.1.2",
|
"jest": "^28.1.2",
|
||||||
"jest-environment-miniflare": "^2.6.0",
|
"jest-environment-miniflare": "^2.6.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.21.0",
|
||||||
|
"@typescript-eslint/parser": "^5.21.0",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
|
"eslint": "^8.14.0",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
"eslint-define-config": "^1.4.0",
|
||||||
|
"eslint-import-resolver-typescript": "^2.7.1",
|
||||||
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||||
|
"eslint-plugin-flowtype": "^8.0.3",
|
||||||
|
"eslint-plugin-import": "^2.26.0",
|
||||||
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"ts-jest": "^28.0.5",
|
"ts-jest": "^28.0.5",
|
||||||
"typescript": "^4.7.4"
|
"typescript": "^4.7.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=11.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
194
src/index.ts
194
src/index.ts
|
@ -10,7 +10,7 @@ import {
|
||||||
specifiedRules,
|
specifiedRules,
|
||||||
getOperationAST,
|
getOperationAST,
|
||||||
GraphQLError,
|
GraphQLError,
|
||||||
} from "graphql";
|
} from 'graphql'
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
GraphQLSchema,
|
GraphQLSchema,
|
||||||
|
@ -18,101 +18,88 @@ import type {
|
||||||
ValidationRule,
|
ValidationRule,
|
||||||
FormattedExecutionResult,
|
FormattedExecutionResult,
|
||||||
GraphQLFormattedError,
|
GraphQLFormattedError,
|
||||||
} from "graphql";
|
} from 'graphql'
|
||||||
|
|
||||||
import type { Context } from "hono";
|
import type { Context } from 'hono'
|
||||||
import type { Next } from "hono";
|
import type { Next } from 'hono'
|
||||||
import { parseBody } from "./parse-body";
|
import { parseBody } from './parse-body'
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
schema: GraphQLSchema;
|
schema: GraphQLSchema
|
||||||
rootValue?: unknown;
|
rootValue?: unknown
|
||||||
pretty?: boolean;
|
pretty?: boolean
|
||||||
validationRules?: ReadonlyArray<ValidationRule>;
|
validationRules?: ReadonlyArray<ValidationRule>
|
||||||
// graphiql?: boolean
|
// graphiql?: boolean
|
||||||
};
|
}
|
||||||
|
|
||||||
export const graphqlServer = (options: Options) => {
|
export const graphqlServer = (options: Options) => {
|
||||||
const schema = options.schema;
|
const schema = options.schema
|
||||||
const rootValue = options.rootValue;
|
const rootValue = options.rootValue
|
||||||
const pretty = options.pretty ?? false;
|
const pretty = options.pretty ?? false
|
||||||
const validationRules = options.validationRules ?? [];
|
const validationRules = options.validationRules ?? []
|
||||||
// const showGraphiQL = options.graphiql ?? false
|
// const showGraphiQL = options.graphiql ?? false
|
||||||
|
|
||||||
return async (c: Context, next: Next) => {
|
return async (c: Context, next: Next) => {
|
||||||
// GraphQL HTTP only supports GET and POST methods.
|
// GraphQL HTTP only supports GET and POST methods.
|
||||||
if (c.req.method !== "GET" && c.req.method !== "POST") {
|
if (c.req.method !== 'GET' && c.req.method !== 'POST') {
|
||||||
return c.json(
|
return c.json(errorMessages(['GraphQL only supports GET and POST requests.']), 405, {
|
||||||
errorMessages(["GraphQL only supports GET and POST requests."]),
|
Allow: 'GET, POST',
|
||||||
405,
|
})
|
||||||
{
|
|
||||||
Allow: "GET, POST",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let params: GraphQLParams;
|
let params: GraphQLParams
|
||||||
try {
|
try {
|
||||||
params = await getGraphQLParams(c.req);
|
params = await getGraphQLParams(c.req)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
console.error(`${e.stack || e.message}`);
|
console.error(`${e.stack || e.message}`)
|
||||||
return c.json(errorMessages([e.message], [e]), 400);
|
return c.json(errorMessages([e.message], [e]), 400)
|
||||||
}
|
}
|
||||||
throw e;
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
const { query, variables, operationName } = params;
|
const { query, variables, operationName } = params
|
||||||
|
|
||||||
if (query == null) {
|
if (query == null) {
|
||||||
return c.json(errorMessages(["Must provide query string."]), 400);
|
return c.json(errorMessages(['Must provide query string.']), 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
const schemaValidationErrors = validateSchema(schema);
|
const schemaValidationErrors = validateSchema(schema)
|
||||||
if (schemaValidationErrors.length > 0) {
|
if (schemaValidationErrors.length > 0) {
|
||||||
// Return 500: Internal Server Error if invalid schema.
|
// Return 500: Internal Server Error if invalid schema.
|
||||||
return c.json(
|
return c.json(
|
||||||
errorMessages(
|
errorMessages(['GraphQL schema validation error.'], schemaValidationErrors),
|
||||||
["GraphQL schema validation error."],
|
|
||||||
schemaValidationErrors
|
|
||||||
),
|
|
||||||
500
|
500
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let documentAST: DocumentNode;
|
let documentAST: DocumentNode
|
||||||
try {
|
try {
|
||||||
documentAST = parse(new Source(query, "GraphQL request"));
|
documentAST = parse(new Source(query, 'GraphQL request'))
|
||||||
} catch (syntaxError: unknown) {
|
} catch (syntaxError: unknown) {
|
||||||
// Return 400: Bad Request if any syntax errors errors exist.
|
// Return 400: Bad Request if any syntax errors errors exist.
|
||||||
if (syntaxError instanceof Error) {
|
if (syntaxError instanceof Error) {
|
||||||
console.error(`${syntaxError.stack || syntaxError.message}`);
|
console.error(`${syntaxError.stack || syntaxError.message}`)
|
||||||
const e = new GraphQLError(syntaxError.message, {
|
const e = new GraphQLError(syntaxError.message, {
|
||||||
originalError: syntaxError,
|
originalError: syntaxError,
|
||||||
});
|
})
|
||||||
return c.json(errorMessages(["GraphQL syntax error."], [e]), 400);
|
return c.json(errorMessages(['GraphQL syntax error.'], [e]), 400)
|
||||||
}
|
}
|
||||||
throw syntaxError;
|
throw syntaxError
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate AST, reporting any errors.
|
// Validate AST, reporting any errors.
|
||||||
const validationErrors = validate(schema, documentAST, [
|
const validationErrors = validate(schema, documentAST, [...specifiedRules, ...validationRules])
|
||||||
...specifiedRules,
|
|
||||||
...validationRules,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (validationErrors.length > 0) {
|
if (validationErrors.length > 0) {
|
||||||
// Return 400: Bad Request if any validation errors exist.
|
// Return 400: Bad Request if any validation errors exist.
|
||||||
return c.json(
|
return c.json(errorMessages(['GraphQL validation error.'], validationErrors), 400)
|
||||||
errorMessages(["GraphQL validation error."], validationErrors),
|
|
||||||
400
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c.req.method === "GET") {
|
if (c.req.method === 'GET') {
|
||||||
// Determine if this GET request will perform a non-query.
|
// Determine if this GET request will perform a non-query.
|
||||||
const operationAST = getOperationAST(documentAST, operationName);
|
const operationAST = getOperationAST(documentAST, operationName)
|
||||||
if (operationAST && operationAST.operation !== "query") {
|
if (operationAST && operationAST.operation !== 'query') {
|
||||||
/*
|
/*
|
||||||
Now , does not support GraphiQL
|
Now , does not support GraphiQL
|
||||||
if (showGraphiQL) {
|
if (showGraphiQL) {
|
||||||
|
@ -126,12 +113,12 @@ export const graphqlServer = (options: Options) => {
|
||||||
`Can only perform a ${operationAST.operation} operation from a POST request.`,
|
`Can only perform a ${operationAST.operation} operation from a POST request.`,
|
||||||
]),
|
]),
|
||||||
405,
|
405,
|
||||||
{ Allow: "POST" }
|
{ Allow: 'POST' }
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result: FormattedExecutionResult;
|
let result: FormattedExecutionResult
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = await execute({
|
result = await execute({
|
||||||
|
@ -140,29 +127,23 @@ export const graphqlServer = (options: Options) => {
|
||||||
rootValue,
|
rootValue,
|
||||||
variableValues: variables,
|
variableValues: variables,
|
||||||
operationName: operationName,
|
operationName: operationName,
|
||||||
});
|
})
|
||||||
} catch (contextError: unknown) {
|
} catch (contextError: unknown) {
|
||||||
if (contextError instanceof Error) {
|
if (contextError instanceof Error) {
|
||||||
console.error(`${contextError.stack || contextError.message}`);
|
console.error(`${contextError.stack || contextError.message}`)
|
||||||
const e = new GraphQLError(contextError.message, {
|
const e = new GraphQLError(contextError.message, {
|
||||||
originalError: contextError,
|
originalError: contextError,
|
||||||
nodes: documentAST,
|
nodes: documentAST,
|
||||||
});
|
})
|
||||||
// Return 400: Bad Request if any execution context errors exist.
|
// Return 400: Bad Request if any execution context errors exist.
|
||||||
return c.json(
|
return c.json(errorMessages(['GraphQL execution context error.'], [e]), 400)
|
||||||
errorMessages(["GraphQL execution context error."], [e]),
|
|
||||||
400
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
throw contextError;
|
throw contextError
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.data) {
|
if (!result.data) {
|
||||||
if (result.errors) {
|
if (result.errors) {
|
||||||
return c.json(
|
return c.json(errorMessages([result.errors.toString()], result.errors), 500)
|
||||||
errorMessages([result.errors.toString()], result.errors),
|
|
||||||
500
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,70 +154,67 @@ export const graphqlServer = (options: Options) => {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (pretty) {
|
if (pretty) {
|
||||||
const payload = JSON.stringify(result, null, pretty ? 2 : 0);
|
const payload = JSON.stringify(result, null, pretty ? 2 : 0)
|
||||||
return c.text(payload, 200, {
|
return c.text(payload, 200, {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
return c.json(result);
|
return c.json(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
await next(); // XXX
|
await next() // XXX
|
||||||
};
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export interface GraphQLParams {
|
|
||||||
query: string | null;
|
|
||||||
variables: { readonly [name: string]: unknown } | null;
|
|
||||||
operationName: string | null;
|
|
||||||
raw: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getGraphQLParams = async (
|
export interface GraphQLParams {
|
||||||
request: Request
|
query: string | null
|
||||||
): Promise<GraphQLParams> => {
|
variables: { readonly [name: string]: unknown } | null
|
||||||
const urlData = new URLSearchParams(request.url.split("?")[1]);
|
operationName: string | null
|
||||||
const bodyData = await parseBody(request);
|
raw: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getGraphQLParams = async (request: Request): Promise<GraphQLParams> => {
|
||||||
|
const urlData = new URLSearchParams(request.url.split('?')[1])
|
||||||
|
const bodyData = await parseBody(request)
|
||||||
|
|
||||||
// GraphQL Query string.
|
// GraphQL Query string.
|
||||||
let query = urlData.get("query") ?? (bodyData.query as string | null);
|
let query = urlData.get('query') ?? (bodyData.query as string | null)
|
||||||
|
|
||||||
if (typeof query !== "string") {
|
if (typeof query !== 'string') {
|
||||||
query = null;
|
query = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the variables if needed.
|
// Parse the variables if needed.
|
||||||
let variables = (urlData.get("variables") ?? bodyData.variables) as {
|
let variables = (urlData.get('variables') ?? bodyData.variables) as {
|
||||||
readonly [name: string]: unknown;
|
readonly [name: string]: unknown
|
||||||
} | null;
|
} | null
|
||||||
if (typeof variables === "string") {
|
if (typeof variables === 'string') {
|
||||||
try {
|
try {
|
||||||
variables = JSON.parse(variables);
|
variables = JSON.parse(variables)
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Variables are invalid JSON.");
|
throw Error('Variables are invalid JSON.')
|
||||||
}
|
}
|
||||||
} else if (typeof variables !== "object") {
|
} else if (typeof variables !== 'object') {
|
||||||
variables = null;
|
variables = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name of GraphQL operation to execute.
|
// Name of GraphQL operation to execute.
|
||||||
let operationName =
|
let operationName = urlData.get('operationName') ?? (bodyData.operationName as string | null)
|
||||||
urlData.get("operationName") ?? (bodyData.operationName as string | null);
|
if (typeof operationName !== 'string') {
|
||||||
if (typeof operationName !== "string") {
|
operationName = null
|
||||||
operationName = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const raw = urlData.get("raw") != null || bodyData.raw !== undefined;
|
const raw = urlData.get('raw') != null || bodyData.raw !== undefined
|
||||||
|
|
||||||
const params: GraphQLParams = {
|
const params: GraphQLParams = {
|
||||||
query: query,
|
query: query,
|
||||||
variables: variables,
|
variables: variables,
|
||||||
operationName: operationName,
|
operationName: operationName,
|
||||||
raw: raw,
|
raw: raw,
|
||||||
};
|
}
|
||||||
|
|
||||||
return params;
|
return params
|
||||||
};
|
}
|
||||||
|
|
||||||
export const errorMessages = (
|
export const errorMessages = (
|
||||||
messages: string[],
|
messages: string[],
|
||||||
|
@ -245,16 +223,16 @@ export const errorMessages = (
|
||||||
if (graphqlErrors) {
|
if (graphqlErrors) {
|
||||||
return {
|
return {
|
||||||
errors: graphqlErrors,
|
errors: graphqlErrors,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
errors: messages.map((message) => {
|
errors: messages.map((message) => {
|
||||||
return {
|
return {
|
||||||
message: message,
|
message: message,
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// export const graphiQLResponse = () => {}
|
// export const graphiQLResponse = () => {}
|
||||||
|
|
Loading…
Reference in New Issue