"A reference to a Function."
scalar FunctionID

extend type Query {
  """
  Load a function by ID.
  """
  loadFunctionFromID(id: FunctionID!): Function!
}

"""
Function represents a resolver provided by a Module.

A function always evaluates against a parent object and is given a set of
named arguments.
"""
type Function {
  "The ID of the function"
  id: FunctionID!

  "The name of the function"
  name: String!

  "The type returned by this function"
  returnType: TypeDef!

  "A doc string for the function, if any"
  description: String

  "Returns the function with the doc string"
  withDescription(description: String!): Function!

  "Arguments accepted by this function, if any"
  args: [FunctionArg!]

  "Returns the function with the provided argument"
  withArg(
    "The name of the argument"
    name: String!
    "The type of the argument"
    typeDef: TypeDefID!
    "A doc string for the argument, if any"
    description: String
    "A default value to use for this argument if not explicitly set by the caller, if any"
    defaultValue: JSON
  ): Function!
}

"A reference to a FunctionArg."
scalar FunctionArgID

extend type Query {
  """
  Load a function argument by ID.
  """
  loadFunctionArgFromID(id: FunctionArgID!): FunctionArg!
}

"""
An argument accepted by a function.

This is a specification for an argument at function definition time, not an
argument passed at function call time.
"""
type FunctionArg {
  "The ID of the argument"
  id: FunctionArgID!

  "The name of the argument"
  name: String!

  "A doc string for the argument, if any"
  description: String

  "The type of the argument"
  typeDef: TypeDef!

  "A default value to use for this argument when not explicitly set by the caller, if any"
  defaultValue: JSON
}

"A reference to a TypeDef."
scalar TypeDefID

extend type Query {
  """
  Create a new TypeDef.
  """
  typeDef: TypeDef!

  """
  Load a TypeDef by ID.
  """
  loadTypeDefFromID(id: TypeDefID!): TypeDef!
}

"""
A definition of a parameter or return type in a Module.
"""
type TypeDef {
  id: TypeDefID!

  "The kind of type this is (e.g. primitive, list, object)"
  kind: TypeDefKind

  "Sets the kind of the type."
  withKind(kind: TypeDefKind!): TypeDef!

  "Whether this type can be set to null. Defaults to false."
  optional: Boolean!

  "Sets whether this type can be set to null."
  withOptional(optional: Boolean!): TypeDef!

  "Returns a TypeDef of kind List with the provided type for its elements."
  withListOf(elementType: TypeDefID!): TypeDef!

  """
  If kind is LIST, the list-specific type definition.
  If kind is not LIST, this will be null.
  """
  asList: ListTypeDef

  """
  Returns a TypeDef of kind Object with the provided name.

  Note that an object's fields and functions may be omitted if the intent is
  only to refer to an object. This is how functions are able to return their
  own object, or any other circular reference.
  """
  withObject(name: String!, description: String): TypeDef!

  """
  Returns a TypeDef of kind Interface with the provided name.
  """
  withInterface(name: String!, description: String): TypeDef!

  "Adds a static field for an Object TypeDef, failing if the type is not an object."
  withField(
    "The name of the field in the object"
    name: String!
    "The type of the field"
    typeDef: TypeDefID!
    "A doc string for the field, if any"
    description: String
  ): TypeDef!

  "Adds a function for an Object or Interface TypeDef, failing if the type is not one of those kinds."
  withFunction(function: FunctionID!): TypeDef!

  "Adds a function for constructing a new instance of an Object TypeDef, failing if the type is not an object."
  withConstructor(function: FunctionID!): TypeDef!

  """
  If kind is OBJECT, the object-specific type definition.
  If kind is not OBJECT, this will be null.
  """
  asObject: ObjectTypeDef

  """
  If kind is INTERFACE, the interface-specific type definition.
  If kind is not INTERFACE, this will be null.
  """
  asInterface: InterfaceTypeDef

  """
  If kind is INPUT, the object-specific type definition.
  If kind is not INPUT, this will be null.
  """
  asInput: InputTypeDef
}

"""
A definition of a custom object defined in a Module.
"""
type ObjectTypeDef {
  "The name of the object"
  name: String!

  "The doc string for the object, if any"
  description: String

  "Static fields defined on this object, if any"
  fields: [FieldTypeDef!]

  "Functions defined on this object, if any"
  functions: [Function!]

  "The function used to construct new instances of this object, if any"
  constructor: Function

  """
  If this ObjectTypeDef is associated with a Module, the name of the module. Unset otherwise.
  """
  sourceModuleName: String
}

"""
A definition of a custom interface defined in a Module.
"""
type InterfaceTypeDef {
  "The name of the interface"
  name: String!

  "The doc string for the interface, if any"
  description: String

  "Functions defined on this interface, if any"
  functions: [Function!]

  """
  If this InterfaceTypeDef is associated with a Module, the name of the module. Unset otherwise.
  """
  sourceModuleName: String
}

"""
A definition of a field on a custom object defined in a Module.
A field on an object has a static value, as opposed to a function on an
object whose value is computed by invoking code (and can accept arguments).
"""
type FieldTypeDef {
  "The name of the field in the object"
  name: String!

  "A doc string for the field, if any"
  description: String

  "The type of the field"
  typeDef: TypeDef!
}

"""
A definition of a list type in a Module.
"""
type ListTypeDef {
  "The type of the elements in the list"
  elementTypeDef: TypeDef!
}

"""
A graphql input type, which is essentially just a group of named args.

This is currently only used to represent pre-existing usage of graphql input types
in the core API. It is not used by user modules and shouldn't ever be as user
module accept input objects via their id rather than graphql input types.
"""
type InputTypeDef {
  "The name of the input type"
  name: String!

  "Fields of the input type, which should only be primitives, other input types or lists of those."
  fields: [FieldTypeDef!]
}

"""
Distinguishes the different kinds of TypeDefs.
"""
enum TypeDefKind {
  "A string value"
  StringKind

  "An integer value"
  IntegerKind

  "A boolean value"
  BooleanKind

  """
  A list of values all having the same type.

  Always paired with a ListTypeDef.
  """
  ListKind

  """
  A named type defined in the GraphQL schema, with fields and functions.

  Always paired with an ObjectTypeDef.
  """
  ObjectKind

  """
  A named type of functions that can be matched+implemented by other objects+interfaces.

  Always paired with an InterfaceTypeDef.
  """
  InterfaceKind

  """
  A special kind used to signify that no value is returned.

  This is used for functions that have no return value. The outer TypeDef
  specifying this Kind is always Optional, as the Void is never actually
  represented.
  """
  VoidKind

  """
  A graphql input type, used only when representing the core API via TypeDefs.
  """
  InputKind
}
