diff --git a/graphql-relay/graphql-relay-tests.ts b/graphql-relay/graphql-relay-tests.ts new file mode 100644 index 0000000000..1ee0b5e111 --- /dev/null +++ b/graphql-relay/graphql-relay-tests.ts @@ -0,0 +1,230 @@ +import { + GraphQLString, + GraphQLObjectType, + GraphQLFieldResolver, + GraphQLFieldConfigMap, + GraphQLResolveInfo, + GraphQLTypeResolver, + GraphQLFieldConfig, + GraphQLInterfaceType, + GraphQLInputType, + GraphQLInputFieldConfigMap, + GraphQLNonNull, + GraphQLID, +} from "graphql"; +import { + // Connections + connectionArgs, + forwardConnectionArgs, + backwardConnectionArgs, + connectionDefinitions, + connectionFromArray, + connectionFromPromisedArray, + cursorForObjectInConnection, + // Object Identification + nodeDefinitions, + toGlobalId, + fromGlobalId, + globalIdField, + pluralIdentifyingRootField, + // Mutations + mutationWithClientMutationId, +} from "."; +// Connections +// connectionArgs returns the arguments that fields should provide when they return a connection type that supports bidirectional pagination. +connectionArgs.first = 10; +connectionArgs.after = "a"; +connectionArgs.before = "b"; +connectionArgs.last = 10; +// forwardConnectionArgs returns the arguments that fields should provide when they return a connection type that only supports forward pagination. +forwardConnectionArgs.after = "a"; +forwardConnectionArgs.first = 10; +// backwardConnectionArgs returns the arguments that fields should provide when they return a connection type that only supports backward pagination. +backwardConnectionArgs.before = "b"; +backwardConnectionArgs.last = 10; +// connectionDefinitions returns a connectionType and its associated edgeType, given a node type. +let resolve: GraphQLFieldResolver = (source, args, context, info) => { + info.fieldName = "f"; +}; +const fields: GraphQLFieldConfigMap = {}; +let t: GraphQLObjectType; +let e: GraphQLObjectType; +let def = connectionDefinitions({ + connectionFields: fields, + edgeFields: fields, + name: "N", + nodeType: new GraphQLObjectType({ + name: "N", + fields: {}, + }), + resolveCursor: resolve, + resolveNode: resolve, +}); +t = def.connectionType; +e = def.edgeType; +// connectionFromArray is a helper method that takes an array and the arguments from connectionArgs, does pagination and filtering, and returns an object in the shape expected by a connectionType's resolve function. +const conn = connectionFromArray([1, 2, 3], { + after: "a", + before: "b", + first: 1, + last: 5, +}); +conn.edges.map((e) => { e.cursor.toLowerCase(); e.node.toExponential(); }); +conn.pageInfo.endCursor = "e"; +conn.pageInfo.hasNextPage = true; +conn.pageInfo.hasPreviousPage = true; +conn.pageInfo.startCursor = "s"; +// connectionFromPromisedArray is similar to connectionFromArray, but it takes a promise that resolves to an array, and returns a promise that resolves to the expected shape by connectionType. +const conn2 = connectionFromPromisedArray(new Promise((resolve) => { + resolve(); +}), { + after: "a", + before: "b", + first: 1, + last: 5, + }); +conn2.then((res) => { + res = conn; +}); +// cursorForObjectInConnection is a helper method that takes an array and a member object, and returns a cursor for use in the mutation payload. +cursorForObjectInConnection(["a"], "b").toLowerCase(); +// An example usage of these methods from the test schema: +let shipType: GraphQLObjectType = new GraphQLObjectType({ + name: "ShipType", + fields: {}, +}); +const {connectionType: ShipConnection} = + connectionDefinitions({ nodeType: shipType }); +const factionType = new GraphQLObjectType({ + name: "Faction", + fields: () => ({ + ships: { + type: ShipConnection, + args: connectionArgs, + resolve: (faction, args) => connectionFromArray( + faction.ships.map((id: any) => id), + args + ), + } + }), +}); +// Object Identification +// nodeDefinitions returns the Node interface that objects can implement, and returns the node root field to include on the query type. To implement this, it takes a function to resolve an ID to an object, and to determine the type of a given object. +const resolver: GraphQLTypeResolver = () => { + return new GraphQLObjectType({ + name: "T", + fields: {}, + }); +}; +const idFetcher = (id: string, context: number, info: GraphQLResolveInfo) => { + info.fieldName = "f"; +}; +const nodeDef = nodeDefinitions(idFetcher, resolver); +const fieldConfig: GraphQLFieldConfig = nodeDef.nodeField; +const interfaceType: GraphQLInterfaceType = nodeDef.nodeInterface; +// toGlobalId takes a type name and an ID specific to that type name, and returns a "global ID" that is unique among all types. +toGlobalId("t", "i").toLowerCase(); +// fromGlobalId takes the "global ID" created by toGlobalID, and returns the type name and ID used to create it. +const fgi = fromGlobalId("gid"); +fgi.id.toLowerCase(); +fgi.type.toUpperCase(); +// globalIdField creates the configuration for an id field on a node. +const idFetcher2 = (object: any, context: any, info: GraphQLResolveInfo) => { + return ""; +}; +const gif: GraphQLFieldConfig = globalIdField("t", idFetcher2); +// pluralIdentifyingRootField creates a field that accepts a list of non-ID identifiers (like a username) and maps them to their corresponding objects. +const input: GraphQLInputType = GraphQLString; +const prf: GraphQLFieldConfig = pluralIdentifyingRootField({ + argName: "a", + inputType: input, + outputType: input, + resolveSingleInput: (input: any, context: any, info: GraphQLResolveInfo) => { + return ""; + }, + description: "d", +}); +// An example usage of these methods from the test schema: +const {nodeInterface, nodeField} = nodeDefinitions( + (globalId) => { + var {type, id} = fromGlobalId(globalId); + return "data[type][id]"; + }, + (obj) => { + return obj.ships ? factionType : shipType; + } +); + +const factionType2 = new GraphQLObjectType({ + name: 'Faction', + fields: () => ({ + id: globalIdField(), + }), + interfaces: [nodeInterface] +}); + +const queryType = new GraphQLObjectType({ + name: 'Query', + fields: () => ({ + node: nodeField + }) +}); +// Mutations +// mutationWithClientMutationId takes a name, input fields, output fields, and a mutation method to map from the input fields to the output fields, performing the mutation along the way. It then creates and returns a field configuration that can be used as a top-level field on the mutation type. +const gifcm: GraphQLInputFieldConfigMap = {}; +const gfcm: GraphQLFieldConfigMap = {}; +mutationWithClientMutationId({ + name: "M", + description: "D", + inputFields: gifcm, + mutateAndGetPayload: (object: any, + ctx: any, + info: GraphQLResolveInfo) => { + return new Promise((resolve) => { + resolve(info.fieldName); + }); + }, + outputFields: gfcm, +}); +// An example usage of these methods from the test schema: +const data: any = {}; +var shipMutation = mutationWithClientMutationId({ + name: 'IntroduceShip', + inputFields: { + shipName: { + type: new GraphQLNonNull(GraphQLString) + }, + factionId: { + type: new GraphQLNonNull(GraphQLID) + } + }, + outputFields: { + ship: { + type: shipType, + resolve: (payload) => data['Ship'][payload.shipId] + }, + faction: { + type: factionType, + resolve: (payload) => data['Faction'][payload.factionId] + } + }, + mutateAndGetPayload: ({shipName, factionId}) => { + var newShip = { + id: "11", + name: shipName + }; + // data.Ship[newShip.id] = newShip; + // data.Faction[factionId].ships.push(newShip.id); + return { + shipId: newShip.id, + factionId, + }; + } +}); + +var mutationType = new GraphQLObjectType({ + name: 'Mutation', + fields: () => ({ + introduceShip: shipMutation + }) +}); diff --git a/graphql-relay/index.d.ts b/graphql-relay/index.d.ts new file mode 100644 index 0000000000..bf09906aea --- /dev/null +++ b/graphql-relay/index.d.ts @@ -0,0 +1,310 @@ +// Type definitions for graphql-relay 0.4 +// Project: https://github.com/graphql/graphql-relay-js +// Definitions by: Arvitaly , nitintutlani , Grelinfo +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + + +import { + GraphQLBoolean, + GraphQLInt, + GraphQLNonNull, + GraphQLList, + GraphQLObjectType, + GraphQLString, + GraphQLFieldConfig, + GraphQLInputFieldConfigMap, + GraphQLFieldConfigMap, + GraphQLFieldConfigArgumentMap, + GraphQLResolveInfo, + GraphQLInterfaceType, + GraphQLInputType, + GraphQLOutputType, + GraphQLFieldResolver, + GraphQLTypeResolver, + Thunk +} from "graphql"; + +// connection/connection.js + +/** + * Returns a GraphQLFieldConfigArgumentMap appropriate to include on a field + * whose return type is a connection type with forward pagination. + */ +type ForwardConnectionArgs = { + after: ConnectionCursor; + first: number; +} +export const forwardConnectionArgs: GraphQLFieldConfigArgumentMap & { + after: ConnectionCursor; + first: number; +}; + +/** + * Returns a GraphQLFieldConfigArgumentMap appropriate to include on a field + * whose return type is a connection type with backward pagination. + */ +type BackwardConnectionArgs = { + before: ConnectionCursor; + last: number; +} +export const backwardConnectionArgs: GraphQLFieldConfigArgumentMap & { + before: ConnectionCursor; + last: number; +}; + +/** + * Returns a GraphQLFieldConfigArgumentMap appropriate to include on a field + * whose return type is a connection type with bidirectional pagination. + */ +export const connectionArgs: GraphQLFieldConfigArgumentMap & ForwardConnectionArgs & BackwardConnectionArgs; + +type ConnectionConfig = { + name?: string | null, + nodeType: GraphQLObjectType, + resolveNode?: GraphQLFieldResolver | null, + resolveCursor?: GraphQLFieldResolver | null, + edgeFields?: Thunk> | null, + connectionFields?: Thunk> | null +}; + +type GraphQLConnectionDefinitions = { + edgeType: GraphQLObjectType, + connectionType: GraphQLObjectType +}; + +/** + * Returns a GraphQLObjectType for a connection with the given name, + * and whose nodes are of the specified type. + */ +export function connectionDefinitions( + config: ConnectionConfig +): GraphQLConnectionDefinitions; + +// connection/connectiontypes.js + +/** + * An flow type alias for cursors in this implementation. + */ +export type ConnectionCursor = string; + +/** + * A flow type designed to be exposed as `PageInfo` over GraphQL. + */ +export type PageInfo = { + startCursor: ConnectionCursor, + endCursor: ConnectionCursor, + hasPreviousPage: boolean, + hasNextPage: boolean +}; + +/** + * A flow type designed to be exposed as a `Connection` over GraphQL. + */ +export type Connection = { + edges: Array>; + pageInfo: PageInfo; +}; + +/** + * A flow type designed to be exposed as a `Edge` over GraphQL. + */ +type Edge = { + node: T; + cursor: ConnectionCursor; +}; + +/** + * A flow type describing the arguments a connection field receives in GraphQL. + */ +type ConnectionArguments = { + before?: ConnectionCursor; + after?: ConnectionCursor; + first?: number; + last?: number; +}; + + +// connection/arrayconnection.js + +type ArraySliceMetaInfo = { + sliceStart: number; + arrayLength: number; +}; + +/** + * A simple function that accepts an array and connection arguments, and returns + * a connection object for use in GraphQL. It uses array offsets as pagination, + * so pagination will only work if the array is static. + */ +export function connectionFromArray( + data: Array, + args: ConnectionArguments +): Connection; + +/** + * A version of `connectionFromArray` that takes a promised array, and returns a + * promised connection. + */ +export function connectionFromPromisedArray( + dataPromise: Promise>, + args: ConnectionArguments +): Promise>; + +/** + * Given a slice (subset) of an array, returns a connection object for use in + * GraphQL. + * +* This function is similar to `connectionFromArray`, but is intended for use +* cases where you know the cardinality of the connection, consider it too large +* to materialize the entire array, and instead wish pass in a slice of the +* total result large enough to cover the range specified in `args`. +*/ +export function connectionFromArraySlice( + arraySlice: Array, + args: ConnectionArguments, + meta: ArraySliceMetaInfo +): Connection; + +/** + * A version of `connectionFromArraySlice` that takes a promised array slice, + * and returns a promised connection. + */ +export function connectionFromPromisedArraySlice( + dataPromise: Promise>, + args: ConnectionArguments, + arrayInfo: ArraySliceMetaInfo +): Promise>; + +/** + * Creates the cursor string from an offset. + */ +export function offsetToCursor(offset: number): ConnectionCursor; + +/** + * Rederives the offset from the cursor string. + */ +export function cursorToOffset(cursor: ConnectionCursor): number; + +/** + * Return the cursor associated with an object in an array. + */ +export function cursorForObjectInConnection( + data: Array, + object: T +): ConnectionCursor; + +/** + * Given an optional cursor and a default offset, returns the offset + * to use; if the cursor contains a valid offset, that will be used, + * otherwise it will be the default. + */ +export function getOffsetWithDefault( + cursor?: ConnectionCursor, + defaultOffset?: number +): number; + +// mutation/mutation.js + +type mutationFn = ( + object: any, + ctx: any, + info: GraphQLResolveInfo +) => Promise | any; + +/** + * A description of a mutation consumable by mutationWithClientMutationId + * to create a GraphQLFieldConfig for that mutation. + * + * The inputFields and outputFields should not include `clientMutationId`, + * as this will be provided automatically. + * + * An input object will be created containing the input fields, and an + * object will be created containing the output fields. + * + * mutateAndGetPayload will receieve an Object with a key for each + * input field, and it should return an Object with a key for each + * output field. It may return synchronously, or return a Promise. + */ +type MutationConfig = { + name: string, + description?: string + inputFields: Thunk, + outputFields: Thunk>, + mutateAndGetPayload: mutationFn, +}; + +/** + * Returns a GraphQLFieldConfig for the mutation described by the + * provided MutationConfig. + */ +export function mutationWithClientMutationId( + config: MutationConfig +): GraphQLFieldConfig; + +// node/node.js + +type GraphQLNodeDefinitions = { + nodeInterface: GraphQLInterfaceType, + nodeField: GraphQLFieldConfig +}; + +type typeResolverFn = ((any: any) => GraphQLObjectType) | + ((any: any) => Promise); + +/** + * Given a function to map from an ID to an underlying object, and a function + * to map from an underlying object to the concrete GraphQLObjectType it + * corresponds to, constructs a `Node` interface that objects can implement, + * and a field config for a `node` root field. + * + * If the typeResolver is omitted, object resolution on the interface will be + * handled with the `isTypeOf` method on object types, as with any GraphQL + * interface without a provided `resolveType` method. + */ +export function nodeDefinitions( + idFetcher: ((id: string, context: TContext, info: GraphQLResolveInfo) => any), + typeResolver?: GraphQLTypeResolver +): GraphQLNodeDefinitions; + +type ResolvedGlobalId = { + type: string, + id: string +}; + +/** + * Takes a type name and an ID specific to that type name, and returns a + * "global ID" that is unique among all types. + */ +export function toGlobalId(type: string, id: string): string; + +/** + * Takes the "global ID" created by toGlobalID, and returns the type name and ID + * used to create it. + */ +export function fromGlobalId(globalId: string): ResolvedGlobalId; + +/** + * Creates the configuration for an id field on a node, using `toGlobalId` to + * construct the ID from the provided typename. The type-specific ID is fetched + * by calling idFetcher on the object, or if not provided, by accessing the `id` + * property on the object. + */ +export function globalIdField( + typeName?: string, + idFetcher?: (object: any, context: any, info: GraphQLResolveInfo) => string +): GraphQLFieldConfig; + + +// node/plural.js + +type PluralIdentifyingRootFieldConfig = { + argName: string, + inputType: GraphQLInputType, + outputType: GraphQLOutputType, + resolveSingleInput: (input: any, context: any, info: GraphQLResolveInfo) => any, + description?: string, +}; + +export function pluralIdentifyingRootField( + config: PluralIdentifyingRootFieldConfig +): GraphQLFieldConfig; \ No newline at end of file diff --git a/graphql-relay/tsconfig.json b/graphql-relay/tsconfig.json new file mode 100644 index 0000000000..bf46522ecc --- /dev/null +++ b/graphql-relay/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "noImplicitAny": true, + "strictNullChecks": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [ + "graphql" + ], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "graphql-relay-tests.ts" + ] +} \ No newline at end of file diff --git a/graphql-relay/tslint.json b/graphql-relay/tslint.json new file mode 100644 index 0000000000..377cc837d4 --- /dev/null +++ b/graphql-relay/tslint.json @@ -0,0 +1 @@ +{ "extends": "../tslint.json" }