diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 1f1ddbaf..9fb51ec6 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -28,7 +28,10 @@ jobs: uses: actions/upload-artifact@v2 with: name: lib - path: lib + path: | + cjs + esm + umd release: name: Release @@ -58,7 +61,6 @@ jobs: uses: actions/download-artifact@v2 with: name: lib - path: lib - name: Release env: GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.gitignore b/.gitignore index f390a854..7ae9fff1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ **/.DS_Store node_modules -lib +cjs +esm +umd dist .vscode .yarn/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d383679..d15d9246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +# [1.11.0](https://github.com/enisdenjo/graphql-ws/compare/v1.10.0...v1.11.0) (2020-11-04) + + +### Bug Fixes + +* Node 10 is the min supported version ([19844d7](https://github.com/enisdenjo/graphql-ws/commit/19844d72f4638f9f7126870f9d9a744cdb4814c4)) +* Support more `graphql` versions ([de69b4e](https://github.com/enisdenjo/graphql-ws/commit/de69b4ea39905f9b3343711e9defe68c6746b842)) +* **server:** Close socket if `onSubscribe` returns invalid array ([#53](https://github.com/enisdenjo/graphql-ws/issues/53)) ([0464a54](https://github.com/enisdenjo/graphql-ws/commit/0464a54eee09dfdf66d65bf539a4d8f596be2697)) +* **server:** Consistently set `rootValue` and `contextValue`, if not overridden ([#49](https://github.com/enisdenjo/graphql-ws/issues/49)) ([7aa3bcd](https://github.com/enisdenjo/graphql-ws/commit/7aa3bcdd38d40e83306f867a5b6b1bd612ec5fe3)) +* **server:** Distribute server error to all clients even if one error listener throws ([#56](https://github.com/enisdenjo/graphql-ws/issues/56)) ([b96dbb9](https://github.com/enisdenjo/graphql-ws/commit/b96dbb98bb6f71956321ce1202af0af50df4e40e)) +* **server:** Don't surface bad request error details in production ([#55](https://github.com/enisdenjo/graphql-ws/issues/55)) ([70317b2](https://github.com/enisdenjo/graphql-ws/commit/70317b2619b7729e5d5556b4e5388f142414b082)) + + +### Features + +* `cjs`, `esm` and `umd` builds with minification and compression for the browser ([#58](https://github.com/enisdenjo/graphql-ws/issues/58)) ([ebb8dfe](https://github.com/enisdenjo/graphql-ws/commit/ebb8dfe8a1e50507bcc2b0929600d755ddd98b1d)) + + +### Performance Improvements + +* Reduce runtime prototype traversal for hasOwnProperty ([#52](https://github.com/enisdenjo/graphql-ws/issues/52)) ([1bb9218](https://github.com/enisdenjo/graphql-ws/commit/1bb9218ad3ee9442442122c1d10910d51951b763)) + # [1.10.0](https://github.com/enisdenjo/graphql-ws/compare/v1.9.3...v1.10.0) (2020-11-03) diff --git a/README.md b/README.md index ab0b059c..dc29494b 100644 --- a/README.md +++ b/README.md @@ -273,14 +273,14 @@ export const network = Network.create(fetchOrSubscribe, fetchOrSubscribe); ```typescript import { ApolloLink, Operation, FetchResult, Observable } from '@apollo/client'; import { print } from 'graphql'; -import { createClient, Config, Client } from 'graphql-ws'; +import { createClient, ClientOptions, Client } from 'graphql-ws'; class WebSocketLink extends ApolloLink { private client: Client; - constructor(config: Config) { + constructor(options: ClientOptions) { super(); - this.client = createClient(config); + this.client = createClient(options); } public request(operation: Operation): Observable { @@ -288,7 +288,8 @@ class WebSocketLink extends ApolloLink { return this.client.subscribe( { ...operation, query: print(operation.query) }, { - ...sink, + next: sink.next, + complete: sink.complete, error: (err) => { if (err instanceof Error) { sink.error(err); @@ -329,6 +330,36 @@ const link = new WebSocketLink({ +
+Client usage in browser + +```html + + + + + GraphQL over WebSocket + + + + + + +``` + +
+
Client usage in Node diff --git a/docs/interfaces/_server_.serveroptions.md b/docs/interfaces/_server_.serveroptions.md index 4e3ef748..e9547ea8 100644 --- a/docs/interfaces/_server_.serveroptions.md +++ b/docs/interfaces/_server_.serveroptions.md @@ -104,6 +104,10 @@ Throwing an error from within this function will close the socket with the `Error` message in the close event reason. +Since the library makes sure to complete streaming +operations even after an abrupt closure, this callback +will still be called. + ___ ### onConnect @@ -204,10 +208,11 @@ If you return `ExecutionArgs` from the callback, it will be used instead of trying to build one internally. In this case, you are responsible for providing a ready set of arguments which will -be directly plugged in the operation execution. Beware, -the `context` server option is an exception. Only if you -dont provide a context alongside the returned value -here, the `context` server option will be used instead. +be directly plugged in the operation execution. + +Omitting the fields `contextValue` or `rootValue` +from the returned value will have the provided server +options fill in the gaps. To report GraphQL errors simply return an array of them from the callback, they will be reported @@ -233,9 +238,9 @@ The GraphQL root fields or resolvers to go alongside the schema. Learn more about them here: https://graphql.org/learn/execution/#root-fields-resolvers. -If you return from the `onSubscribe` callback, the -root field value will NOT be injected. You should add it -in the returned `ExecutionArgs` from the callback. +If you return from `onSubscribe`, and the returned value is +missing the `rootValue` field, the relevant operation root +will be used instead. ___ diff --git a/docs/interfaces/_types_.sink.md b/docs/interfaces/_types_.sink.md index b2da9d42..a733dfac 100644 --- a/docs/interfaces/_types_.sink.md +++ b/docs/interfaces/_types_.sink.md @@ -38,18 +38,19 @@ ___ ### error -▸ **error**(`error`: Error \| readonly GraphQLError[] \| unknown): void +▸ **error**(`error`: unknown): void An error that has occured. Calling this function "closes" the sink. -The error can be also `CloseEvent`, but to avoid bundling DOM typings -because the client can run in Node env too, you should assert -the close event type during implementation. +Besides the errors being `Error` and `readonly GraphQLError[]`, it +can also be a `CloseEvent`, but to avoid bundling DOM typings because +the client can run in Node env too, you should assert the close event +type during implementation. #### Parameters: Name | Type | ------ | ------ | -`error` | Error \| readonly GraphQLError[] \| unknown | +`error` | unknown | **Returns:** void diff --git a/docs/modules/_server_.md b/docs/modules/_server_.md index f333a4a1..07c73d72 100644 --- a/docs/modules/_server_.md +++ b/docs/modules/_server_.md @@ -25,7 +25,7 @@ ### GraphQLExecutionContextValue -Ƭ **GraphQLExecutionContextValue**: object \| symbol \| number \| string \| boolean \| null +Ƭ **GraphQLExecutionContextValue**: object \| symbol \| number \| string \| boolean \| undefined \| null A concrete GraphQL execution context value type. diff --git a/docs/modules/_types_.md b/docs/modules/_types_.md index 830ec255..645a4339 100644 --- a/docs/modules/_types_.md +++ b/docs/modules/_types_.md @@ -4,6 +4,8 @@ # Module: "types" +types + ## Index ### Interfaces diff --git a/package.json b/package.json index bba3b8aa..857070e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graphql-ws", - "version": "1.10.0", + "version": "1.11.0", "description": "Coherent, zero-dependency, lazy, simple, GraphQL over WebSocket Protocol compliant server and client", "keywords": [ "protocol", @@ -21,11 +21,17 @@ "type": "git", "url": "git+https://github.com/enisdenjo/graphql-ws.git" }, - "main": "lib/index.js", - "browser": "lib/client.js", - "types": "lib/index.d.ts", + "engines": { + "node": ">=10" + }, + "main": "cjs/index.js", + "module": "esm/index.js", + "browser": "umd/graphql-ws.js", + "types": "esm/index.d.ts", "files": [ - "lib", + "cjs", + "esm", + "umd", "README.md", "LICENSE.md", "PROTOCOL.md" @@ -38,11 +44,14 @@ "lint": "eslint 'src'", "type-check": "tsc --noEmit", "test": "jest", - "build": "tsc -b tsconfig.build.json", + "build:cjs": "tsc -b tsconfig.cjs.json", + "build:esm": "tsc -b tsconfig.esm.json", + "build:umd": "rollup -c && gzip umd/graphql-ws.min.js -c > umd/graphql-ws.min.js.gz", + "build": "yarn build:cjs && yarn build:esm && yarn build:umd", "release": "semantic-release" }, "peerDependencies": { - "graphql": "^15.0.0" + "graphql": ">=0.11 <=15" }, "dependencies": { "ws": "^7.3.1" @@ -68,6 +77,8 @@ "graphql": "^15.4.0", "jest": "^26.6.1", "prettier": "^2.1.2", + "rollup": "^2.33.1", + "rollup-plugin-terser": "^7.0.2", "semantic-release": "^17.2.2", "typedoc": "^0.19.2", "typedoc-plugin-markdown": "^3.0.11", diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 00000000..febf09e4 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,18 @@ +import { terser } from 'rollup-plugin-terser'; + +export default { + input: './esm/client.js', + output: [ + { + file: './umd/graphql-ws.js', + format: 'umd', + name: 'graphqlWs', + }, + { + file: './umd/graphql-ws.min.js', + format: 'umd', + name: 'graphqlWs', + plugins: [terser()], + }, + ], +}; diff --git a/src/server.ts b/src/server.ts index 37267f01..1f19359c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -58,6 +58,7 @@ export type GraphQLExecutionContextValue = | number | string | boolean + | undefined | null; export interface ServerOptions { @@ -96,9 +97,9 @@ export interface ServerOptions { * alongside the schema. Learn more about them * here: https://graphql.org/learn/execution/#root-fields-resolvers. * - * If you return from the `onSubscribe` callback, the - * root field value will NOT be injected. You should add it - * in the returned `ExecutionArgs` from the callback. + * If you return from `onSubscribe`, and the returned value is + * missing the `rootValue` field, the relevant operation root + * will be used instead. */ roots?: { [operation in OperationTypeNode]?: Record< @@ -177,10 +178,11 @@ export interface ServerOptions { * it will be used instead of trying to build one * internally. In this case, you are responsible * for providing a ready set of arguments which will - * be directly plugged in the operation execution. Beware, - * the `context` server option is an exception. Only if you - * dont provide a context alongside the returned value - * here, the `context` server option will be used instead. + * be directly plugged in the operation execution. + * + * Omitting the fields `contextValue` or `rootValue` + * from the returned value will have the provided server + * options fill in the gaps. * * To report GraphQL errors simply return an array * of them from the callback, they will be reported @@ -276,6 +278,10 @@ export interface ServerOptions { * Throwing an error from within this function will * close the socket with the `Error` message * in the close event reason. + * + * Since the library makes sure to complete streaming + * operations even after an abrupt closure, this callback + * will still be called. */ onComplete?: (ctx: Context, message: CompleteMessage) => Promise | void; } @@ -351,12 +357,12 @@ export function createServer( ? websocketOptionsOrServer : new WebSocket.Server(websocketOptionsOrServer); + webSocketServer.on('connection', handleConnection); function handleConnection(socket: WebSocket, request: http.IncomingMessage) { if ( - socket.protocol === undefined || - socket.protocol !== GRAPHQL_TRANSPORT_WS_PROTOCOL || - (Array.isArray(socket.protocol) && - socket.protocol.indexOf(GRAPHQL_TRANSPORT_WS_PROTOCOL) === -1) + Array.isArray(socket.protocol) + ? socket.protocol.indexOf(GRAPHQL_TRANSPORT_WS_PROTOCOL) === -1 + : socket.protocol !== GRAPHQL_TRANSPORT_WS_PROTOCOL ) { return socket.close(1002, 'Protocol Error'); } @@ -374,42 +380,42 @@ export function createServer( // kick the client off (close socket) if the connection has // not been initialised after the specified wait timeout const connectionInitWait = - connectionInitWaitTimeout && // even 0 disables it - connectionInitWaitTimeout !== Infinity && - setTimeout(() => { - if (!ctxRef.current.connectionInitReceived) { - ctxRef.current.socket.close( - 4408, - 'Connection initialisation timeout', - ); - } - }, connectionInitWaitTimeout); + connectionInitWaitTimeout > 0 && isFinite(connectionInitWaitTimeout) + ? setTimeout(() => { + if (!ctxRef.current.connectionInitReceived) { + ctxRef.current.socket.close( + 4408, + 'Connection initialisation timeout', + ); + } + }, connectionInitWaitTimeout) + : null; // keep alive through ping-pong messages // read more about the websocket heartbeat here: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#Pings_and_Pongs_The_Heartbeat_of_WebSockets - let pongWait: NodeJS.Timeout | null; + let pongWait: NodeJS.Timeout | null = null; const pingInterval = - keepAlive && // even 0 disables it - keepAlive !== Infinity && - setInterval(() => { - // ping pong on open sockets only - if (socket.readyState === WebSocket.OPEN) { - // terminate the connection after pong wait has passed because the client is idle - pongWait = setTimeout(() => { - socket.terminate(); - }, keepAlive); - - // listen for client's pong and stop socket termination - socket.once('pong', () => { - if (pongWait) { - clearTimeout(pongWait); - pongWait = null; - } - }); + keepAlive > 0 && isFinite(keepAlive) + ? setInterval(() => { + // ping pong on open sockets only + if (socket.readyState === WebSocket.OPEN) { + // terminate the connection after pong wait has passed because the client is idle + pongWait = setTimeout(() => { + socket.terminate(); + }, keepAlive); + + // listen for client's pong and stop socket termination + socket.once('pong', () => { + if (pongWait) { + clearTimeout(pongWait); + pongWait = null; + } + }); - socket.ping(); - } - }, keepAlive); + socket.ping(); + } + }, keepAlive) + : null; function errorOrCloseHandler( errorOrClose: WebSocket.ErrorEvent | WebSocket.CloseEvent, @@ -431,22 +437,31 @@ export function createServer( ); } - Object.entries(ctxRef.current.subscriptions).forEach( - ([, subscription]) => { - subscription.return?.(); - }, - ); + Object.values(ctxRef.current.subscriptions).forEach((subscription) => { + subscription.return?.(); + }); } socket.onerror = errorOrCloseHandler; socket.onclose = errorOrCloseHandler; socket.onmessage = makeOnMessage(ctxRef.current); } - webSocketServer.on('connection', handleConnection); + webSocketServer.on('error', (err) => { + // catch the first thrown error and re-throw it once all clients have been notified + let firstErr: Error | null = null; + + // report server errors by erroring out all clients with the same error for (const client of webSocketServer.clients) { - // report server errors by erroring out all clients with the same error - client.emit('error', err); + try { + client.emit('error', err); + } catch (err) { + firstErr = firstErr ?? err; + } + } + + if (firstErr) { + throw firstErr; } }); @@ -553,6 +568,10 @@ export function createServer( if (maybeExecArgsOrErrors) { if (areGraphQLErrors(maybeExecArgsOrErrors)) { return await emit.error(maybeExecArgsOrErrors); + } else if (Array.isArray(maybeExecArgsOrErrors)) { + throw new Error( + 'Invalid return value from onSubscribe hook, expected an array of GraphQLError objects', + ); } // not errors, is exec args execArgs = maybeExecArgsOrErrors; @@ -593,14 +612,13 @@ export function createServer( ]); } - // if onsubscribe didnt return anything, inject roots - if (!maybeExecArgsOrErrors) { + // if `onSubscribe` didnt specify a rootValue, inject one + if (!('rootValue' in execArgs)) { execArgs.rootValue = roots?.[operationAST.operation]; } - // inject the context, if provided, before the operation. - // but, only if the `onSubscribe` didnt provide one already - if (context !== undefined && !execArgs.contextValue) { + // if `onSubscribe` didn't specify a context, inject one + if (!('contextValue' in execArgs)) { execArgs.contextValue = typeof context === 'function' ? context(ctx, message, execArgs) @@ -665,7 +683,7 @@ export function createServer( } } catch (err) { // TODO-db-201031 we perceive this as a client bad request error, but is it always? - ctx.socket.close(4400, err.message); + ctx.socket.close(4400, isProd ? 'Bad Request' : err.message); } }; } diff --git a/src/tests/server.ts b/src/tests/server.ts index 356b1e7d..8ffeeeca 100644 --- a/src/tests/server.ts +++ b/src/tests/server.ts @@ -364,6 +364,51 @@ it('should pass the `onSubscribe` exec args to the `context` option and use it', ); }); +it('should use the root from the `roots` option if the `onSubscribe` doesnt provide one', async (done) => { + const rootValue = {}; + const execArgs = { + // no rootValue here + schema, + document: parse(`query { getValue }`), + }; + + const { url } = await startTServer({ + roots: { + query: rootValue, + }, + onSubscribe: () => { + return execArgs; + }, + execute: (args) => { + expect(args).toBe(execArgs); // from `onSubscribe` + expect(args.rootValue).toBe(rootValue); // injected by `roots` + done(); + return execute(args); + }, + subscribe, + }); + + const client = await createTClient(url); + client.ws.send( + stringifyMessage({ + type: MessageType.ConnectionInit, + }), + ); + await client.waitForMessage(({ data }) => { + expect(parseMessage(data).type).toBe(MessageType.ConnectionAck); + }); + + client.ws.send( + stringifyMessage({ + id: '1', + type: MessageType.Subscribe, + payload: { + query: `{ getValue }`, + }, + }), + ); +}); + it('should prefer the `onSubscribe` context value even if `context` option is set', async (done) => { const context = 'not-me'; const execArgs = { @@ -407,6 +452,41 @@ it('should prefer the `onSubscribe` context value even if `context` option is se ); }); +it('should handle errors thrown from client error listeners', async () => { + const { server, url } = await startTServer(); + + const client = await createTClient(url); + client.ws.send( + stringifyMessage({ + type: MessageType.ConnectionInit, + }), + ); + await client.waitForMessage(({ data }) => { + expect(parseMessage(data).type).toBe(MessageType.ConnectionAck); + }); + + const surpriseErr1 = new Error('Well hello there!'); + const surpriseErr2 = new Error('I wont be thrown!'); // first to throw stops emission + for (const client of server.webSocketServer.clients) { + client.on('error', () => { + throw surpriseErr1; + }); + client.on('error', () => { + throw surpriseErr2; + }); + } + + expect(() => { + server.webSocketServer.emit('error', new Error('I am a nice error')); + }).toThrowError(surpriseErr1); + + await client.waitForClose((event) => { + expect(event.code).toBe(1011); + expect(event.reason).toBe('I am a nice error'); + expect(event.wasClean).toBeTruthy(); + }); +}); + describe('Connect', () => { it('should refuse connection and close socket if returning `false`', async () => { const { url } = await startTServer({ @@ -790,6 +870,7 @@ describe('Subscribe', () => { schema, operationName: 'Nope', document: parse(`query Nope { getValue }`), + rootValue: null, }; const { url } = await startTServer({ schema: undefined, @@ -798,7 +879,7 @@ describe('Subscribe', () => { }, execute: (args) => { expect(args.schema).toBe(nopeArgs.schema); // schema from nopeArgs - expect(args.rootValue).toBeUndefined(); // nopeArgs didnt provide any root value + expect(args.rootValue).toBeNull(); // nopeArgs provided rootValue: null, so don't overwrite expect(args.operationName).toBe('Nope'); expect(args.variableValues).toBeUndefined(); // nopeArgs didnt provide variables return execute(args); @@ -895,6 +976,44 @@ describe('Subscribe', () => { }, 30); }); + it('should close the socket on empty arrays returned from `onSubscribe`', async () => { + const { url } = await startTServer({ + onSubscribe: () => { + return []; + }, + }); + + const client = await createTClient(url); + + client.ws.send( + stringifyMessage({ + type: MessageType.ConnectionInit, + }), + ); + + await client.waitForMessage(({ data }) => { + expect(parseMessage(data).type).toBe(MessageType.ConnectionAck); + }); + + client.ws.send( + stringifyMessage({ + id: '1', + type: MessageType.Subscribe, + payload: { + query: 'subscription { ping }', + }, + }), + ); + + await client.waitForClose((event) => { + expect(event.code).toBe(4400); + expect(event.reason).toBe( + 'Invalid return value from onSubscribe hook, expected an array of GraphQLError objects', + ); + expect(event.wasClean).toBeTruthy(); + }); + }); + it('should use the execution result returned from `onNext`', async () => { const { url } = await startTServer({ onNext: (_ctx, _message) => { @@ -1461,6 +1580,45 @@ describe('Subscribe', () => { }), ); }); + + it('should call `onComplete` callback even if socket terminates abruptly', async (done) => { + const server = await startTServer({ + onComplete: () => { + done(); + }, + }); + + const client = await createTClient(server.url); + client.ws.send( + stringifyMessage({ + type: MessageType.ConnectionInit, + }), + ); + + await client.waitForMessage(({ data }) => { + expect(parseMessage(data).type).toBe(MessageType.ConnectionAck); + }); + + client.ws.send( + stringifyMessage({ + id: '1', + type: MessageType.Subscribe, + payload: { + query: 'subscription { ping }', + }, + }), + ); + await server.waitForOperation(); + + // just to make sure we're streaming + server.pong(); + await client.waitForMessage(({ data }) => { + expect(parseMessage(data).type).toBe(MessageType.Next); + }); + + // terminate socket abruptly + client.ws.terminate(); + }); }); describe('Keep-Alive', () => { diff --git a/src/types.ts b/src/types.ts index 2777fcd9..54a68adb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,8 +4,6 @@ * */ -import { GraphQLError } from 'graphql'; - /** * ID is a string type alias representing * the globally unique ID used for identifying @@ -26,11 +24,12 @@ export interface Sink { next(value: T): void; /** * An error that has occured. Calling this function "closes" the sink. - * The error can be also `CloseEvent`, but to avoid bundling DOM typings - * because the client can run in Node env too, you should assert - * the close event type during implementation. + * Besides the errors being `Error` and `readonly GraphQLError[]`, it + * can also be a `CloseEvent`, but to avoid bundling DOM typings because + * the client can run in Node env too, you should assert the close event + * type during implementation. */ - error(error: Error | readonly GraphQLError[] | unknown): void; + error(error: unknown): void; /** The sink has completed. This function "closes" the sink. */ complete(): void; } diff --git a/src/utils.ts b/src/utils.ts index dc93c969..128b75fa 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,6 +5,9 @@ */ import { GraphQLError } from 'graphql'; +// Extremely small optimisation, reduces runtime prototype traversal +const baseHasOwnProperty = Object.prototype.hasOwnProperty; + export function isObject(val: unknown): val is Record { return typeof val === 'object' && val !== null; } @@ -29,31 +32,26 @@ export function hasOwnProperty< O extends Record, P extends PropertyKey >(obj: O, prop: P): obj is O & Record { - return Object.prototype.hasOwnProperty.call(obj, prop); + return baseHasOwnProperty.call(obj, prop); } export function hasOwnObjectProperty< O extends Record, P extends PropertyKey >(obj: O, prop: P): obj is O & Record> { - return Object.prototype.hasOwnProperty.call(obj, prop) && isObject(obj[prop]); + return baseHasOwnProperty.call(obj, prop) && isObject(obj[prop]); } export function hasOwnArrayProperty< O extends Record, P extends PropertyKey >(obj: O, prop: P): obj is O & Record { - return ( - Object.prototype.hasOwnProperty.call(obj, prop) && Array.isArray(obj[prop]) - ); + return baseHasOwnProperty.call(obj, prop) && Array.isArray(obj[prop]); } export function hasOwnStringProperty< O extends Record, P extends PropertyKey >(obj: O, prop: P): obj is O & Record { - return ( - Object.prototype.hasOwnProperty.call(obj, prop) && - typeof obj[prop] === 'string' - ); + return baseHasOwnProperty.call(obj, prop) && typeof obj[prop] === 'string'; } diff --git a/tsconfig.build.json b/tsconfig.build.json deleted file mode 100644 index 78513920..00000000 --- a/tsconfig.build.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "./tsconfig.json", - "exclude": ["src/tests", "lib"] -} diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 00000000..ff45ec3f --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "./cjs" + }, + "exclude": ["src/tests", "cjs", "esm"] +} diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 00000000..c3bd7f0c --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "es2015", + "outDir": "./esm" + }, + "exclude": ["src/tests", "cjs", "esm"] +} diff --git a/tsconfig.json b/tsconfig.json index 3588f871..bcaadf83 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,17 @@ { "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ - "target": "es2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, - "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, - "declaration": true /* Generates corresponding '.d.ts' file. */, - "outDir": "./lib" /* Redirect output structure to the directory. */, - "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, - "strict": true /* Enable all strict type-checking options. */, - "noUnusedLocals": true /* Report errors on unused locals. */, - "noUnusedParameters": true /* Report errors on unused parameters. */, - "baseUrl": "./src" /* Base directory to resolve non-absolute module names. */, - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, - "skipLibCheck": true /* Skip type checking of declaration files. */, - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "moduleResolution": "node", + "target": "es2017", + "declaration": true, + "rootDir": "./src", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "baseUrl": "./src", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true }, "exclude": ["lib"] } diff --git a/yarn.lock b/yarn.lock index 79e0aa4a..86252c0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3129,6 +3129,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^2.20.0": + version: 2.20.3 + resolution: "commander@npm:2.20.3" + checksum: b73428e97de7624323f81ba13f8ed9271de487017432d18b4da3f07cfc528ad754bbd199004bd5d14e0ccd67d1fdfe0ec8dbbd4c438b401df3c4cc387bfd1daa + languageName: node + linkType: hard + "compare-func@npm:^2.0.0": version: 2.0.0 resolution: "compare-func@npm:2.0.0" @@ -4540,7 +4547,7 @@ __metadata: languageName: node linkType: hard -fsevents@^2.1.2: +"fsevents@^2.1.2, fsevents@~2.1.2": version: 2.1.3 resolution: "fsevents@npm:2.1.3" dependencies: @@ -4549,7 +4556,7 @@ fsevents@^2.1.2: languageName: node linkType: hard -"fsevents@patch:fsevents@^2.1.2#builtin": +"fsevents@patch:fsevents@^2.1.2#builtin, fsevents@patch:fsevents@~2.1.2#builtin": version: 2.1.3 resolution: "fsevents@patch:fsevents@npm%3A2.1.3#builtin::version=2.1.3&hash=127e8e" dependencies: @@ -4827,13 +4834,15 @@ fsevents@^2.1.2: graphql: ^15.4.0 jest: ^26.6.1 prettier: ^2.1.2 + rollup: ^2.33.1 + rollup-plugin-terser: ^7.0.2 semantic-release: ^17.2.2 typedoc: ^0.19.2 typedoc-plugin-markdown: ^3.0.11 typescript: ^4.0.5 ws: ^7.3.1 peerDependencies: - graphql: ^15.0.0 + graphql: ">=0.11 <=15" languageName: unknown linkType: soft @@ -6187,6 +6196,17 @@ fsevents@^2.1.2: languageName: node linkType: hard +"jest-worker@npm:^26.2.1": + version: 26.6.2 + resolution: "jest-worker@npm:26.6.2" + dependencies: + "@types/node": "*" + merge-stream: ^2.0.0 + supports-color: ^7.0.0 + checksum: 5eb349833b5e9750ce8700388961dfd5d5e207c913122221e418e48b9cda3c17b0fb418f6a90f1614cfdc3ca836158b720c5dc1de82cb1e708266b4d76e31a38 + languageName: node + linkType: hard + "jest-worker@npm:^26.6.1": version: 26.6.1 resolution: "jest-worker@npm:26.6.1" @@ -8573,6 +8593,15 @@ fsevents@^2.1.2: languageName: node linkType: hard +"randombytes@npm:^2.1.0": + version: 2.1.0 + resolution: "randombytes@npm:2.1.0" + dependencies: + safe-buffer: ^5.1.0 + checksum: ede2693af09732ceab1c273dd70db787f34a7b8d95bab13f1aca763483c0113452a78e53d61ff18d393dcea586d388e01f198a5132a4a85cebba31ec54164b75 + languageName: node + linkType: hard + "rc@npm:^1.0.1, rc@npm:^1.1.6, rc@npm:^1.2.8": version: 1.2.8 resolution: "rc@npm:1.2.8" @@ -9089,6 +9118,34 @@ fsevents@^2.1.2: languageName: node linkType: hard +"rollup-plugin-terser@npm:^7.0.2": + version: 7.0.2 + resolution: "rollup-plugin-terser@npm:7.0.2" + dependencies: + "@babel/code-frame": ^7.10.4 + jest-worker: ^26.2.1 + serialize-javascript: ^4.0.0 + terser: ^5.0.0 + peerDependencies: + rollup: ^2.0.0 + checksum: 553cc21efcea3e4d46c61fbd41cb4a82a3ab8e02ae4ce7c03f9248dea93e5a91c3624e2271490ee05b2bb481568305733b496d968d3ac9c99b777a588a336f01 + languageName: node + linkType: hard + +"rollup@npm:^2.33.1": + version: 2.33.1 + resolution: "rollup@npm:2.33.1" + dependencies: + fsevents: ~2.1.2 + dependenciesMeta: + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: cf727ca4d1c6d6ad7679c8cf0a8258e41339d7d3eef5a7671cd54f4506c23f5492f6331c05d76697fa608d870407a5d2eb796647ce6add68101ca3bbb77c7d66 + languageName: node + linkType: hard + "rsvp@npm:^4.8.4": version: 4.8.5 resolution: "rsvp@npm:4.8.5" @@ -9269,6 +9326,15 @@ fsevents@^2.1.2: languageName: node linkType: hard +"serialize-javascript@npm:^4.0.0": + version: 4.0.0 + resolution: "serialize-javascript@npm:4.0.0" + dependencies: + randombytes: ^2.1.0 + checksum: f17305aaabab9ae443505d1bf477c13b09adb7031c397d18400bec16f43f788febdd3311ca6043fdebd1d446cfa70a5804ef7268da54351dec51080f56d52fa9 + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0, set-blocking@npm:~2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -9492,7 +9558,7 @@ fsevents@^2.1.2: languageName: node linkType: hard -"source-map-support@npm:^0.5.6": +"source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.19": version: 0.5.19 resolution: "source-map-support@npm:0.5.19" dependencies: @@ -9523,7 +9589,7 @@ fsevents@^2.1.2: languageName: node linkType: hard -"source-map@npm:^0.7.3": +"source-map@npm:^0.7.3, source-map@npm:~0.7.2": version: 0.7.3 resolution: "source-map@npm:0.7.3" checksum: 351ce26ffa1ebf203660c0d70d7566c81e65d2d994d1c2d94da140808e02da34961673ce12ecea9b40797b96fbeb8c70bf71a4ad9f779f1a4fdbba75530bb386 @@ -10027,6 +10093,19 @@ fsevents@^2.1.2: languageName: node linkType: hard +"terser@npm:^5.0.0": + version: 5.3.8 + resolution: "terser@npm:5.3.8" + dependencies: + commander: ^2.20.0 + source-map: ~0.7.2 + source-map-support: ~0.5.19 + bin: + terser: bin/terser + checksum: 3fc070378ba9981d8088a4012060f16428a97dc8f98d5a731b2ab51b8c9bc8cb651ca068aef499faad863584ae4d4883872c4f51db053bc2575c2e0bc11ab411 + languageName: node + linkType: hard + "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0"