-
-
Notifications
You must be signed in to change notification settings - Fork 570
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(types): ship automatic TypeScript typings #794
Conversation
package.json
Outdated
@@ -34,6 +34,9 @@ | |||
"prepack": "./scripts/build" | |||
}, | |||
"dependencies": { | |||
"@types/pg": "^7.4.10", | |||
"@types/graphql": "^0.8.2", | |||
"@types/jsonwebtoken": "<7.2.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because our interfaces expose types that use these types, the Typescript documentation states to bundle them as dependencies. i was mixed feelings on this!
@@ -75,8 +76,8 @@ | |||
"source-map-support": "^0.4.6", | |||
"supertest": "^2.0.1", | |||
"ts-node": "^2.0.0", | |||
"tslint": "^4.2.0", | |||
"typescript": "2.1.5" | |||
"tslint": "^5.10.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so we can disable new rules. will discuss later
@@ -4,4 +4,4 @@ npm_bin=$(npm bin) | |||
|
|||
set -e | |||
|
|||
$npm_bin/tslint $(find src -name "*.[tj]s" -a \! -wholename '*/node_modules/*') $@ | |||
$npm_bin/tslint --format verbose $(find src -name "*.[tj]s" -a \! -wholename '*/node_modules/*') $@ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adds rule names to output, essentially.
src/interfaces.ts
Outdated
import jwt = require('jsonwebtoken') | ||
import { GraphQLError } from 'graphql/error' | ||
|
||
export namespace Postgraphile { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
types that were used in various places i moved into a namespace. for instance, PostGraphileOptions
was almost fully duped in 2 places. i defined it once, imported it back two places, and extended it as needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capital g please 🤗
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/postgraphile/cli.ts
Outdated
|
||
const debugCli = debugFactory('postgraphile:cli') | ||
|
||
// tslint:disable no-console | ||
// tslint:disable no-console no-shadowed-variable | ||
// no-shadowed-variable is erroroneously reporting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
im betting the older tslint didnt do this :/ it was claiming that plugins
in a typedef was shadowed. infact, it was just annotating the type. bonkers AST parse. as you can see, i didn't modify anything to do w/ a variable called plugins
in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope; this was a legit error (my bad) 5599c82
@@ -1,101 +0,0 @@ | |||
import { IncomingMessage, ServerResponse } from 'http' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all of this content was trashed or moved into interfaces.d.ts
, as referenced above
@@ -1,30 +1,49 @@ | |||
/* eslint-disable */// Because we use tslint | |||
/* tslint:disable:no-any no-var-requires */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and here's the real only significant offender of this PR. to get the build to auto-populate typings, allowJs
must be set to false. this was the lone .js
in the lib.
this file could use have used some deep, deep tidy before i put my fingers on it. do to the size and complexity of it, rather than put proper types on everything in the true TS spirit, I declared many of the internals as any
. this was to prevent any major refactor or create even more change than we already want for this PR.
* - `express`. | ||
* - `koa` (2.0). | ||
*/ | ||
export interface ICreateRequestHandler extends Postgraphile.PostGraphileOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
boom! that huge red-file (createPostGraphileHttpRequestHandler.d.ts
), gets compacted down to these 9 LOC. mo betta, much betta.
if (options.showErrorStack) | ||
formattedError.stack = | ||
options.showErrorStack === 'json' | ||
options.showErrorStack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 needs discussion. showErrorStack
is a bool everywhere else, and the lib wouldn't compile with the above conditional. is this really a supported feature? it looks like in both places, the stack makes it through.
im not sure of the intent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was before my time, but I believe there used to be an option where you could have the stack return an array rather than a string. I don't think this is currently possible, but a review of the code would be needed to assert this is the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ya. it logically doesn't make sense that showErrorStack
would affect the return type of the stack anyway. i'm prone to change this whole logic entirely to:
formattedError.stack = option.showErrorStack ? (error.stack || '') : ''
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it turns out that this is actually a documented feature:
showErrorStack
: Enables adding a stack field to the error response. Can be either the booleantrue
(which results in a single stack string) or the string'json'
(which causes the stack to become an array with elements for each line of the stack). Recommended in development, not recommended in production.
-- https://www.graphile.org/postgraphile/usage-library/#api-postgraphilepgconfig-schemaname-options
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the original ternary, either way, a stack was assigned. this is a bug with the original impl. let's file a bug and address it independently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i filed #804 for addressing this
if (error) { | ||
return next(error) | ||
} | ||
fn(req, res, next) | ||
}) | ||
} | ||
}, | ||
(req, res, next) => next(), | ||
(req: any, res: any, next: any) => next(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
honestly, i dont know how this is working at all right now (before i touched it, or after). i can't find next
on 245 in scope? our initial value is a function which calls next
, which i dont see defined anywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know how that feels. It’s one of the function arguments: req, res, NEXT 🙃
Basically this is a no-op middleware we use as a base so we can efficiently combine the other middlewares into one middleware. It helps the JIT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, next isnt really used. we may as well have literally used a no-op on 245. 237-241 provides a next
implementation for us. doh! thx :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this reduce is super confusing; the next
is actually called, it's just the inner most "no-op" middleware; each middleware wraps the previous one and we have this be the initial one.
@@ -430,7 +438,7 @@ export default function createPostGraphileHttpRequestHandler(options) { | |||
req._koaCtx.compress = false | |||
} | |||
await new Promise((resolve, reject) => { | |||
const stream = sendFile(req, assetPathRelative, { | |||
sendFile(req, assetPathRelative, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ya ya ya. the linter made me do all of this pedantic junk. im sorry!
@@ -742,7 +749,7 @@ export default function createPostGraphileHttpRequestHandler(options) { | |||
|
|||
res.setHeader('Content-Type', 'application/json; charset=utf-8') | |||
|
|||
res.end(JSON.stringify(returnArray ? results : results[0])) | |||
res.end(JSON.stringify(returnArray ? results : results![0])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a new TS feature i learned recently. assert that <preceding_var> is defined!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is nice! This has been annoying me for a while 👍
@@ -1,9 +1,10 @@ | |||
/* eslint-disable */// Because we use tslint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only change below is changing where types are imported from
@@ -1,188 +1,18 @@ | |||
import { Pool, PoolConfig } from 'pg' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only changes below are changing where types are imported from, and migrating these types to a common interfaces file
tsconfig.json
Outdated
@@ -14,10 +16,9 @@ | |||
"suppressImplicitAnyIndexErrors": true, | |||
"strictNullChecks": true, | |||
"noFallthroughCasesInSwitch": true, | |||
"noUnusedParameters": true, | |||
"noUnusedParameters": false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because so often we have (req, res, next)
where res
isn't used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we configure it like ESLint using these rules rather than disabling it outright? I'm not sure how flexible it is in this regard.
- we don't care if parameters start with underscores (e.g.
_res
would be ignored) - we don't care if one of the parameters after this one is used (e.g.
(req, res, next) => next()
shouldn't raise)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, perfect. i wasn't aware that that was a thing. looks like ts honors it as well. 2396b0e
@@ -1,6 +1,8 @@ | |||
{ | |||
"compilerOptions": { | |||
"allowJs": true, | |||
"declaration": true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
@@ -1,18 +0,0 @@ | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👋 bye bye
@@ -1,4 +0,0 @@ | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👋 adddddiiioos!
a net -1K LOC! i say that's a win :) |
src/interfaces.ts
Outdated
import jwt = require('jsonwebtoken') | ||
import { GraphQLError } from 'graphql/error' | ||
|
||
export namespace Postgraphile { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capital g please 🤗
src/interfaces.ts
Outdated
(ctx: { req: IncomingMessage, res: ServerResponse }, next: () => void): Promise<void> | ||
getGraphQLSchema: () => Promise<GraphQLSchema> | ||
formatError: (e: GraphQLError) => GraphQLError | ||
pgPool: Pool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this has more stuff on it now, I should check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
withPostGraphileContextFromReqRes
should be added:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this one was a bit of a drag, inducing a little more typing refactor than I had wanted. ill see if i can annotate it. i bundled your remark from https://github.com/graphile/postgraphile/pull/794/files#r200834248 in this commit too.
if (error) { | ||
return next(error) | ||
} | ||
fn(req, res, next) | ||
}) | ||
} | ||
}, | ||
(req, res, next) => next(), | ||
(req: any, res: any, next: any) => next(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know how that feels. It’s one of the function arguments: req, res, NEXT 🙃
Basically this is a no-op middleware we use as a base so we can efficiently combine the other middlewares into one middleware. It helps the JIT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent start! Here's some feedback and although it took me a solid 45 minutes to go through this I've not reviewed it in enough detail to be confident I've not missed anything.
Please make sure any changes from now are performed as additional commits (please do not rebase!) to make further reviews easier.
src/interfaces.ts
Outdated
(ctx: { req: IncomingMessage, res: ServerResponse }, next: () => void): Promise<void> | ||
getGraphQLSchema: () => Promise<GraphQLSchema> | ||
formatError: (e: GraphQLError) => GraphQLError | ||
pgPool: Pool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
withPostGraphileContextFromReqRes
should be added:
).toBeInstanceOf(http.ServerResponse) | ||
}) | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you've replaced this test file after deleting it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(This is possibly where 855 of the removed 1K LOC came from 😳 )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh brother. 🤦♂️
@@ -36,12 +55,12 @@ const sendFile = require('send') | |||
const LRU = require('lru-cache') | |||
const crypto = require('crypto') | |||
|
|||
const calculateQueryHash = queryString => crypto.createHash('sha1').update(queryString).digest('base64') | |||
const calculateQueryHash = (query: string) => crypto.createHash('sha1').update(query).digest('base64') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer queryString: string
since "query string" is what I would call it aloud. "query" has a different meaning to me.
@@ -94,15 +111,14 @@ const origGraphiqlHtml = new Promise((resolve, reject) => { | |||
* We need to be able to share the withPostGraphileContext logic between HTTP | |||
* and websockets | |||
*/ | |||
const withPostGraphileContextFromReqResGenerator = options => { | |||
function withPostGraphileContextFromReqResGenerator(options: ICreateRequestHandler): any { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than any
, consider using something like:
function withPostGraphileContextFromReqResGenerator<T>(options: ICreateRequestHandler): (req: IncomingMessage, res: ServerResponse, moreOptions: any, fn: (pgClient: PoolClient) => T) => T
You can use any
instead of T
if you like. Effectively whatever is returned by the callback function fn
will be returned by the function returned from withPostGraphileContextFromReqResGenerator
src/interfaces.ts
Outdated
// "omit" (default) - relay connections only, | ||
// "only" - simple collections only (no Relay connections), | ||
// "both" - both | ||
simpleCollections?: 'omit' | 'both' | 'only', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should consider adding [propName: string]: any
here because PostGraphile plugins can add additional options to this object (e.g. the supporter plugin adds simpleSubscriptions
as an option)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -850,6 +857,13 @@ function addCORSHeaders(res) { | |||
) | |||
} | |||
|
|||
function createBadAuthorizationHeaderError(): void { | |||
throw httpError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't throw here, we throw down below (and the name is just create
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoops! great catch. thx. fb7430b
const sse = str => { | ||
if (isKoa) { | ||
const sse = (str: string) => { | ||
if (isKoa && stream) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh. This is one of the things I hate about typed JS. The logic here would be confusing to the reader as they'd be "if it's koa but there is no stream then do res.write? Why would there not be a stream...?" and then read the code to find it's impossible to not have a stream if it's Koa.
Can you use stream!.write(str)
below instead so that it's easier to read and doesn't change the compiled code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tsconfig.json
Outdated
@@ -14,10 +16,9 @@ | |||
"suppressImplicitAnyIndexErrors": true, | |||
"strictNullChecks": true, | |||
"noFallthroughCasesInSwitch": true, | |||
"noUnusedParameters": true, | |||
"noUnusedParameters": false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we configure it like ESLint using these rules rather than disabling it outright? I'm not sure how flexible it is in this regard.
- we don't care if parameters start with underscores (e.g.
_res
would be ignored) - we don't care if one of the parameters after this one is used (e.g.
(req, res, next) => next()
shouldn't raise)
tsconfig.json
Outdated
"noUnusedLocals": true | ||
}, | ||
"include": ["src", "typings"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have you removed this? I store a lot of extra stuff (tests for user issues, WIP code, local test scripts, etc) that I wouldn't want to be affected by tsconfig
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mmm. i think i see what you mean. flipping tsconfig to allowJs: false
prevents .js
files from being copied into the build, so those files would be omitted anyway if we auto gen the declarations. if that content needs to ship, we can do it in a post processing step. does that work? are you certain those need to be shipped?
@@ -6,6 +6,6 @@ declare module 'pg-sql2' { | |||
|
|||
export function compile(query: SQLQuery): QueryConfig | |||
export function query(strings: TemplateStringsArray, ...values: Array<SQLNode | SQLQuery>): SQLQuery | |||
export function value(val: mixed): SQLNode | |||
export function value(val: any): SQLNode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this change required? Specifically this should only receive JSON-like values, I think...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i confess. laziness as i wasn't not sure how to import a locally defined interface :/ ../src/interfaces
Hour and a half into reviewing this, few minor changes but looking good so far. It was too intense to review on it's own so I've checked out the All the minor files are now done; just the major one |
Cool! Ya, sorry it blew up. |
😅 Okay; I've reviewed it fully and made a load of changes and I'm pretty much ready to merge it. I need to give it a last check over (and fix any CI errors) but I'm already running super late for my evening plans so I'm going to head out. I've reverted a few things to their previous behaviour. |
I just went through the compiled bundle and inspected every d.ts file; the external requires that we need to bundle types for are:
And |
Thank you for doing the work! Being TypeScript through and through should make a big difference to a lot of users, and greater confidence in PRs. |
problem statment
partially address #520
solution