diff --git a/package.json b/package.json index b61b673d5..56bd46fbf 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "scripts": { "check": "yarn clean && yarn lint && yarn prepack:all && yarn test", "lint": "eslint packages && lerna run tslint", + "lint:fix": "eslint --fix 'packages/**/*.{ts,js}'", "flow": "flow", "flow:check": "flow check", "test": "lerna run --concurrency 1 test", diff --git a/packages/graphile-build-pg/src/GraphQLJSON.js b/packages/graphile-build-pg/src/GraphQLJSON.ts similarity index 100% rename from packages/graphile-build-pg/src/GraphQLJSON.js rename to packages/graphile-build-pg/src/GraphQLJSON.ts diff --git a/packages/graphile-build-pg/src/PgLiveProvider.js b/packages/graphile-build-pg/src/PgLiveProvider.ts similarity index 92% rename from packages/graphile-build-pg/src/PgLiveProvider.js rename to packages/graphile-build-pg/src/PgLiveProvider.ts index 4a42c9a7e..020895da2 100644 --- a/packages/graphile-build-pg/src/PgLiveProvider.js +++ b/packages/graphile-build-pg/src/PgLiveProvider.ts @@ -1,6 +1,5 @@ -// @flow import { LiveProvider } from "graphile-build"; -import type { PgClass } from "./plugins/PgIntrospectionPlugin"; +import { PgClass } from "./plugins/PgIntrospectionPlugin"; export default class PgLiveProvider extends LiveProvider { // eslint-disable-next-line flowtype/no-weak-types diff --git a/packages/graphile-build-pg/src/QueryBuilder.js b/packages/graphile-build-pg/src/QueryBuilder.ts similarity index 92% rename from packages/graphile-build-pg/src/QueryBuilder.js rename to packages/graphile-build-pg/src/QueryBuilder.ts index 11c207604..6640eee13 100644 --- a/packages/graphile-build-pg/src/QueryBuilder.js +++ b/packages/graphile-build-pg/src/QueryBuilder.ts @@ -1,9 +1,8 @@ -// @flow import * as sql from "pg-sql2"; -import type { SQL } from "pg-sql2"; +import { SQL } from "pg-sql2"; import isSafeInteger from "lodash/isSafeInteger"; import chunk from "lodash/chunk"; -import type { PgClass, PgType } from "./plugins/PgIntrospectionPlugin"; +import { PgClass, PgType } from "./plugins/PgIntrospectionPlugin"; // eslint-disable-next-line flowtype/no-weak-types type GraphQLContext = any; @@ -11,8 +10,9 @@ type GraphQLContext = any; const isDev = process.env.POSTGRAPHILE_ENV === "development"; type GenContext = { - queryBuilder: QueryBuilder, + queryBuilder: QueryBuilder; }; + type Gen = (context: GenContext) => T; function callIfNecessary(o: Gen | T, context: GenContext): T { @@ -39,10 +39,10 @@ type SQLAlias = SQL; type SQLGen = Gen | SQL; type NumberGen = Gen | number; type CursorValue = {}; -type CursorComparator = (val: CursorValue, isAfter: boolean) => void; +type CursorComparator = (val: CursorValue, isAfter: boolean) => undefined; export type QueryBuilderOptions = { - supportsJSONB?: boolean, // Defaults to true + supportsJSONB?: boolean; }; function escapeLarge(sqlFragment: SQL, type: PgType) { @@ -66,62 +66,71 @@ function escapeLarge(sqlFragment: SQL, type: PgType) { } class QueryBuilder { - parentQueryBuilder: QueryBuilder | void; + parentQueryBuilder: QueryBuilder | undefined; context: GraphQLContext; rootValue: any; // eslint-disable-line flowtype/no-weak-types supportsJSONB: boolean; locks: { - [string]: false | true | string, + [a: string]: false | true | string; }; + finalized: boolean; selectedIdentifiers: boolean; data: { - cursorPrefix: Array, - select: Array<[SQLGen, RawAlias]>, - selectCursor: ?SQLGen, - from: ?[SQLGen, SQLAlias], - join: Array, - where: Array, + cursorPrefix: Array; + select: Array<[SQLGen, RawAlias]>; + selectCursor: SQLGen | null | undefined; + from: [SQLGen, SQLAlias] | null | undefined; + join: Array; + where: Array; whereBound: { - lower: Array, - upper: Array, - }, - orderBy: Array<[SQLGen, boolean, boolean | null]>, - orderIsUnique: boolean, - limit: ?NumberGen, - offset: ?NumberGen, - first: ?number, - last: ?number, + lower: Array; + upper: Array; + }; + + orderBy: Array<[SQLGen, boolean, boolean | null]>; + orderIsUnique: boolean; + limit: NumberGen | null | undefined; + offset: NumberGen | null | undefined; + first: number | null | undefined; + last: number | null | undefined; beforeLock: { - [string]: Array<() => void> | null, - }, - cursorComparator: ?CursorComparator, + [a: string]: Array<() => undefined> | null; + }; + + cursorComparator: CursorComparator | null | undefined; liveConditions: Array< // eslint-disable-next-line flowtype/no-weak-types - [(data: {}) => (record: any) => boolean, { [key: string]: SQL } | void] - >, + [ + (data: {}) => (record: any) => boolean, + { [key: string]: SQL } | undefined + ] + >; }; + compiledData: { - cursorPrefix: Array, - select: Array<[SQL, RawAlias]>, - selectCursor: ?SQL, - from: ?[SQL, SQLAlias], - join: Array, - where: Array, + cursorPrefix: Array; + select: Array<[SQL, RawAlias]>; + selectCursor: SQL | null | undefined; + from: [SQL, SQLAlias] | null | undefined; + join: Array; + where: Array; whereBound: { - lower: Array, - upper: Array, - }, - orderBy: Array<[SQL, boolean, boolean | null]>, - orderIsUnique: boolean, - limit: ?number, - offset: ?number, - first: ?number, - last: ?number, - cursorComparator: ?CursorComparator, + lower: Array; + upper: Array; + }; + + orderBy: Array<[SQL, boolean, boolean | null]>; + orderIsUnique: boolean; + limit: number | null | undefined; + offset: number | null | undefined; + first: number | null | undefined; + last: number | null | undefined; + cursorComparator: CursorComparator | null | undefined; }; + lockContext: { - queryBuilder: QueryBuilder, + queryBuilder: QueryBuilder; }; constructor( @@ -154,6 +163,7 @@ class QueryBuilder { limit: false, offset: false, }; + this.finalized = false; this.selectedIdentifiers = false; this.data = { @@ -168,6 +178,7 @@ class QueryBuilder { lower: [], upper: [], }, + orderBy: [], orderIsUnique: false, limit: null, @@ -191,9 +202,11 @@ class QueryBuilder { limit: [], offset: [], }, + cursorComparator: null, liveConditions: [], }; + this.compiledData = { cursorPrefix: ["natural"], select: [], @@ -205,6 +218,7 @@ class QueryBuilder { lower: [], upper: [], }, + orderBy: [], orderIsUnique: false, limit: null, @@ -213,6 +227,7 @@ class QueryBuilder { last: null, cursorComparator: null, }; + this.beforeLock("select", () => { this.lock("selectCursor"); if (this.compiledData.selectCursor) { @@ -254,6 +269,7 @@ class QueryBuilder { ([expr, alias]) => sql.fragment`${sql.literal(alias)}::text, ${expr}` ), + ", " )})`; return sql.fragment`(${sql.join( @@ -266,6 +282,7 @@ class QueryBuilder { fields.map( ([expr, alias]) => sql.fragment`${sql.literal(alias)}::text, ${expr}` ), + ", " )})`; } @@ -273,7 +290,7 @@ class QueryBuilder { // ---------------------------------------- - beforeLock(field: string, fn: () => void) { + beforeLock(field: string, fn: () => undefined) { this.checkLock(field); if (!this.data.beforeLock[field]) { this.data.beforeLock[field] = []; @@ -285,7 +302,7 @@ class QueryBuilder { makeLiveCollection( table: PgClass, // eslint-disable-next-line flowtype/no-weak-types - cb?: (checker: (data: any) => (record: any) => boolean) => void + cb?: (checker: (data: any) => (record: any) => boolean) => undefined ) { /* the actual condition doesn't matter hugely, 'select' should work */ if (!this.rootValue || !this.rootValue.liveConditions) return; @@ -295,6 +312,7 @@ class QueryBuilder { const checkers = liveConditions.map(([checkerGenerator]) => checkerGenerator(data) ); + return record => checkers.every(checker => checker(record)); }; if (this.parentQueryBuilder) { @@ -311,6 +329,7 @@ class QueryBuilder { requirements ? Object.assign(memo, requirements) : memo, {} ); + // $FlowFixMe this.parentQueryBuilder.select( sql.fragment`\ @@ -319,6 +338,7 @@ ${sql.join( Object.keys(allRequirements).map( key => sql.fragment`, ${sql.literal(key)}::text, ${allRequirements[key]}` ), + "" )})`, "__live" @@ -392,10 +412,12 @@ ${sql.join( key.type ) ), + ", " )})`, "__identifiers" ); + this.selectedIdentifiers = true; } selectCursor(exprGen: SQLGen) { @@ -574,6 +596,7 @@ ${sql.join( ([sqlFragment, alias]) => sql.fragment`to_json(${sqlFragment}) as ${sql.identifier(alias)}` ), + ", " ); } @@ -581,8 +604,8 @@ ${sql.join( addNullCase, addNotDistinctFromNullCase, }: { - addNullCase?: boolean, - addNotDistinctFromNullCase?: boolean, + addNullCase?: boolean; + addNotDistinctFromNullCase?: boolean; }) { this.lockEverything(); let buildObject = this.compiledData.select.length @@ -622,7 +645,7 @@ ${sql.join( { addNullCase, addNotDistinctFromNullCase, - }: { addNullCase?: boolean, addNotDistinctFromNullCase?: boolean } + }: { addNullCase?: boolean; addNotDistinctFromNullCase?: boolean } ) { this.lock("where"); const clauses = [ @@ -658,18 +681,19 @@ ${sql.join( ...(includeLowerBound ? [this.buildWhereBoundClause(true)] : []), ...(includeUpperBound ? [this.buildWhereBoundClause(false)] : []), ]; + return clauses.length ? sql.fragment`(${sql.join(clauses, ") and (")})` : sql.fragment`1 = 1`; } build( options: { - asJson?: boolean, - asJsonAggregate?: boolean, - onlyJsonField?: boolean, - addNullCase?: boolean, - addNotDistinctFromNullCase?: boolean, - useAsterisk?: boolean, + asJson?: boolean; + asJsonAggregate?: boolean; + onlyJsonField?: boolean; + addNullCase?: boolean; + addNotDistinctFromNullCase?: boolean; + useAsterisk?: boolean; } = {} ) { const { @@ -717,6 +741,7 @@ ${ : null }` ), + "," )}` : "" @@ -775,6 +800,7 @@ order by (row_number() over (partition by 1)) desc`; this.data[type].lower, context ); + this.compiledData[type].upper = callIfNecessaryArray( this.data[type].upper, context diff --git a/packages/graphile-build-pg/src/index.js b/packages/graphile-build-pg/src/index.ts similarity index 99% rename from packages/graphile-build-pg/src/index.js rename to packages/graphile-build-pg/src/index.ts index 4701c1add..f46db7651 100644 --- a/packages/graphile-build-pg/src/index.js +++ b/packages/graphile-build-pg/src/index.ts @@ -1,4 +1,3 @@ -// @flow import PgBasicsPlugin from "./plugins/PgBasicsPlugin"; import PgIntrospectionPlugin from "./plugins/PgIntrospectionPlugin"; import PgTypesPlugin from "./plugins/PgTypesPlugin"; diff --git a/packages/graphile-build-pg/src/inflections.js b/packages/graphile-build-pg/src/inflections.ts similarity index 77% rename from packages/graphile-build-pg/src/inflections.js rename to packages/graphile-build-pg/src/inflections.ts index 6b1fb7bd2..19e2e7f81 100644 --- a/packages/graphile-build-pg/src/inflections.js +++ b/packages/graphile-build-pg/src/inflections.ts @@ -1,5 +1,5 @@ /* THIS ENTIRE FILE IS DEPRECATED. DO NOT USE THIS. DO NOT EDIT THIS. */ -// @flow + import { upperCamelCase, camelCase, @@ -29,30 +29,31 @@ function deprecate(fn: (...input: Array) => string, message: string) { function deprecateEverything(obj: { // eslint-disable-next-line flowtype/no-weak-types - [string]: (...input: Array) => string, + [a: string]: (...input: Array) => string; }) { return Object.keys(obj).reduce((memo, key) => { memo[key] = deprecate( obj[key], `Something (probably a plugin) called the old inflection system (inflector: '${key}'). This system has been deprecated since 4.0.0-beta.6 (4th May 2018) and is not used internally so using it may cause inconsistencies, instead please use the plugin-capable inflection system https://www.graphile.org/postgraphile/inflection/` ); + return memo; }, {}); } type Keys = Array<{ - column: string, - table: string, - schema: ?string, + column: string; + table: string; + schema: string | null | undefined; }>; -type InflectorUtils = {| - constantCase: string => string, - camelCase: string => string, - upperCamelCase: string => string, - pluralize: string => string, - singularize: string => string, -|}; +type InflectorUtils = { + constantCase: (a: string) => string; + camelCase: (a: string) => string; + upperCamelCase: (a: string) => string; + pluralize: (a: string) => string; + singularize: (a: string) => string; +}; export const defaultUtils: InflectorUtils = { constantCase, @@ -65,11 +66,11 @@ export const defaultUtils: InflectorUtils = { export type Inflector = { // TODO: tighten this up! // eslint-disable-next-line flowtype/no-weak-types - [string]: (...input: Array) => string, + [a: string]: (...input: Array) => string; }; export const newInflector = ( - overrides: ?{ [string]: () => string } = undefined, + overrides: { [a: string]: () => string } | null | undefined = undefined, { constantCase, camelCase, @@ -88,7 +89,7 @@ export const newInflector = ( return deprecateEverything( preventEmptyResult({ pluralize, - argument(name: ?string, index: number) { + argument(name: string | null | undefined, index: number) { return camelCase(name || `arg${index}`); }, orderByType(typeName: string) { @@ -98,7 +99,7 @@ export const newInflector = ( name: string, ascending: boolean, _table: string, - _schema: ?string + _schema: string | null | undefined ) { return constantCase(`${name}_${ascending ? "asc" : "desc"}`); }, @@ -207,68 +208,84 @@ export const newInflector = ( patchField(itemName: string) { return camelCase(`${itemName}-patch`); }, - tableName(name: string, _schema: ?string) { + tableName(name: string, _schema: string | null | undefined) { return camelCase(singularizeTable(name)); }, - tableNode(name: string, _schema: ?string) { + tableNode(name: string, _schema: string | null | undefined) { return camelCase(singularizeTable(name)); }, - allRows(name: string, schema: ?string) { + allRows(name: string, schema: string | null | undefined) { return camelCase(`all-${this.pluralize(this.tableName(name, schema))}`); }, - functionName(name: string, _schema: ?string) { + functionName(name: string, _schema: string | null | undefined) { return camelCase(name); }, - functionPayloadType(name: string, _schema: ?string) { + functionPayloadType(name: string, _schema: string | null | undefined) { return upperCamelCase(`${name}-payload`); }, - functionInputType(name: string, _schema: ?string) { + functionInputType(name: string, _schema: string | null | undefined) { return upperCamelCase(`${name}-input`); }, - tableType(name: string, schema: ?string) { + tableType(name: string, schema: string | null | undefined) { return upperCamelCase(this.tableName(name, schema)); }, - column(name: string, _table: string, _schema: ?string) { + column(name: string, _table: string, _schema: string | null | undefined) { return camelCase(name); }, - singleRelationByKeys(detailedKeys: Keys, table: string, schema: ?string) { + singleRelationByKeys( + detailedKeys: Keys, + table: string, + schema: string | null | undefined + ) { return camelCase( `${this.tableName(table, schema)}-by-${detailedKeys .map(key => this.column(key.column, key.table, key.schema)) .join("-and-")}` ); }, - rowByUniqueKeys(detailedKeys: Keys, table: string, schema: ?string) { + rowByUniqueKeys( + detailedKeys: Keys, + table: string, + schema: string | null | undefined + ) { return camelCase( `${this.tableName(table, schema)}-by-${detailedKeys .map(key => this.column(key.column, key.table, key.schema)) .join("-and-")}` ); }, - updateByKeys(detailedKeys: Keys, table: string, schema: ?string) { + updateByKeys( + detailedKeys: Keys, + table: string, + schema: string | null | undefined + ) { return camelCase( `update-${this.tableName(table, schema)}-by-${detailedKeys .map(key => this.column(key.column, key.table, key.schema)) .join("-and-")}` ); }, - deleteByKeys(detailedKeys: Keys, table: string, schema: ?string) { + deleteByKeys( + detailedKeys: Keys, + table: string, + schema: string | null | undefined + ) { return camelCase( `delete-${this.tableName(table, schema)}-by-${detailedKeys .map(key => this.column(key.column, key.table, key.schema)) .join("-and-")}` ); }, - updateNode(name: string, _schema: ?string) { + updateNode(name: string, _schema: string | null | undefined) { return camelCase(`update-${singularizeTable(name)}`); }, - deleteNode(name: string, _schema: ?string) { + deleteNode(name: string, _schema: string | null | undefined) { return camelCase(`delete-${singularizeTable(name)}`); }, updateByKeysInputType( detailedKeys: Keys, name: string, - _schema: ?string + _schema: string | null | undefined ) { return upperCamelCase( `update-${singularizeTable(name)}-by-${detailedKeys @@ -279,7 +296,7 @@ export const newInflector = ( deleteByKeysInputType( detailedKeys: Keys, name: string, - _schema: ?string + _schema: string | null | undefined ) { return upperCamelCase( `delete-${singularizeTable(name)}-by-${detailedKeys @@ -287,18 +304,18 @@ export const newInflector = ( .join("-and-")}-input` ); }, - updateNodeInputType(name: string, _schema: ?string) { + updateNodeInputType(name: string, _schema: string | null | undefined) { return upperCamelCase(`update-${singularizeTable(name)}-input`); }, - deleteNodeInputType(name: string, _schema: ?string) { + deleteNodeInputType(name: string, _schema: string | null | undefined) { return upperCamelCase(`delete-${singularizeTable(name)}-input`); }, manyRelationByKeys( detailedKeys: Keys, table: string, - schema: ?string, + schema: string | null | undefined, _foreignTable: string, - _foreignSchema: ?string + _foreignSchema: string | null | undefined ) { return camelCase( `${this.pluralize( @@ -311,31 +328,37 @@ export const newInflector = ( edge(typeName: string) { return upperCamelCase(`${pluralize(typeName)}-edge`); }, - edgeField(name: string, _schema: ?string) { + edgeField(name: string, _schema: string | null | undefined) { return camelCase(`${singularizeTable(name)}-edge`); }, connection(typeName: string) { return upperCamelCase(`${this.pluralize(typeName)}-connection`); }, - scalarFunctionConnection(procName: string, _procSchema: ?string) { + scalarFunctionConnection( + procName: string, + _procSchema: string | null | undefined + ) { return upperCamelCase(`${procName}-connection`); }, - scalarFunctionEdge(procName: string, _procSchema: ?string) { + scalarFunctionEdge( + procName: string, + _procSchema: string | null | undefined + ) { return upperCamelCase(`${procName}-edge`); }, - createField(name: string, _schema: ?string) { + createField(name: string, _schema: string | null | undefined) { return camelCase(`create-${singularizeTable(name)}`); }, - createInputType(name: string, _schema: ?string) { + createInputType(name: string, _schema: string | null | undefined) { return upperCamelCase(`create-${singularizeTable(name)}-input`); }, - createPayloadType(name: string, _schema: ?string) { + createPayloadType(name: string, _schema: string | null | undefined) { return upperCamelCase(`create-${singularizeTable(name)}-payload`); }, - updatePayloadType(name: string, _schema: ?string) { + updatePayloadType(name: string, _schema: string | null | undefined) { return upperCamelCase(`update-${singularizeTable(name)}-payload`); }, - deletePayloadType(name: string, _schema: ?string) { + deletePayloadType(name: string, _schema: string | null | undefined) { return upperCamelCase(`delete-${singularizeTable(name)}-payload`); }, ...overrides, diff --git a/packages/graphile-build-pg/src/omit.js b/packages/graphile-build-pg/src/omit.ts similarity index 99% rename from packages/graphile-build-pg/src/omit.js rename to packages/graphile-build-pg/src/omit.ts index 8c9491244..618ed8fbc 100644 --- a/packages/graphile-build-pg/src/omit.js +++ b/packages/graphile-build-pg/src/omit.ts @@ -1,6 +1,4 @@ -// @flow - -import type { +import { PgProc, PgClass, PgAttribute, @@ -67,6 +65,7 @@ function parse(arrOrNot, errorPrefix = "Error") { } }) ); + if (all) { return true; } @@ -98,6 +97,7 @@ export default function omit( entity.name }'` ); + const includeSpec = parse( includeSpecRaw, `Error when processing @include instructions for ${entity.kind} '${ @@ -113,6 +113,7 @@ export default function omit( const bad = PERMISSIONS_THAT_REQUIRE_READ.filter( p => omitSpec.indexOf(p) === -1 ); + if (bad.length > 0) { throw new Error( `Processing @omit for ${entity.kind} '${entity.name}' - '${bad.join( @@ -136,6 +137,7 @@ export default function omit( const bad = PERMISSIONS_THAT_REQUIRE_READ.find( p => includeSpec.indexOf(p) >= 0 ); + if (bad) { throw new Error( `Error when processing @include for ${entity.kind} '${ diff --git a/packages/graphile-build-pg/src/parseIdentifier.js b/packages/graphile-build-pg/src/parseIdentifier.ts similarity index 100% rename from packages/graphile-build-pg/src/parseIdentifier.js rename to packages/graphile-build-pg/src/parseIdentifier.ts diff --git a/packages/graphile-build-pg/src/pgPrepareAndRun.js b/packages/graphile-build-pg/src/pgPrepareAndRun.ts similarity index 97% rename from packages/graphile-build-pg/src/pgPrepareAndRun.js rename to packages/graphile-build-pg/src/pgPrepareAndRun.ts index 5ab35dedc..cc1814a31 100644 --- a/packages/graphile-build-pg/src/pgPrepareAndRun.js +++ b/packages/graphile-build-pg/src/pgPrepareAndRun.ts @@ -1,7 +1,6 @@ -//@flow import { createHash } from "crypto"; import LRU from "@graphile/lru"; -import type { PoolClient } from "pg"; +import { PoolClient } from "pg"; const POSTGRAPHILE_PREPARED_STATEMENT_CACHE_SIZE = parseInt(process.env.POSTGRAPHILE_PREPARED_STATEMENT_CACHE_SIZE, 10) || 100; diff --git a/packages/graphile-build-pg/src/plugins/PageInfoStartEndCursor.js b/packages/graphile-build-pg/src/plugins/PageInfoStartEndCursor.ts similarity index 91% rename from packages/graphile-build-pg/src/plugins/PageInfoStartEndCursor.js rename to packages/graphile-build-pg/src/plugins/PageInfoStartEndCursor.ts index 25ed0101c..9ee37c67f 100644 --- a/packages/graphile-build-pg/src/plugins/PageInfoStartEndCursor.js +++ b/packages/graphile-build-pg/src/plugins/PageInfoStartEndCursor.ts @@ -1,7 +1,6 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; -export default (function PageInfoStartEndCursor(builder) { +export default function PageInfoStartEndCursor(builder) { builder.hook( "GraphQLObjectType:fields", (fields, build, context) => { @@ -28,6 +27,7 @@ export default (function PageInfoStartEndCursor(builder) { isPageInfoStartCursorField: true, } ), + endCursor: fieldWithHooks( "endCursor", ({ addDataGenerator }) => { @@ -43,6 +43,7 @@ export default (function PageInfoStartEndCursor(builder) { } ), }, + `Adding startCursor/endCursor to ${Self.name}` ); }, @@ -50,4 +51,4 @@ export default (function PageInfoStartEndCursor(builder) { [], ["Cursor"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgAllRows.js b/packages/graphile-build-pg/src/plugins/PgAllRows.ts similarity index 98% rename from packages/graphile-build-pg/src/plugins/PgAllRows.js rename to packages/graphile-build-pg/src/plugins/PgAllRows.ts index 372e5c5ca..c48ea739b 100644 --- a/packages/graphile-build-pg/src/plugins/PgAllRows.js +++ b/packages/graphile-build-pg/src/plugins/PgAllRows.ts @@ -1,9 +1,7 @@ -// @flow - -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; import debugSql from "./debugSql"; -export default (async function PgAllRows( +export default async function PgAllRows( builder, { pgViewUniqueKey, pgSimpleCollections, subscriptions } ) { @@ -43,6 +41,7 @@ export default (async function PgAllRows( table.type.id, null ); + if (!TableType) { return memo; } @@ -50,6 +49,7 @@ export default (async function PgAllRows( const ConnectionType = getTypeByName( inflection.connection(TableType.name) ); + if (!TableType) { throw new Error( `Could not find GraphQL type for table '${table.name}'` @@ -98,11 +98,13 @@ export default (async function PgAllRows( const parsedResolveInfoFragment = parseResolveInfo( resolveInfo ); + parsedResolveInfoFragment.args = args; // Allow overriding via makeWrapResolversPlugin const resolveData = getDataFromParsedResolveInfoFragment( parsedResolveInfoFragment, resolveInfo.returnType ); + let checkerGenerator; const query = queryFromResolveData( sqlFullTableName, @@ -112,6 +114,7 @@ export default (async function PgAllRows( useAsterisk: table.canUseAsterisk, withPaginationAsFields: isConnection, }, + queryBuilder => { if (subscriptions) { queryBuilder.makeLiveCollection( @@ -131,6 +134,7 @@ export default (async function PgAllRows( queryBuilder.data.cursorPrefix = [ "primary_key_asc", ]; + primaryKeys.forEach(key => { queryBuilder.orderBy( sql.fragment`${queryBuilder.getTableAlias()}.${sql.identifier( @@ -148,12 +152,14 @@ export default (async function PgAllRows( queryBuilder.data.cursorPrefix = [ "view_unique_key_asc", ]; + queryBuilder.orderBy( sql.fragment`${queryBuilder.getTableAlias()}.${sql.identifier( uniqueIdAttribute.name )}`, true ); + queryBuilder.setOrderIsUnique(); } }); @@ -162,6 +168,7 @@ export default (async function PgAllRows( resolveContext, resolveInfo.rootValue ); + const { text, values } = sql.compile(query); if (debugSql.enabled) debugSql(text); const result = await pgPrepareAndRun( @@ -230,4 +237,4 @@ export default (async function PgAllRows( [], ["PgTables"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgBackwardRelationPlugin.js b/packages/graphile-build-pg/src/plugins/PgBackwardRelationPlugin.ts similarity index 99% rename from packages/graphile-build-pg/src/plugins/PgBackwardRelationPlugin.js rename to packages/graphile-build-pg/src/plugins/PgBackwardRelationPlugin.ts index da15452df..ca7e5fc2a 100644 --- a/packages/graphile-build-pg/src/plugins/PgBackwardRelationPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgBackwardRelationPlugin.ts @@ -1,7 +1,6 @@ -// @flow import debugFactory from "debug"; -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; const debug = debugFactory("graphile-build-pg"); @@ -9,7 +8,7 @@ const OMIT = 0; const DEPRECATED = 1; const ONLY = 2; -export default (function PgBackwardRelationPlugin( +export default function PgBackwardRelationPlugin( builder, { pgLegacyRelations, pgSimpleCollections, subscriptions } ) { @@ -49,17 +48,20 @@ export default (function PgBackwardRelationPlugin( const foreignKeyConstraints = foreignTable.foreignConstraints.filter( con => con.type === "f" ); + const foreignTableTypeName = inflection.tableType(foreignTable); const gqlForeignTableType = pgGetGqlTypeByTypeIdAndModifier( foreignTable.type.id, null ); + if (!gqlForeignTableType) { debug( `Could not determine type for foreign table with id ${ foreignTable.type.id }` ); + return fields; } @@ -80,10 +82,12 @@ export default (function PgBackwardRelationPlugin( table.type.id, null ); + if (!gqlTableType) { debug( `Could not determine type for table with id ${constraint.classId}` ); + return memo; } if (!table) { @@ -159,6 +163,7 @@ export default (function PgBackwardRelationPlugin( parsedResolveInfoFragment, gqlTableType ); + const tableAlias = sql.identifier(Symbol()); const foreignTableAlias = queryBuilder.getTableAlias(); const query = queryFromResolveData( @@ -171,6 +176,7 @@ export default (function PgBackwardRelationPlugin( addNullCase: true, withPagination: false, }, + innerQueryBuilder => { innerQueryBuilder.parentQueryBuilder = queryBuilder; if ( @@ -192,6 +198,7 @@ export default (function PgBackwardRelationPlugin( queryBuilder.context, queryBuilder.rootValue ); + return sql.fragment`(${query})`; }, getSafeAliasFromAlias(parsedResolveInfoFragment.alias)); }, @@ -207,6 +214,7 @@ export default (function PgBackwardRelationPlugin( const safeAlias = getSafeAliasFromResolveInfo( resolveInfo ); + const record = data[safeAlias]; const liveRecord = resolveInfo.rootValue && @@ -224,6 +232,7 @@ export default (function PgBackwardRelationPlugin( } ), }, + `Backward relation (single) for ${describePgEntity( constraint )}. To rename this relation with smart comments:\n\n ${sqlCommentByAddingTags( @@ -270,6 +279,7 @@ export default (function PgBackwardRelationPlugin( withPaginationAsFields: false, asJsonAggregate: !isConnection, }; + addDataGenerator(parsedResolveInfoFragment => { return { pgQuery: queryBuilder => { @@ -278,6 +288,7 @@ export default (function PgBackwardRelationPlugin( parsedResolveInfoFragment, isConnection ? ConnectionType : TableType ); + const tableAlias = sql.identifier(Symbol()); const foreignTableAlias = queryBuilder.getTableAlias(); const query = queryFromResolveData( @@ -328,6 +339,7 @@ export default (function PgBackwardRelationPlugin( innerQueryBuilder.data.cursorPrefix = [ "primary_key_asc", ]; + primaryKeys.forEach(key => { innerQueryBuilder.orderBy( sql.fragment`${innerQueryBuilder.getTableAlias()}.${sql.identifier( @@ -355,6 +367,7 @@ export default (function PgBackwardRelationPlugin( queryBuilder.context, queryBuilder.rootValue ); + return sql.fragment`(${query})`; }, getSafeAliasFromAlias(parsedResolveInfoFragment.alias)); }, @@ -363,10 +376,12 @@ export default (function PgBackwardRelationPlugin( const ConnectionType = getTypeByName( inflection.connection(gqlTableType.name) ); + const TableType = pgGetGqlTypeByTypeIdAndModifier( table.type.id, null ); + return { description: constraint.tags.backwardDescription || @@ -376,11 +391,13 @@ export default (function PgBackwardRelationPlugin( : new GraphQLNonNull( new GraphQLList(new GraphQLNonNull(TableType)) ), + args: {}, resolve: (data, _args, resolveContext, resolveInfo) => { const safeAlias = getSafeAliasFromResolveInfo( resolveInfo ); + const liveCollection = resolveInfo.rootValue && resolveInfo.rootValue.liveCollection; @@ -470,4 +487,4 @@ export default (function PgBackwardRelationPlugin( }, ["PgBackwardRelation"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgBasicsPlugin.js b/packages/graphile-build-pg/src/plugins/PgBasicsPlugin.ts similarity index 97% rename from packages/graphile-build-pg/src/plugins/PgBasicsPlugin.js rename to packages/graphile-build-pg/src/plugins/PgBasicsPlugin.ts index b093824a3..b0cef25d5 100644 --- a/packages/graphile-build-pg/src/plugins/PgBasicsPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgBasicsPlugin.ts @@ -1,8 +1,7 @@ -// @flow import * as sql from "pg-sql2"; -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; import { version } from "../../package.json"; -import type { +import { PgProc, PgType, PgClass, @@ -35,17 +34,16 @@ import pgPrepareAndRun from "../pgPrepareAndRun"; const defaultPgColumnFilter = (_attr, _build, _context) => true; type Keys = Array<{ - column: string, - table: string, - schema: ?string, + column: string; + table: string; + schema: string | null | undefined; }>; const identity = _ => _; export function preventEmptyResult< - // eslint-disable-next-line flowtype/no-weak-types - O: { [key: string]: (...args: Array) => string } ->(obj: O): $ObjMap(V) => V> { + O extends { [key: string]: (...args: Array) => string } +>(obj: O): $ObjMap(a: V) => V> { return Object.keys(obj).reduce((memo, key) => { const fn = obj[key]; memo[key] = function(...args) { @@ -87,20 +85,20 @@ const omitWithRBACChecks = omit => ( const tableEntity: PgClass = entity; if ( (permission === READ || permission === ALL || permission === MANY) && - (!tableEntity.aclSelectable && - !tableEntity.attributes.some(attr => attr.aclSelectable)) + !tableEntity.aclSelectable && + !tableEntity.attributes.some(attr => attr.aclSelectable) ) { return true; } else if ( permission === CREATE && - (!tableEntity.aclInsertable && - !tableEntity.attributes.some(attr => attr.aclInsertable)) + !tableEntity.aclInsertable && + !tableEntity.attributes.some(attr => attr.aclInsertable) ) { return true; } else if ( permission === UPDATE && - (!tableEntity.aclUpdatable && - !tableEntity.attributes.some(attr => attr.aclUpdatable)) + !tableEntity.aclUpdatable && + !tableEntity.attributes.some(attr => attr.aclUpdatable) ) { return true; } else if (permission === DELETE && !tableEntity.aclDeletable) { @@ -188,6 +186,7 @@ function describePgEntity(entity: PgEntity, includeAlias = true) { entity.tags, (value, key) => key === "name" || key.endsWith("Name") ); + if (Object.keys(tags).length) { return ` (with smart comments: ${chalk.bold( Object.keys(tags) @@ -319,7 +318,7 @@ function sqlCommentByAddingTags(entity, tagsToAdd) { return `COMMENT ON ${sqlThing} IS ${commentValue};`; } -export default (function PgBasicsPlugin( +export default function PgBasicsPlugin( builder, { pgStrictFunctions = false, @@ -446,7 +445,7 @@ export default (function PgBasicsPlugin( enumType(type: PgType) { return this.upperCamelCase(this._typeName(type)); }, - argument(name: ?string, index: number) { + argument(name: string | null | undefined, index: number) { return this.coerceToGraphQLName( this.camelCase(name || `arg${index}`) ); @@ -473,6 +472,7 @@ export default (function PgBasicsPlugin( proc, table ); + return this.orderByEnum(columnName, ascending); }, domainType(type: PgType) { @@ -873,9 +873,10 @@ export default (function PgBasicsPlugin( ); }, }), + "Default inflectors from PgBasicsPlugin. You can override these with `makeAddInflectorsPlugin(..., true)`." ); }, ["PgBasics"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgColumnDeprecationPlugin.js b/packages/graphile-build-pg/src/plugins/PgColumnDeprecationPlugin.ts similarity index 82% rename from packages/graphile-build-pg/src/plugins/PgColumnDeprecationPlugin.js rename to packages/graphile-build-pg/src/plugins/PgColumnDeprecationPlugin.ts index d75d72767..a6198f7ac 100644 --- a/packages/graphile-build-pg/src/plugins/PgColumnDeprecationPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgColumnDeprecationPlugin.ts @@ -1,7 +1,6 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; -export default (function PgColumnDeprecationPlugin(builder) { +export default function PgColumnDeprecationPlugin(builder) { builder.hook( "GraphQLObjectType:fields:field", (field, build, context) => { @@ -24,4 +23,4 @@ export default (function PgColumnDeprecationPlugin(builder) { }, ["PgColumnDeprecation"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgColumnsPlugin.js b/packages/graphile-build-pg/src/plugins/PgColumnsPlugin.ts similarity index 98% rename from packages/graphile-build-pg/src/plugins/PgColumnsPlugin.js rename to packages/graphile-build-pg/src/plugins/PgColumnsPlugin.ts index b640dd403..953526cd1 100644 --- a/packages/graphile-build-pg/src/plugins/PgColumnsPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgColumnsPlugin.ts @@ -1,10 +1,9 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; const nullableIf = (GraphQLNonNull, condition, Type) => condition ? Type : new GraphQLNonNull(Type); -export default (function PgColumnsPlugin(builder) { +export default function PgColumnsPlugin(builder) { builder.hook( "build", build => { @@ -45,6 +44,7 @@ end parsedResolveInfoFragment, ReturnType ); + if (type.type === "c") { const isDefinitelyNotATable = type.class && !type.class.isSelectable; @@ -58,6 +58,7 @@ end addNotDistinctFromNullCase: isDefinitelyNotATable, } ); + return jsonBuildObject; } else { return pgTweakFragmentForTypeAndModifier( @@ -148,6 +149,7 @@ end type, typeModifier ), + fieldName ); }, @@ -163,6 +165,7 @@ end !attr.tags.notNull, ReturnType ), + resolve: (data, _args, _context, _resolveInfo) => { return convertFromPg(data[fieldName]); }, @@ -171,6 +174,7 @@ end { pgFieldIntrospection: attr } ), }, + `Adding field for ${describePgEntity( attr )}. You can rename this field with:\n\n ${sqlCommentByAddingTags( @@ -180,6 +184,7 @@ end } )}` ); + return memo; }, {}), `Adding columns to '${describePgEntity(table)}'` @@ -187,6 +192,7 @@ end }, ["PgColumns"] ); + builder.hook( "GraphQLInputObjectType:fields", (fields, build, context) => { @@ -209,6 +215,7 @@ end pgIntrospection: table, pgAddSubfield, }, + fieldWithHooks, } = context; if ( @@ -266,11 +273,14 @@ end ) || GraphQLString ), }, + attr.typeModifier ), + { pgFieldIntrospection: attr } ), }, + `Adding input object field for ${describePgEntity( attr )}. You can rename this field with:\n\n ${sqlCommentByAddingTags( @@ -280,6 +290,7 @@ end } )}` ); + return memo; }, {}), `Adding columns to input object for ${describePgEntity(table)}` @@ -287,4 +298,4 @@ end }, ["PgColumns"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgComputedColumnsPlugin.js b/packages/graphile-build-pg/src/plugins/PgComputedColumnsPlugin.ts similarity index 95% rename from packages/graphile-build-pg/src/plugins/PgComputedColumnsPlugin.js rename to packages/graphile-build-pg/src/plugins/PgComputedColumnsPlugin.ts index ba9f2ca84..4ea6f6c5c 100644 --- a/packages/graphile-build-pg/src/plugins/PgComputedColumnsPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgComputedColumnsPlugin.ts @@ -1,6 +1,5 @@ -// @flow -import type { Plugin, Build } from "graphile-build"; -import type { PgClass, PgProc } from "./PgIntrospectionPlugin"; +import { Plugin, Build } from "graphile-build"; +import { PgClass, PgProc } from "./PgIntrospectionPlugin"; // This interface is not official yet, don't rely on it. export const getComputedColumnDetails = ( @@ -37,7 +36,7 @@ export const getComputedColumnDetails = ( return { argTypes, pseudoColumnName }; }; -export default (function PgComputedColumnsPlugin( +export default function PgComputedColumnsPlugin( builder, { pgSimpleCollections } ) { @@ -51,6 +50,7 @@ export default (function PgComputedColumnsPlugin( isInputType, pgIntrospection: table, }, + fieldWithHooks, Self, } = context; @@ -88,6 +88,7 @@ export default (function PgComputedColumnsPlugin( table, proc ); + if (!computedColumnDetails) return memo; const { pseudoColumnName } = computedColumnDetails; function makeField(forceList) { @@ -104,6 +105,7 @@ export default (function PgComputedColumnsPlugin( forceList, }), }, + `Adding computed column for ${describePgEntity( proc )}. You can rename this field with:\n\n ${sqlCommentByAddingTags( @@ -135,4 +137,4 @@ export default (function PgComputedColumnsPlugin( }, ["PgComputedColumns"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgConditionComputedColumnPlugin.js b/packages/graphile-build-pg/src/plugins/PgConditionComputedColumnPlugin.ts similarity index 97% rename from packages/graphile-build-pg/src/plugins/PgConditionComputedColumnPlugin.js rename to packages/graphile-build-pg/src/plugins/PgConditionComputedColumnPlugin.ts index 8bdd1259e..ed96f16e0 100644 --- a/packages/graphile-build-pg/src/plugins/PgConditionComputedColumnPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgConditionComputedColumnPlugin.ts @@ -1,5 +1,4 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; import { getComputedColumnDetails } from "./PgComputedColumnsPlugin"; import assert from "assert"; function getCompatibleComputedColumns(build, table) { @@ -44,7 +43,7 @@ function getCompatibleComputedColumns(build, table) { }, []); } -export default (function PgConditionComputedColumnPlugin(builder) { +export default function PgConditionComputedColumnPlugin(builder) { builder.hook( "GraphQLInputObjectType:fields", (fields, build, context) => { @@ -65,6 +64,7 @@ export default (function PgConditionComputedColumnPlugin(builder) { build, table ); + return extend( fields, compatibleComputedColumns.reduce((memo, { proc, pseudoColumnName }) => { @@ -73,10 +73,12 @@ export default (function PgConditionComputedColumnPlugin(builder) { proc, table ); + const Type = pgGetGqlInputTypeByTypeIdAndModifier( proc.returnTypeId, null ); + if (!Type) return memo; memo = build.extend( memo, @@ -87,16 +89,19 @@ export default (function PgConditionComputedColumnPlugin(builder) { description: `Checks for equality with the object’s \`${fieldName}\` field.`, type: Type, }, + { isPgConnectionConditionInputField: true, pgFieldIntrospection: proc, } ), }, + `Adding computed column condition argument for ${describePgEntity( proc )}` ); + return memo; }, {}) ); @@ -123,6 +128,7 @@ export default (function PgConditionComputedColumnPlugin(builder) { pgFieldIntrospection, pgFieldIntrospectionTable, }, + addArgDataGenerator, } = context; @@ -152,6 +158,7 @@ export default (function PgConditionComputedColumnPlugin(builder) { const TableConditionType = getTypeByName( inflection.conditionType(TableType.name) ); + if (!TableConditionType) { return args; } @@ -171,6 +178,7 @@ export default (function PgConditionComputedColumnPlugin(builder) { proc, table ); + const sqlFnName = sql.identifier(proc.namespaceName, proc.name); return { ...o, @@ -208,4 +216,4 @@ export default (function PgConditionComputedColumnPlugin(builder) { }, ["PgConditionComputedColumn"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgConnectionArgCondition.js b/packages/graphile-build-pg/src/plugins/PgConnectionArgCondition.ts similarity index 97% rename from packages/graphile-build-pg/src/plugins/PgConnectionArgCondition.js rename to packages/graphile-build-pg/src/plugins/PgConnectionArgCondition.ts index a52b5d7a9..8f0a19b5a 100644 --- a/packages/graphile-build-pg/src/plugins/PgConnectionArgCondition.js +++ b/packages/graphile-build-pg/src/plugins/PgConnectionArgCondition.ts @@ -1,7 +1,6 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; -export default (function PgConnectionArgCondition(builder) { +export default function PgConnectionArgCondition(builder) { builder.hook( "init", (_, build) => { @@ -49,17 +48,21 @@ export default (function PgConnectionArgCondition(builder) { attr.typeModifier ) || GraphQLString, }, + { isPgConnectionConditionInputField: true, } ), }, + `Adding condition argument for ${describePgEntity(attr)}` ); + return memo; }, {}); }, }, + { __origin: `Adding condition type for ${describePgEntity( table @@ -72,6 +75,7 @@ export default (function PgConnectionArgCondition(builder) { pgIntrospection: table, isPgCondition: true, }, + true // Conditions might all be filtered ); }); @@ -103,6 +107,7 @@ export default (function PgConnectionArgCondition(builder) { pgFieldIntrospection, pgFieldIntrospectionTable, }, + addArgDataGenerator, Self, } = context; @@ -137,6 +142,7 @@ export default (function PgConnectionArgCondition(builder) { const TableConditionType = getTypeByName( inflection.conditionType(TableType.name) ); + if (!TableConditionType) { return args; } @@ -156,6 +162,7 @@ export default (function PgConnectionArgCondition(builder) { queryBuilder.addLiveCondition(() => record => record[attr.name] === val ); + queryBuilder.where( sql.fragment`${queryBuilder.getTableAlias()}.${sql.identifier( attr.name @@ -165,6 +172,7 @@ export default (function PgConnectionArgCondition(builder) { queryBuilder.addLiveCondition(() => record => record[attr.name] == null ); + queryBuilder.where( sql.fragment`${queryBuilder.getTableAlias()}.${sql.identifier( attr.name @@ -186,9 +194,10 @@ export default (function PgConnectionArgCondition(builder) { type: TableConditionType, }, }, + `Adding condition to connection field '${fieldName}' of '${Self.name}'` ); }, ["PgConnectionArgCondition"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgConnectionArgFirstLastBeforeAfter.js b/packages/graphile-build-pg/src/plugins/PgConnectionArgFirstLastBeforeAfter.ts similarity index 96% rename from packages/graphile-build-pg/src/plugins/PgConnectionArgFirstLastBeforeAfter.js rename to packages/graphile-build-pg/src/plugins/PgConnectionArgFirstLastBeforeAfter.ts index c8d73499b..268e0e6f5 100644 --- a/packages/graphile-build-pg/src/plugins/PgConnectionArgFirstLastBeforeAfter.js +++ b/packages/graphile-build-pg/src/plugins/PgConnectionArgFirstLastBeforeAfter.ts @@ -1,9 +1,8 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; const base64Decode = str => Buffer.from(String(str), "base64").toString("utf8"); -export default (function PgConnectionArgs(builder) { +export default function PgConnectionArgs(builder) { builder.hook( "GraphQLObjectType:fields:field:args", (args, build, context) => { @@ -19,6 +18,7 @@ export default (function PgConnectionArgs(builder) { isPgFieldSimpleCollection, pgFieldIntrospection: source, }, + addArgDataGenerator, Self, } = context; @@ -90,6 +90,7 @@ export default (function PgConnectionArgs(builder) { description: "Only read the first `n` values of the set.", type: GraphQLInt, }, + ...(isPgFieldConnection ? { last: { @@ -104,6 +105,7 @@ export default (function PgConnectionArgs(builder) { : "Skip the first `n` values.", type: GraphQLInt, }, + ...(isPgFieldConnection ? { before: { @@ -111,6 +113,7 @@ export default (function PgConnectionArgs(builder) { "Read all values in the set before (above) this cursor.", type: Cursor, }, + after: { description: "Read all values in the set after (below) this cursor.", @@ -119,6 +122,7 @@ export default (function PgConnectionArgs(builder) { } : null), }, + isPgFieldConnection ? `Adding connection pagination args to field '${fieldName}' of '${ Self.name @@ -130,4 +134,4 @@ export default (function PgConnectionArgs(builder) { }, ["PgConnectionArgFirstLastBeforeAfter"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgConnectionArgOrderBy.js b/packages/graphile-build-pg/src/plugins/PgConnectionArgOrderBy.ts similarity index 97% rename from packages/graphile-build-pg/src/plugins/PgConnectionArgOrderBy.js rename to packages/graphile-build-pg/src/plugins/PgConnectionArgOrderBy.ts index 1cb7c857a..debe39d75 100644 --- a/packages/graphile-build-pg/src/plugins/PgConnectionArgOrderBy.js +++ b/packages/graphile-build-pg/src/plugins/PgConnectionArgOrderBy.ts @@ -1,8 +1,7 @@ -// @flow import isString from "lodash/isString"; -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; -export default (function PgConnectionArgOrderBy(builder, { orderByNullsLast }) { +export default function PgConnectionArgOrderBy(builder, { orderByNullsLast }) { builder.hook( "init", (_, build) => { @@ -36,6 +35,7 @@ export default (function PgConnectionArgOrderBy(builder, { orderByNullsLast }) { }, }, }, + { __origin: `Adding connection "orderBy" argument for ${describePgEntity( table @@ -75,6 +75,7 @@ export default (function PgConnectionArgOrderBy(builder, { orderByNullsLast }) { pgFieldIntrospection, pgFieldIntrospectionTable, }, + addArgDataGenerator, Self, } = context; @@ -109,6 +110,7 @@ export default (function PgConnectionArgOrderBy(builder, { orderByNullsLast }) { const TableOrderByType = getTypeByName( inflection.orderByType(tableTypeName) ); + const cursorPrefixFromOrderBy = orderBy => { if (orderBy) { let cursorPrefixes = []; @@ -178,9 +180,10 @@ export default (function PgConnectionArgOrderBy(builder, { orderByNullsLast }) { type: new GraphQLList(new GraphQLNonNull(TableOrderByType)), }, }, + `Adding 'orderBy' argument to field '${fieldName}' of '${Self.name}'` ); }, ["PgConnectionArgOrderBy"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgConnectionArgOrderByDefaultValue.js b/packages/graphile-build-pg/src/plugins/PgConnectionArgOrderByDefaultValue.ts similarity index 90% rename from packages/graphile-build-pg/src/plugins/PgConnectionArgOrderByDefaultValue.js rename to packages/graphile-build-pg/src/plugins/PgConnectionArgOrderByDefaultValue.ts index 90e523bdd..8f435e85c 100644 --- a/packages/graphile-build-pg/src/plugins/PgConnectionArgOrderByDefaultValue.js +++ b/packages/graphile-build-pg/src/plugins/PgConnectionArgOrderByDefaultValue.ts @@ -1,7 +1,6 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; -export default (function PgConnectionArgOrderByDefaultValue(builder) { +export default function PgConnectionArgOrderByDefaultValue(builder) { builder.hook( "GraphQLObjectType:fields:field:args", (args, build, context) => { @@ -31,6 +30,7 @@ export default (function PgConnectionArgOrderByDefaultValue(builder) { const TableOrderByType = getTypeByName( inflection.orderByType(tableTypeName) ); + if (!TableOrderByType) { return args; } @@ -45,6 +45,7 @@ export default (function PgConnectionArgOrderByDefaultValue(builder) { { defaultValue: defaultValueEnum && [defaultValueEnum.value], }, + `Adding defaultValue to orderBy for field '${fieldName}' of '${ Self.name }'` @@ -53,4 +54,4 @@ export default (function PgConnectionArgOrderByDefaultValue(builder) { }, ["PgConnectionArgOrderByDefaultValue"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgConnectionTotalCount.js b/packages/graphile-build-pg/src/plugins/PgConnectionTotalCount.ts similarity index 93% rename from packages/graphile-build-pg/src/plugins/PgConnectionTotalCount.js rename to packages/graphile-build-pg/src/plugins/PgConnectionTotalCount.ts index 049b24823..6b2a005ef 100644 --- a/packages/graphile-build-pg/src/plugins/PgConnectionTotalCount.js +++ b/packages/graphile-build-pg/src/plugins/PgConnectionTotalCount.ts @@ -1,7 +1,6 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; -export default (function PgConnectionTotalCount(builder) { +export default function PgConnectionTotalCount(builder) { builder.hook( "GraphQLObjectType:fields", (fields, build, context) => { @@ -58,9 +57,10 @@ export default (function PgConnectionTotalCount(builder) { } ), }, + `Adding totalCount to connection '${Self.name}'` ); }, ["PgConnectionTotalCount"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgForwardRelationPlugin.js b/packages/graphile-build-pg/src/plugins/PgForwardRelationPlugin.ts similarity index 98% rename from packages/graphile-build-pg/src/plugins/PgForwardRelationPlugin.js rename to packages/graphile-build-pg/src/plugins/PgForwardRelationPlugin.ts index f063749ec..ba83f53d5 100644 --- a/packages/graphile-build-pg/src/plugins/PgForwardRelationPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgForwardRelationPlugin.ts @@ -1,10 +1,9 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; import debugFactory from "debug"; const debug = debugFactory("graphile-build-pg"); -export default (function PgForwardRelationPlugin(builder, { subscriptions }) { +export default function PgForwardRelationPlugin(builder, { subscriptions }) { builder.hook( "GraphQLObjectType:fields", (fields, build, context) => { @@ -29,6 +28,7 @@ export default (function PgForwardRelationPlugin(builder, { subscriptions }) { pgIntrospection, pgIntrospectionTable, }, + fieldWithHooks, Self, } = context; @@ -66,11 +66,13 @@ export default (function PgForwardRelationPlugin(builder, { subscriptions }) { table.type.id, null ); + const tableTypeName = gqlTableType.name; if (!gqlTableType) { debug( `Could not determine type for table with id ${constraint.classId}` ); + return memo; } const foreignTable = @@ -79,6 +81,7 @@ export default (function PgForwardRelationPlugin(builder, { subscriptions }) { foreignTable.type.id, null ); + const foreignTableTypeName = gqlForeignTableType.name; if (!gqlForeignTableType) { debug( @@ -86,6 +89,7 @@ export default (function PgForwardRelationPlugin(builder, { subscriptions }) { constraint.foreignClassId }` ); + return memo; } if (!foreignTable) { @@ -136,18 +140,21 @@ export default (function PgForwardRelationPlugin(builder, { subscriptions }) { parsedResolveInfoFragment, gqlForeignTableType ); + const foreignTableAlias = sql.identifier(Symbol()); const query = queryFromResolveData( sql.identifier( foreignSchema.name, foreignTable.name ), + foreignTableAlias, resolveData, { useAsterisk: false, // Because it's only a single relation, no need asJson: true, }, + innerQueryBuilder => { innerQueryBuilder.parentQueryBuilder = queryBuilder; if (subscriptions && table.primaryKeyConstraint) { @@ -174,6 +181,7 @@ export default (function PgForwardRelationPlugin(builder, { subscriptions }) { queryBuilder.context, queryBuilder.rootValue ); + return sql.fragment`(${query})`; }, getSafeAliasFromAlias(parsedResolveInfoFragment.alias)); }, @@ -190,6 +198,7 @@ export default (function PgForwardRelationPlugin(builder, { subscriptions }) { const safeAlias = getSafeAliasFromResolveInfo( resolveInfo ); + const record = data[safeAlias]; const liveRecord = resolveInfo.rootValue && @@ -207,6 +216,7 @@ export default (function PgForwardRelationPlugin(builder, { subscriptions }) { } ), }, + `Forward relation for ${describePgEntity( constraint )}. To rename this relation with smart comments:\n\n ${sqlCommentByAddingTags( @@ -216,6 +226,7 @@ export default (function PgForwardRelationPlugin(builder, { subscriptions }) { } )}` ); + return memo; }, {}), `Adding forward relations to '${Self.name}'` @@ -223,4 +234,4 @@ export default (function PgForwardRelationPlugin(builder, { subscriptions }) { }, ["PgForwardRelation"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgIntrospectionPlugin.js b/packages/graphile-build-pg/src/plugins/PgIntrospectionPlugin.ts similarity index 85% rename from packages/graphile-build-pg/src/plugins/PgIntrospectionPlugin.js rename to packages/graphile-build-pg/src/plugins/PgIntrospectionPlugin.ts index b65d12e10..d969f2545 100644 --- a/packages/graphile-build-pg/src/plugins/PgIntrospectionPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgIntrospectionPlugin.ts @@ -1,6 +1,5 @@ -// @flow -import type { Plugin } from "graphile-build"; -import type { Client } from "pg"; +import { Plugin } from "graphile-build"; +import { Client } from "pg"; import withPgClient, { getPgClientAndReleaserFromConfig, } from "../withPgClient"; @@ -21,172 +20,172 @@ const WATCH_FIXTURES_PATH = `${__dirname}/../../res/watch-fixtures.sql`; // Ref: https://github.com/graphile/postgraphile/tree/master/src/postgres/introspection/object export type PgNamespace = { - kind: "namespace", - id: string, - name: string, - comment: ?string, - description: ?string, - tags: { [string]: string }, + kind: "namespace"; + id: string; + name: string; + comment: string | null | undefined; + description: string | null | undefined; + tags: { [a: string]: string }; }; export type PgProc = { - kind: "procedure", - id: string, - name: string, - comment: ?string, - description: ?string, - namespaceId: string, - namespaceName: string, - isStrict: boolean, - returnsSet: boolean, - isStable: boolean, - returnTypeId: string, - argTypeIds: Array, - argNames: Array, - argModes: Array<"i" | "o" | "b" | "v" | "t">, - inputArgsCount: number, - argDefaultsNum: number, - namespace: PgNamespace, - tags: { [string]: string }, - cost: number, - aclExecutable: boolean, - language: string, + kind: "procedure"; + id: string; + name: string; + comment: string | null | undefined; + description: string | null | undefined; + namespaceId: string; + namespaceName: string; + isStrict: boolean; + returnsSet: boolean; + isStable: boolean; + returnTypeId: string; + argTypeIds: Array; + argNames: Array; + argModes: Array<"i" | "o" | "b" | "v" | "t">; + inputArgsCount: number; + argDefaultsNum: number; + namespace: PgNamespace; + tags: { [a: string]: string }; + cost: number; + aclExecutable: boolean; + language: string; }; export type PgClass = { - kind: "class", - id: string, - name: string, - comment: ?string, - description: ?string, - classKind: string, - namespaceId: string, - namespaceName: string, - typeId: string, - isSelectable: boolean, - isInsertable: boolean, - isUpdatable: boolean, - isDeletable: boolean, - isExtensionConfigurationTable: boolean, - namespace: PgNamespace, - type: PgType, - tags: { [string]: string }, - attributes: [PgAttribute], - constraints: [PgConstraint], - foreignConstraints: [PgConstraint], - primaryKeyConstraint: ?PgConstraint, - aclSelectable: boolean, - aclInsertable: boolean, - aclUpdatable: boolean, - aclDeletable: boolean, - canUseAsterisk: boolean, + kind: "class"; + id: string; + name: string; + comment: string | null | undefined; + description: string | null | undefined; + classKind: string; + namespaceId: string; + namespaceName: string; + typeId: string; + isSelectable: boolean; + isInsertable: boolean; + isUpdatable: boolean; + isDeletable: boolean; + isExtensionConfigurationTable: boolean; + namespace: PgNamespace; + type: PgType; + tags: { [a: string]: string }; + attributes: [PgAttribute]; + constraints: [PgConstraint]; + foreignConstraints: [PgConstraint]; + primaryKeyConstraint: PgConstraint | null | undefined; + aclSelectable: boolean; + aclInsertable: boolean; + aclUpdatable: boolean; + aclDeletable: boolean; + canUseAsterisk: boolean; }; export type PgType = { - kind: "type", - id: string, - name: string, - comment: ?string, - description: ?string, - namespaceId: string, - namespaceName: string, - type: string, - category: string, - domainIsNotNull: boolean, - arrayItemTypeId: ?string, - arrayItemType: ?PgType, - arrayType: ?PgType, - typeLength: ?number, - isPgArray: boolean, - classId: ?string, - class: ?PgClass, - domainBaseTypeId: ?string, - domainBaseType: ?PgType, - domainTypeModifier: ?number, - tags: { [string]: string }, + kind: "type"; + id: string; + name: string; + comment: string | null | undefined; + description: string | null | undefined; + namespaceId: string; + namespaceName: string; + type: string; + category: string; + domainIsNotNull: boolean; + arrayItemTypeId: string | null | undefined; + arrayItemType: PgType | null | undefined; + arrayType: PgType | null | undefined; + typeLength: number | null | undefined; + isPgArray: boolean; + classId: string | null | undefined; + class: PgClass | null | undefined; + domainBaseTypeId: string | null | undefined; + domainBaseType: PgType | null | undefined; + domainTypeModifier: number | null | undefined; + tags: { [a: string]: string }; }; export type PgAttribute = { - kind: "attribute", - classId: string, - num: number, - name: string, - comment: ?string, - description: ?string, - typeId: string, - typeModifier: number, - isNotNull: boolean, - hasDefault: boolean, - identity: "" | "a" | "d", - class: PgClass, - type: PgType, - namespace: PgNamespace, - tags: { [string]: string }, - aclSelectable: boolean, - aclInsertable: boolean, - aclUpdatable: boolean, - isIndexed: ?boolean, - isUnique: ?boolean, - columnLevelSelectGrant: boolean, + kind: "attribute"; + classId: string; + num: number; + name: string; + comment: string | null | undefined; + description: string | null | undefined; + typeId: string; + typeModifier: number; + isNotNull: boolean; + hasDefault: boolean; + identity: "" | "a" | "d"; + class: PgClass; + type: PgType; + namespace: PgNamespace; + tags: { [a: string]: string }; + aclSelectable: boolean; + aclInsertable: boolean; + aclUpdatable: boolean; + isIndexed: boolean | null | undefined; + isUnique: boolean | null | undefined; + columnLevelSelectGrant: boolean; }; export type PgConstraint = { - kind: "constraint", - id: string, - name: string, - type: string, - classId: string, - class: PgClass, - foreignClassId: ?string, - foreignClass: ?PgClass, - comment: ?string, - description: ?string, - keyAttributeNums: Array, - keyAttributes: [PgAttribute], - foreignKeyAttributeNums: Array, - foreignKeyAttributes: [PgAttribute], - namespace: PgNamespace, - isIndexed: ?boolean, - tags: { [string]: string }, + kind: "constraint"; + id: string; + name: string; + type: string; + classId: string; + class: PgClass; + foreignClassId: string | null | undefined; + foreignClass: PgClass | null | undefined; + comment: string | null | undefined; + description: string | null | undefined; + keyAttributeNums: Array; + keyAttributes: [PgAttribute]; + foreignKeyAttributeNums: Array; + foreignKeyAttributes: [PgAttribute]; + namespace: PgNamespace; + isIndexed: boolean | null | undefined; + tags: { [a: string]: string }; }; export type PgExtension = { - kind: "extension", - id: string, - name: string, - namespaceId: string, - namespaceName: string, - relocatable: boolean, - version: string, - configurationClassIds?: Array, - comment: ?string, - description: ?string, - tags: { [string]: string }, + kind: "extension"; + id: string; + name: string; + namespaceId: string; + namespaceName: string; + relocatable: boolean; + version: string; + configurationClassIds?: Array; + comment: string | null | undefined; + description: string | null | undefined; + tags: { [a: string]: string }; }; export type PgIndex = { - kind: "index", - id: string, - name: string, - namespaceName: string, - classId: string, - numberOfAttributes: number, - indexType: string, - isUnique: boolean, - isPrimary: boolean, + kind: "index"; + id: string; + name: string; + namespaceName: string; + classId: string; + numberOfAttributes: number; + indexType: string; + isUnique: boolean; + isPrimary: boolean; /* - Though these exist, we don't want to officially - support them yet. - - isImmediate: boolean, - isReplicaIdentity: boolean, - isValid: boolean, - */ - attributeNums: Array, - attributePropertiesAsc: ?Array, - attributePropertiesNullsFirst: ?Array, - description: ?string, - tags: { [string]: string }, + Though these exist, we don't want to officially + support them yet. + isImmediate: boolean, + isReplicaIdentity: boolean, + isValid: boolean, + */ + + attributeNums: Array; + attributePropertiesAsc: Array | null | undefined; + attributePropertiesNullsFirst: Array | null | undefined; + description: string | null | undefined; + tags: { [a: string]: string }; }; export type PgEntity = @@ -251,6 +250,7 @@ function smartCommentConstraints(introspectionResults) { const pk = introspectionResults.constraint.find( c => c.classId == tbl.id && c.type === "p" ); + if (pk) { return pk.keyAttributeNums.map(n => attributes.find(a => a.num === n)); } else { @@ -279,6 +279,7 @@ function smartCommentConstraints(introspectionResults) { const namespace = introspectionResults.namespace.find( n => n.id === klass.namespaceId ); + if (!namespace) { return; } @@ -293,6 +294,7 @@ function smartCommentConstraints(introspectionResults) { const { spec: pkSpec, tags, description } = parseConstraintSpec( klass.tags.primaryKey ); + // $FlowFixMe const columns: string[] = parseSqlColumn(pkSpec, true); const attributes = attributesByNames( @@ -300,6 +302,7 @@ function smartCommentConstraints(introspectionResults) { columns, `@primaryKey ${klass.tags.primaryKey}` ); + attributes.forEach(attr => { attr.tags.notNull = true; }); @@ -320,6 +323,7 @@ function smartCommentConstraints(introspectionResults) { foreignKeyAttributeNums: null, tags, }; + introspectionResults.constraint.push(fakeConstraint); } }); @@ -328,6 +332,7 @@ function smartCommentConstraints(introspectionResults) { const namespace = introspectionResults.namespace.find( n => n.id === klass.namespaceId ); + if (!namespace) { return; } @@ -355,9 +360,11 @@ function smartCommentConstraints(introspectionResults) { const { spec: fkSpec, tags, description } = parseConstraintSpec( fkSpecRaw ); + const matches = fkSpec.match( /^\(([^()]+)\) references ([^().]+)(?:\.([^().]+))?(?:\s*\(([^()]+)\))?$/i ); + if (!matches) { throw new Error( `Invalid foreignKey syntax for '${klass.namespaceName}.${ @@ -390,6 +397,7 @@ function smartCommentConstraints(introspectionResults) { const foreignKlass = introspectionResults.class.find( k => k.name === foreignTable && k.namespaceName === foreignSchema ); + if (!foreignKlass) { throw new Error( `@foreignKey smart comment referenced non-existant table/view '${foreignSchema}'.'${foreignTable}'. Note that this reference must use *database names* (i.e. it does not respect @name). (${fkSpecRaw})` @@ -398,6 +406,7 @@ function smartCommentConstraints(introspectionResults) { const foreignNamespace = introspectionResults.namespace.find( n => n.id === foreignKlass.namespaceId ); + if (!foreignNamespace) { return; } @@ -429,13 +438,14 @@ function smartCommentConstraints(introspectionResults) { foreignKeyAttributeNums, tags, }; + introspectionResults.constraint.push(fakeConstraint); }); } }); } -export default (async function PgIntrospectionPlugin( +export default async function PgIntrospectionPlugin( builder, { pgConfig, @@ -474,13 +484,16 @@ export default (async function PgIntrospectionPlugin( const versionResult = await pgClient.query( "show server_version_num;" ); + const serverVersionNum = parseInt( versionResult.rows[0].server_version_num, 10 ); + const introspectionQuery = makeIntrospectionQuery(serverVersionNum, { pgLegacyFunctionsOnly, }); + const { rows } = await pgClient.query(introspectionQuery, [ schemas, pgIncludeExtensionResources, @@ -497,6 +510,7 @@ export default (async function PgIntrospectionPlugin( extension: [], index: [], }; + for (const { object } of rows) { result[object.kind].push(object); } @@ -529,6 +543,7 @@ export default (async function PgIntrospectionPlugin( result.extension, e => e.configurationClassIds ); + result.class.forEach(klass => { klass.isExtensionConfigurationTable = extensionConfigurationClassIds.indexOf(klass.id) >= 0; @@ -582,19 +597,23 @@ export default (async function PgIntrospectionPlugin( introspectionResultsByKind.namespace, "id" ); + introspectionResultsByKind.classById = xByY( introspectionResultsByKind.class, "id" ); + introspectionResultsByKind.typeById = xByY( introspectionResultsByKind.type, "id" ); + introspectionResultsByKind.attributeByClassIdAndNum = xByYAndZ( introspectionResultsByKind.attribute, "classId", "num" ); + introspectionResultsByKind.extensionById = xByY( introspectionResultsByKind.extension, "id" @@ -749,15 +768,19 @@ export default (async function PgIntrospectionPlugin( klass.attributes = introspectionResultsByKind.attribute.filter( attr => attr.classId === klass.id ); + klass.canUseAsterisk = !klass.attributes.some( attr => attr.columnLevelSelectGrant ); + klass.constraints = introspectionResultsByKind.constraint.filter( constraint => constraint.classId === klass.id ); + klass.foreignConstraints = introspectionResultsByKind.constraint.filter( constraint => constraint.foreignClassId === klass.id ); + klass.primaryKeyConstraint = klass.constraints.find( constraint => constraint.type === "p" ); @@ -818,10 +841,10 @@ export default (async function PgIntrospectionPlugin( let listener; class Listener { - _handleChange: () => void; + _handleChange: () => undefined; client: Client | null; stopped: boolean; - _reallyReleaseClient: (() => Promise) | null; + _reallyReleaseClient: (() => Promise) | null; _haveDisplayedError: boolean; constructor(triggerRebuild) { this.stopped = false; @@ -843,6 +866,7 @@ export default (async function PgIntrospectionPlugin( trailing: true, } ); + this._listener = this._listener.bind(this); this._handleClientError = this._handleClientError.bind(this); this._start(); @@ -887,16 +911,19 @@ export default (async function PgIntrospectionPlugin( "Failed to setup watch fixtures in Postgres database" )} ️️⚠️` ); + console.warn( chalk.yellow( "This is likely because the PostgreSQL user in the connection string does not have sufficient privileges; you can solve this by passing the 'owner' connection string via '--owner-connection' / 'ownerConnectionString'. If the fixtures already exist, the watch functionality may still work." ) ); + console.warn( chalk.yellow( "Enable DEBUG='graphile-build-pg' to see the error" ) ); + /* eslint-enable no-console */ } debug(error); @@ -916,7 +943,7 @@ export default (async function PgIntrospectionPlugin( } } - _handleClientError: (e: Error) => void; + _handleClientError: (e: Error) => undefined; _handleClientError(e) { // Client is already cleaned up this.client = null; @@ -932,6 +959,7 @@ export default (async function PgIntrospectionPlugin( "Error occurred for PG watching client; reconnecting in 2 seconds.", e.message ); + await this._releaseClient(); setTimeout(() => { if (!this.stopped) { @@ -942,7 +970,7 @@ export default (async function PgIntrospectionPlugin( } // eslint-disable-next-line flowtype/no-weak-types - _listener: (notification: any) => void; + _listener: (notification: any) => undefined; // eslint-disable-next-line flowtype/no-weak-types async _listener(notification: any) { if (notification.channel !== "postgraphile_watch") { @@ -964,6 +992,7 @@ export default (async function PgIntrospectionPlugin( const affectsOurSchemas = payload.payload.some( schemaName => schemas.indexOf(schemaName) >= 0 ); + if (affectsOurSchemas) { this._handleChange(); } @@ -1036,4 +1065,4 @@ export default (async function PgIntrospectionPlugin( [], ["PgBasics"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgJWTPlugin.js b/packages/graphile-build-pg/src/plugins/PgJWTPlugin.ts similarity index 97% rename from packages/graphile-build-pg/src/plugins/PgJWTPlugin.js rename to packages/graphile-build-pg/src/plugins/PgJWTPlugin.ts index 3f1313195..753734b1d 100644 --- a/packages/graphile-build-pg/src/plugins/PgJWTPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgJWTPlugin.ts @@ -1,8 +1,7 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; import { sign as signJwt } from "jsonwebtoken"; -export default (function PgJWTPlugin( +export default function PgJWTPlugin( builder, { pgJwtTypeIdentifier, pgJwtSecret, pgJwtSignOptions } ) { @@ -44,6 +43,7 @@ export default (function PgJWTPlugin( table.name === typeName && table.namespaceName === namespaceName ); + if (!compositeClass) { throw new Error( `Could not find JWT type '"${namespaceName}"."${typeName}"'` @@ -84,11 +84,13 @@ export default (function PgJWTPlugin( : { audience: "postgraphile", }, + token.iss || (pgJwtSignOptions && pgJwtSignOptions.issuer) ? null : { issuer: "postgraphile", }, + token.exp || (pgJwtSignOptions && pgJwtSignOptions.expiresIn) ? null : { @@ -98,6 +100,7 @@ export default (function PgJWTPlugin( ); }, }, + { __origin: `Adding JWT type based on ${describePgEntity( compositeType @@ -105,6 +108,7 @@ export default (function PgJWTPlugin( isPgJwtType: true, } ); + cb(JWTType); pg2GqlMapper[compositeType.id] = { @@ -136,6 +140,7 @@ export default (function PgJWTPlugin( {} )}` ), + ", " )})`; }); @@ -145,4 +150,4 @@ export default (function PgJWTPlugin( [], ["PgIntrospection"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgMutationCreatePlugin.js b/packages/graphile-build-pg/src/plugins/PgMutationCreatePlugin.ts similarity index 98% rename from packages/graphile-build-pg/src/plugins/PgMutationCreatePlugin.js rename to packages/graphile-build-pg/src/plugins/PgMutationCreatePlugin.ts index e69f05513..f74970d78 100644 --- a/packages/graphile-build-pg/src/plugins/PgMutationCreatePlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgMutationCreatePlugin.ts @@ -1,10 +1,9 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; import debugFactory from "debug"; const debug = debugFactory("graphile-build-pg"); -export default (function PgMutationCreatePlugin( +export default function PgMutationCreatePlugin( builder, { pgDisableDefaultMutations } ) { @@ -30,6 +29,7 @@ export default (function PgMutationCreatePlugin( GraphQLNonNull, GraphQLString, }, + pgColumnFilter, inflection, pgQueryFromResolveData: queryFromResolveData, @@ -63,12 +63,14 @@ export default (function PgMutationCreatePlugin( table.name }', so we're not generating a create mutation for it.` ); + return memo; } const TableInput = pgGetGqlInputTypeByTypeIdAndModifier( table.type.id, null ); + if (!TableInput) { debug( `There was no input type for table '${table.namespace.name}.${ @@ -88,6 +90,7 @@ export default (function PgMutationCreatePlugin( "An arbitrary string value with no semantic meaning. Will be included in the payload verbatim. May be used to track mutations by the client.", type: GraphQLString, }, + ...(TableInput ? { [inflection.tableFieldName(table)]: { @@ -98,6 +101,7 @@ export default (function PgMutationCreatePlugin( : null), }, }, + { __origin: `Adding table create input type for ${describePgEntity( table @@ -112,6 +116,7 @@ export default (function PgMutationCreatePlugin( pgIntrospection: table, } ); + const PayloadType = newWithHooks( GraphQLObjectType, { @@ -125,6 +130,7 @@ export default (function PgMutationCreatePlugin( "The exact same `clientMutationId` that was provided in the mutation input, unchanged and unused. May be used by a client to track mutations.", type: GraphQLString, }, + [tableName]: pgField( build, fieldWithHooks, @@ -133,6 +139,7 @@ export default (function PgMutationCreatePlugin( description: `The \`${tableTypeName}\` that was created by this mutation.`, type: Table, }, + { isPgCreatePayloadResultField: true, pgFieldIntrospection: table, @@ -141,6 +148,7 @@ export default (function PgMutationCreatePlugin( }; }, }, + { __origin: `Adding table create payload type for ${describePgEntity( table @@ -158,6 +166,7 @@ export default (function PgMutationCreatePlugin( pgIntrospection: table, } ); + const fieldName = inflection.createField(table); memo = build.extend( memo, @@ -171,6 +180,7 @@ export default (function PgMutationCreatePlugin( pgColumnFilter(attr, build, context) && !omit(attr, "create") ); + return { description: `Creates a single \`${tableTypeName}\`.`, type: PayloadType, @@ -179,17 +189,20 @@ export default (function PgMutationCreatePlugin( type: new GraphQLNonNull(InputType), }, }, + async resolve(data, args, resolveContext, resolveInfo) { const { input } = args; const { pgClient } = resolveContext; const parsedResolveInfoFragment = parseResolveInfo( resolveInfo ); + parsedResolveInfoFragment.args = args; // Allow overriding via makeWrapResolversPlugin const resolveData = getDataFromParsedResolveInfoFragment( parsedResolveInfoFragment, PayloadType ); + const insertedRowAlias = sql.identifier(Symbol()); const query = queryFromResolveData( insertedRowAlias, @@ -200,6 +213,7 @@ export default (function PgMutationCreatePlugin( resolveContext, resolveInfo.rootValue ); + const sqlColumns = []; const sqlValues = []; const inputData = input[inflection.tableFieldName(table)]; @@ -239,6 +253,7 @@ insert into ${sql.identifier(table.namespace.name, table.name)} ${ insertedRowAlias, query ); + row = rows[0]; await pgClient.query( "RELEASE SAVEPOINT graphql_mutation" @@ -247,6 +262,7 @@ insert into ${sql.identifier(table.namespace.name, table.name)} ${ await pgClient.query( "ROLLBACK TO SAVEPOINT graphql_mutation" ); + throw e; } return { @@ -262,6 +278,7 @@ insert into ${sql.identifier(table.namespace.name, table.name)} ${ } ), }, + `Adding create mutation for ${describePgEntity( table )}. You can omit this default mutation with:\n\n ${sqlCommentByAddingTags( @@ -271,6 +288,7 @@ insert into ${sql.identifier(table.namespace.name, table.name)} ${ } )}` ); + return memo; }, {}), `Adding default 'create' mutation to root mutation` @@ -280,4 +298,4 @@ insert into ${sql.identifier(table.namespace.name, table.name)} ${ [], ["PgTables"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgMutationPayloadEdgePlugin.js b/packages/graphile-build-pg/src/plugins/PgMutationPayloadEdgePlugin.ts similarity index 98% rename from packages/graphile-build-pg/src/plugins/PgMutationPayloadEdgePlugin.js rename to packages/graphile-build-pg/src/plugins/PgMutationPayloadEdgePlugin.ts index 670fba45f..068eeeed1 100644 --- a/packages/graphile-build-pg/src/plugins/PgMutationPayloadEdgePlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgMutationPayloadEdgePlugin.ts @@ -1,8 +1,7 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; import isString from "lodash/isString"; -export default (function PgMutationPayloadEdgePlugin( +export default function PgMutationPayloadEdgePlugin( builder, { pgSimpleCollections, disableIssue397Fix } ) { @@ -57,6 +56,7 @@ export default (function PgMutationPayloadEdgePlugin( const TableOrderByType = getTypeByName( inflection.orderByType(tableTypeName) ); + const TableEdgeType = getTypeByName(inflection.edge(tableTypeName)); if (!TableEdgeType) { return fields; @@ -89,6 +89,7 @@ export default (function PgMutationPayloadEdgePlugin( type: new GraphQLList( new GraphQLNonNull(TableOrderByType) ), + defaultValue: defaultValueEnum ? [defaultValueEnum.value] : null, @@ -133,10 +134,12 @@ export default (function PgMutationPayloadEdgePlugin( }; }, }, + { isPgMutationPayloadEdgeField: true, pgFieldIntrospection: table, }, + false, { withQueryBuilder(queryBuilder, { parsedResolveInfoFragment }) { @@ -195,6 +198,7 @@ export default (function PgMutationPayloadEdgePlugin( } ), }, + `Adding edge field for table ${describePgEntity( table )} to mutation payload '${Self.name}'` @@ -202,4 +206,4 @@ export default (function PgMutationPayloadEdgePlugin( }, ["PgMutationPayloadEdge"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgMutationProceduresPlugin.js b/packages/graphile-build-pg/src/plugins/PgMutationProceduresPlugin.ts similarity index 92% rename from packages/graphile-build-pg/src/plugins/PgMutationProceduresPlugin.js rename to packages/graphile-build-pg/src/plugins/PgMutationProceduresPlugin.ts index ce97f062a..d567e1d0b 100644 --- a/packages/graphile-build-pg/src/plugins/PgMutationProceduresPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgMutationProceduresPlugin.ts @@ -1,7 +1,6 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; -export default (function PgMutationProceduresPlugin(builder) { +export default function PgMutationProceduresPlugin(builder) { builder.hook( "GraphQLObjectType:fields", (fields, build, context) => { @@ -42,6 +41,7 @@ export default (function PgMutationProceduresPlugin(builder) { isMutation: true, }), }, + `Adding mutation field for ${describePgEntity( proc )}. You can rename this field with:\n\n ${sqlCommentByAddingTags( @@ -61,4 +61,4 @@ export default (function PgMutationProceduresPlugin(builder) { }, ["PgMutationProcedures"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgMutationUpdateDeletePlugin.js b/packages/graphile-build-pg/src/plugins/PgMutationUpdateDeletePlugin.ts similarity index 99% rename from packages/graphile-build-pg/src/plugins/PgMutationUpdateDeletePlugin.js rename to packages/graphile-build-pg/src/plugins/PgMutationUpdateDeletePlugin.ts index d163aa12c..6bb93373b 100644 --- a/packages/graphile-build-pg/src/plugins/PgMutationUpdateDeletePlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgMutationUpdateDeletePlugin.ts @@ -1,10 +1,9 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; import debugFactory from "debug"; const debug = debugFactory("graphile-build-pg"); -export default (async function PgMutationUpdateDeletePlugin( +export default async function PgMutationUpdateDeletePlugin( builder, { pgDisableDefaultMutations } ) { @@ -36,6 +35,7 @@ export default (async function PgMutationUpdateDeletePlugin( GraphQLObjectType, GraphQLID, }, + pgColumnFilter, inflection, pgQueryFromResolveData: queryFromResolveData, @@ -75,6 +75,7 @@ export default (async function PgMutationUpdateDeletePlugin( table.type.id, null ); + if (!TableType) { return memo; } @@ -109,6 +110,7 @@ export default (async function PgMutationUpdateDeletePlugin( input[ inflection.patchField(inflection.tableFieldName(table)) ]; + table.attributes.forEach(attr => { // PERFORMANCE: These used to be .filter(...) calls if (!pgColumnFilter(attr, build, context)) return; @@ -131,6 +133,7 @@ update ${sql.identifier(table.namespace.name, table.name)} set ${sql.join( sqlColumns.map( (col, i) => sql.fragment`${col} = ${sqlValues[i]}` ), + ", " )} where ${condition} @@ -152,6 +155,7 @@ returning *`; resolveContext, resolveInfo.rootValue ); + let row; try { await pgClient.query("SAVEPOINT graphql_mutation"); @@ -162,12 +166,14 @@ returning *`; modifiedRowAlias, query ); + row = rows[0]; await pgClient.query("RELEASE SAVEPOINT graphql_mutation"); } catch (e) { await pgClient.query( "ROLLBACK TO SAVEPOINT graphql_mutation" ); + throw e; } if (!row) { @@ -186,14 +192,17 @@ returning *`; const uniqueConstraints = table.constraints.filter( con => con.type === "u" || con.type === "p" ); + const Table = pgGetGqlTypeByTypeIdAndModifier( table.type.id, null ); + const tableTypeName = Table.name; const TablePatch = getTypeByName( inflection.patchType(Table.name) ); + const PayloadType = newWithHooks( GraphQLObjectType, { @@ -209,6 +218,7 @@ returning *`; const deletedNodeIdFieldName = inflection.deletedNodeId( table ); + return Object.assign( { clientMutationId: { @@ -216,6 +226,7 @@ returning *`; "The exact same `clientMutationId` that was provided in the mutation input, unchanged and unused. May be used by a client to track mutations.", type: GraphQLString, }, + [tableName]: pgField( build, fieldWithHooks, @@ -224,10 +235,12 @@ returning *`; description: `The \`${tableTypeName}\` that was ${mode}d by this mutation.`, type: Table, }, + {}, false ), }, + mode === "delete" ? { [deletedNodeIdFieldName]: fieldWithHooks( @@ -242,6 +255,7 @@ returning *`; fieldDataGeneratorsByTableType[ nodeIdFieldName ]; + if (gens) { gens.forEach(gen => addDataGenerator(gen)); } @@ -267,6 +281,7 @@ returning *`; ); }, }, + { __origin: `Adding table ${mode} mutation payload type for ${describePgEntity( table @@ -307,11 +322,13 @@ returning *`; "An arbitrary string value with no semantic meaning. Will be included in the payload verbatim. May be used to track mutations by the client.", type: GraphQLString, }, + [nodeIdFieldName]: { description: `The globally unique \`ID\` which will identify a single \`${tableTypeName}\` to be ${mode}d.`, type: new GraphQLNonNull(GraphQLID), }, }, + mode === "update" ? { [inflection.patchField( @@ -324,6 +341,7 @@ returning *`; : null ), }, + { __origin: `Adding table ${mode} (by node ID) mutation input type for ${describePgEntity( table @@ -363,6 +381,7 @@ returning *`; type: new GraphQLNonNull(InputType), }, }, + async resolve( parent, args, @@ -401,6 +420,7 @@ returning *`; key.typeModifier )}` ), + ") and (" )})`, context, @@ -422,6 +442,7 @@ returning *`; } ), }, + "Adding ${mode} mutation for ${describePgEntity(table)}" ); } @@ -460,6 +481,7 @@ returning *`; type: GraphQLString, }, }, + mode === "update" ? { [inflection.patchField( @@ -480,10 +502,12 @@ returning *`; ) ), }; + return memo; }, {}) ), }, + { __origin: `Adding table ${mode} mutation input type for ${describePgEntity( constraint @@ -524,6 +548,7 @@ returning *`; type: new GraphQLNonNull(InputType), }, }, + async resolve( parent, args, @@ -549,6 +574,7 @@ returning *`; key.typeModifier )}` ), + ") and (" )})`, context, @@ -567,6 +593,7 @@ returning *`; } ), }, + `Adding ${mode} mutation for ${describePgEntity( constraint )}` @@ -577,9 +604,10 @@ returning *`; }, outerMemo), {} ), + `Adding default update/delete mutations to root Mutation type` ); }, ["PgMutationUpdateDelete"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgNodeAliasPostGraphile.js b/packages/graphile-build-pg/src/plugins/PgNodeAliasPostGraphile.ts similarity index 80% rename from packages/graphile-build-pg/src/plugins/PgNodeAliasPostGraphile.js rename to packages/graphile-build-pg/src/plugins/PgNodeAliasPostGraphile.ts index adaacc140..e04aa8f8c 100644 --- a/packages/graphile-build-pg/src/plugins/PgNodeAliasPostGraphile.js +++ b/packages/graphile-build-pg/src/plugins/PgNodeAliasPostGraphile.ts @@ -1,7 +1,6 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; -export default (async function PgNodeAliasPostGraphile(builder) { +export default async function PgNodeAliasPostGraphile(builder) { builder.hook( "GraphQLObjectType", (object, build, context) => { @@ -23,4 +22,4 @@ export default (async function PgNodeAliasPostGraphile(builder) { }, ["PgNodeAliasPostGraphile"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgOrderAllColumnsPlugin.js b/packages/graphile-build-pg/src/plugins/PgOrderAllColumnsPlugin.ts similarity index 94% rename from packages/graphile-build-pg/src/plugins/PgOrderAllColumnsPlugin.js rename to packages/graphile-build-pg/src/plugins/PgOrderAllColumnsPlugin.ts index fd8c12bd8..38dab7d28 100644 --- a/packages/graphile-build-pg/src/plugins/PgOrderAllColumnsPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgOrderAllColumnsPlugin.ts @@ -1,7 +1,6 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; -export default (function PgOrderAllColumnsPlugin(builder) { +export default function PgOrderAllColumnsPlugin(builder) { builder.hook( "GraphQLEnumType:values", (values, build, context) => { @@ -40,6 +39,7 @@ export default (function PgOrderAllColumnsPlugin(builder) { }, }, }, + `Adding ascending orderBy enum value for ${describePgEntity( attr )}. You can rename this field with:\n\n ${sqlCommentByAddingTags( @@ -49,6 +49,7 @@ export default (function PgOrderAllColumnsPlugin(builder) { } )}` ); + memo = extend( memo, { @@ -60,6 +61,7 @@ export default (function PgOrderAllColumnsPlugin(builder) { }, }, }, + `Adding descending orderBy enum value for ${describePgEntity( attr )}. You can rename this field with:\n\n ${sqlCommentByAddingTags( @@ -69,6 +71,7 @@ export default (function PgOrderAllColumnsPlugin(builder) { } )}` ); + return memo; }, {}), `Adding order values from table '${table.name}'` @@ -76,4 +79,4 @@ export default (function PgOrderAllColumnsPlugin(builder) { }, ["PgOrderAllColumns"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgOrderByPrimaryKeyPlugin.js b/packages/graphile-build-pg/src/plugins/PgOrderByPrimaryKeyPlugin.ts similarity index 89% rename from packages/graphile-build-pg/src/plugins/PgOrderByPrimaryKeyPlugin.js rename to packages/graphile-build-pg/src/plugins/PgOrderByPrimaryKeyPlugin.ts index dc905fa00..8954694cf 100644 --- a/packages/graphile-build-pg/src/plugins/PgOrderByPrimaryKeyPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgOrderByPrimaryKeyPlugin.ts @@ -1,7 +1,6 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; -export default (function PgOrderByPrimaryKeyPlugin(builder) { +export default function PgOrderByPrimaryKeyPlugin(builder) { builder.hook( "GraphQLEnumType:values", (values, build, context) => { @@ -29,6 +28,7 @@ export default (function PgOrderByPrimaryKeyPlugin(builder) { unique: true, }, }, + PRIMARY_KEY_DESC: { value: { alias: "primary_key_desc", @@ -37,9 +37,10 @@ export default (function PgOrderByPrimaryKeyPlugin(builder) { }, }, }, + `Adding primary key asc/desc sort to table '${table.name}'` ); }, ["PgOrderByPrimaryKey"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgOrderComputedColumnsPlugin.js b/packages/graphile-build-pg/src/plugins/PgOrderComputedColumnsPlugin.ts similarity index 96% rename from packages/graphile-build-pg/src/plugins/PgOrderComputedColumnsPlugin.js rename to packages/graphile-build-pg/src/plugins/PgOrderComputedColumnsPlugin.ts index 7ba59ea0f..be1b80c92 100644 --- a/packages/graphile-build-pg/src/plugins/PgOrderComputedColumnsPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgOrderComputedColumnsPlugin.ts @@ -1,8 +1,7 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; import { getComputedColumnDetails } from "./PgComputedColumnsPlugin"; -export default (function PgOrderComputedColumnsPlugin(builder) { +export default function PgOrderComputedColumnsPlugin(builder) { builder.hook( "GraphQLEnumType:values", (values, build, context) => { @@ -36,6 +35,7 @@ export default (function PgOrderComputedColumnsPlugin(builder) { table, proc ); + if (!computedColumnDetails) return memo; const { pseudoColumnName } = computedColumnDetails; @@ -65,6 +65,7 @@ export default (function PgOrderComputedColumnsPlugin(builder) { }, [] ); + return extend( values, compatibleComputedColumns.reduce((memo, { proc, pseudoColumnName }) => { @@ -74,6 +75,7 @@ export default (function PgOrderComputedColumnsPlugin(builder) { table, true ); + const descFieldName = inflection.orderByComputedColumnEnum( pseudoColumnName, proc, @@ -100,10 +102,12 @@ export default (function PgOrderComputedColumnsPlugin(builder) { }, }, }, + `Adding ascending orderBy enum value for ${describePgEntity( proc )}. You can rename this field by removing the '@sortable' smart comment from the function.` ); + memo = extend( memo, { @@ -115,10 +119,12 @@ export default (function PgOrderComputedColumnsPlugin(builder) { }, }, }, + `Adding descending orderBy enum value for ${describePgEntity( proc )}. You can rename this field by removing the '@sortable' smart comment from the function.` ); + return memo; }, {}), `Adding order values from table '${table.name}'` @@ -126,4 +132,4 @@ export default (function PgOrderComputedColumnsPlugin(builder) { }, ["PgOrderComputedColumns"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgQueryProceduresPlugin.js b/packages/graphile-build-pg/src/plugins/PgQueryProceduresPlugin.ts similarity index 96% rename from packages/graphile-build-pg/src/plugins/PgQueryProceduresPlugin.js rename to packages/graphile-build-pg/src/plugins/PgQueryProceduresPlugin.ts index 907bd39ab..8a6ca12af 100644 --- a/packages/graphile-build-pg/src/plugins/PgQueryProceduresPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgQueryProceduresPlugin.ts @@ -1,7 +1,6 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; -export default (function PgQueryProceduresPlugin( +export default function PgQueryProceduresPlugin( builder, { pgSimpleCollections } ) { @@ -78,6 +77,7 @@ export default (function PgQueryProceduresPlugin( forceList, }), }, + `Adding query field for ${describePgEntity( proc )}. You can rename this field with:\n\n ${sqlCommentByAddingTags( @@ -109,4 +109,4 @@ export default (function PgQueryProceduresPlugin( }, ["PgQueryProcedures"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgRecordFunctionConnectionPlugin.js b/packages/graphile-build-pg/src/plugins/PgRecordFunctionConnectionPlugin.ts similarity index 98% rename from packages/graphile-build-pg/src/plugins/PgRecordFunctionConnectionPlugin.js rename to packages/graphile-build-pg/src/plugins/PgRecordFunctionConnectionPlugin.ts index 684c8093a..0a2a4fcab 100644 --- a/packages/graphile-build-pg/src/plugins/PgRecordFunctionConnectionPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgRecordFunctionConnectionPlugin.ts @@ -1,9 +1,8 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; const base64 = str => Buffer.from(String(str)).toString("base64"); -export default (function PgRecordFunctionConnectionPlugin( +export default function PgRecordFunctionConnectionPlugin( builder, { pgForbidSetofFunctionsToReturnNull = false } ) { @@ -44,6 +43,7 @@ export default (function PgRecordFunctionConnectionPlugin( const NodeType = getTypeByName( inflection.recordFunctionReturnType(proc) ); + if (!NodeType) { throw new Error( `Do not have a node type '${inflection.recordFunctionReturnType( @@ -64,6 +64,7 @@ export default (function PgRecordFunctionConnectionPlugin( addDataGenerator(() => ({ usesCursor: [true], })); + return { description: "A cursor for use in pagination.", type: Cursor, @@ -76,6 +77,7 @@ export default (function PgRecordFunctionConnectionPlugin( isCursorField: true, } ), + node: pgField( build, fieldWithHooks, @@ -88,19 +90,23 @@ export default (function PgRecordFunctionConnectionPlugin( !pgForbidSetofFunctionsToReturnNull, NodeType ), + resolve(data, _args, _context, resolveInfo) { const safeAlias = getSafeAliasFromResolveInfo( resolveInfo ); + return data[safeAlias]; }, }, + {}, false ), }; }, }, + { __origin: `Adding function result edge type for ${describePgEntity( proc @@ -133,11 +139,13 @@ export default (function PgRecordFunctionConnectionPlugin( nullableIf(!pgForbidSetofFunctionsToReturnNull, NodeType) ) ), + resolve(data, _args, _context, resolveInfo) { const safeAlias = getSafeAliasFromResolveInfo(resolveInfo); return data.data.map(entry => entry[safeAlias]); }, }), + edges: pgField( build, fieldWithHooks, @@ -149,16 +157,19 @@ export default (function PgRecordFunctionConnectionPlugin( type: new GraphQLNonNull( new GraphQLList(new GraphQLNonNull(EdgeType)) ), + resolve(data, _args, _context, resolveInfo) { const safeAlias = getSafeAliasFromResolveInfo( resolveInfo ); + return data.data.map(entry => ({ __cursor: entry.__cursor, ...entry[safeAlias], })); }, }, + {}, false, { @@ -168,6 +179,7 @@ export default (function PgRecordFunctionConnectionPlugin( }; }, }, + { __origin: `Adding function connection type for ${describePgEntity( proc @@ -188,4 +200,4 @@ export default (function PgRecordFunctionConnectionPlugin( }, ["PgRecordFunctionConnection"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgRecordReturnTypesPlugin.js b/packages/graphile-build-pg/src/plugins/PgRecordReturnTypesPlugin.ts similarity index 97% rename from packages/graphile-build-pg/src/plugins/PgRecordReturnTypesPlugin.js rename to packages/graphile-build-pg/src/plugins/PgRecordReturnTypesPlugin.ts index 44f4bb0c1..d1f437bc4 100644 --- a/packages/graphile-build-pg/src/plugins/PgRecordReturnTypesPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgRecordReturnTypesPlugin.ts @@ -1,7 +1,6 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; -export default (function PgRecordReturnTypesPlugin(builder) { +export default function PgRecordReturnTypesPlugin(builder) { builder.hook( "init", (_, build) => { @@ -43,8 +42,9 @@ export default (function PgRecordReturnTypesPlugin(builder) { const argModesWithOutput = [ "o", // OUT, "b", // INOUT - "t", // TABLE + "t", ]; + const outputArgNames = proc.argTypeIds.reduce((prev, _, idx) => { if (argModesWithOutput.includes(proc.argModes[idx])) { prev.push(proc.argNames[idx] || ""); @@ -87,10 +87,12 @@ export default (function PgRecordReturnTypesPlugin(builder) { outputArgName, idx + 1 ); + const fieldType = pgGetGqlTypeByTypeIdAndModifier( outputArgTypes[idx].id, null ); + if (memo[fieldName]) { throw new Error( `Tried to register field name '${fieldName}' twice in '${describePgEntity( @@ -106,6 +108,7 @@ export default (function PgRecordReturnTypesPlugin(builder) { const safeAlias = getSafeAliasFromAlias( parsedResolveInfoFragment.alias ); + return { pgQuery: queryBuilder => { queryBuilder.select( @@ -124,6 +127,7 @@ export default (function PgRecordReturnTypesPlugin(builder) { outputArgTypes[idx], null ), + safeAlias ); }, @@ -135,16 +139,19 @@ export default (function PgRecordReturnTypesPlugin(builder) { const safeAlias = getSafeAliasFromResolveInfo( resolveInfo ); + return data[safeAlias]; }, }; }, {} ); + return memo; }, {}); }, }, + { __origin: `Adding record return type for ${describePgEntity( proc @@ -168,4 +175,4 @@ export default (function PgRecordReturnTypesPlugin(builder) { }, ["PgRecordReturnTypes"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgRowByUniqueConstraint.js b/packages/graphile-build-pg/src/plugins/PgRowByUniqueConstraint.ts similarity index 98% rename from packages/graphile-build-pg/src/plugins/PgRowByUniqueConstraint.js rename to packages/graphile-build-pg/src/plugins/PgRowByUniqueConstraint.ts index 2d265c24d..8c26e6340 100644 --- a/packages/graphile-build-pg/src/plugins/PgRowByUniqueConstraint.js +++ b/packages/graphile-build-pg/src/plugins/PgRowByUniqueConstraint.ts @@ -1,8 +1,7 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; import debugSql from "./debugSql"; -export default (async function PgRowByUniqueConstraint( +export default async function PgRowByUniqueConstraint( builder, { subscriptions } ) { @@ -43,14 +42,17 @@ export default (async function PgRowByUniqueConstraint( table.type.id, null ); + const sqlFullTableName = sql.identifier( table.namespace.name, table.name ); + if (TableType) { const uniqueConstraints = table.constraints.filter( con => con.type === "u" || con.type === "p" ); + uniqueConstraints.forEach(constraint => { if (omit(constraint, "read")) { return; @@ -69,11 +71,13 @@ export default (async function PgRowByUniqueConstraint( table, constraint ); + const keysIncludingMeta = keys.map(key => ({ ...key, sqlIdentifier: sql.identifier(key.name), columnName: inflection.column(key), })); + // Precomputation for performance const queryFromResolveDataOptions = { useAsterisk: false, // Because it's only a single relation, no need @@ -107,6 +111,7 @@ export default (async function PgRowByUniqueConstraint( typeId, typeModifier ); + if (!InputType) { throw new Error( `Could not find input type for key '${name}' on type '${ @@ -117,10 +122,12 @@ export default (async function PgRowByUniqueConstraint( memo[columnName] = { type: new GraphQLNonNull(InputType), }; + return memo; }, {} ), + async resolve(parent, args, resolveContext, resolveInfo) { const { pgClient } = resolveContext; const liveRecord = @@ -129,11 +136,13 @@ export default (async function PgRowByUniqueConstraint( const parsedResolveInfoFragment = parseResolveInfo( resolveInfo ); + parsedResolveInfoFragment.args = args; // Allow overriding via makeWrapResolversPlugin const resolveData = getDataFromParsedResolveInfoFragment( parsedResolveInfoFragment, TableType ); + const query = queryFromResolveData( sqlFullTableName, undefined, @@ -144,6 +153,7 @@ export default (async function PgRowByUniqueConstraint( resolveContext, resolveInfo.rootValue ); + const { text, values } = sql.compile(query); if (debugSql.enabled) debugSql(text); const { @@ -170,4 +180,4 @@ export default (async function PgRowByUniqueConstraint( }, ["PgRowByUniqueConstraint"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgRowNode.js b/packages/graphile-build-pg/src/plugins/PgRowNode.ts similarity index 98% rename from packages/graphile-build-pg/src/plugins/PgRowNode.js rename to packages/graphile-build-pg/src/plugins/PgRowNode.ts index 4cee497e8..c9f5b3a16 100644 --- a/packages/graphile-build-pg/src/plugins/PgRowNode.js +++ b/packages/graphile-build-pg/src/plugins/PgRowNode.ts @@ -1,8 +1,7 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; import debugSql from "./debugSql"; -export default (async function PgRowNode(builder, { subscriptions }) { +export default async function PgRowNode(builder, { subscriptions }) { builder.hook( "GraphQLObjectType", (object, build, context) => { @@ -78,6 +77,7 @@ export default (async function PgRowNode(builder, { subscriptions }) { resolveContext, resolveInfo && resolveInfo.rootValue ); + const { text, values } = sql.compile(query); if (debugSql.enabled) debugSql(text); const { @@ -89,6 +89,7 @@ export default (async function PgRowNode(builder, { subscriptions }) { return row; } ); + return object; }, ["PgRowNode"] @@ -134,10 +135,12 @@ export default (async function PgRowNode(builder, { subscriptions }) { table.type.id, null ); + const sqlFullTableName = sql.identifier( table.namespace.name, table.name ); + if (TableType) { const primaryKeyConstraint = table.primaryKeyConstraint; if (!primaryKeyConstraint) { @@ -165,6 +168,7 @@ export default (async function PgRowNode(builder, { subscriptions }) { type: new GraphQLNonNull(GraphQLID), }, }, + async resolve(parent, args, resolveContext, resolveInfo) { const { pgClient } = resolveContext; const liveRecord = @@ -186,11 +190,13 @@ export default (async function PgRowNode(builder, { subscriptions }) { const parsedResolveInfoFragment = parseResolveInfo( resolveInfo ); + parsedResolveInfoFragment.args = args; // Allow overriding via makeWrapResolversPlugin const resolveData = getDataFromParsedResolveInfoFragment( parsedResolveInfoFragment, TableType ); + const query = queryFromResolveData( sqlFullTableName, undefined, @@ -217,6 +223,7 @@ export default (async function PgRowNode(builder, { subscriptions }) { resolveContext, resolveInfo.rootValue ); + const { text, values } = sql.compile(query); if (debugSql.enabled) debugSql(text); const { @@ -238,6 +245,7 @@ export default (async function PgRowNode(builder, { subscriptions }) { } ), }, + `Adding row by globally unique identifier field for ${describePgEntity( table )}. You can rename this table via:\n\n ${sqlCommentByAddingTags( @@ -253,4 +261,4 @@ export default (async function PgRowNode(builder, { subscriptions }) { }, ["PgRowNode"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgScalarFunctionConnectionPlugin.js b/packages/graphile-build-pg/src/plugins/PgScalarFunctionConnectionPlugin.ts similarity index 97% rename from packages/graphile-build-pg/src/plugins/PgScalarFunctionConnectionPlugin.js rename to packages/graphile-build-pg/src/plugins/PgScalarFunctionConnectionPlugin.ts index ad00ca704..d53b539e2 100644 --- a/packages/graphile-build-pg/src/plugins/PgScalarFunctionConnectionPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgScalarFunctionConnectionPlugin.ts @@ -1,9 +1,8 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; const base64 = str => Buffer.from(String(str)).toString("base64"); -export default (function PgScalarFunctionConnectionPlugin(builder) { +export default function PgScalarFunctionConnectionPlugin(builder) { builder.hook( "init", (_, build) => { @@ -18,6 +17,7 @@ export default (function PgScalarFunctionConnectionPlugin(builder) { GraphQLList, GraphQLString, }, + inflection, pgOmit: omit, describePgEntity, @@ -62,6 +62,7 @@ export default (function PgScalarFunctionConnectionPlugin(builder) { addDataGenerator(() => ({ usesCursor: [true], })); + return { description: "A cursor for use in pagination.", type: Cursor, @@ -74,6 +75,7 @@ export default (function PgScalarFunctionConnectionPlugin(builder) { isCursorField: true, } ), + node: { description: `The \`${ NodeType.name @@ -86,6 +88,7 @@ export default (function PgScalarFunctionConnectionPlugin(builder) { }; }, }, + { __origin: `Adding function result edge type for ${describePgEntity( proc @@ -118,6 +121,7 @@ export default (function PgScalarFunctionConnectionPlugin(builder) { return data.data.map(entry => entry.value); }, }), + edges: pgField( build, fieldWithHooks, @@ -129,10 +133,12 @@ export default (function PgScalarFunctionConnectionPlugin(builder) { type: new GraphQLNonNull( new GraphQLList(new GraphQLNonNull(EdgeType)) ), + resolve(data) { return data.data; }, }, + {}, false, { @@ -142,6 +148,7 @@ export default (function PgScalarFunctionConnectionPlugin(builder) { }; }, }, + { __origin: `Adding function connection type for ${describePgEntity( proc @@ -164,4 +171,4 @@ export default (function PgScalarFunctionConnectionPlugin(builder) { [], ["PgTypes"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgTablesPlugin.js b/packages/graphile-build-pg/src/plugins/PgTablesPlugin.ts similarity index 99% rename from packages/graphile-build-pg/src/plugins/PgTablesPlugin.js rename to packages/graphile-build-pg/src/plugins/PgTablesPlugin.ts index 891346110..29315f716 100644 --- a/packages/graphile-build-pg/src/plugins/PgTablesPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgTablesPlugin.ts @@ -1,5 +1,4 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; const base64 = str => Buffer.from(String(str)).toString("base64"); const hasNonNullKey = row => { @@ -19,7 +18,7 @@ const hasNonNullKey = row => { return false; }; -export default (function PgTablesPlugin( +export default function PgTablesPlugin( builder, { pgForbidSetofFunctionsToReturnNull = false, subscriptions = false } ) { @@ -57,6 +56,7 @@ export default (function PgTablesPlugin( GraphQLList, GraphQLInputObjectType, }, + inflection, describePgEntity, sqlCommentByAddingTags, @@ -164,6 +164,7 @@ export default (function PgTablesPlugin( return identifier; } ); + return getNodeIdForTypeAndIdentifiers( Self, ...finalIdentifiers @@ -174,6 +175,7 @@ export default (function PgTablesPlugin( return fields; }, }, + { __origin: `Adding table type for ${describePgEntity( table @@ -189,6 +191,7 @@ export default (function PgTablesPlugin( isPgCompositeType: !table.isSelectable, } ); + cb(TableType); const pgCreateInputFields = {}; const pgPatchInputFields = {}; @@ -199,6 +202,7 @@ export default (function PgTablesPlugin( description: `An input for mutations affecting \`${tableTypeName}\``, name: inflection.inputType(TableType), }, + { __origin: `Adding table input type for ${describePgEntity( table @@ -218,9 +222,11 @@ export default (function PgTablesPlugin( type: pgType, typeModifier, }; + return spec; }, }, + true // If no fields, skip type automatically ); @@ -236,6 +242,7 @@ export default (function PgTablesPlugin( description: `Represents an update to a \`${tableTypeName}\`. Fields that are set will be updated.`, name: inflection.patchType(TableType), }, + { __origin: `Adding table patch type for ${describePgEntity( table @@ -261,9 +268,11 @@ export default (function PgTablesPlugin( type: pgType, typeModifier, }; + return spec; }, }, + true // Safe to skip this if no fields support updating ); TableBaseInputType = newWithHooks( @@ -272,6 +281,7 @@ export default (function PgTablesPlugin( description: `An input representation of \`${tableTypeName}\` with nullable fields.`, name: inflection.baseInputType(TableType), }, + { __origin: `Adding table base input type for ${describePgEntity( table @@ -297,6 +307,7 @@ export default (function PgTablesPlugin( type: pgType, typeModifier, }; + return spec; }, } @@ -360,6 +371,7 @@ export default (function PgTablesPlugin( } }, })); + return { description: "A cursor for use in pagination.", type: Cursor, @@ -375,6 +387,7 @@ export default (function PgTablesPlugin( isCursorField: true, } ), + node: pgField( build, fieldWithHooks, @@ -385,14 +398,17 @@ export default (function PgTablesPlugin( !pgForbidSetofFunctionsToReturnNull, TableType ), + resolve(data, _args, resolveContext, resolveInfo) { const safeAlias = getSafeAliasFromResolveInfo( resolveInfo ); + const record = handleNullRow( data[safeAlias], data.__identifiers ); + const liveRecord = resolveInfo.rootValue && resolveInfo.rootValue.liveRecord; @@ -407,6 +423,7 @@ export default (function PgTablesPlugin( return record; }, }, + {}, false, { @@ -420,6 +437,7 @@ export default (function PgTablesPlugin( }; }, }, + { __origin: `Adding table edge type for ${describePgEntity( table @@ -435,6 +453,7 @@ export default (function PgTablesPlugin( pgIntrospection: table, } ); + const PageInfo = getTypeByName(inflection.builtin("PageInfo")); /*const ConnectionType = */ @@ -460,10 +479,12 @@ export default (function PgTablesPlugin( ) ) ), + resolve(data, _args, resolveContext, resolveInfo) { const safeAlias = getSafeAliasFromResolveInfo( resolveInfo ); + const liveRecord = resolveInfo.rootValue && resolveInfo.rootValue.liveRecord; @@ -472,6 +493,7 @@ export default (function PgTablesPlugin( entry[safeAlias], entry[safeAlias].__identifiers ); + if ( record && liveRecord && @@ -489,6 +511,7 @@ export default (function PgTablesPlugin( }); }, }, + {}, false, { @@ -499,6 +522,7 @@ export default (function PgTablesPlugin( }, } ), + edges: pgField( build, fieldWithHooks, @@ -508,22 +532,26 @@ export default (function PgTablesPlugin( type: new GraphQLNonNull( new GraphQLList(new GraphQLNonNull(EdgeType)) ), + resolve(data, _args, _context, resolveInfo) { const safeAlias = getSafeAliasFromResolveInfo( resolveInfo ); + return data.data.map(entry => ({ ...entry, ...entry[safeAlias], })); }, }, + {}, false, { hoistCursor: true, } ), + pageInfo: PageInfo && { description: "Information to aid in pagination.", type: new GraphQLNonNull(PageInfo), @@ -534,6 +562,7 @@ export default (function PgTablesPlugin( }; }, }, + { __origin: `Adding table connection type for ${describePgEntity( table @@ -553,6 +582,7 @@ export default (function PgTablesPlugin( }, true ); + pgRegisterGqlInputTypeByTypeId( tablePgType.id, (_set, modifier) => { @@ -561,6 +591,7 @@ export default (function PgTablesPlugin( tablePgType.id, null ); + // This must come after the pgGetGqlTypeByTypeIdAndModifier call if (modifier === "patch") { // TODO: v5: move the definition from above down here @@ -593,10 +624,12 @@ export default (function PgTablesPlugin( tablePgType.id, null ); + return new GraphQLList(TableType); }, true ); + pgRegisterGqlInputTypeByTypeId( arrayTablePgType.id, (_set, modifier) => { @@ -604,6 +637,7 @@ export default (function PgTablesPlugin( tablePgType.id, modifier ); + if (RelevantTableInputType) { return new GraphQLList(RelevantTableInputType); } @@ -618,4 +652,4 @@ export default (function PgTablesPlugin( [], ["PgTypes"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/PgTypesPlugin.js b/packages/graphile-build-pg/src/plugins/PgTypesPlugin.ts similarity index 98% rename from packages/graphile-build-pg/src/plugins/PgTypesPlugin.js rename to packages/graphile-build-pg/src/plugins/PgTypesPlugin.ts index bdec8b7e6..77a822e3e 100644 --- a/packages/graphile-build-pg/src/plugins/PgTypesPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgTypesPlugin.ts @@ -1,5 +1,4 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; import makeGraphQLJSONType from "../GraphQLJSON"; @@ -25,7 +24,7 @@ function parseInterval(str) { return result; } -export default (function PgTypesPlugin( +export default function PgTypesPlugin( builder, { pgExtendedTypes = true, @@ -86,9 +85,11 @@ export default (function PgTypesPlugin( const gqlTypeByTypeIdAndModifier = { ...build.pgGqlTypeByTypeIdAndModifier, }; + const gqlInputTypeByTypeIdAndModifier = { ...build.pgGqlInputTypeByTypeIdAndModifier, }; + const isNull = val => val == null || val.__isNull; const pg2GqlMapper = {}; const pg2gqlForType = type => { @@ -128,6 +129,7 @@ export default (function PgTypesPlugin( "gql2pg should be called with three arguments, the third being the type modifier (or `null`); " + (stack || "") ); + // Hack for backwards compatibility: modifier = null; } @@ -164,22 +166,27 @@ export default (function PgTypesPlugin( "A quantity of seconds. This is the only non-integer field, as all the other fields will dump their overflow into a smaller unit of time. Intervals don’t have a smaller unit than seconds.", type: GraphQLFloat, }, + minutes: { description: "A quantity of minutes.", type: GraphQLInt, }, + hours: { description: "A quantity of hours.", type: GraphQLInt, }, + days: { description: "A quantity of days.", type: GraphQLInt, }, + months: { description: "A quantity of months.", type: GraphQLInt, }, + years: { description: "A quantity of years.", type: GraphQLInt, @@ -194,10 +201,12 @@ export default (function PgTypesPlugin( "An interval of time that has passed where the smallest distinct unit is a second.", fields: makeIntervalFields(), }, + { isIntervalType: true, } ); + addType(GQLInterval, "graphile-build-pg built-in"); const GQLIntervalInput = newWithHooks( @@ -208,10 +217,12 @@ export default (function PgTypesPlugin( "An interval of time that has passed where the smallest distinct unit is a second.", fields: makeIntervalFields(), }, + { isIntervalInputType: true, } ); + addType(GQLIntervalInput, "graphile-build-pg built-in"); const stringType = (name, description) => @@ -232,10 +243,12 @@ export default (function PgTypesPlugin( inflection.builtin("BigFloat"), "A floating point number that requires more precision than IEEE 754 binary 64" ); + const BitString = stringType( inflection.builtin("BitString"), "A string representing a series of binary bits" ); + addType(BigFloat, "graphile-build-pg built-in"); addType(BitString, "graphile-build-pg built-in"); @@ -245,7 +258,7 @@ export default (function PgTypesPlugin( 1114, // timestamp 1184, // timestamptz 1083, // time - 1266, // timetz + 1266, ]; const tweakToJson = fragment => fragment; // Since everything is to_json'd now, just pass through @@ -299,6 +312,7 @@ export default (function PgTypesPlugin( type.namespaceName }.${type.name}")` ); + if (process.env.NODE_ENV === "test") { // This is to ensure that Graphile core does not introduce these problems throw error; @@ -311,32 +325,36 @@ export default (function PgTypesPlugin( } }; /* - Determined by running: - - select oid, typname, typarray, typcategory, typtype from pg_catalog.pg_type where typtype = 'b' order by oid; + Determined by running: + select oid, typname, typarray, typcategory, typtype from pg_catalog.pg_type where typtype = 'b' order by oid; + We only need to add oidLookups for types that don't have the correct fallback + */ - We only need to add oidLookups for types that don't have the correct fallback - */ const SimpleDate = stringType( inflection.builtin("Date"), "The day, does not include a time." ); + const SimpleDatetime = stringType( inflection.builtin("Datetime"), "A point in time as described by the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) standard. May or may not include a timezone." ); + const SimpleTime = stringType( inflection.builtin("Time"), "The exact time of day, does not include the date. May or may not have a timezone offset." ); + const SimpleJSON = stringType( inflection.builtin("JSON"), "A JavaScript object encoded in the JSON format as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf)." ); + const SimpleUUID = stringType( inflection.builtin("UUID"), "A universally unique identifier as defined by [RFC 4122](https://tools.ietf.org/html/rfc4122)." ); + const InetType = stringType( inflection.builtin("InternetAddress"), "An IPv4 or IPv6 host address, and optionally its subnet." @@ -360,15 +378,18 @@ export default (function PgTypesPlugin( x: { type: new GraphQLNonNull(GraphQLFloat), }, + y: { type: new GraphQLNonNull(GraphQLFloat), }, }, }, + { isPointType: true, } ); + const PointInput = newWithHooks( GraphQLInputObjectType, { @@ -377,11 +398,13 @@ export default (function PgTypesPlugin( x: { type: new GraphQLNonNull(GraphQLFloat), }, + y: { type: new GraphQLNonNull(GraphQLFloat), }, }, }, + { isPointInputType: true, } @@ -398,7 +421,8 @@ export default (function PgTypesPlugin( "20": stringType( inflection.builtin("BigInt"), "A signed eight-byte integer. The upper big integer values are greater than the max value for a JavaScript number. Therefore all big integers will be output as strings and not numbers." - ), // bitint - even though this is int8, it's too big for JS int, so cast to string. + ), + // bitint - even though this is int8, it's too big for JS int, so cast to string. "21": GraphQLInt, // int2 "23": GraphQLInt, // int4 "700": GraphQLFloat, // float4 @@ -428,6 +452,7 @@ export default (function PgTypesPlugin( "869": InetType, }; + const oidInputLookup = { "1186": GQLIntervalInput, // interval "600": PointInput, // point @@ -458,6 +483,7 @@ export default (function PgTypesPlugin( "months", "years", ]; + const parts = []; for (const key of keys) { if (o[key]) { @@ -510,6 +536,7 @@ export default (function PgTypesPlugin( type.namespaceName }.${type.name}' (type=${type.type}):\n${indent(e.message)}` ); + // $FlowFixMe error.originalError = e; throw error; @@ -559,9 +586,11 @@ export default (function PgTypesPlugin( memo[inflection.enumName(value)] = { value: value, }; + return memo; }, {}), }, + { pgIntrospection: type, isPgEnumType: true, @@ -579,6 +608,7 @@ export default (function PgTypesPlugin( subtype.id, typeModifier ); + if (!gqlRangeSubType) { throw new Error("Range of unsupported"); } @@ -596,6 +626,7 @@ export default (function PgTypesPlugin( description: "The value at one end of our range.", type: new GraphQLNonNull(gqlRangeSubType), }, + inclusive: { description: "Whether or not the value of this bound is included in the range.", @@ -603,6 +634,7 @@ export default (function PgTypesPlugin( }, }, }, + { isPgRangeBoundType: true, pgIntrospection: type, @@ -610,6 +642,7 @@ export default (function PgTypesPlugin( pgTypeModifier: typeModifier, } ); + const RangeBoundInput = newWithHooks( GraphQLInputObjectType, { @@ -621,6 +654,7 @@ export default (function PgTypesPlugin( description: "The value at one end of our range.", type: new GraphQLNonNull(gqlRangeSubType), }, + inclusive: { description: "Whether or not the value of this bound is included in the range.", @@ -628,6 +662,7 @@ export default (function PgTypesPlugin( }, }, }, + { isPgRangeBoundInputType: true, pgIntrospection: type, @@ -635,6 +670,7 @@ export default (function PgTypesPlugin( pgTypeModifier: typeModifier, } ); + Range = newWithHooks( GraphQLObjectType, { @@ -645,12 +681,14 @@ export default (function PgTypesPlugin( description: "The starting bound of our range.", type: RangeBound, }, + end: { description: "The ending bound of our range.", type: RangeBound, }, }, }, + { isPgRangeType: true, pgIntrospection: type, @@ -658,6 +696,7 @@ export default (function PgTypesPlugin( pgTypeModifier: typeModifier, } ); + RangeInput = newWithHooks( GraphQLInputObjectType, { @@ -668,12 +707,14 @@ export default (function PgTypesPlugin( description: "The starting bound of our range.", type: RangeBoundInput, }, + end: { description: "The ending bound of our range.", type: RangeBoundInput, }, }, }, + { isPgRangeInputType: true, pgIntrospection: type, @@ -681,6 +722,7 @@ export default (function PgTypesPlugin( pgTypeModifier: typeModifier, } ); + addType(Range, "graphile-build-pg built-in"); addType(RangeInput, "graphile-build-pg built-in"); } else { @@ -749,10 +791,12 @@ end`; type.domainBaseTypeId, typeModifier ); + const baseInputType = gqlInputTypeByTypeIdAndModifier[type.domainBaseTypeId][ typeModifierKey ]; + // Hack stolen from: https://github.com/graphile/postgraphile/blob/ade728ed8f8e3ecdc5fdad7d770c67aa573578eb/src/graphql/schema/type/aliasGqlType.ts#L16 gqlTypeByTypeIdAndModifier[type.id][typeModifierKey] = Object.assign( Object.create(baseType), @@ -761,6 +805,7 @@ end`; description: type.description, } ); + if (baseInputType && baseInputType !== baseType) { gqlInputTypeByTypeIdAndModifier[type.id][ typeModifierKey @@ -768,6 +813,7 @@ end`; name: inflection.inputType( gqlTypeByTypeIdAndModifier[type.id][typeModifierKey] ), + description: type.description, }); } @@ -782,6 +828,7 @@ end`; type.arrayItemTypeId, typeModifier ); + gqlTypeByTypeIdAndModifier[type.id][ typeModifierKey ] = new GraphQLList(arrayEntryOutputType); @@ -790,6 +837,7 @@ end`; type.arrayItemTypeId, typeModifier ); + if (arrayEntryInputType) { gqlInputTypeByTypeIdAndModifier[type.id][ typeModifierKey @@ -833,6 +881,7 @@ end`; addType( getNamedType(gqlTypeByTypeIdAndModifier[type.id][typeModifierKey]) ); + return gqlTypeByTypeIdAndModifier[type.id][typeModifierKey]; }; @@ -852,6 +901,7 @@ end`; const type = introspectionResultsByKind.type.find( t => t.id === typeId ); + if (!type) { throw new Error( `Type '${typeId}' not present in introspection results` @@ -1063,6 +1113,7 @@ end`; }, ["PgTypesHstore"] ); + builder.hook( "build", build => { @@ -1082,6 +1133,7 @@ end`; const hstoreExtension = introspectionResultsByKind.extension.find( e => e.name === "hstore" ); + if (!hstoreExtension) { return build; } @@ -1091,6 +1143,7 @@ end`; t => t.name === "hstore" && t.namespaceId === hstoreExtension.namespaceId ); + if (!hstoreType) { return build; } @@ -1124,8 +1177,9 @@ end`; [], ["PgTypes"] ); + /* End of hstore type */ -}: Plugin); +} as Plugin; function makeGraphQLHstoreType(graphql, hstoreTypeName) { const { GraphQLScalarType, Kind } = graphql; @@ -1232,6 +1286,7 @@ function makeGraphQLHstoreType(graphql, hstoreTypeName) { parseValue: identityWithCheck, parseLiteral, }); + return GraphQLHStore; } diff --git a/packages/graphile-build-pg/src/plugins/addStartEndCursor.js b/packages/graphile-build-pg/src/plugins/addStartEndCursor.ts similarity index 78% rename from packages/graphile-build-pg/src/plugins/addStartEndCursor.js rename to packages/graphile-build-pg/src/plugins/addStartEndCursor.ts index 60ccd6e4c..af0666405 100644 --- a/packages/graphile-build-pg/src/plugins/addStartEndCursor.js +++ b/packages/graphile-build-pg/src/plugins/addStartEndCursor.ts @@ -1,12 +1,11 @@ -// @flow -import type { Plugin } from "graphile-build"; +import { Plugin } from "graphile-build"; const base64 = str => Buffer.from(String(str)).toString("base64"); function cursorify(val) { return val && val.__cursor ? base64(JSON.stringify(val.__cursor)) : null; } -export default (function addStartEndCursor(value) { +export default function addStartEndCursor(value) { const data = value && value.data && value.data.length ? value.data : null; const startCursor = cursorify(data && data[0]); const endCursor = cursorify(data && data[value.data.length - 1]); @@ -15,4 +14,4 @@ export default (function addStartEndCursor(value) { startCursor, endCursor, }; -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build-pg/src/plugins/debugSql.js b/packages/graphile-build-pg/src/plugins/debugSql.ts similarity index 99% rename from packages/graphile-build-pg/src/plugins/debugSql.js rename to packages/graphile-build-pg/src/plugins/debugSql.ts index db667a59e..5da18feed 100644 --- a/packages/graphile-build-pg/src/plugins/debugSql.js +++ b/packages/graphile-build-pg/src/plugins/debugSql.ts @@ -13,6 +13,7 @@ export function formatSQLForDebugging(sql) { chalk.white, chalk.black, ]; + function nextColor() { colourIndex = (colourIndex + 1) % allowedColours.length; return allowedColours[colourIndex]; diff --git a/packages/graphile-build-pg/src/plugins/introspectionQuery.js b/packages/graphile-build-pg/src/plugins/introspectionQuery.ts similarity index 99% rename from packages/graphile-build-pg/src/plugins/introspectionQuery.js rename to packages/graphile-build-pg/src/plugins/introspectionQuery.ts index 93558d933..a7ad8a016 100644 --- a/packages/graphile-build-pg/src/plugins/introspectionQuery.js +++ b/packages/graphile-build-pg/src/plugins/introspectionQuery.ts @@ -1,4 +1,3 @@ -// @flow /* * IMPORTANT: when editing this file, ensure all operators (e.g. `@>`) are * specified in the correct namespace (e.g. `operator(pg_catalog.@>)`). It looks diff --git a/packages/graphile-build-pg/src/plugins/makeProcField.js b/packages/graphile-build-pg/src/plugins/makeProcField.ts similarity index 98% rename from packages/graphile-build-pg/src/plugins/makeProcField.js rename to packages/graphile-build-pg/src/plugins/makeProcField.ts index f1fb0a62b..0ffc2018f 100644 --- a/packages/graphile-build-pg/src/plugins/makeProcField.js +++ b/packages/graphile-build-pg/src/plugins/makeProcField.ts @@ -1,10 +1,9 @@ -// @flow const nullableIf = (GraphQLNonNull, condition, Type) => condition ? Type : new GraphQLNonNull(Type); -import type { Build, FieldWithHooksFunction } from "graphile-build"; -import type { PgProc } from "./PgIntrospectionPlugin"; -import type { SQL } from "pg-sql2"; +import { Build, FieldWithHooksFunction } from "graphile-build"; +import { PgProc } from "./PgIntrospectionPlugin"; +import { SQL } from "pg-sql2"; import debugSql from "./debugSql"; import chalk from "chalk"; @@ -21,17 +20,17 @@ const firstValue = obj => { export default function makeProcField( fieldName: string, proc: PgProc, - build: {| ...Build |}, + build: Build, { fieldWithHooks, computed = false, isMutation = false, forceList = false, }: { - fieldWithHooks: FieldWithHooksFunction, - computed?: boolean, - isMutation?: boolean, - forceList?: boolean, + fieldWithHooks: FieldWithHooksFunction; + computed?: boolean; + isMutation?: boolean; + forceList?: boolean; } ) { const { @@ -57,6 +56,7 @@ export default function makeProcField( getNamedType, isCompositeType, }, + inflection, pgQueryFromResolveData: queryFromResolveData, pgAddStartEndCursor: addStartEndCursor, @@ -68,6 +68,7 @@ export default function makeProcField( subscriptions = false, pgForbidSetofFunctionsToReturnNull = false, }, + pgPrepareAndRun, } = build; @@ -100,8 +101,9 @@ export default function makeProcField( const argModesWithOutput = [ "o", // OUT, "b", // INOUT - "t", // TABLE + "t", ]; + const outputArgNames = proc.argTypeIds.reduce((prev, _, idx) => { if (argModesWithOutput.includes(proc.argModes[idx])) { prev.push(proc.argNames[idx] || ""); @@ -202,6 +204,7 @@ export default function makeProcField( const ConnectionType = getTypeByName( inflection.connection(TableType.name) ); + if (!ConnectionType) { throw new Error( `Do not have a connection type '${inflection.connection( @@ -242,6 +245,7 @@ export default function makeProcField( const ConnectionType = getTypeByName( inflection.recordFunctionConnection(proc) ); + if (!ConnectionType) { throw new Error( `Do not have a connection type '${inflection.recordFunctionConnection( @@ -365,6 +369,7 @@ export default function makeProcField( parsedResolveInfoFragment, ReturnType ); + const isConnection = !forceList && !isMutation && proc.returnsSet; const query = queryFromResolveData( sqlMutationQuery, @@ -389,6 +394,7 @@ export default function makeProcField( !rawReturnType.isPgArray && (isTableLike || isRecordLike), }, + innerQueryBuilder => { innerQueryBuilder.parentQueryBuilder = parentQueryBuilder; if (!isTableLike) { @@ -400,6 +406,7 @@ export default function makeProcField( null, resolveData ), + "value" ); } else { @@ -410,6 +417,7 @@ export default function makeProcField( null, // We can't determine a type modifier for functions resolveData ), + "value" ); } @@ -427,6 +435,7 @@ export default function makeProcField( ? parentQueryBuilder.rootValue : resolveInfo && resolveInfo.rootValue ); + return query; } if (computed) { @@ -443,6 +452,7 @@ export default function makeProcField( implicitArgs: [parentTableAlias], } ); + const query = makeQuery( parsedResolveInfoFragment, ReturnType, @@ -450,6 +460,7 @@ export default function makeProcField( functionAlias, queryBuilder ); + return sql.fragment`(${query})`; }, getSafeAliasFromAlias(parsedResolveInfoFragment.alias)); }, @@ -464,6 +475,7 @@ export default function makeProcField( memo[gqlArgName] = { type: argGqlTypes[argIndex], }; + return memo; }, {}); if (isMutation) { @@ -473,6 +485,7 @@ export default function makeProcField( proc.returnsSet || rawReturnType.isPgArray, outputArgNames ); + const isNotVoid = String(returnType.id) !== "2278"; // If set then plural name PayloadType = newWithHooks( @@ -490,6 +503,7 @@ export default function makeProcField( type: GraphQLString, }, }, + isNotVoid ? { [resultFieldName]: pgField( @@ -506,18 +520,21 @@ export default function makeProcField( } : null), }, + {}, false, { pgType: returnType, } ), + // Result } : null ); }, }, + { __origin: `Adding mutation function payload type for ${describePgEntity( proc @@ -531,6 +548,7 @@ export default function makeProcField( ...payloadTypeScope, } ); + ReturnType = PayloadType; const InputType = newWithHooks( GraphQLInputObjectType, @@ -543,9 +561,11 @@ export default function makeProcField( clientMutationId: { type: GraphQLString, }, + ...args, }, }, + { __origin: `Adding mutation function input type for ${describePgEntity( proc @@ -558,6 +578,7 @@ export default function makeProcField( isMutationInput: true, } ); + args = { input: { type: new GraphQLNonNull(InputType), @@ -664,6 +685,7 @@ export default function makeProcField( resolveContext, resolveInfo ); + const intermediateIdentifier = sql.identifier(Symbol()); const isVoid = returnType.id === "2278"; const isPgRecord = returnType.id === "2249"; @@ -680,6 +702,7 @@ export default function makeProcField( returnType.namespaceName, returnType.name ), + sql.query`select ${ isPgClass ? sql.query`${intermediateIdentifier}.*` @@ -697,11 +720,13 @@ export default function makeProcField( } : null ); + await pgClient.query("RELEASE SAVEPOINT graphql_mutation"); } catch (e) { await pgClient.query( "ROLLBACK TO SAVEPOINT graphql_mutation" ); + throw e; } } else { @@ -714,6 +739,7 @@ export default function makeProcField( resolveContext, resolveInfo ); + const { text, values } = sql.compile(query); if (debugSql.enabled) debugSql(text); const queryResult = await pgPrepareAndRun( @@ -721,6 +747,7 @@ export default function makeProcField( text, values ); + queryResultRows = queryResult.rows; } const rows = queryResultRows; diff --git a/packages/graphile-build-pg/src/plugins/pgField.js b/packages/graphile-build-pg/src/plugins/pgField.ts similarity index 99% rename from packages/graphile-build-pg/src/plugins/pgField.js rename to packages/graphile-build-pg/src/plugins/pgField.ts index d4b2e5a17..f3b4f4f9a 100644 --- a/packages/graphile-build-pg/src/plugins/pgField.js +++ b/packages/graphile-build-pg/src/plugins/pgField.ts @@ -42,10 +42,12 @@ export default function pgField( const safeAlias = getSafeAliasFromAlias( parsedResolveInfoFragment.alias ); + const resolveData = getDataFromParsedResolveInfoFragment( parsedResolveInfoFragment, FieldType ); + return { ...(options.hoistCursor && resolveData.usesCursor && @@ -83,6 +85,7 @@ export default function pgField( queryBuilder.context, queryBuilder.rootValue ); + return sql.fragment`(${query})`; }, safeAlias); }, diff --git a/packages/graphile-build-pg/src/plugins/viaTemporaryTable.js b/packages/graphile-build-pg/src/plugins/viaTemporaryTable.ts similarity index 95% rename from packages/graphile-build-pg/src/plugins/viaTemporaryTable.js rename to packages/graphile-build-pg/src/plugins/viaTemporaryTable.ts index 456e8316e..738f805b4 100644 --- a/packages/graphile-build-pg/src/plugins/viaTemporaryTable.js +++ b/packages/graphile-build-pg/src/plugins/viaTemporaryTable.ts @@ -1,8 +1,6 @@ -// @flow - import * as sql from "pg-sql2"; -import type { Client } from "pg"; -import type { SQL, SQLQuery } from "pg-sql2"; +import { Client } from "pg"; +import { SQL, SQLQuery } from "pg-sql2"; import debugSql from "./debugSql"; /* @@ -37,16 +35,19 @@ import debugSql from "./debugSql"; export default async function viaTemporaryTable( pgClient: Client, - sqlTypeIdentifier: ?SQL, + sqlTypeIdentifier: SQL | null | undefined, sqlMutationQuery: SQL, sqlResultSourceAlias: SQL, sqlResultQuery: SQL, isPgClassLike: boolean = true, - pgRecordInfo: ?{ - // eslint-disable-next-line flowtype/no-weak-types - outputArgTypes: Array, - outputArgNames: Array, - } = undefined + pgRecordInfo: + | { + // eslint-disable-next-line flowtype/no-weak-types + outputArgTypes: Array; + outputArgNames: Array; + } + | null + | undefined = undefined ) { const isPgRecord = pgRecordInfo != null; const { outputArgTypes, outputArgNames } = pgRecordInfo || {}; @@ -64,6 +65,7 @@ export default async function viaTemporaryTable( pgClient, sql.query`with ${sqlResultSourceAlias} as (${sqlMutationQuery}) ${sqlResultQuery}` ); + return rows; } else { /* @@ -97,6 +99,7 @@ export default async function viaTemporaryTable( outputArgName !== "" ? outputArgName : `column${idx + 1}` )}::text` ), + " ," )}]` : sql.query`(${sqlResultSourceAlias}.${sqlResultSourceAlias})::${sqlTypeIdentifier}`; @@ -104,6 +107,7 @@ export default async function viaTemporaryTable( pgClient, sql.query`with ${sqlResultSourceAlias} as (${sqlMutationQuery}) select (${selectionField})::text from ${sqlResultSourceAlias}` ); + const { rows } = result; const firstNonNullRow = rows.find(row => row !== null); // TODO: we should be able to have `pg` not interpret the results as @@ -134,6 +138,7 @@ select ${sql.join( outputArgName !== "" ? outputArgName : `column${idx + 1}` )}` ), + ", " )} from (values ${sql.join( @@ -159,6 +164,7 @@ from unnest((${sql.value(values)})::text[]) str`; */ rawValue === null ? { __isNull: true } : filteredValuesResults.shift() ); + return finalRows; } } diff --git a/packages/graphile-build-pg/src/queryFromResolveDataFactory.js b/packages/graphile-build-pg/src/queryFromResolveDataFactory.ts similarity index 96% rename from packages/graphile-build-pg/src/queryFromResolveDataFactory.js rename to packages/graphile-build-pg/src/queryFromResolveDataFactory.ts index ebd3d8f4d..bd4dce40a 100644 --- a/packages/graphile-build-pg/src/queryFromResolveDataFactory.js +++ b/packages/graphile-build-pg/src/queryFromResolveDataFactory.ts @@ -1,10 +1,9 @@ -// @flow import QueryBuilder from "./QueryBuilder"; -import type QueryBuilderOptions from "./QueryBuilder"; -import type { RawAlias } from "./QueryBuilder"; +import QueryBuilderOptions from "./QueryBuilder"; +import { RawAlias } from "./QueryBuilder"; import * as sql from "pg-sql2"; -import type { SQL } from "pg-sql2"; -import type { DataForType } from "graphile-build"; +import { SQL } from "pg-sql2"; +import { DataForType } from "graphile-build"; import isSafeInteger from "lodash/isSafeInteger"; import assert from "assert"; @@ -16,20 +15,20 @@ const identity = _ => _ !== null && _ !== undefined; // $FlowFixMe export default (queryBuilderOptions: QueryBuilderOptions = {}) => ( from: SQL, - fromAlias: ?SQL, + fromAlias: SQL | null | undefined, resolveData: DataForType, inOptions: { - withPagination?: boolean, - withPaginationAsFields?: boolean, - asJson?: boolean, - asJsonAggregate?: boolean, - addNullCase?: boolean, - addNotDistinctFromNullCase?: boolean, - onlyJsonField?: boolean, - useAsterisk?: boolean, + withPagination?: boolean; + withPaginationAsFields?: boolean; + asJson?: boolean; + asJsonAggregate?: boolean; + addNullCase?: boolean; + addNotDistinctFromNullCase?: boolean; + onlyJsonField?: boolean; + useAsterisk?: boolean; }, - // TODO:v5: context is not optional - withBuilder?: ((builder: QueryBuilder) => void) | null | void, + + withBuilder?: ((builder: QueryBuilder) => undefined) | null | undefined, context?: GraphQLContext = {}, rootValue?: any // eslint-disable-line flowtype/no-weak-types ) => { @@ -67,6 +66,7 @@ export default (queryBuilderOptions: QueryBuilderOptions = {}) => ( context, rootValue ); + queryBuilder.from(from, fromAlias ? fromAlias : undefined); if (withBuilder) { @@ -293,6 +293,7 @@ exists( ...getPgCursorPrefix(), sql.fragment`json_build_array(${sql.join(orderBy, ", ")})`, ], + ", " )})`; } else { @@ -409,6 +410,7 @@ OR\ queryHasFirst, queryBuilder.getFinalOffset() || 0 ); + const hasPreviousPage = queryHasZeroLimit ? sql.literal(false) : generateNextPrevPageSql( @@ -430,6 +432,7 @@ OR\ sql.fragment`coalesce((select ${sqlSummaryAlias}.data from ${sqlSummaryAlias}), '[]'::json)`, "data", ]); + if (calculateHasNextPage) { fields.push([hasNextPage, "hasNextPage"]); } @@ -444,6 +447,7 @@ OR\ context, rootValue ); + aggregateQueryBuilder.from( queryBuilder.getTableExpression(), queryBuilder.getTableAlias() @@ -455,6 +459,7 @@ OR\ const aggregateJsonBuildObject = aggregateQueryBuilder.build({ onlyJsonField: true, }); + const aggregatesSql = sql.fragment`\ ( select ${aggregateJsonBuildObject} @@ -468,6 +473,7 @@ OR\ fields.map( ([expr, alias]) => sql.fragment`${expr} as ${sql.identifier(alias)}` ), + ", " )} ${sqlFrom}`; } else { diff --git a/packages/graphile-build-pg/src/utils.js b/packages/graphile-build-pg/src/utils.ts similarity index 98% rename from packages/graphile-build-pg/src/utils.js rename to packages/graphile-build-pg/src/utils.ts index 5cdd16d00..e545331cd 100644 --- a/packages/graphile-build-pg/src/utils.js +++ b/packages/graphile-build-pg/src/utils.ts @@ -1,4 +1,3 @@ -// @flow export const parseTags = (str: string) => { return str.split(/\r?\n/).reduce( (prev, curr) => { diff --git a/packages/graphile-build-pg/src/withPgClient.js b/packages/graphile-build-pg/src/withPgClient.ts similarity index 91% rename from packages/graphile-build-pg/src/withPgClient.js rename to packages/graphile-build-pg/src/withPgClient.ts index 397b6fede..29d8b442c 100644 --- a/packages/graphile-build-pg/src/withPgClient.js +++ b/packages/graphile-build-pg/src/withPgClient.ts @@ -1,4 +1,3 @@ -// @flow import pg from "pg"; import debugFactory from "debug"; @@ -10,7 +9,7 @@ function constructorName(obj) { // Some duck-typing -function quacksLikePgClient(pgConfig: mixed): boolean { +function quacksLikePgClient(pgConfig: unknown): boolean { // A diagnosis of exclusion if (!pgConfig || typeof pgConfig !== "object") return false; if (constructorName(pgConfig) !== "Client") return false; @@ -21,7 +20,7 @@ function quacksLikePgClient(pgConfig: mixed): boolean { return true; } -export function quacksLikePgPool(pgConfig: mixed): boolean { +export function quacksLikePgPool(pgConfig: unknown): boolean { // A diagnosis of exclusion if (!pgConfig || typeof pgConfig !== "object") return false; if ( @@ -44,14 +43,14 @@ export const getPgClientAndReleaserFromConfig = async ( let releasePgClient = () => {}; let pgClient: pg.Client; if (pgConfig instanceof pg.Client || quacksLikePgClient(pgConfig)) { - pgClient = (pgConfig: pg.Client); + pgClient = pgConfig as pg.Client; if (!pgClient.release) { throw new Error( "We only support PG clients from a PG pool (because otherwise the `await` call can hang indefinitely if an error occurs and there's no error handler)" ); } } else if (pgConfig instanceof pg.Pool || quacksLikePgPool(pgConfig)) { - const pgPool = (pgConfig: pg.Pool); + const pgPool = pgConfig as pg.Pool; pgClient = await pgPool.connect(); releasePgClient = () => pgClient.release(); } else if (pgConfig === undefined || typeof pgConfig === "string") { @@ -63,6 +62,7 @@ export const getPgClientAndReleaserFromConfig = async ( new Promise((resolve, reject) => pgClient.end(err => (err ? reject(err) : resolve())) ); + await new Promise((resolve, reject) => pgClient.connect(err => (err ? reject(err) : resolve())) ); @@ -75,8 +75,8 @@ export const getPgClientAndReleaserFromConfig = async ( }; const withPgClient = async ( - pgConfig: pg.Client | pg.Pool | string | void, - fn: (pgClient: pg.Client) => * + pgConfig: pg.Client | pg.Pool | string | undefined, + fn: (pgClient: pg.Client) => any ) => { if (!fn) { throw new Error("Nothing to do!"); @@ -84,6 +84,7 @@ const withPgClient = async ( const { pgClient, releasePgClient } = await getPgClientAndReleaserFromConfig( pgConfig ); + const errorHandler = e => { // eslint-disable-next-line no-console console.error("withPgClient client error:", e.message); diff --git a/packages/graphile-build/src/Live.d.ts b/packages/graphile-build/src/Live.d.ts index 6bc97edb5..8495aa4f4 100644 --- a/packages/graphile-build/src/Live.d.ts +++ b/packages/graphile-build/src/Live.d.ts @@ -41,12 +41,15 @@ export abstract class LiveProvider { export class LiveMonitor { providers: { [namespace: string]: LiveProvider }; subscriptionReleasersByCounter: { - [counter: string]: (() => void)[], + [counter: string]: (() => void)[]; }; liveConditionsByCounter: { [counter: string]: Array }; changeCounter: number; - constructor(providers: { [namespace: string]: LiveProvider }, extraRootValue: any); + constructor( + providers: { [namespace: string]: LiveProvider }, + extraRootValue: any + ); resetBefore(currentCounter: number): void; release(): void; diff --git a/packages/graphile-build/src/Live.js b/packages/graphile-build/src/Live.ts similarity index 96% rename from packages/graphile-build/src/Live.js rename to packages/graphile-build/src/Live.ts index 9f6f93ed2..dfe93a8db 100644 --- a/packages/graphile-build/src/Live.js +++ b/packages/graphile-build/src/Live.ts @@ -1,11 +1,10 @@ -// @flow /* eslint-disable flowtype/no-weak-types */ import callbackToAsyncIterator from "./callbackToAsyncIterator"; -import type { GraphQLResolveInfo } from "graphql"; +import { GraphQLResolveInfo } from "graphql"; import { throttle } from "lodash"; -type SubscriptionReleaser = () => void; -type SubscriptionCallback = () => void; +type SubscriptionReleaser = () => undefined; +type SubscriptionCallback = () => undefined; type Predicate = (record: any) => boolean; type PredicateGenerator = (data: any) => Predicate; @@ -78,10 +77,11 @@ export class LiveMonitor { released: boolean; providers: { [namespace: string]: LiveProvider }; subscriptionReleasersByCounter: { - [counter: string]: (() => void)[], + [counter: string]: () => undefined[]; }; + liveConditionsByCounter: { [counter: string]: Array }; - changeCallback: ((arg: any) => void) | null; + changeCallback: ((arg: any) => undefined) | null; changeCounter: number; extraRootValue: any; @@ -107,6 +107,7 @@ export class LiveMonitor { trailing: true, } ); + if (!this._reallyHandleChange) { throw new Error("This is just to make flow happy"); } @@ -118,6 +119,7 @@ export class LiveMonitor { trailing: true, } ); + this.onChange = this.onChange.bind(this); } @@ -141,6 +143,7 @@ export class LiveMonitor { const oldCounters = Object.keys(this.liveConditionsByCounter).filter( n => parseInt(n, 10) < currentCounter ); + for (const oldCounter of oldCounters) { delete this.liveConditionsByCounter[oldCounter]; } @@ -162,7 +165,7 @@ export class LiveMonitor { } // Tell Flow that we're okay with overwriting this - handleChange: (() => void) | null; + handleChange: (() => undefined) | null; handleChange() { /* This function is throttled to ~25ms (see constructor); it's purpose is * to bundle up all the changes that occur in a small window into the same @@ -177,7 +180,7 @@ export class LiveMonitor { } // Tell Flow that we're okay with overwriting this - _reallyHandleChange: (() => void) | null; + _reallyHandleChange: (() => undefined) | null; _reallyHandleChange() { // This function is throttled to MONITOR_THROTTLE_DURATION (see constructor) if (this.changeCallback) { @@ -204,6 +207,7 @@ export class LiveMonitor { this.resetBefore(counter); }, }; + cb(changeRootValue); } else { // eslint-disable-next-line no-console @@ -212,8 +216,8 @@ export class LiveMonitor { } // Tell Flow that we're okay with overwriting this - onChange: (callback: () => void) => void; - onChange(callback: () => void) { + onChange: (callback: () => undefined) => undefined; + onChange(callback: () => undefined) { if (this.released) { throw new Error("Monitors cannot be reused."); } @@ -256,6 +260,7 @@ export class LiveMonitor { collectionIdentifier, predicate ); + if (releaser) { this.subscriptionReleasersByCounter[String(counter)].push(releaser); } @@ -293,6 +298,7 @@ export class LiveMonitor { collectionIdentifier, recordIdentifier ); + if (releaser) { this.subscriptionReleasersByCounter[String(counter)].push(releaser); } @@ -327,6 +333,7 @@ export class LiveCoordinator { console.warn( `LiveProvider '${namespace}' is not registered, skipping live source.` ); + return; } this.providers[namespace].registerSource(source); @@ -354,6 +361,7 @@ export class LiveCoordinator { if (iterator) iterator.throw(e); }, }); + const iterator = makeAsyncIteratorFromMonitor(monitor); return iterator; } diff --git a/packages/graphile-build/src/SchemaBuilder.js b/packages/graphile-build/src/SchemaBuilder.ts similarity index 84% rename from packages/graphile-build/src/SchemaBuilder.js rename to packages/graphile-build/src/SchemaBuilder.ts index b38dd9bdf..e7aaee07c 100644 --- a/packages/graphile-build/src/SchemaBuilder.js +++ b/packages/graphile-build/src/SchemaBuilder.ts @@ -1,9 +1,8 @@ -// @flow import debugFactory from "debug"; import makeNewBuild from "./makeNewBuild"; import { bindAll } from "./utils"; import * as graphql from "graphql"; -import type { +import { GraphQLType, GraphQLNamedType, GraphQLInterfaceType, @@ -12,14 +11,14 @@ import type { import EventEmitter from "events"; // TODO: when we move to TypeScript, change this to: // import { EventEmitter } from "events"; -import type { +import { simplifyParsedResolveInfoFragmentWithType, parseResolveInfo, } from "graphql-parse-resolve-info"; -import type { GraphQLResolveInfo } from "graphql/type/definition"; -import type resolveNode from "./resolveNode"; +import { GraphQLResolveInfo } from "graphql/type/definition"; +import resolveNode from "./resolveNode"; -import type { FieldWithHooksFunction } from "./makeNewBuild"; +import { FieldWithHooksFunction } from "./makeNewBuild"; const { GraphQLSchema } = graphql; const debug = debugFactory("graphile-builder"); @@ -27,123 +26,130 @@ const debug = debugFactory("graphile-builder"); const INDENT = " "; export type Options = { - [string]: mixed, + [a: string]: unknown; }; export type Plugin = ( builder: SchemaBuilder, options: Options -) => Promise | void; +) => Promise | undefined; -type TriggerChangeType = () => void; +type TriggerChangeType = () => undefined; export type DataForType = { - [string]: Array, + [a: string]: Array; }; -export type Build = {| - graphileBuildVersion: string, - graphql: *, - parseResolveInfo: parseResolveInfo, - simplifyParsedResolveInfoFragmentWithType: simplifyParsedResolveInfoFragmentWithType, +export type Build = { + graphileBuildVersion: string; + graphql: any; + parseResolveInfo: parseResolveInfo; + simplifyParsedResolveInfoFragmentWithType: simplifyParsedResolveInfoFragmentWithType; // DEPRECATED: getAliasFromResolveInfo: (resolveInfo: GraphQLResolveInfo) => string, - getSafeAliasFromResolveInfo: (resolveInfo: GraphQLResolveInfo) => string, - getSafeAliasFromAlias: (alias: string) => string, - resolveAlias( + getSafeAliasFromResolveInfo: (resolveInfo: GraphQLResolveInfo) => string; + getSafeAliasFromAlias: (alias: string) => string; + resolveAlias: ( data: {}, - _args: mixed, - _context: mixed, + _args: unknown, + _context: unknown, resolveInfo: GraphQLResolveInfo - ): string, - addType(type: GraphQLNamedType, origin?: ?string): void, - getTypeByName(typeName: string): ?GraphQLType, - extend(base: Obj1, extra: Obj2, hint?: string): Obj1 & Obj2, - newWithHooks( - Class, + ) => string; + addType: ( + type: GraphQLNamedType, + origin: string | null | undefined + ) => undefined; + getTypeByName: (typeName: string) => GraphQLType | null | undefined; + extend: ( + base: Obj1, + extra: Obj2, + hint: string + ) => Obj1 & Obj2; + newWithHooks: < + T extends GraphQLNamedType | GraphQLSchema, + ConfigType extends any + >( + a: Class, spec: ConfigType, scope: Scope, - performNonEmptyFieldsCheck?: boolean - ): ?T, - fieldDataGeneratorsByType: Map<*, *>, // @deprecated - use fieldDataGeneratorsByFieldNameByType instead - fieldDataGeneratorsByFieldNameByType: Map<*, *>, - fieldArgDataGeneratorsByFieldNameByType: Map<*, *>, + performNonEmptyFieldsCheck: boolean + ) => T | null | undefined; + fieldDataGeneratorsByType: Map; // @deprecated - use fieldDataGeneratorsByFieldNameByType instead + fieldDataGeneratorsByFieldNameByType: Map; + fieldArgDataGeneratorsByFieldNameByType: Map; inflection: { // eslint-disable-next-line flowtype/no-weak-types - [string]: (...args: Array) => string, - }, - swallowError: (e: Error) => void, + [a: string]: (...args: Array) => string; + }; + + swallowError: (e: Error) => undefined; // resolveNode: EXPERIMENTAL, API might change! - resolveNode: resolveNode, + resolveNode: resolveNode; status: { - currentHookName: ?string, - currentHookEvent: ?string, - }, - scopeByType: Map, -|}; + currentHookName: string | null | undefined; + currentHookEvent: string | null | undefined; + }; + + scopeByType: Map; +}; -export type BuildExtensionQuery = {| - $$isQuery: Symbol, -|}; +export type BuildExtensionQuery = { + $$isQuery: Symbol; +}; export type Scope = { - __origin: ?string, - [string]: mixed, + __origin: string | null | undefined; + [a: string]: unknown; }; -export type Context = {| - scope: Scope, - type: string, - [string]: mixed, -|}; +export type Context = { + scope: Scope; + type: string; + [a: string]: unknown; +}; type DataGeneratorFunction = () => {}; -export type ContextGraphQLObjectTypeFields = {| +export type ContextGraphQLObjectTypeFields = { addDataGeneratorForField: ( fieldName: string, fn: DataGeneratorFunction - ) => void, - recurseDataGeneratorsForField: (fieldName: string) => void, // @deprecated - DO NOT USE! - Self: GraphQLNamedType, - GraphQLObjectType: GraphQLObjectTypeConfig<*, *>, - fieldWithHooks: FieldWithHooksFunction, -|}; + ) => undefined; + recurseDataGeneratorsForField: (fieldName: string) => undefined; // @deprecated - DO NOT USE! + Self: GraphQLNamedType; + GraphQLObjectType: GraphQLObjectTypeConfig; + fieldWithHooks: FieldWithHooksFunction; +}; type SupportedHookTypes = {} | Build | Array; export type Hook< - Type: SupportedHookTypes, - BuildExtensions: *, - ContextExtensions: * + Type extends SupportedHookTypes, + BuildExtensions extends any, + ContextExtensions extends any > = { - ( - input: Type, - build: { ...Build, ...BuildExtensions }, - context: { ...Context, ...ContextExtensions } - ): Type, - displayName?: string, - provides?: Array, - before?: Array, - after?: Array, + displayName?: string; + provides?: Array; + before?: Array; + after?: Array; }; -export type WatchUnwatch = (triggerChange: TriggerChangeType) => void; +export type WatchUnwatch = (triggerChange: TriggerChangeType) => undefined; -export type SchemaListener = (newSchema: GraphQLSchema) => void; +export type SchemaListener = (newSchema: GraphQLSchema) => undefined; class SchemaBuilder extends EventEmitter { options: Options; watchers: Array; unwatchers: Array; - triggerChange: ?TriggerChangeType; + triggerChange: TriggerChangeType | null | undefined; depth: number; hooks: { - [string]: Array>, + [a: string]: Array>; }; - _currentPluginName: ?string; - _generatedSchema: ?GraphQLSchema; - _explicitSchemaListener: ?SchemaListener; + _currentPluginName: string | null | undefined; + _generatedSchema: GraphQLSchema | null | undefined; + _explicitSchemaListener: SchemaListener | null | undefined; _busy: boolean; _watching: boolean; @@ -230,7 +236,7 @@ class SchemaBuilder extends EventEmitter { }; } - _setPluginName(name: ?string) { + _setPluginName(name: string | null | undefined) { this._currentPluginName = name; } @@ -242,9 +248,9 @@ class SchemaBuilder extends EventEmitter { * * The function must either return a replacement object for `obj` or `obj` itself */ - hook( + hook( hookName: string, - fn: Hook, + fn: Hook, provides?: Array, before?: Array, after?: Array @@ -356,8 +362,8 @@ class SchemaBuilder extends EventEmitter { } } - applyHooks( - build: { ...Build }, + applyHooks( + build: Build, hookName: string, input: T, context: Context, @@ -370,13 +376,13 @@ class SchemaBuilder extends EventEmitter { try { debug(`${INDENT.repeat(this.depth)}[${hookName}${debugStr}]: Running...`); - const hooks: Array> = this.hooks[hookName]; + const hooks: Array> = this.hooks[hookName]; if (!hooks) { throw new Error(`Sorry, '${hookName}' is not a registered hook`); } let newObj = input; - for (const hook: Hook of hooks) { + for (const hook: Hook of hooks) { this.depth++; try { const hookDisplayName = hook.displayName || hook.name || "anonymous"; @@ -405,6 +411,7 @@ class SchemaBuilder extends EventEmitter { console.warn( `Build hook '${hookDisplayName}' returned a new object; please use 'return build.extend(build, {...})' instead.` ); + // Copy everything from newObj back to oldObj Object.assign(oldObj, newObj); // Go back to the old objectect @@ -447,7 +454,7 @@ class SchemaBuilder extends EventEmitter { this.unwatchers.push(unlisten); } - createBuild(): { ...Build } { + createBuild(): Build { const initialBuild = makeNewBuild(this); // Inflection needs to come first, in case 'build' hooks depend on it initialBuild.inflection = this.applyHooks( @@ -458,14 +465,17 @@ class SchemaBuilder extends EventEmitter { scope: {}, } ); + const build = this.applyHooks(initialBuild, "build", initialBuild, { scope: {}, }); + // Bind all functions so they can be dereferenced bindAll( build, Object.keys(build).filter(key => typeof build[key] === "function") ); + Object.freeze(build); this.applyHooks(build, "init", {}, { scope: {} }); return build; @@ -479,11 +489,13 @@ class SchemaBuilder extends EventEmitter { { directives: [...build.graphql.specifiedDirectives], }, + { __origin: `GraphQL built-in`, isSchema: true, } ); + this._generatedSchema = this.applyHooks( build, "finalize", @@ -523,6 +535,7 @@ class SchemaBuilder extends EventEmitter { console.error( "⚠️⚠️⚠️ An error occured when building the schema on watch:" ); + // eslint-disable-next-line no-console console.error(e); } diff --git a/packages/graphile-build/src/callbackToAsyncIterator.js b/packages/graphile-build/src/callbackToAsyncIterator.ts similarity index 83% rename from packages/graphile-build/src/callbackToAsyncIterator.js rename to packages/graphile-build/src/callbackToAsyncIterator.ts index d51a21945..2bbbfdd29 100644 --- a/packages/graphile-build/src/callbackToAsyncIterator.js +++ b/packages/graphile-build/src/callbackToAsyncIterator.ts @@ -1,4 +1,3 @@ -// @flow /* eslint-disable flowtype/no-weak-types */ // Turn a callback-based listener into an async iterator // From https://raw.githubusercontent.com/withspectrum/callback-to-async-iterator/master/src/index.js @@ -11,14 +10,16 @@ const defaultOnError = (err: Error) => { }; export default function callbackToAsyncIterator< - CallbackInput: any, - ReturnVal: any + CallbackInput extends any, + ReturnVal extends any >( - listener: ((arg: CallbackInput) => any) => ?ReturnVal | Promise, + listener: ( + a: (arg: CallbackInput) => any + ) => (ReturnVal | null | undefined) | Promise, options?: { - onError?: (err: Error) => void, - onClose?: (arg?: ?ReturnVal) => void, - buffering?: boolean, + onError?: (err: Error) => undefined; + onClose?: (arg: ReturnVal | null | undefined) => undefined; + buffering?: boolean; } = {} ) { const { onError = defaultOnError, buffering = true, onClose } = options; @@ -66,10 +67,10 @@ export default function callbackToAsyncIterator< }); return { - next(): Promise<{ value?: CallbackInput, done: boolean }> { + next(): Promise<{ value?: CallbackInput; done: boolean }> { return listening ? pullValue() : this.return(); }, - return(): Promise<{ value: typeof undefined, done: boolean }> { + return(): Promise<{ value: typeof undefined; done: boolean }> { emptyQueue(); return Promise.resolve({ value: undefined, done: true }); }, diff --git a/packages/graphile-build/src/extend.js b/packages/graphile-build/src/extend.ts similarity index 91% rename from packages/graphile-build/src/extend.js rename to packages/graphile-build/src/extend.ts index 48306ebde..3a2e254c8 100644 --- a/packages/graphile-build/src/extend.js +++ b/packages/graphile-build/src/extend.ts @@ -1,4 +1,3 @@ -// @flow import chalk from "chalk"; const INDENT = " "; @@ -10,7 +9,7 @@ export function indent(text: string) { ); } -export default function extend( +export default function extend( base: Obj1, extra: Obj2, hint?: string @@ -25,7 +24,7 @@ export default function extend( const hintB = extraHints[key] || hint; if (key in base && base[key] !== newValue) { // $FlowFixMe - const hintA: ?string = hints[key]; + const hintA: string | null | undefined = hints[key]; const firstEntityDetails = !hintA ? "We don't have any information about the first entity." : `The first entity was:\n\n${indent(chalk.magenta(hintA))}`; diff --git a/packages/graphile-build/src/index.d.ts b/packages/graphile-build/src/index.d.ts index 56c739285..73dbfcdf5 100644 --- a/packages/graphile-build/src/index.d.ts +++ b/packages/graphile-build/src/index.d.ts @@ -3,9 +3,9 @@ import { ResolveTree } from "graphql-parse-resolve-info"; type CaseChangeFunction = (str: string) => string; export const constantCaseAll: CaseChangeFunction; -export const formatInsideUnderscores: (( +export const formatInsideUnderscores: ( fn: CaseChangeFunction -) => CaseChangeFunction); +) => CaseChangeFunction; export const upperFirst: CaseChangeFunction; export const camelCase: CaseChangeFunction; export const constantCase: CaseChangeFunction; @@ -35,12 +35,7 @@ export { Context, Hook, } from "./SchemaBuilder"; -export { - LiveSource, - LiveProvider, - LiveMonitor, - LiveCoordinator, -} from "./Live"; +export { LiveSource, LiveProvider, LiveMonitor, LiveCoordinator } from "./Live"; export { SchemaBuilder }; export const getBuilder: ( diff --git a/packages/graphile-build/src/index.js b/packages/graphile-build/src/index.ts similarity index 92% rename from packages/graphile-build/src/index.js rename to packages/graphile-build/src/index.ts index 5822caa86..dad4b8b7d 100644 --- a/packages/graphile-build/src/index.js +++ b/packages/graphile-build/src/index.ts @@ -1,5 +1,3 @@ -// @flow - import util from "util"; import SchemaBuilder from "./SchemaBuilder"; import { @@ -14,9 +12,9 @@ import { AddQueriesToSubscriptionsPlugin, } from "./plugins"; import resolveNode from "./resolveNode"; -import type { GraphQLSchema } from "graphql"; +import { GraphQLSchema } from "graphql"; -import type { Plugin, Options } from "./SchemaBuilder"; +import { Plugin, Options } from "./SchemaBuilder"; export { constantCaseAll, @@ -29,9 +27,9 @@ export { singularize, } from "./utils"; -export type { SchemaBuilder }; +export { SchemaBuilder }; -export type { +export { Plugin, Options, Build, @@ -41,7 +39,7 @@ export type { Hook, WatchUnwatch, SchemaListener, -} from "./SchemaBuilder"; +}; export { LiveSource, LiveProvider, LiveMonitor, LiveCoordinator } from "./Live"; diff --git a/packages/graphile-build/src/makeNewBuild.js b/packages/graphile-build/src/makeNewBuild.ts similarity index 96% rename from packages/graphile-build/src/makeNewBuild.js rename to packages/graphile-build/src/makeNewBuild.ts index 5eed83570..5e5c4db38 100644 --- a/packages/graphile-build/src/makeNewBuild.js +++ b/packages/graphile-build/src/makeNewBuild.ts @@ -1,7 +1,5 @@ -// @flow - import * as graphql from "graphql"; -import type { +import { GraphQLNamedType, GraphQLInputField, GraphQLFieldResolver, @@ -13,7 +11,7 @@ import { getAliasFromResolveInfo as rawGetAliasFromResolveInfo, } from "graphql-parse-resolve-info"; import debugFactory from "debug"; -import type { ResolveTree } from "graphql-parse-resolve-info"; +import { ResolveTree } from "graphql-parse-resolve-info"; import pluralize from "pluralize"; import LRU from "@graphile/lru"; import semver from "semver"; @@ -22,7 +20,7 @@ import swallowError from "./swallowError"; import resolveNode from "./resolveNode"; import { LiveCoordinator } from "./Live"; -import type SchemaBuilder, { +import SchemaBuilder, { Build, Context, Scope, @@ -107,43 +105,44 @@ function getSafeAliasFromResolveInfo(resolveInfo) { } type MetaData = { - [string]: Array, + [a: string]: Array; }; + type DataGeneratorFunction = ( parsedResolveInfoFragment: ResolveTree, ReturnType: GraphQLType, - ...args: Array + ...args: Array ) => Array; type FieldSpecIsh = { - type?: GraphQLType, - args?: {}, - resolve?: GraphQLFieldResolver<*, *>, - deprecationReason?: string, - description?: ?string, + type?: GraphQLType; + args?: {}; + resolve?: GraphQLFieldResolver; + deprecationReason?: string; + description?: string | null | undefined; }; type ContextAndGenerators = | Context | { - addDataGenerator: DataGeneratorFunction => void, - addArgDataGenerator: DataGeneratorFunction => void, + addDataGenerator: (a: DataGeneratorFunction) => undefined; + addArgDataGenerator: (a: DataGeneratorFunction) => undefined; getDataFromParsedResolveInfoFragment: ( parsedResolveInfoFragment: ResolveTree, Type: GraphQLType - ) => DataForType, + ) => DataForType; }; export type FieldWithHooksFunction = ( fieldName: string, - spec: FieldSpecIsh | (ContextAndGenerators => FieldSpecIsh), - fieldScope?: {} + spec: FieldSpecIsh | ((a: ContextAndGenerators) => FieldSpecIsh), + fieldScope: {} ) => {}; export type InputFieldWithHooksFunction = ( fieldName: string, spec: GraphQLInputField, - fieldScope?: {} + fieldScope: {} ) => GraphQLInputField; function getNameFromType(Type: GraphQLNamedType | GraphQLSchema) { @@ -172,7 +171,9 @@ const mergeData = ( ReturnType, arg ) => { - const results: ?Array = ensureArray(gen(arg, ReturnType, data)); + const results: Array | null | undefined = ensureArray( + gen(arg, ReturnType, data) + ); if (!results) { return; } @@ -186,8 +187,8 @@ const mergeData = ( for (let i = 0, l = keys.length; i < l; i++) { const k = keys[i]; data[k] = data[k] || []; - const value: mixed = result[k]; - const newData: ?Array = ensureArray(value); + const value: unknown = result[k]; + const newData: Array | null | undefined = ensureArray(value); if (newData) { data[k].push(...newData); } @@ -202,9 +203,10 @@ const knownTypes = [ GraphQLEnumType, GraphQLUnionType, ]; + const knownTypeNames = knownTypes.map(k => k.name); -function ensureArray(val: void | Array | T): void | Array { +function ensureArray(val: undefined | Array | T): undefined | Array { if (val == null) { return; } else if (Array.isArray(val)) { @@ -227,7 +229,7 @@ if (["development", "test"].indexOf(process.env.NODE_ENV) >= 0) { }; } -export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { +export default function makeNewBuild(builder: SchemaBuilder): Build { const allTypes = { Int: graphql.GraphQLInt, Float: graphql.GraphQLFloat, @@ -235,6 +237,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { Boolean: graphql.GraphQLBoolean, ID: graphql.GraphQLID, }; + const allTypesSources = { Int: "GraphQL Built-in", Float: "GraphQL Built-in", @@ -265,6 +268,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { graphql: require("graphql/package.json").version, "graphile-build": version, }, + hasVersion( packageName: string, range: string, @@ -284,7 +288,10 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { const alias = getSafeAliasFromResolveInfo(resolveInfo); return data[alias]; }, - addType(type: GraphQLNamedType, origin?: ?string): void { + addType( + type: GraphQLNamedType, + origin?: string | null | undefined + ): undefined { if (!type.name) { throw new Error( `addType must only be called with named types, try using require('graphql').getNamedType` @@ -326,12 +333,15 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { return allTypes[typeName]; }, extend, - newWithHooks( + newWithHooks< + T extends GraphQLNamedType | GraphQLSchema, + ConfigType extends any + >( Type: Class, spec: ConfigType, inScope: Scope, performNonEmptyFieldsCheck = false - ): ?T { + ): T | null | undefined { const scope = inScope || {}; if (!inScope) { // eslint-disable-next-line no-console @@ -404,14 +414,17 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { parsedResolveInfoFragment, ReturnType ); + const results = []; const StrippedType: GraphQLNamedType = getNamedType(ReturnType); const fieldDataGeneratorsByFieldName = fieldDataGeneratorsByFieldNameByType.get( StrippedType ); + const argDataGeneratorsForSelfByFieldName = fieldArgDataGeneratorsByFieldNameByType.get( Self ); + if (argDataGeneratorsForSelfByFieldName) { const argDataGenerators = argDataGeneratorsForSelfByFieldName[fieldName]; @@ -453,6 +466,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { const local = ensureArray( gen(field, typeFields[field.name].type, ...rest) ); + if (local) { results.push(...local); } @@ -473,6 +487,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { type: "GraphQLObjectType", scope, }; + newSpec = builder.applyHooks( this, "GraphQLObjectType", @@ -482,6 +497,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { addDataGeneratorForField, recurseDataGeneratorsForField, }, + `|${newSpec.name}` ); @@ -494,6 +510,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { Self, GraphQLObjectType: rawSpec, }; + let rawInterfaces = rawSpec.interfaces || []; if (typeof rawInterfaces === "function") { rawInterfaces = rawInterfaces(interfacesContext); @@ -579,6 +596,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { fieldName, getNameFromType(Self) ); + throw e; } } @@ -592,6 +610,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { const fieldDataGeneratorsByFieldName = fieldDataGeneratorsByFieldNameByType.get( Type ); + if ( fieldDataGeneratorsByFieldName && isCompositeType(Type) && @@ -623,14 +642,17 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { { fieldName, }, + `Within context for GraphQLObjectType '${rawSpec.name}'` ), + fieldScope, `Extending scope for field '${fieldName}' within context for GraphQLObjectType '${ rawSpec.name }'` ), }; + if (typeof newSpec === "function") { newSpec = newSpec(context); } @@ -641,6 +663,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { context, `|${getNameFromType(Self)}.fields.${fieldName}` ); + newSpec.args = newSpec.args || {}; newSpec = { ...newSpec, @@ -653,14 +676,17 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { field: newSpec, returnType: newSpec.type, }, + `|${getNameFromType(Self)}.fields.${fieldName}` ), }; + const finalSpec = newSpec; processedFields.push(finalSpec); return finalSpec; - }: FieldWithHooksFunction), + }) as FieldWithHooksFunction, }; + let rawFields = rawSpec.fields || {}; if (typeof rawFields === "function") { rawFields = rawFields(fieldsContext); @@ -675,9 +701,11 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { rawSpec.name }'. ${inScope.__origin || ""}` ), + fieldsContext, `|${rawSpec.name}` ); + // Finally, check through all the fields that they've all been processed; any that have not we should do so now. for (const fieldName in fieldsSpec) { const fieldSpec = fieldsSpec[fieldName]; @@ -700,6 +728,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { type: "GraphQLInputObjectType", scope, }; + newSpec = builder.applyHooks( this, "GraphQLInputObjectType", @@ -707,6 +736,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { commonContext, `|${newSpec.name}` ); + newSpec.fields = newSpec.fields || {}; const rawSpec = newSpec; @@ -733,16 +763,19 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { { fieldName, }, + `Within context for GraphQLInputObjectType '${ rawSpec.name }'` ), + fieldScope, `Extending scope for field '${fieldName}' within context for GraphQLInputObjectType '${ rawSpec.name }'` ), }; + let newSpec = spec; if (typeof newSpec === "function") { newSpec = newSpec(context); @@ -754,11 +787,13 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { context, `|${getNameFromType(Self)}.fields.${fieldName}` ); + const finalSpec = newSpec; processedFields.push(finalSpec); return finalSpec; - }: InputFieldWithHooksFunction), + }) as InputFieldWithHooksFunction, }; + let rawFields = rawSpec.fields; if (typeof rawFields === "function") { rawFields = rawFields(fieldsContext); @@ -773,9 +808,11 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { rawSpec.name }'. ${inScope.__origin || ""}` ), + fieldsContext, `|${getNameFromType(Self)}` ); + // Finally, check through all the fields that they've all been processed; any that have not we should do so now. for (const fieldName in fieldsSpec) { const fieldSpec = fieldsSpec[fieldName]; @@ -798,6 +835,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { type: "GraphQLEnumType", scope, }; + newSpec = builder.applyHooks( this, "GraphQLEnumType", @@ -813,6 +851,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { commonContext, `|${newSpec.name}` ); + const values = newSpec.values; newSpec.values = Object.keys(values).reduce((memo, valueKey) => { const value = values[valueKey]; @@ -823,6 +862,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { commonContext, `|${newSpec.name}|${valueKey}` ); + memo[valueKey] = newValue; return memo; }, {}); @@ -831,6 +871,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { type: "GraphQLUnionType", scope, }; + newSpec = builder.applyHooks( this, "GraphQLUnionType", @@ -848,6 +889,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { Self, GraphQLUnionType: rawSpec, }; + let rawTypes = rawSpec.types || []; if (typeof rawTypes === "function") { rawTypes = rawTypes(typesContext); @@ -894,6 +936,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { const isProbablyAnEmptyObjectError = !!e.message.match( /function which returns such an object/ ); + if (!isProbablyAnEmptyObjectError) { this.swallowError(e); } @@ -917,10 +960,12 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { Self, fieldDataGeneratorsByFieldName ); + fieldArgDataGeneratorsByFieldNameByType.set( Self, fieldArgDataGeneratorsByFieldName ); + return Self; }, fieldDataGeneratorsByType: fieldDataGeneratorsByFieldNameByType, // @deprecated @@ -995,6 +1040,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { return resultingName; }, }, + swallowError, // resolveNode: EXPERIMENTAL, API might change! resolveNode, @@ -1002,6 +1048,7 @@ export default function makeNewBuild(builder: SchemaBuilder): { ...Build } { currentHookName: null, currentHookEvent: null, }, + liveCoordinator: new LiveCoordinator(), scopeByType: new Map(), }; diff --git a/packages/graphile-build/src/plugins/AddQueriesToSubscriptionsPlugin.js b/packages/graphile-build/src/plugins/AddQueriesToSubscriptionsPlugin.ts similarity index 95% rename from packages/graphile-build/src/plugins/AddQueriesToSubscriptionsPlugin.js rename to packages/graphile-build/src/plugins/AddQueriesToSubscriptionsPlugin.ts index 4a9d161b1..caa8b5455 100644 --- a/packages/graphile-build/src/plugins/AddQueriesToSubscriptionsPlugin.js +++ b/packages/graphile-build/src/plugins/AddQueriesToSubscriptionsPlugin.ts @@ -1,6 +1,5 @@ -// @flow -import type { Plugin } from "../SchemaBuilder"; -import type { GraphQLObjectType } from "graphql"; +import { Plugin } from "../SchemaBuilder"; +import { GraphQLObjectType } from "graphql"; const AddQueriesToSubscriptionsPlugin: Plugin = function( builder, @@ -24,6 +23,7 @@ const AddQueriesToSubscriptionsPlugin: Plugin = function( const Query: GraphQLObjectType = getTypeByName( inflection.builtin("Query") ); + const queryFields = Query.getFields(); const subscriptionFields = Object.keys(queryFields).reduce( (memo, queryFieldName) => { @@ -41,6 +41,7 @@ const AddQueriesToSubscriptionsPlugin: Plugin = function( type, defaultValue, }; + return newArgs; }, {}), ...(oldResolve @@ -66,15 +67,18 @@ const AddQueriesToSubscriptionsPlugin: Plugin = function( ? queryField.deprecationReason : undefined, }, + { isLiveField: true, originalField: queryField, } ); + return memo; }, {} ); + return extend(fields, subscriptionFields); }, ["AddQueriesToSubscriptions"] diff --git a/packages/graphile-build/src/plugins/ClientMutationIdDescriptionPlugin.js b/packages/graphile-build/src/plugins/ClientMutationIdDescriptionPlugin.ts similarity index 93% rename from packages/graphile-build/src/plugins/ClientMutationIdDescriptionPlugin.js rename to packages/graphile-build/src/plugins/ClientMutationIdDescriptionPlugin.ts index 143e968d1..7b6341fb6 100644 --- a/packages/graphile-build/src/plugins/ClientMutationIdDescriptionPlugin.js +++ b/packages/graphile-build/src/plugins/ClientMutationIdDescriptionPlugin.ts @@ -1,7 +1,6 @@ -// @flow -import type SchemaBuilder, { Plugin } from "../SchemaBuilder"; +import SchemaBuilder, { Plugin } from "../SchemaBuilder"; -export default (function ClientMutationIdDescriptionPlugin( +export default function ClientMutationIdDescriptionPlugin( builder: SchemaBuilder ) { builder.hook( @@ -25,6 +24,7 @@ export default (function ClientMutationIdDescriptionPlugin( description: "An arbitrary string value with no semantic meaning. Will be included in the payload verbatim. May be used to track mutations by the client.", }, + `Tweaking '${fieldName}' field in '${Self.name}'` ); }, @@ -52,6 +52,7 @@ export default (function ClientMutationIdDescriptionPlugin( description: "The exact same `clientMutationId` that was provided in the mutation input, unchanged and unused. May be used by a client to track mutations.", }, + `Tweaking '${fieldName}' field in '${Self.name}'` ); }, @@ -77,6 +78,7 @@ export default (function ClientMutationIdDescriptionPlugin( description: "The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.", }, + `Adding a description to input arg for field '${fieldName}' field in '${ Self.name }'` @@ -85,4 +87,4 @@ export default (function ClientMutationIdDescriptionPlugin( }, ["ClientMutationIdDescription"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build/src/plugins/MutationPayloadQueryPlugin.js b/packages/graphile-build/src/plugins/MutationPayloadQueryPlugin.ts similarity index 71% rename from packages/graphile-build/src/plugins/MutationPayloadQueryPlugin.js rename to packages/graphile-build/src/plugins/MutationPayloadQueryPlugin.ts index 32bda96b0..35af86391 100644 --- a/packages/graphile-build/src/plugins/MutationPayloadQueryPlugin.js +++ b/packages/graphile-build/src/plugins/MutationPayloadQueryPlugin.ts @@ -1,15 +1,10 @@ -// @flow -import type { Plugin, Build } from "../SchemaBuilder"; -import type { BuildExtensionQuery } from "./QueryPlugin"; +import { Plugin, Build } from "../SchemaBuilder"; +import { BuildExtensionQuery } from "./QueryPlugin"; -export default (function MutationPayloadQueryPlugin(builder) { +export default function MutationPayloadQueryPlugin(builder) { builder.hook( "GraphQLObjectType:fields", - ( - fields: {}, - build: {| ...Build, ...BuildExtensionQuery |}, - context - ): {} => { + (fields: {}, build: Build & BuildExtensionQuery, context): {} => { const { $$isQuery, extend, getTypeByName, inflection } = build; const { scope: { isMutationPayload }, @@ -31,9 +26,10 @@ export default (function MutationPayloadQueryPlugin(builder) { }, }, }, + `Adding 'query' field to mutation payload ${Self.name}` ); }, ["MutationPayloadQuery"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build/src/plugins/MutationPlugin.js b/packages/graphile-build/src/plugins/MutationPlugin.ts similarity index 89% rename from packages/graphile-build/src/plugins/MutationPlugin.js rename to packages/graphile-build/src/plugins/MutationPlugin.ts index aa3e53d2f..e16c3d07b 100644 --- a/packages/graphile-build/src/plugins/MutationPlugin.js +++ b/packages/graphile-build/src/plugins/MutationPlugin.ts @@ -1,5 +1,4 @@ -// @flow -import type { Plugin } from "../SchemaBuilder"; +import { Plugin } from "../SchemaBuilder"; function isValidMutation(Mutation) { try { @@ -15,7 +14,7 @@ function isValidMutation(Mutation) { return true; } -export default (async function MutationPlugin(builder) { +export default async function MutationPlugin(builder) { builder.hook( "GraphQLSchema", (schema: {}, build) => { @@ -32,18 +31,22 @@ export default (async function MutationPlugin(builder) { description: "The root mutation type which contains root level fields which mutate data.", }, + { __origin: `graphile-build built-in (root mutation type)`, isRootMutation: true, }, + true ); + if (isValidMutation(Mutation)) { return extend( schema, { mutation: Mutation, }, + "Adding mutation type to schema" ); } else { @@ -54,4 +57,4 @@ export default (async function MutationPlugin(builder) { [], ["Query"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build/src/plugins/NodePlugin.js b/packages/graphile-build/src/plugins/NodePlugin.ts similarity index 85% rename from packages/graphile-build/src/plugins/NodePlugin.js rename to packages/graphile-build/src/plugins/NodePlugin.ts index 264371a78..a3ebbd664 100644 --- a/packages/graphile-build/src/plugins/NodePlugin.js +++ b/packages/graphile-build/src/plugins/NodePlugin.ts @@ -1,53 +1,52 @@ -// @flow -import type { +import { Plugin, Build, DataForType, Context, ContextGraphQLObjectTypeFields, } from "../SchemaBuilder"; -import type { ResolveTree } from "graphql-parse-resolve-info"; -import type { - GraphQLType, - GraphQLInterfaceType, - GraphQLResolveInfo, -} from "graphql"; -import type { BuildExtensionQuery } from "./QueryPlugin"; +import { ResolveTree } from "graphql-parse-resolve-info"; +import { GraphQLType, GraphQLInterfaceType, GraphQLResolveInfo } from "graphql"; +import { BuildExtensionQuery } from "./QueryPlugin"; const base64 = str => Buffer.from(String(str)).toString("base64"); const base64Decode = str => Buffer.from(String(str), "base64").toString("utf8"); export type NodeFetcher = ( - data: mixed, - identifiers: Array, - context: mixed, + data: unknown, + identifiers: Array, + context: unknown, parsedResolveInfoFragment: ResolveTree, type: GraphQLType, resolveData: DataForType, resolveInfo: GraphQLResolveInfo ) => {}; -export type BuildExtensionNode = {| - nodeIdFieldName: string, - $$nodeType: Symbol, - nodeFetcherByTypeName: { [string]: NodeFetcher }, - getNodeIdForTypeAndIdentifiers( +export type BuildExtensionNode = { + nodeIdFieldName: string; + $$nodeType: Symbol; + nodeFetcherByTypeName: { [a: string]: NodeFetcher }; + getNodeIdForTypeAndIdentifiers: ( Type: GraphQLType, - ...identifiers: Array - ): string, - getTypeAndIdentifiersFromNodeId( + ...identifiers: Array + ) => string; + getTypeAndIdentifiersFromNodeId: ( nodeId: string - ): { - Type: GraphQLType, - identifiers: Array, - }, - addNodeFetcherForTypeName(typeName: string, fetcher: NodeFetcher): void, - getNodeAlias(typeName: string): string, - getNodeType(alias: string): GraphQLType, - setNodeAlias(typeName: string, alias: string): void, -|}; + ) => { + Type: GraphQLType; + identifiers: Array; + }; + + addNodeFetcherForTypeName: ( + typeName: string, + fetcher: NodeFetcher + ) => undefined; + getNodeAlias: (typeName: string) => string; + getNodeType: (alias: string) => GraphQLType; + setNodeAlias: (typeName: string, alias: string) => undefined; +}; -export default (function NodePlugin( +export default function NodePlugin( builder, { nodeIdFieldName: inNodeIdFieldName } ) { @@ -109,6 +108,7 @@ export default (function NodePlugin( nodeTypeNameByAlias[alias] = typeName; }, }, + `Adding 'Node' interface support to the Build` ); }, @@ -119,7 +119,7 @@ export default (function NodePlugin( "init", function defineNodeInterfaceType( _: {}, - build: {| ...Build, ...BuildExtensionQuery, ...BuildExtensionNode |} + build: Build & BuildExtensionQuery & BuildExtensionNode ) { const { $$isQuery, @@ -132,6 +132,7 @@ export default (function NodePlugin( GraphQLInterfaceType, getNullableType, }, + inflection, } = build; let Query; @@ -156,10 +157,12 @@ export default (function NodePlugin( }, }, }, + { __origin: `graphile-build built-in (NodePlugin); you can omit this plugin if you like, but you'll lose compatibility with Relay`, } ); + return _; }, ["Node"] @@ -193,8 +196,8 @@ export default (function NodePlugin( "GraphQLObjectType:fields", ( fields: {}, - build: {| ...Build, ...BuildExtensionQuery, ...BuildExtensionNode |}, - context: {| ...Context, ...ContextGraphQLObjectTypeFields |} + build: Build & BuildExtensionQuery & BuildExtensionNode, + context: Context & ContextGraphQLObjectTypeFields ) => { const { scope: { isRootQuery }, @@ -221,6 +224,7 @@ export default (function NodePlugin( return "query"; }, }, + node: fieldWithHooks( "node", ({ getDataFromParsedResolveInfoFragment }) => ({ @@ -232,6 +236,7 @@ export default (function NodePlugin( type: new GraphQLNonNull(GraphQLID), }, }, + resolve(data, args, context, resolveInfo) { const nodeId = args[nodeIdFieldName]; return resolveNode( @@ -244,14 +249,16 @@ export default (function NodePlugin( ); }, }), + { isRootNodeField: true, } ), }, + `Adding Relay Global Object Identification support to the root Query via 'node' and '${nodeIdFieldName}' fields` ); }, ["Node"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build/src/plugins/QueryPlugin.js b/packages/graphile-build/src/plugins/QueryPlugin.ts similarity index 89% rename from packages/graphile-build/src/plugins/QueryPlugin.js rename to packages/graphile-build/src/plugins/QueryPlugin.ts index 0c9d46756..6055f7c15 100644 --- a/packages/graphile-build/src/plugins/QueryPlugin.js +++ b/packages/graphile-build/src/plugins/QueryPlugin.ts @@ -1,11 +1,10 @@ -// @flow -import type { Plugin, Build } from "../SchemaBuilder"; +import { Plugin, Build } from "../SchemaBuilder"; -export type BuildExtensionQuery = {| - $$isQuery: Symbol, -|}; +export type BuildExtensionQuery = { + $$isQuery: Symbol; +}; -export default (async function QueryPlugin(builder) { +export default async function QueryPlugin(builder) { builder.hook( "build", (build: Build): Build & BuildExtensionQuery => @@ -14,10 +13,13 @@ export default (async function QueryPlugin(builder) { { $$isQuery: Symbol("isQuery"), }, + `Extending Build` ), + ["Query"] ); + builder.hook( "GraphQLSchema", (schema: {}, build) => { @@ -47,18 +49,22 @@ export default (async function QueryPlugin(builder) { }, }), }, + { __origin: `graphile-build built-in (root query type)`, isRootQuery: true, }, + true ); + if (queryType) { return extend( schema, { query: queryType, }, + `Adding 'query' type to Schema` ); } else { @@ -67,4 +73,4 @@ export default (async function QueryPlugin(builder) { }, ["Query"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build/src/plugins/StandardTypesPlugin.js b/packages/graphile-build/src/plugins/StandardTypesPlugin.ts similarity index 95% rename from packages/graphile-build/src/plugins/StandardTypesPlugin.js rename to packages/graphile-build/src/plugins/StandardTypesPlugin.ts index de841dc87..832643973 100644 --- a/packages/graphile-build/src/plugins/StandardTypesPlugin.js +++ b/packages/graphile-build/src/plugins/StandardTypesPlugin.ts @@ -1,8 +1,7 @@ -// @flow -import type { Plugin, Build } from "../SchemaBuilder"; +import { Plugin, Build } from "../SchemaBuilder"; import { Kind } from "graphql/language"; -export default (function StandardTypesPlugin(builder) { +export default function StandardTypesPlugin(builder) { // XXX: this should be in an "init" plugin, but PgTypesPlugin requires it in build - fix that, then fix this builder.hook( "build", @@ -25,11 +24,13 @@ export default (function StandardTypesPlugin(builder) { "Cursor", "A location in a connection that can be used for resuming pagination." ); + build.addType(Cursor, "graphile-build built-in"); return build; }, ["StandardTypes"] ); + builder.hook( "init", (_: {}, build) => { @@ -62,6 +63,7 @@ export default (function StandardTypesPlugin(builder) { }, { isPageInfoHasNextPageField: true } ), + hasPreviousPage: fieldWithHooks( "hasPreviousPage", ({ addDataGenerator }) => { @@ -80,13 +82,15 @@ export default (function StandardTypesPlugin(builder) { ), }), }, + { __origin: `graphile-build built-in`, isPageInfo: true, } ); + return _; }, ["StandardTypes", "PageInfo"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build/src/plugins/SubscriptionPlugin.js b/packages/graphile-build/src/plugins/SubscriptionPlugin.ts similarity index 94% rename from packages/graphile-build/src/plugins/SubscriptionPlugin.js rename to packages/graphile-build/src/plugins/SubscriptionPlugin.ts index 567713aa8..1ebeb936c 100644 --- a/packages/graphile-build/src/plugins/SubscriptionPlugin.js +++ b/packages/graphile-build/src/plugins/SubscriptionPlugin.ts @@ -1,5 +1,4 @@ -// @flow -import type { Plugin } from "../SchemaBuilder"; +import { Plugin } from "../SchemaBuilder"; function isValidSubscription(Subscription) { try { @@ -35,7 +34,7 @@ Live queries can be very expensive, so try and keep them small and focussed. Event fields will run their selection set when, and only when, the specified server-side event occurs. \ This makes them a lot more efficient than Live Queries, but it is still recommended that you keep payloads fairly small.`; -export default (async function SubscriptionPlugin(builder, { live }) { +export default async function SubscriptionPlugin(builder, { live }) { builder.hook( "GraphQLSchema", (schema: {}, build) => { @@ -51,18 +50,22 @@ export default (async function SubscriptionPlugin(builder, { live }) { name: inflection.builtin("Subscription"), description: live ? liveDescription : description, }, + { __origin: `graphile-build built-in (root subscription type)`, isRootSubscription: true, }, + true ); + if (isValidSubscription(Subscription)) { return extend( schema, { subscription: Subscription, }, + "Adding subscription type to schema" ); } else { @@ -73,4 +76,4 @@ export default (async function SubscriptionPlugin(builder, { live }) { [], ["Query"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build/src/plugins/SwallowErrorsPlugin.js b/packages/graphile-build/src/plugins/SwallowErrorsPlugin.ts similarity index 83% rename from packages/graphile-build/src/plugins/SwallowErrorsPlugin.js rename to packages/graphile-build/src/plugins/SwallowErrorsPlugin.ts index dbc42295a..3837ccc08 100644 --- a/packages/graphile-build/src/plugins/SwallowErrorsPlugin.js +++ b/packages/graphile-build/src/plugins/SwallowErrorsPlugin.ts @@ -1,7 +1,6 @@ -// @flow -import type { Plugin, Build } from "../SchemaBuilder"; +import { Plugin, Build } from "../SchemaBuilder"; -export default (function SwallowErrorsPlugin( +export default function SwallowErrorsPlugin( builder, { dontSwallowErrors = false } ) { @@ -26,4 +25,4 @@ export default (function SwallowErrorsPlugin( }, ["SwallowErrors"] ); -}: Plugin); +} as Plugin; diff --git a/packages/graphile-build/src/plugins/index.js b/packages/graphile-build/src/plugins/index.ts similarity index 98% rename from packages/graphile-build/src/plugins/index.js rename to packages/graphile-build/src/plugins/index.ts index c5a039834..541b59f20 100644 --- a/packages/graphile-build/src/plugins/index.js +++ b/packages/graphile-build/src/plugins/index.ts @@ -1,5 +1,3 @@ -// @flow - import ClientMutationIdDescriptionPlugin from "./ClientMutationIdDescriptionPlugin"; import MutationPayloadQueryPlugin from "./MutationPayloadQueryPlugin"; import MutationPlugin from "./MutationPlugin"; diff --git a/packages/graphile-build/src/resolveNode.js b/packages/graphile-build/src/resolveNode.ts similarity index 99% rename from packages/graphile-build/src/resolveNode.js rename to packages/graphile-build/src/resolveNode.ts index 03b50b58a..c82ca99aa 100644 --- a/packages/graphile-build/src/resolveNode.js +++ b/packages/graphile-build/src/resolveNode.ts @@ -28,6 +28,7 @@ export default async function resolveNode( parsedResolveInfoFragment, getNamedType(Type) ); + const node = await resolver( data, identifiers, @@ -37,11 +38,13 @@ export default async function resolveNode( resolveData, resolveInfo ); + Object.defineProperty(node, $$nodeType, { enumerable: false, configurable: false, value: Type, }); + return node; } catch (e) { return null; diff --git a/packages/graphile-build/src/swallowError.js b/packages/graphile-build/src/swallowError.ts similarity index 94% rename from packages/graphile-build/src/swallowError.js rename to packages/graphile-build/src/swallowError.ts index 92035e860..c8c2a5f4d 100644 --- a/packages/graphile-build/src/swallowError.js +++ b/packages/graphile-build/src/swallowError.ts @@ -1,9 +1,8 @@ -// @flow import debugFactory from "debug"; const debugWarn = debugFactory("graphile-build:warn"); -export default function swallowError(e: Error): void { +export default function swallowError(e: Error): undefined { // BE VERY CAREFUL NOT TO THROW! // XXX: Improve this if (debugWarn.enabled) { diff --git a/packages/graphile-build/src/utils.js b/packages/graphile-build/src/utils.ts similarity index 99% rename from packages/graphile-build/src/utils.js rename to packages/graphile-build/src/utils.ts index faa874273..ae90b7390 100644 --- a/packages/graphile-build/src/utils.js +++ b/packages/graphile-build/src/utils.ts @@ -1,4 +1,3 @@ -// @flow import upperFirstAll from "lodash/upperFirst"; import camelCaseAll from "lodash/camelCase"; import plz from "pluralize";