diff --git a/.prettierrc.json b/.prettierrc.json index c4be887..757fd64 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,3 +1,3 @@ { "trailingComma": "es5" -} \ No newline at end of file +} diff --git a/__tests__/__snapshots__/queries.test.js.snap b/__tests__/__snapshots__/queries.test.js.snap index 02c6b9d..dc7af62 100644 --- a/__tests__/__snapshots__/queries.test.js.snap +++ b/__tests__/__snapshots__/queries.test.js.snap @@ -893,6 +893,75 @@ exports[`schema=h query=many-to-many.graphql 1`] = ` } `; +exports[`schema=i query=many-to-many.graphql 1`] = ` +{ + "data": { + "a": { + "nodes": [ + { + "__typename": "InternalEmployee", + "department": "Department1", + "personName": "Person1", + "personType": "EMPLOYEE", + "teamsList": [ + { + "id": 1, + }, + { + "id": 2, + }, + ], + }, + { + "__typename": "ExternalPerson", + "companyName": "Company1", + "personName": "Person2", + "personType": "EXTERNAL", + "teamsList": [ + { + "id": 1, + }, + ], + }, + { + "__typename": "InternalEmployee", + "department": "Department1", + "personName": "Person3", + "personType": "EMPLOYEE", + "teamsList": [], + }, + ], + }, + }, +} +`; + +exports[`schema=j query=many-to-many.graphql 1`] = ` +{ + "data": { + "a": { + "nodes": [ + { + "__typename": "ExternalPerson", + "companyName": "Company1", + "personName": "Person2", + }, + { + "__typename": "InternalEmployee", + "department": "Department1", + "personName": "Person1", + }, + { + "__typename": "InternalEmployee", + "department": "Department2", + "personName": "Person3", + }, + ], + }, + }, +} +`; + exports[`schema=p query=edge-fields.graphql 1`] = ` { "data": { diff --git a/__tests__/helpers.js b/__tests__/helpers.js index 07e4fe9..aad7eb7 100644 --- a/__tests__/helpers.js +++ b/__tests__/helpers.js @@ -32,7 +32,6 @@ const withPgClient = async (url, fn) => { } }; - const getSchemaPath = (sqlSchema) => path.resolve(__dirname, "schemas", sqlSchema); diff --git a/__tests__/integration/schema/__snapshots__/i.test.js.snap b/__tests__/integration/schema/__snapshots__/i.test.js.snap new file mode 100644 index 0000000..0b3ce1a --- /dev/null +++ b/__tests__/integration/schema/__snapshots__/i.test.js.snap @@ -0,0 +1,830 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`prints a schema using the 'i' database schema 1`] = ` +"""A location in a connection that can be used for resuming pagination.""" +scalar Cursor + +type ExternalPerson implements Node & Person { + companyName: String + id: Int! + + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! + personName: String! + personType: PersonType! + + """Reads and enables pagination through a set of \`TeamMembership\`.""" + teamMembershipsByPersonId( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`TeamMembership\`.""" + orderBy: [TeamMembershipsOrderBy!] = [PRIMARY_KEY_ASC] + ): TeamMembershipsConnection! + + """Reads and enables pagination through a set of \`TeamMembership\`.""" + teamMembershipsByPersonIdList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`TeamMembership\`.""" + orderBy: [TeamMembershipsOrderBy!] + ): [TeamMembership!]! + + """Reads and enables pagination through a set of \`Team\`.""" + teams( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`Team\`.""" + orderBy: [TeamsOrderBy!] = [PRIMARY_KEY_ASC] + ): PersonTeamsManyToManyConnection! + + """Reads and enables pagination through a set of \`Team\`.""" + teamsList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`Team\`.""" + orderBy: [TeamsOrderBy!] + ): [Team!]! +} + +type InternalEmployee implements Node & Person { + department: String + id: Int! + + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! + personName: String! + personType: PersonType! + + """Reads and enables pagination through a set of \`TeamMembership\`.""" + teamMembershipsByPersonId( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`TeamMembership\`.""" + orderBy: [TeamMembershipsOrderBy!] = [PRIMARY_KEY_ASC] + ): TeamMembershipsConnection! + + """Reads and enables pagination through a set of \`TeamMembership\`.""" + teamMembershipsByPersonIdList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`TeamMembership\`.""" + orderBy: [TeamMembershipsOrderBy!] + ): [TeamMembership!]! + + """Reads and enables pagination through a set of \`Team\`.""" + teams( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`Team\`.""" + orderBy: [TeamsOrderBy!] = [PRIMARY_KEY_ASC] + ): PersonTeamsManyToManyConnection! + + """Reads and enables pagination through a set of \`Team\`.""" + teamsList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`Team\`.""" + orderBy: [TeamsOrderBy!] + ): [Team!]! +} + +"""An object with a globally unique \`ID\`.""" +interface Node { + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! +} + +"""Information about pagination in a connection.""" +type PageInfo { + """When paginating forwards, the cursor to continue.""" + endCursor: Cursor + + """When paginating forwards, are there more items?""" + hasNextPage: Boolean! + + """When paginating backwards, are there more items?""" + hasPreviousPage: Boolean! + + """When paginating backwards, the cursor to continue.""" + startCursor: Cursor +} + +"""A connection to a list of \`Person\` values.""" +type PeopleConnection { + """ + A list of edges which contains the \`Person\` and cursor to aid in pagination. + """ + edges: [PeopleEdge]! + + """A list of \`Person\` objects.""" + nodes: [Person]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* \`Person\` you could get from the connection.""" + totalCount: Int! +} + +"""A \`Person\` edge in the connection.""" +type PeopleEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`Person\` at the end of the edge.""" + node: Person +} + +"""Methods to use when ordering \`Person\`.""" +enum PeopleOrderBy { + ID_ASC + ID_DESC + NATURAL + PERSON_NAME_ASC + PERSON_NAME_DESC + PERSON_TYPE_ASC + PERSON_TYPE_DESC + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC +} + +interface Person implements Node { + id: Int! + + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! + personName: String! + personType: PersonType! + + """Reads and enables pagination through a set of \`TeamMembership\`.""" + teamMembershipsByPersonId( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + ): TeamMembershipsConnection! + + """Reads and enables pagination through a set of \`TeamMembership\`.""" + teamMembershipsByPersonIdList( + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + ): [TeamMembership!]! + + """Reads and enables pagination through a set of \`Team\`.""" + teams( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + ): PersonTeamsManyToManyConnection! + + """Reads and enables pagination through a set of \`Team\`.""" + teamsList( + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + ): [Team!]! +} + +""" +A condition to be used against \`Person\` object types. All fields are tested for equality and combined with a logical ‘and.’ +""" +input PersonCondition { + """Checks for equality with the object’s \`id\` field.""" + id: Int + + """Checks for equality with the object’s \`personName\` field.""" + personName: String + + """Checks for equality with the object’s \`personType\` field.""" + personType: PersonType +} + +""" +A connection to a list of \`Team\` values, with data from \`TeamMembership\`. +""" +type PersonTeamsManyToManyConnection { + """ + A list of edges which contains the \`Team\`, info from the \`TeamMembership\`, and the cursor to aid in pagination. + """ + edges: [PersonTeamsManyToManyEdge!]! + + """A list of \`Team\` objects.""" + nodes: [Team]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* \`Team\` you could get from the connection.""" + totalCount: Int! +} + +"""A \`Team\` edge in the connection, with data from \`TeamMembership\`.""" +type PersonTeamsManyToManyEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`Team\` at the end of the edge.""" + node: Team +} + +enum PersonType { + EMPLOYEE + EXTERNAL +} + +"""The root query type which gives access points into the data universe.""" +type Query implements Node { + """Reads and enables pagination through a set of \`Person\`.""" + allPeople( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: PersonCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`Person\`.""" + orderBy: [PeopleOrderBy!] = [PRIMARY_KEY_ASC] + ): PeopleConnection + + """Reads a set of \`Person\`.""" + allPeopleList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: PersonCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`Person\`.""" + orderBy: [PeopleOrderBy!] + ): [Person!] + + """Reads and enables pagination through a set of \`TeamMembership\`.""" + allTeamMemberships( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`TeamMembership\`.""" + orderBy: [TeamMembershipsOrderBy!] = [PRIMARY_KEY_ASC] + ): TeamMembershipsConnection + + """Reads a set of \`TeamMembership\`.""" + allTeamMembershipsList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`TeamMembership\`.""" + orderBy: [TeamMembershipsOrderBy!] + ): [TeamMembership!] + + """Reads and enables pagination through a set of \`Team\`.""" + allTeams( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`Team\`.""" + orderBy: [TeamsOrderBy!] = [PRIMARY_KEY_ASC] + ): TeamsConnection + + """Reads a set of \`Team\`.""" + allTeamsList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`Team\`.""" + orderBy: [TeamsOrderBy!] + ): [Team!] + + """Reads a single \`ExternalPerson\` using its globally unique \`ID\`.""" + externalPerson( + """ + The globally unique \`ID\` to be used in selecting a single \`ExternalPerson\`. + """ + nodeId: ID! + ): ExternalPerson + + """Reads a single \`InternalEmployee\` using its globally unique \`ID\`.""" + internalEmployee( + """ + The globally unique \`ID\` to be used in selecting a single \`InternalEmployee\`. + """ + nodeId: ID! + ): InternalEmployee + + """Fetches an object given its globally unique \`ID\`.""" + node( + """The globally unique \`ID\`.""" + nodeId: ID! + ): Node + + """ + The root query type must be a \`Node\` to work well with Relay 1 mutations. This just resolves to \`query\`. + """ + nodeId: ID! + + """ + Exposes the root query type nested one level down. This is helpful for Relay 1 + which can only query top level fields if they are in a particular form. + """ + query: Query! + + """Reads a single \`Team\` using its globally unique \`ID\`.""" + team( + """The globally unique \`ID\` to be used in selecting a single \`Team\`.""" + nodeId: ID! + ): Team + + """Get a single \`Team\`.""" + teamById(id: Int!): Team + + """Reads a single \`TeamMembership\` using its globally unique \`ID\`.""" + teamMembership( + """ + The globally unique \`ID\` to be used in selecting a single \`TeamMembership\`. + """ + nodeId: ID! + ): TeamMembership + + """Get a single \`TeamMembership\`.""" + teamMembershipByPersonIdAndTeamId(personId: Int!, teamId: Int!): TeamMembership +} + +type Team implements Node { + id: Int! + + """Reads and enables pagination through a set of \`Person\`.""" + members( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: PersonCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`Person\`.""" + orderBy: [PeopleOrderBy!] = [PRIMARY_KEY_ASC] + ): TeamMembersManyToManyConnection! + + """Reads and enables pagination through a set of \`Person\`.""" + membersList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: PersonCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`Person\`.""" + orderBy: [PeopleOrderBy!] + ): [Person!]! + + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! + + """Reads and enables pagination through a set of \`TeamMembership\`.""" + teamMembershipsByTeamId( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`TeamMembership\`.""" + orderBy: [TeamMembershipsOrderBy!] = [PRIMARY_KEY_ASC] + ): TeamMembershipsConnection! + + """Reads and enables pagination through a set of \`TeamMembership\`.""" + teamMembershipsByTeamIdList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`TeamMembership\`.""" + orderBy: [TeamMembershipsOrderBy!] + ): [TeamMembership!]! + teamName: String +} + +""" +A condition to be used against \`Team\` object types. All fields are tested for equality and combined with a logical ‘and.’ +""" +input TeamCondition { + """Checks for equality with the object’s \`id\` field.""" + id: Int + + """Checks for equality with the object’s \`teamName\` field.""" + teamName: String +} + +""" +A connection to a list of \`Person\` values, with data from \`TeamMembership\`. +""" +type TeamMembersManyToManyConnection { + """ + A list of edges which contains the \`Person\`, info from the \`TeamMembership\`, and the cursor to aid in pagination. + """ + edges: [TeamMembersManyToManyEdge!]! + + """A list of \`Person\` objects.""" + nodes: [Person]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* \`Person\` you could get from the connection.""" + totalCount: Int! +} + +"""A \`Person\` edge in the connection, with data from \`TeamMembership\`.""" +type TeamMembersManyToManyEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`Person\` at the end of the edge.""" + node: Person +} + +type TeamMembership implements Node { + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! + + """Reads a single \`Person\` that is related to this \`TeamMembership\`.""" + personByPersonId: Person + personId: Int! + + """Reads a single \`Team\` that is related to this \`TeamMembership\`.""" + teamByTeamId: Team + teamId: Int! +} + +""" +A condition to be used against \`TeamMembership\` object types. All fields are +tested for equality and combined with a logical ‘and.’ +""" +input TeamMembershipCondition { + """Checks for equality with the object’s \`personId\` field.""" + personId: Int + + """Checks for equality with the object’s \`teamId\` field.""" + teamId: Int +} + +"""A connection to a list of \`TeamMembership\` values.""" +type TeamMembershipsConnection { + """ + A list of edges which contains the \`TeamMembership\` and cursor to aid in pagination. + """ + edges: [TeamMembershipsEdge]! + + """A list of \`TeamMembership\` objects.""" + nodes: [TeamMembership]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* \`TeamMembership\` you could get from the connection.""" + totalCount: Int! +} + +"""A \`TeamMembership\` edge in the connection.""" +type TeamMembershipsEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`TeamMembership\` at the end of the edge.""" + node: TeamMembership +} + +"""Methods to use when ordering \`TeamMembership\`.""" +enum TeamMembershipsOrderBy { + NATURAL + PERSON_ID_ASC + PERSON_ID_DESC + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC + TEAM_ID_ASC + TEAM_ID_DESC +} + +"""A connection to a list of \`Team\` values.""" +type TeamsConnection { + """ + A list of edges which contains the \`Team\` and cursor to aid in pagination. + """ + edges: [TeamsEdge]! + + """A list of \`Team\` objects.""" + nodes: [Team]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* \`Team\` you could get from the connection.""" + totalCount: Int! +} + +"""A \`Team\` edge in the connection.""" +type TeamsEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`Team\` at the end of the edge.""" + node: Team +} + +"""Methods to use when ordering \`Team\`.""" +enum TeamsOrderBy { + ID_ASC + ID_DESC + NATURAL + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC + TEAM_NAME_ASC + TEAM_NAME_DESC +} +`; diff --git a/__tests__/integration/schema/__snapshots__/j.test.js.snap b/__tests__/integration/schema/__snapshots__/j.test.js.snap new file mode 100644 index 0000000..1c2d6f8 --- /dev/null +++ b/__tests__/integration/schema/__snapshots__/j.test.js.snap @@ -0,0 +1,1419 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`prints a schema using the 'j' database schema 1`] = ` +"""A location in a connection that can be used for resuming pagination.""" +scalar Cursor + +"""A connection to a list of \`ExternalPerson\` values.""" +type ExternalPeopleConnection { + """ + A list of edges which contains the \`ExternalPerson\` and cursor to aid in pagination. + """ + edges: [ExternalPeopleEdge]! + + """A list of \`ExternalPerson\` objects.""" + nodes: [ExternalPerson]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* \`ExternalPerson\` you could get from the connection.""" + totalCount: Int! +} + +"""A \`ExternalPerson\` edge in the connection.""" +type ExternalPeopleEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`ExternalPerson\` at the end of the edge.""" + node: ExternalPerson +} + +"""Methods to use when ordering \`ExternalPerson\`.""" +enum ExternalPeopleOrderBy { + COMPANY_NAME_ASC + COMPANY_NAME_DESC + ID_ASC + ID_DESC + NATURAL + PERSON_NAME_ASC + PERSON_NAME_DESC + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC +} + +type ExternalPerson implements Node & Person { + companyName: String + + """ + Reads and enables pagination through a set of \`ExternalTeamMembership\`. + """ + externalTeamMembershipsByPersonId( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: ExternalTeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`ExternalTeamMembership\`.""" + orderBy: [ExternalTeamMembershipsOrderBy!] = [PRIMARY_KEY_ASC] + ): ExternalTeamMembershipsConnection! + + """ + Reads and enables pagination through a set of \`ExternalTeamMembership\`. + """ + externalTeamMembershipsByPersonIdList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: ExternalTeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`ExternalTeamMembership\`.""" + orderBy: [ExternalTeamMembershipsOrderBy!] + ): [ExternalTeamMembership!]! + id: Int! + + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! + personName: String! + + """Reads and enables pagination through a set of \`Team\`.""" + teamsByExternalTeamMembershipPersonIdAndTeamId( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`Team\`.""" + orderBy: [TeamsOrderBy!] = [PRIMARY_KEY_ASC] + ): ExternalPersonTeamsByExternalTeamMembershipPersonIdAndTeamIdManyToManyConnection! + + """Reads and enables pagination through a set of \`Team\`.""" + teamsByExternalTeamMembershipPersonIdAndTeamIdList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`Team\`.""" + orderBy: [TeamsOrderBy!] + ): [Team!]! +} + +""" +A condition to be used against \`ExternalPerson\` object types. All fields are +tested for equality and combined with a logical ‘and.’ +""" +input ExternalPersonCondition { + """Checks for equality with the object’s \`companyName\` field.""" + companyName: String + + """Checks for equality with the object’s \`id\` field.""" + id: Int + + """Checks for equality with the object’s \`personName\` field.""" + personName: String +} + +""" +A connection to a list of \`Team\` values, with data from \`ExternalTeamMembership\`. +""" +type ExternalPersonTeamsByExternalTeamMembershipPersonIdAndTeamIdManyToManyConnection { + """ + A list of edges which contains the \`Team\`, info from the \`ExternalTeamMembership\`, and the cursor to aid in pagination. + """ + edges: [ExternalPersonTeamsByExternalTeamMembershipPersonIdAndTeamIdManyToManyEdge!]! + + """A list of \`Team\` objects.""" + nodes: [Team]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* \`Team\` you could get from the connection.""" + totalCount: Int! +} + +""" +A \`Team\` edge in the connection, with data from \`ExternalTeamMembership\`. +""" +type ExternalPersonTeamsByExternalTeamMembershipPersonIdAndTeamIdManyToManyEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`Team\` at the end of the edge.""" + node: Team +} + +type ExternalTeamMembership implements Node & TeamMembership { + """ + Reads a single \`ExternalPerson\` that is related to this \`ExternalTeamMembership\`. + """ + externalPersonByPersonId: ExternalPerson + + """ + Reads a single \`ExternalPerson\` that is related to this \`ExternalTeamMembership\`. + """ + member: ExternalPerson + + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! + personId: Int! + + """ + Reads a single \`Team\` that is related to this \`ExternalTeamMembership\`. + """ + team: Team + + """ + Reads a single \`Team\` that is related to this \`ExternalTeamMembership\`. + """ + teamByTeamId: Team + teamId: Int! +} + +""" +A condition to be used against \`ExternalTeamMembership\` object types. All fields +are tested for equality and combined with a logical ‘and.’ +""" +input ExternalTeamMembershipCondition { + """Checks for equality with the object’s \`personId\` field.""" + personId: Int + + """Checks for equality with the object’s \`teamId\` field.""" + teamId: Int +} + +"""A connection to a list of \`ExternalTeamMembership\` values.""" +type ExternalTeamMembershipsConnection { + """ + A list of edges which contains the \`ExternalTeamMembership\` and cursor to aid in pagination. + """ + edges: [ExternalTeamMembershipsEdge]! + + """A list of \`ExternalTeamMembership\` objects.""" + nodes: [ExternalTeamMembership]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """ + The count of *all* \`ExternalTeamMembership\` you could get from the connection. + """ + totalCount: Int! +} + +"""A \`ExternalTeamMembership\` edge in the connection.""" +type ExternalTeamMembershipsEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`ExternalTeamMembership\` at the end of the edge.""" + node: ExternalTeamMembership +} + +"""Methods to use when ordering \`ExternalTeamMembership\`.""" +enum ExternalTeamMembershipsOrderBy { + NATURAL + PERSON_ID_ASC + PERSON_ID_DESC + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC + TEAM_ID_ASC + TEAM_ID_DESC +} + +type InternalEmployee implements Node & Person { + department: String + id: Int! + + """ + Reads and enables pagination through a set of \`InternalTeamMembership\`. + """ + internalTeamMembershipsByPersonId( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: InternalTeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`InternalTeamMembership\`.""" + orderBy: [InternalTeamMembershipsOrderBy!] = [PRIMARY_KEY_ASC] + ): InternalTeamMembershipsConnection! + + """ + Reads and enables pagination through a set of \`InternalTeamMembership\`. + """ + internalTeamMembershipsByPersonIdList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: InternalTeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`InternalTeamMembership\`.""" + orderBy: [InternalTeamMembershipsOrderBy!] + ): [InternalTeamMembership!]! + + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! + personName: String! + + """Reads and enables pagination through a set of \`Team\`.""" + teamsByInternalTeamMembershipPersonIdAndTeamId( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`Team\`.""" + orderBy: [TeamsOrderBy!] = [PRIMARY_KEY_ASC] + ): InternalEmployeeTeamsByInternalTeamMembershipPersonIdAndTeamIdManyToManyConnection! + + """Reads and enables pagination through a set of \`Team\`.""" + teamsByInternalTeamMembershipPersonIdAndTeamIdList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`Team\`.""" + orderBy: [TeamsOrderBy!] + ): [Team!]! +} + +""" +A condition to be used against \`InternalEmployee\` object types. All fields are +tested for equality and combined with a logical ‘and.’ +""" +input InternalEmployeeCondition { + """Checks for equality with the object’s \`department\` field.""" + department: String + + """Checks for equality with the object’s \`id\` field.""" + id: Int + + """Checks for equality with the object’s \`personName\` field.""" + personName: String +} + +""" +A connection to a list of \`Team\` values, with data from \`InternalTeamMembership\`. +""" +type InternalEmployeeTeamsByInternalTeamMembershipPersonIdAndTeamIdManyToManyConnection { + """ + A list of edges which contains the \`Team\`, info from the \`InternalTeamMembership\`, and the cursor to aid in pagination. + """ + edges: [InternalEmployeeTeamsByInternalTeamMembershipPersonIdAndTeamIdManyToManyEdge!]! + + """A list of \`Team\` objects.""" + nodes: [Team]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* \`Team\` you could get from the connection.""" + totalCount: Int! +} + +""" +A \`Team\` edge in the connection, with data from \`InternalTeamMembership\`. +""" +type InternalEmployeeTeamsByInternalTeamMembershipPersonIdAndTeamIdManyToManyEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`Team\` at the end of the edge.""" + node: Team +} + +"""A connection to a list of \`InternalEmployee\` values.""" +type InternalEmployeesConnection { + """ + A list of edges which contains the \`InternalEmployee\` and cursor to aid in pagination. + """ + edges: [InternalEmployeesEdge]! + + """A list of \`InternalEmployee\` objects.""" + nodes: [InternalEmployee]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """ + The count of *all* \`InternalEmployee\` you could get from the connection. + """ + totalCount: Int! +} + +"""A \`InternalEmployee\` edge in the connection.""" +type InternalEmployeesEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`InternalEmployee\` at the end of the edge.""" + node: InternalEmployee +} + +"""Methods to use when ordering \`InternalEmployee\`.""" +enum InternalEmployeesOrderBy { + DEPARTMENT_ASC + DEPARTMENT_DESC + ID_ASC + ID_DESC + NATURAL + PERSON_NAME_ASC + PERSON_NAME_DESC + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC +} + +type InternalTeamMembership implements Node & TeamMembership { + """ + Reads a single \`InternalEmployee\` that is related to this \`InternalTeamMembership\`. + """ + internalEmployeeByPersonId: InternalEmployee + + """ + Reads a single \`InternalEmployee\` that is related to this \`InternalTeamMembership\`. + """ + member: InternalEmployee + + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! + personId: Int! + + """ + Reads a single \`Team\` that is related to this \`InternalTeamMembership\`. + """ + team: Team + + """ + Reads a single \`Team\` that is related to this \`InternalTeamMembership\`. + """ + teamByTeamId: Team + teamId: Int! +} + +""" +A condition to be used against \`InternalTeamMembership\` object types. All fields +are tested for equality and combined with a logical ‘and.’ +""" +input InternalTeamMembershipCondition { + """Checks for equality with the object’s \`personId\` field.""" + personId: Int + + """Checks for equality with the object’s \`teamId\` field.""" + teamId: Int +} + +"""A connection to a list of \`InternalTeamMembership\` values.""" +type InternalTeamMembershipsConnection { + """ + A list of edges which contains the \`InternalTeamMembership\` and cursor to aid in pagination. + """ + edges: [InternalTeamMembershipsEdge]! + + """A list of \`InternalTeamMembership\` objects.""" + nodes: [InternalTeamMembership]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """ + The count of *all* \`InternalTeamMembership\` you could get from the connection. + """ + totalCount: Int! +} + +"""A \`InternalTeamMembership\` edge in the connection.""" +type InternalTeamMembershipsEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`InternalTeamMembership\` at the end of the edge.""" + node: InternalTeamMembership +} + +"""Methods to use when ordering \`InternalTeamMembership\`.""" +enum InternalTeamMembershipsOrderBy { + NATURAL + PERSON_ID_ASC + PERSON_ID_DESC + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC + TEAM_ID_ASC + TEAM_ID_DESC +} + +"""An object with a globally unique \`ID\`.""" +interface Node { + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! +} + +"""Information about pagination in a connection.""" +type PageInfo { + """When paginating forwards, the cursor to continue.""" + endCursor: Cursor + + """When paginating forwards, are there more items?""" + hasNextPage: Boolean! + + """When paginating backwards, are there more items?""" + hasPreviousPage: Boolean! + + """When paginating backwards, the cursor to continue.""" + startCursor: Cursor +} + +"""A connection to a list of \`Person\` values.""" +type PeopleConnection { + """ + A list of edges which contains the \`Person\` and cursor to aid in pagination. + """ + edges: [PeopleEdge]! + + """A list of \`Person\` objects.""" + nodes: [Person]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* \`Person\` you could get from the connection.""" + totalCount: Int! +} + +"""A \`Person\` edge in the connection.""" +type PeopleEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`Person\` at the end of the edge.""" + node: Person +} + +"""Methods to use when ordering \`Person\`.""" +enum PeopleOrderBy { + ID_ASC + ID_DESC + NATURAL + PERSON_NAME_ASC + PERSON_NAME_DESC +} + +interface Person implements Node { + id: Int + + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! + personName: String +} + +""" +A condition to be used against \`Person\` object types. All fields are tested for equality and combined with a logical ‘and.’ +""" +input PersonCondition { + """Checks for equality with the object’s \`id\` field.""" + id: Int + + """Checks for equality with the object’s \`personName\` field.""" + personName: String +} + +enum PersonType { + ExternalPerson + InternalEmployee +} + +"""The root query type which gives access points into the data universe.""" +type Query implements Node { + """Reads and enables pagination through a set of \`ExternalPerson\`.""" + allExternalPeople( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: ExternalPersonCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`ExternalPerson\`.""" + orderBy: [ExternalPeopleOrderBy!] = [PRIMARY_KEY_ASC] + ): ExternalPeopleConnection + + """Reads a set of \`ExternalPerson\`.""" + allExternalPeopleList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: ExternalPersonCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`ExternalPerson\`.""" + orderBy: [ExternalPeopleOrderBy!] + ): [ExternalPerson!] + + """ + Reads and enables pagination through a set of \`ExternalTeamMembership\`. + """ + allExternalTeamMemberships( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: ExternalTeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`ExternalTeamMembership\`.""" + orderBy: [ExternalTeamMembershipsOrderBy!] = [PRIMARY_KEY_ASC] + ): ExternalTeamMembershipsConnection + + """Reads a set of \`ExternalTeamMembership\`.""" + allExternalTeamMembershipsList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: ExternalTeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`ExternalTeamMembership\`.""" + orderBy: [ExternalTeamMembershipsOrderBy!] + ): [ExternalTeamMembership!] + + """Reads and enables pagination through a set of \`InternalEmployee\`.""" + allInternalEmployees( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: InternalEmployeeCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`InternalEmployee\`.""" + orderBy: [InternalEmployeesOrderBy!] = [PRIMARY_KEY_ASC] + ): InternalEmployeesConnection + + """Reads a set of \`InternalEmployee\`.""" + allInternalEmployeesList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: InternalEmployeeCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`InternalEmployee\`.""" + orderBy: [InternalEmployeesOrderBy!] + ): [InternalEmployee!] + + """ + Reads and enables pagination through a set of \`InternalTeamMembership\`. + """ + allInternalTeamMemberships( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: InternalTeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`InternalTeamMembership\`.""" + orderBy: [InternalTeamMembershipsOrderBy!] = [PRIMARY_KEY_ASC] + ): InternalTeamMembershipsConnection + + """Reads a set of \`InternalTeamMembership\`.""" + allInternalTeamMembershipsList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: InternalTeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`InternalTeamMembership\`.""" + orderBy: [InternalTeamMembershipsOrderBy!] + ): [InternalTeamMembership!] + allPeople( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: PersonCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """Filter results to only those of the given types""" + only: [PersonType!] @deprecated(reason: "EXPERIMENTAL") + + """The method to use when ordering \`Person\`.""" + orderBy: [PeopleOrderBy!] + ): PeopleConnection + allPeopleList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: PersonCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """Filter results to only those of the given types""" + only: [PersonType!] @deprecated(reason: "EXPERIMENTAL") + + """The method to use when ordering \`Person\`.""" + orderBy: [PeopleOrderBy!] + ): [Person!] + allTeamMemberships( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """Filter results to only those of the given types""" + only: [TeamMembershipType!] @deprecated(reason: "EXPERIMENTAL") + + """The method to use when ordering \`TeamMembership\`.""" + orderBy: [TeamMembershipsOrderBy!] + ): TeamMembershipsConnection + allTeamMembershipsList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """Filter results to only those of the given types""" + only: [TeamMembershipType!] @deprecated(reason: "EXPERIMENTAL") + + """The method to use when ordering \`TeamMembership\`.""" + orderBy: [TeamMembershipsOrderBy!] + ): [TeamMembership!] + + """Reads and enables pagination through a set of \`Team\`.""" + allTeams( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`Team\`.""" + orderBy: [TeamsOrderBy!] = [PRIMARY_KEY_ASC] + ): TeamsConnection + + """Reads a set of \`Team\`.""" + allTeamsList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TeamCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`Team\`.""" + orderBy: [TeamsOrderBy!] + ): [Team!] + + """Reads a single \`ExternalPerson\` using its globally unique \`ID\`.""" + externalPerson( + """ + The globally unique \`ID\` to be used in selecting a single \`ExternalPerson\`. + """ + nodeId: ID! + ): ExternalPerson + + """Get a single \`ExternalPerson\`.""" + externalPersonById(id: Int!): ExternalPerson + + """ + Reads a single \`ExternalTeamMembership\` using its globally unique \`ID\`. + """ + externalTeamMembership( + """ + The globally unique \`ID\` to be used in selecting a single \`ExternalTeamMembership\`. + """ + nodeId: ID! + ): ExternalTeamMembership + + """Get a single \`ExternalTeamMembership\`.""" + externalTeamMembershipByPersonIdAndTeamId(personId: Int!, teamId: Int!): ExternalTeamMembership + + """Reads a single \`InternalEmployee\` using its globally unique \`ID\`.""" + internalEmployee( + """ + The globally unique \`ID\` to be used in selecting a single \`InternalEmployee\`. + """ + nodeId: ID! + ): InternalEmployee + + """Get a single \`InternalEmployee\`.""" + internalEmployeeById(id: Int!): InternalEmployee + + """ + Reads a single \`InternalTeamMembership\` using its globally unique \`ID\`. + """ + internalTeamMembership( + """ + The globally unique \`ID\` to be used in selecting a single \`InternalTeamMembership\`. + """ + nodeId: ID! + ): InternalTeamMembership + + """Get a single \`InternalTeamMembership\`.""" + internalTeamMembershipByPersonIdAndTeamId(personId: Int!, teamId: Int!): InternalTeamMembership + + """Fetches an object given its globally unique \`ID\`.""" + node( + """The globally unique \`ID\`.""" + nodeId: ID! + ): Node + + """ + The root query type must be a \`Node\` to work well with Relay 1 mutations. This just resolves to \`query\`. + """ + nodeId: ID! + + """ + Exposes the root query type nested one level down. This is helpful for Relay 1 + which can only query top level fields if they are in a particular form. + """ + query: Query! + + """Reads a single \`Team\` using its globally unique \`ID\`.""" + team( + """The globally unique \`ID\` to be used in selecting a single \`Team\`.""" + nodeId: ID! + ): Team + + """Get a single \`Team\`.""" + teamById(id: Int!): Team +} + +type Team implements Node { + """Reads and enables pagination through a set of \`ExternalPerson\`.""" + externalPeopleByExternalTeamMembershipTeamIdAndPersonId( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: ExternalPersonCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`ExternalPerson\`.""" + orderBy: [ExternalPeopleOrderBy!] = [PRIMARY_KEY_ASC] + ): TeamExternalPeopleByExternalTeamMembershipTeamIdAndPersonIdManyToManyConnection! + + """Reads and enables pagination through a set of \`ExternalPerson\`.""" + externalPeopleByExternalTeamMembershipTeamIdAndPersonIdList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: ExternalPersonCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`ExternalPerson\`.""" + orderBy: [ExternalPeopleOrderBy!] + ): [ExternalPerson!]! + + """ + Reads and enables pagination through a set of \`ExternalTeamMembership\`. + """ + externalTeamMembershipsByTeamId( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: ExternalTeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`ExternalTeamMembership\`.""" + orderBy: [ExternalTeamMembershipsOrderBy!] = [PRIMARY_KEY_ASC] + ): ExternalTeamMembershipsConnection! + + """ + Reads and enables pagination through a set of \`ExternalTeamMembership\`. + """ + externalTeamMembershipsByTeamIdList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: ExternalTeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`ExternalTeamMembership\`.""" + orderBy: [ExternalTeamMembershipsOrderBy!] + ): [ExternalTeamMembership!]! + id: Int! + + """Reads and enables pagination through a set of \`InternalEmployee\`.""" + internalEmployeesByInternalTeamMembershipTeamIdAndPersonId( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: InternalEmployeeCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`InternalEmployee\`.""" + orderBy: [InternalEmployeesOrderBy!] = [PRIMARY_KEY_ASC] + ): TeamInternalEmployeesByInternalTeamMembershipTeamIdAndPersonIdManyToManyConnection! + + """Reads and enables pagination through a set of \`InternalEmployee\`.""" + internalEmployeesByInternalTeamMembershipTeamIdAndPersonIdList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: InternalEmployeeCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`InternalEmployee\`.""" + orderBy: [InternalEmployeesOrderBy!] + ): [InternalEmployee!]! + + """ + Reads and enables pagination through a set of \`InternalTeamMembership\`. + """ + internalTeamMembershipsByTeamId( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: InternalTeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Only read the last \`n\` values of the set.""" + last: Int + + """ + Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor + based pagination. May not be used with \`last\`. + """ + offset: Int + + """The method to use when ordering \`InternalTeamMembership\`.""" + orderBy: [InternalTeamMembershipsOrderBy!] = [PRIMARY_KEY_ASC] + ): InternalTeamMembershipsConnection! + + """ + Reads and enables pagination through a set of \`InternalTeamMembership\`. + """ + internalTeamMembershipsByTeamIdList( + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: InternalTeamMembershipCondition + + """Only read the first \`n\` values of the set.""" + first: Int + + """Skip the first \`n\` values.""" + offset: Int + + """The method to use when ordering \`InternalTeamMembership\`.""" + orderBy: [InternalTeamMembershipsOrderBy!] + ): [InternalTeamMembership!]! + + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! + teamName: String +} + +""" +A condition to be used against \`Team\` object types. All fields are tested for equality and combined with a logical ‘and.’ +""" +input TeamCondition { + """Checks for equality with the object’s \`id\` field.""" + id: Int + + """Checks for equality with the object’s \`teamName\` field.""" + teamName: String +} + +""" +A connection to a list of \`ExternalPerson\` values, with data from \`ExternalTeamMembership\`. +""" +type TeamExternalPeopleByExternalTeamMembershipTeamIdAndPersonIdManyToManyConnection { + """ + A list of edges which contains the \`ExternalPerson\`, info from the \`ExternalTeamMembership\`, and the cursor to aid in pagination. + """ + edges: [TeamExternalPeopleByExternalTeamMembershipTeamIdAndPersonIdManyToManyEdge!]! + + """A list of \`ExternalPerson\` objects.""" + nodes: [ExternalPerson]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* \`ExternalPerson\` you could get from the connection.""" + totalCount: Int! +} + +""" +A \`ExternalPerson\` edge in the connection, with data from \`ExternalTeamMembership\`. +""" +type TeamExternalPeopleByExternalTeamMembershipTeamIdAndPersonIdManyToManyEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`ExternalPerson\` at the end of the edge.""" + node: ExternalPerson +} + +""" +A connection to a list of \`InternalEmployee\` values, with data from \`InternalTeamMembership\`. +""" +type TeamInternalEmployeesByInternalTeamMembershipTeamIdAndPersonIdManyToManyConnection { + """ + A list of edges which contains the \`InternalEmployee\`, info from the \`InternalTeamMembership\`, and the cursor to aid in pagination. + """ + edges: [TeamInternalEmployeesByInternalTeamMembershipTeamIdAndPersonIdManyToManyEdge!]! + + """A list of \`InternalEmployee\` objects.""" + nodes: [InternalEmployee]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """ + The count of *all* \`InternalEmployee\` you could get from the connection. + """ + totalCount: Int! +} + +""" +A \`InternalEmployee\` edge in the connection, with data from \`InternalTeamMembership\`. +""" +type TeamInternalEmployeesByInternalTeamMembershipTeamIdAndPersonIdManyToManyEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`InternalEmployee\` at the end of the edge.""" + node: InternalEmployee +} + +interface TeamMembership implements Node { + """Reads a single \`Person\` that is related to this \`TeamMembership\`.""" + member: Person + + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + nodeId: ID! + personId: Int + + """Reads a single \`Team\` that is related to this \`TeamMembership\`.""" + team: Team + teamId: Int +} + +""" +A condition to be used against \`TeamMembership\` object types. All fields are +tested for equality and combined with a logical ‘and.’ +""" +input TeamMembershipCondition { + """Checks for equality with the object’s \`personId\` field.""" + personId: Int + + """Checks for equality with the object’s \`teamId\` field.""" + teamId: Int +} + +enum TeamMembershipType { + ExternalTeamMembership + InternalTeamMembership +} + +"""A connection to a list of \`TeamMembership\` values.""" +type TeamMembershipsConnection { + """ + A list of edges which contains the \`TeamMembership\` and cursor to aid in pagination. + """ + edges: [TeamMembershipsEdge]! + + """A list of \`TeamMembership\` objects.""" + nodes: [TeamMembership]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* \`TeamMembership\` you could get from the connection.""" + totalCount: Int! +} + +"""A \`TeamMembership\` edge in the connection.""" +type TeamMembershipsEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`TeamMembership\` at the end of the edge.""" + node: TeamMembership +} + +"""Methods to use when ordering \`TeamMembership\`.""" +enum TeamMembershipsOrderBy { + NATURAL + PERSON_ID_ASC + PERSON_ID_DESC + TEAM_ID_ASC + TEAM_ID_DESC +} + +"""A connection to a list of \`Team\` values.""" +type TeamsConnection { + """ + A list of edges which contains the \`Team\` and cursor to aid in pagination. + """ + edges: [TeamsEdge]! + + """A list of \`Team\` objects.""" + nodes: [Team]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* \`Team\` you could get from the connection.""" + totalCount: Int! +} + +"""A \`Team\` edge in the connection.""" +type TeamsEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The \`Team\` at the end of the edge.""" + node: Team +} + +"""Methods to use when ordering \`Team\`.""" +enum TeamsOrderBy { + ID_ASC + ID_DESC + NATURAL + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC + TEAM_NAME_ASC + TEAM_NAME_DESC +} +`; diff --git a/__tests__/integration/schema/i.test.js b/__tests__/integration/schema/i.test.js new file mode 100644 index 0000000..1924768 --- /dev/null +++ b/__tests__/integration/schema/i.test.js @@ -0,0 +1,7 @@ +const { getSchemaConfig } = require("../../helpers"); +const core = require("./core"); + +test("prints a schema using the 'i' database schema", async () => { + const config = await getSchemaConfig("i"); + return core.test(["i"], config)(); +}); diff --git a/__tests__/integration/schema/j.test.js b/__tests__/integration/schema/j.test.js new file mode 100644 index 0000000..493901a --- /dev/null +++ b/__tests__/integration/schema/j.test.js @@ -0,0 +1,7 @@ +const { getSchemaConfig } = require("../../helpers"); +const core = require("./core"); + +test("prints a schema using the 'j' database schema", async () => { + const config = await getSchemaConfig("j"); + return core.test(["j"], config)(); +}); diff --git a/__tests__/schemas/a/fixtures/queries/edges.graphql b/__tests__/schemas/a/fixtures/queries/edges.graphql index 5e93d0e..8aaeb63 100644 --- a/__tests__/schemas/a/fixtures/queries/edges.graphql +++ b/__tests__/schemas/a/fixtures/queries/edges.graphql @@ -19,4 +19,4 @@ } } } -} \ No newline at end of file +} diff --git a/__tests__/schemas/a/fixtures/queries/nodes.graphql b/__tests__/schemas/a/fixtures/queries/nodes.graphql index 3cf1688..3817b80 100644 --- a/__tests__/schemas/a/fixtures/queries/nodes.graphql +++ b/__tests__/schemas/a/fixtures/queries/nodes.graphql @@ -16,4 +16,4 @@ } } } -} \ No newline at end of file +} diff --git a/__tests__/schemas/b/fixtures/queries/edges.graphql b/__tests__/schemas/b/fixtures/queries/edges.graphql index 5807f3e..f0c5335 100644 --- a/__tests__/schemas/b/fixtures/queries/edges.graphql +++ b/__tests__/schemas/b/fixtures/queries/edges.graphql @@ -20,4 +20,4 @@ } } } -} \ No newline at end of file +} diff --git a/__tests__/schemas/b/fixtures/queries/nodes.graphql b/__tests__/schemas/b/fixtures/queries/nodes.graphql index 3cf1688..3817b80 100644 --- a/__tests__/schemas/b/fixtures/queries/nodes.graphql +++ b/__tests__/schemas/b/fixtures/queries/nodes.graphql @@ -16,4 +16,4 @@ } } } -} \ No newline at end of file +} diff --git a/__tests__/schemas/c/fixtures/queries/edges.graphql b/__tests__/schemas/c/fixtures/queries/edges.graphql index caae850..05c7683 100644 --- a/__tests__/schemas/c/fixtures/queries/edges.graphql +++ b/__tests__/schemas/c/fixtures/queries/edges.graphql @@ -34,4 +34,4 @@ } } } -} \ No newline at end of file +} diff --git a/__tests__/schemas/c/fixtures/queries/nodes.graphql b/__tests__/schemas/c/fixtures/queries/nodes.graphql index 3cf1688..3817b80 100644 --- a/__tests__/schemas/c/fixtures/queries/nodes.graphql +++ b/__tests__/schemas/c/fixtures/queries/nodes.graphql @@ -16,4 +16,4 @@ } } } -} \ No newline at end of file +} diff --git a/__tests__/schemas/d/fixtures/queries/edges.graphql b/__tests__/schemas/d/fixtures/queries/edges.graphql index 9062d8f..b12c34d 100644 --- a/__tests__/schemas/d/fixtures/queries/edges.graphql +++ b/__tests__/schemas/d/fixtures/queries/edges.graphql @@ -35,4 +35,4 @@ } } } -} \ No newline at end of file +} diff --git a/__tests__/schemas/d/fixtures/queries/nodes.graphql b/__tests__/schemas/d/fixtures/queries/nodes.graphql index 3cf1688..3817b80 100644 --- a/__tests__/schemas/d/fixtures/queries/nodes.graphql +++ b/__tests__/schemas/d/fixtures/queries/nodes.graphql @@ -16,4 +16,4 @@ } } } -} \ No newline at end of file +} diff --git a/__tests__/schemas/h/config.json b/__tests__/schemas/h/config.json index ce90c1b..99b410c 100644 --- a/__tests__/schemas/h/config.json +++ b/__tests__/schemas/h/config.json @@ -1,4 +1,4 @@ { - "disableDefaultMutations": true, - "simpleCollections": "both" - } + "disableDefaultMutations": true, + "simpleCollections": "both" +} diff --git a/__tests__/schemas/i/config.json b/__tests__/schemas/i/config.json new file mode 100644 index 0000000..99b410c --- /dev/null +++ b/__tests__/schemas/i/config.json @@ -0,0 +1,4 @@ +{ + "disableDefaultMutations": true, + "simpleCollections": "both" +} diff --git a/__tests__/schemas/i/data.sql b/__tests__/schemas/i/data.sql new file mode 100644 index 0000000..d1cde94 --- /dev/null +++ b/__tests__/schemas/i/data.sql @@ -0,0 +1,21 @@ +insert into i.person (id, person_type, person_name, department, company_name) values + (1, 'EMPLOYEE', 'Person1', 'Department1', null), + (2, 'EXTERNAL', 'Person2', null, 'Company1'), + (3, 'EMPLOYEE', 'Person3', 'Department1', null); + +insert into i.team (id, team_name) values + (1, 'Team1'), + (2, 'Team2'), + (3, 'Team3'); + +insert into i.team_membership (person_id, team_id) values + (1, 1), + (1, 2), + (2, 1); + +-- Person1: [Team1,Team2] +-- Person2: [Team1] +-- Person3: [] +-- Team1: [Person1,Person2] +-- Team2: [Person1] +-- Team3: [] diff --git a/__tests__/schemas/i/fixtures/queries/many-to-many.graphql b/__tests__/schemas/i/fixtures/queries/many-to-many.graphql new file mode 100644 index 0000000..64bb0de --- /dev/null +++ b/__tests__/schemas/i/fixtures/queries/many-to-many.graphql @@ -0,0 +1,19 @@ +query { + a: allPeople { + nodes { + __typename + personType + personName + # Ensure that we can access the polymorphic join table from the person interface + teamsList { + id + } + ... on InternalEmployee { + department + } + ... on ExternalPerson { + companyName + } + } + } +} diff --git a/__tests__/schemas/i/schema.sql b/__tests__/schemas/i/schema.sql new file mode 100644 index 0000000..c60609b --- /dev/null +++ b/__tests__/schemas/i/schema.sql @@ -0,0 +1,37 @@ +drop schema if exists i cascade; + +create schema i; + +create type i.person_type as ENUM ('EMPLOYEE', 'EXTERNAL'); + +create table i.person ( + id int primary key, + person_type i.person_type not null, + person_name text not null, + department text, + company_name text +); + +-- Both internal and external employees can be part of a team +create table i.team ( + id int primary key, + team_name text +); + +create table i.team_membership ( + person_id int, + team_id int, + primary key (person_id, team_id), + constraint team_membership_person_id_fkey foreign key (person_id) references i.person (id), + constraint team_membership_team_id_fkey foreign key (team_id) references i.team (id) +); + +comment on table i.person is $$ +@interface mode:single type:person_type +@type EMPLOYEE name:InternalEmployee attributes:department +@type EXTERNAL name:ExternalPerson attributes:company_name +$$; + + +comment on constraint team_membership_person_id_fkey on i.team_membership is E'@manyToManyFieldName members'; +comment on constraint team_membership_team_id_fkey on i.team_membership is E'@manyToManyFieldName teams'; diff --git a/__tests__/schemas/j/config.json b/__tests__/schemas/j/config.json new file mode 100644 index 0000000..99b410c --- /dev/null +++ b/__tests__/schemas/j/config.json @@ -0,0 +1,4 @@ +{ + "disableDefaultMutations": true, + "simpleCollections": "both" +} diff --git a/__tests__/schemas/j/data.sql b/__tests__/schemas/j/data.sql new file mode 100644 index 0000000..cd89626 --- /dev/null +++ b/__tests__/schemas/j/data.sql @@ -0,0 +1,25 @@ +insert into j.internal_employee (id, person_name, department) values + (1, 'Person1', 'Department1'), + (3, 'Person3', 'Department2'); + +insert into j.external_person (id, person_name, company_name) values + (2, 'Person2', 'Company1'); + +insert into j.team (id, team_name) values + (1, 'Team1'), + (2, 'Team2'), + (3, 'Team3'); + +insert into j.internal_team_membership (person_id, team_id) values + (1, 1), + (1, 2); + +insert into j.external_team_membership (person_id, team_id) values + (2, 1); + +-- Person1: [Team1,Team2] +-- Person2: [Team1] +-- Person3: [] +-- Team1: [Person1,Person2] +-- Team2: [Person1] +-- Team3: [] diff --git a/__tests__/schemas/j/fixtures/queries/many-to-many.graphql b/__tests__/schemas/j/fixtures/queries/many-to-many.graphql new file mode 100644 index 0000000..4ccd812 --- /dev/null +++ b/__tests__/schemas/j/fixtures/queries/many-to-many.graphql @@ -0,0 +1,19 @@ +query { + a: allPeople { + nodes { + __typename + personName + # TODO: Get interface level many-to-many working with polymorphic juntions + # Ensure that we can access the polymorphic join table from the person interface + # teamsList { + # id + # } + ... on InternalEmployee { + department + } + ... on ExternalPerson { + companyName + } + } + } +} diff --git a/__tests__/schemas/j/schema.sql b/__tests__/schemas/j/schema.sql new file mode 100644 index 0000000..a334b63 --- /dev/null +++ b/__tests__/schemas/j/schema.sql @@ -0,0 +1,84 @@ +drop schema if exists j cascade; + +create schema j; + +create type j.person as ( + id int, + person_name text +); + +create type j.team_membership as ( + person_id int, + team_id int +); + +create table j.internal_employee ( + id int primary key, + person_name text not null, + department text +); + +create table j.external_person ( + id int primary key, + person_name text not null, + company_name text +); + +-- Both internal and external employees can be part of a team +create table j.team ( + id int primary key, + team_name text +); + +create table j.internal_team_membership ( + person_id int, + team_id int, + primary key (person_id, team_id), + constraint internal_team_membership_person_id_fkey foreign key (person_id) references j.internal_employee (id), + constraint internal_team_membership_team_id_fkey foreign key (team_id) references j.team (id) +); + +create table j.external_team_membership ( + person_id int, + team_id int, + primary key (person_id, team_id), + constraint external_team_membership_person_id_fkey foreign key (person_id) references j.external_person (id), + constraint external_team_membership_team_id_fkey foreign key (team_id) references j.team (id) +); + +COMMENT ON TYPE j.person IS $$ +@interface mode:union +@name Person +@behavior node +$$; + +COMMENT ON TABLE j.internal_employee IS $$ +@implements Person +$$; + +COMMENT ON TABLE j.external_person IS $$ +@implements Person +$$; + +-- TODO: At the moment, relationships do not include polymorphic interfaces. +-- As a result, the following annotations do not produce many-to-many relationships +-- on the Person interface. +COMMENT ON TYPE j.team_membership IS $$ +@interface mode:union +@name TeamMembership +@behavior node +@ref member to:Person singular +@ref team to:Team singular +$$; + +COMMENT ON TABLE j.internal_team_membership IS $$ +@implements TeamMembership +@ref member via:(person_id)->j.internal_employee(id) singular +@ref team via:(team_id)->j.team(id) singular +$$; + +COMMENT ON TABLE j.external_team_membership IS $$ +@implements TeamMembership +@ref member via:(person_id)->j.external_person(id) singular +@ref team via:(team_id)->j.team(id) singular +$$; diff --git a/src/PgManyToManyRelationPlugin.ts b/src/PgManyToManyRelationPlugin.ts index 82566ca..4444028 100644 --- a/src/PgManyToManyRelationPlugin.ts +++ b/src/PgManyToManyRelationPlugin.ts @@ -1,11 +1,11 @@ -import type { PgResource, PgSelectSingleStep } from "@dataplan/pg"; +import type { PgCodec, PgResource, PgSelectSingleStep } from "@dataplan/pg"; import type {} from "graphile-config"; import type { GraphQLObjectType } from "graphql"; import type {} from "postgraphile"; import createManyToManyConnectionType from "./createManyToManyConnectionType"; import manyToManyRelationships from "./manyToManyRelationships"; import { PgManyToManyRelationDetails, PgTableResource } from "."; -import type { SQL } from "pg-sql2"; +import type { PgSQL, SQL } from "pg-sql2"; const version = require("../package.json").version; @@ -28,8 +28,333 @@ declare global { } } -function isPgTableResource(r: PgResource): r is PgTableResource { - return !!r.codec.attributes && !r.parameters; +function isPgTableResource(resource: PgResource): resource is PgTableResource { + return !!resource.codec.attributes && !resource.parameters; +} + +function getPgTableResourceByCodec( + build: GraphileBuild.Build, + pgCodec: PgCodec +) { + const pgTableResourceMatches = Object.values( + build.input.pgRegistry.pgResources + ).filter( + (resource) => resource.codec === pgCodec && isPgTableResource(resource) + ) as PgTableResource[]; + if (pgTableResourceMatches.length !== 1) { + if (pgTableResourceMatches.length > 1) { + throw new Error( + `PgManyToMany: there are multiple parameterless sources for codec '${pgCodec.name}', we can't determine which one to use.` + ); + } + return null; + } + return pgTableResourceMatches[0]; +} + +function makeRelationPlan( + build: GraphileBuild.Build, + isConnection: boolean, + allowsMultipleEdgesToNode: boolean, + leftTable: PgTableResource, + leftRelationName: string, + junctionTable: PgTableResource, + rightRelationName: string +) { + const { + sql, + grafast: { connection }, + } = build; + const leftRelation = leftTable.getRelation(leftRelationName); + if (typeof leftRelation.remoteResource.from === "function") { + throw new Error(`Function resource not supported for relation`); + } + const junctionFrom = leftRelation.remoteResource.from; + const leftTableAttributeNames = leftRelation.localAttributes; + const leftJunctionAttributeNames = leftRelation.remoteAttributes; + const rightRelation = junctionTable.getRelation(rightRelationName); + const rightJunctionAttributeNames = rightRelation.localAttributes; + const rightTableAttributeNames = rightRelation.remoteAttributes; + const rightResource = rightRelation.remoteResource; + const junctionAlias = sql.identifier(junctionSymbol); + const leftAttributeCount = leftJunctionAttributeNames.length; + const rightAttributeCount = rightJunctionAttributeNames.length; + + if (allowsMultipleEdgesToNode && isConnection) { + // Distinct join strategy so we can determine the joined-on records for edges. + return ($left: PgSelectSingleStep) => { + const $rights = rightResource.find(); + + const leftConditions: SQL[] = []; + for (let i = 0; i < leftAttributeCount; i++) { + leftConditions.push( + sql`${junctionAlias}.${sql.identifier( + leftJunctionAttributeNames[i] + )} = ${$rights.placeholder($left.get(leftTableAttributeNames[i]))}` + ); + } + + const rightConditions: SQL[] = []; + for (let i = 0; i < rightAttributeCount; i++) { + rightConditions.push( + sql`${junctionAlias}.${sql.identifier( + rightJunctionAttributeNames[i] + )} = ${$rights.alias}.${sql.identifier(rightTableAttributeNames[i])}` + ); + } + + // Join to a distinct version of junction table + const leftDistinctFrom = sql`(${sql.indent`select distinct ${sql.join( + leftJunctionAttributeNames.map( + (c) => sql`${junctionAlias}.${sql.identifier(c)}` + ), + ", " + )}, ${sql.join( + rightJunctionAttributeNames.map( + (c) => sql`${junctionAlias}.${sql.identifier(c)}` + ), + ", " + )}\n +from ${junctionFrom} ${junctionAlias} +where ${sql.join(leftConditions, "\nand ")} +`})`; + $rights.join({ + type: "inner", + conditions: rightConditions, + alias: junctionAlias, + from: leftDistinctFrom, + }); + + return connection($rights) as any; + }; + } + if (isConnection) { + return ($left: PgSelectSingleStep) => { + const $rights = rightResource.find(); + + const leftConditions: SQL[] = []; + for (let i = 0; i < leftAttributeCount; i++) { + leftConditions.push( + sql`${junctionAlias}.${sql.identifier( + leftJunctionAttributeNames[i] + )} = ${$rights.placeholder($left.get(leftTableAttributeNames[i]))}` + ); + } + + const rightConditions: SQL[] = []; + for (let i = 0; i < rightAttributeCount; i++) { + rightConditions.push( + sql`${junctionAlias}.${sql.identifier( + rightJunctionAttributeNames[i] + )} = ${$rights.alias}.${sql.identifier(rightTableAttributeNames[i])}` + ); + } + + // Join to junction table + $rights.join({ + type: "inner", + conditions: rightConditions, + alias: junctionAlias, + from: junctionFrom, + }); + + // Limit to only the junction entries that match $left + for (const leftCondition of leftConditions) { + $rights.where(leftCondition); + } + + return connection($rights) as any; + }; + } + // Subquery strategy - most efficient, but we cannot query attributes from the junction table + return ($left: PgSelectSingleStep) => { + const $rights = rightResource.find(); + + const leftConditions: SQL[] = []; + for (let i = 0; i < leftAttributeCount; i++) { + leftConditions.push( + sql`${junctionAlias}.${sql.identifier( + leftJunctionAttributeNames[i] + )} = ${$rights.placeholder($left.get(leftTableAttributeNames[i]))}` + ); + } + + const rightJunctionAttributes = sql`${sql.join( + rightJunctionAttributeNames.map( + (n) => sql`${junctionAlias}.${sql.identifier(n)}` + ), + ", " + )}`; + const rightTableAttribute = sql`(${sql.join( + rightTableAttributeNames.map( + (n) => sql`${$rights.alias}.${sql.identifier(n)}` + ), + ", " + )})`; + const junctionSubquery = sql.indent`select ${rightJunctionAttributes} +from ${junctionFrom} ${junctionAlias} +where ${sql.join(leftConditions, "\nand ")}`; + + $rights.where(sql`${rightTableAttribute} in (${junctionSubquery})`); + + return $rights; + }; +} + +type FieldsContext = + | GraphileBuild.ContextObjectFields + | GraphileBuild.ContextInterfaceFields; + +function isInterfaceContext( + context: FieldsContext +): context is GraphileBuild.ContextInterfaceFields { + return context.type === "GraphQLInterfaceType"; +} + +function extendFields( + fields: GraphileBuild.GrafastFieldConfigMap, + build: GraphileBuild.Build, + context: FieldsContext +) { + const { + extend, + inflection, + graphql: { GraphQLNonNull, GraphQLList }, + } = build; + const { + fieldWithHooks, + scope: { pgCodec: leftTableCodec }, + Self, + } = context; + const isInterface = isInterfaceContext(context); + if (!leftTableCodec) { + return fields; + } + + const leftTable = getPgTableResourceByCodec(build, leftTableCodec); + if (!leftTable) { + return fields; + } + + const relationships = + build.pgManyToManyRealtionshipsByResource.get(leftTable); + if (!relationships || relationships.length === 0) { + return fields; + } + return extend( + fields, + relationships.reduce( + (memo: GraphileBuild.GrafastFieldConfigMap, relationship) => + build.recoverable(memo, () => { + const { + leftTable, + leftRelationName, + rightRelationName, + rightTable, + junctionTable, + allowsMultipleEdgesToNode, + } = relationship; + const RightTableType = build.getGraphQLTypeByPgCodec( + rightTable.codec, + "output" + ) as GraphQLObjectType | null; + if (!RightTableType) { + throw new Error( + `Could not determine output type for table ${rightTable.name}` + ); + } + const leftTableTypeName = inflection.tableType(leftTable.codec); + const connectionTypeName = + inflection.manyToManyRelationConnectionType({ + ...relationship, + leftTableTypeName, + }); + const RightTableConnectionType = build.getTypeByName( + connectionTypeName + ) as GraphQLObjectType | null; + if (!RightTableConnectionType) { + throw new Error( + `Could not find connection type for table ${rightTable.name}` + ); + } + + const leftRelation = leftTable.getRelation(leftRelationName); + if (typeof leftRelation.remoteResource.from === "function") { + throw new Error(`Function resource not supported for relation`); + } + + function makeFields(isConnection: boolean) { + const manyRelationFieldName = isConnection + ? inflection.manyToManyRelationConnectionField(relationship) + : inflection.manyToManyRelationListField(relationship); + + memo = build.recoverable(memo, () => + extend( + memo, + { + [manyRelationFieldName]: fieldWithHooks( + { + fieldName: manyRelationFieldName, + pgFieldResource: rightTable, + isPgFieldConnection: isConnection, + isPgFieldSimpleCollection: !isConnection, + isPgManyToManyRelationField: isInterface + ? undefined + : true, + pgManyToManyRightTable: isInterface + ? undefined + : rightTable, + }, + () => ({ + description: `Reads and enables pagination through a set of \`${ + RightTableType!.name + }\`.`, + type: isConnection + ? new GraphQLNonNull(RightTableConnectionType!) + : new GraphQLNonNull( + new GraphQLList(new GraphQLNonNull(RightTableType!)) + ), + args: Object.create(null), + plan: isInterface + ? undefined + : makeRelationPlan( + build, + isConnection, + allowsMultipleEdgesToNode, + leftTable, + leftRelationName, + junctionTable, + rightRelationName + ), + }) + ), + }, + + `Many-to-many relation field (${ + isConnection ? "connection" : "simple collection" + }) on ${ + Self.name + } type for ${leftRelationName} and ${rightRelationName}.` + ) + ); + } + + if (build.behavior.pgManyToManyMatches(relationship, "manyToMany")) { + if ( + build.behavior.pgManyToManyMatches(relationship, "connection") + ) { + makeFields(true); + } + if (build.behavior.pgManyToManyMatches(relationship, "list")) { + makeFields(false); + } + } + return memo; + }), + Object.create(null) + ), + `Adding many-to-many relations for ${Self.name}` + ); } export const PgManyToManyRelationPlugin: GraphileConfig.Plugin = { @@ -111,328 +436,22 @@ export const PgManyToManyRelationPlugin: GraphileConfig.Plugin = { GraphQLObjectType_fields(fields, build, context) { const { - extend, - sql, - graphql: { GraphQLNonNull, GraphQLList }, - grafast: { connection }, - inflection, - } = build; - const { - scope: { isPgClassType, pgCodec: leftTableCodec }, - fieldWithHooks, - Self, + scope: { isPgClassType }, } = context; - if (!isPgClassType || !leftTableCodec || !leftTableCodec.attributes) { - return fields; - } - - const leftTables = Object.values( - build.input.pgRegistry.pgResources - ).filter((s) => s.codec === leftTableCodec && !s.parameters); - if (leftTables.length !== 1) { - if (leftTables.length > 2) { - throw new Error( - `PgManyToMany: there are multiple parameterless sources for codec '${leftTableCodec.name}', we can't determine which one to use.` - ); - } + if (!isPgClassType) { return fields; } - const leftTable = leftTables[0] as PgTableResource; + return extendFields(fields, build, context); + }, - const relationships = - build.pgManyToManyRealtionshipsByResource.get(leftTable); - if (!relationships || relationships.length === 0) { + GraphQLInterfaceType_fields(fields, build, context) { + const { + scope: { isPgPolymorphicTableType }, + } = context; + if (!isPgPolymorphicTableType) { return fields; } - return extend( - fields, - relationships.reduce( - (memo, relationship) => - build.recoverable(memo, () => { - const { - leftTable, - leftRelationName, - junctionTable, - rightRelationName, - rightTable, - allowsMultipleEdgesToNode, - } = relationship; - const RightTableType = build.getGraphQLTypeByPgCodec( - rightTable.codec, - "output" - ) as GraphQLObjectType | null; - if (!RightTableType) { - throw new Error( - `Could not determine output type for table ${rightTable.name}` - ); - } - const leftTableTypeName = inflection.tableType(leftTable.codec); - const connectionTypeName = - inflection.manyToManyRelationConnectionType({ - ...relationship, - leftTableTypeName, - }); - const RightTableConnectionType = build.getTypeByName( - connectionTypeName - ) as GraphQLObjectType | null; - if (!RightTableConnectionType) { - throw new Error( - `Could not find connection type for table ${rightTable.name}` - ); - } - - const leftRelation = leftTable.getRelation(leftRelationName); - if (typeof leftRelation.remoteResource.from === "function") { - throw new Error( - `Function resource not supported for relation` - ); - } - const junctionFrom = leftRelation.remoteResource.from; - const leftTableAttributeNames = leftRelation.localAttributes; - const leftJunctionAttributeNames = - leftRelation.remoteAttributes; - const rightRelation = - junctionTable.getRelation(rightRelationName); - const rightJunctionAttributeNames = - rightRelation.localAttributes; - const rightTableAttributeNames = rightRelation.remoteAttributes; - const rightResource = rightRelation.remoteResource; - const junctionAlias = sql.identifier(junctionSymbol); - const leftAttributeCount = leftJunctionAttributeNames.length; - const rightAttributeCount = rightJunctionAttributeNames.length; - - // TODO: throw an error if localAttributes or remoteAttributes involve - // `via` or `expression` - we only want pure column relations. - - function makeFields(isConnection: boolean) { - const manyRelationFieldName = isConnection - ? inflection.manyToManyRelationConnectionField(relationship) - : inflection.manyToManyRelationListField(relationship); - - memo = build.recoverable(memo, () => - extend( - memo, - { - [manyRelationFieldName]: fieldWithHooks( - { - fieldName: manyRelationFieldName, - pgFieldResource: rightTable, - isPgFieldConnection: isConnection, - isPgFieldSimpleCollection: !isConnection, - isPgManyToManyRelationField: true, - pgManyToManyRightTable: rightTable, - }, - () => ({ - description: `Reads and enables pagination through a set of \`${ - RightTableType!.name - }\`.`, - type: isConnection - ? new GraphQLNonNull(RightTableConnectionType!) - : new GraphQLNonNull( - new GraphQLList( - new GraphQLNonNull(RightTableType!) - ) - ), - args: Object.create(null), - plan: - allowsMultipleEdgesToNode && isConnection - ? // Distinct join strategy so we can determine the joined-on records for edges. - ($left: PgSelectSingleStep) => { - const $rights = rightResource.find(); - - const leftConditions: SQL[] = []; - for ( - let i = 0; - i < leftAttributeCount; - i++ - ) { - leftConditions.push( - sql`${junctionAlias}.${sql.identifier( - leftJunctionAttributeNames[i] - )} = ${$rights.placeholder( - $left.get(leftTableAttributeNames[i]) - )}` - ); - } - - const rightConditions: SQL[] = []; - for ( - let i = 0; - i < rightAttributeCount; - i++ - ) { - rightConditions.push( - sql`${junctionAlias}.${sql.identifier( - rightJunctionAttributeNames[i] - )} = ${$rights.alias}.${sql.identifier( - rightTableAttributeNames[i] - )}` - ); - } - - // Join to a distinct version of junction table - const leftDistinctFrom = sql`(${sql.indent`select distinct ${sql.join( - leftJunctionAttributeNames.map( - (c) => - sql`${junctionAlias}.${sql.identifier( - c - )}` - ), - ", " - )}, ${sql.join( - rightJunctionAttributeNames.map( - (c) => - sql`${junctionAlias}.${sql.identifier( - c - )}` - ), - ", " - )}\n -from ${junctionFrom} ${junctionAlias} -where ${sql.join(leftConditions, "\nand ")} -`})`; - $rights.join({ - type: "inner", - conditions: rightConditions, - alias: junctionAlias, - from: leftDistinctFrom, - }); - - return connection($rights) as any; - } - : isConnection - ? // Simple join strategy so we can grab attributes on the connection edges - ($left: PgSelectSingleStep) => { - const $rights = rightResource.find(); - - const leftConditions: SQL[] = []; - for ( - let i = 0; - i < leftAttributeCount; - i++ - ) { - leftConditions.push( - sql`${junctionAlias}.${sql.identifier( - leftJunctionAttributeNames[i] - )} = ${$rights.placeholder( - $left.get(leftTableAttributeNames[i]) - )}` - ); - } - - const rightConditions: SQL[] = []; - for ( - let i = 0; - i < rightAttributeCount; - i++ - ) { - rightConditions.push( - sql`${junctionAlias}.${sql.identifier( - rightJunctionAttributeNames[i] - )} = ${$rights.alias}.${sql.identifier( - rightTableAttributeNames[i] - )}` - ); - } - - // Join to junction table - $rights.join({ - type: "inner", - conditions: rightConditions, - alias: junctionAlias, - from: junctionFrom, - }); - - // Limit to only the junction entries that match $left - for (const leftCondition of leftConditions) { - $rights.where(leftCondition); - } - - return connection($rights) as any; - } - : // Subquery strategy - most efficient, but we cannot query attributes from the junction table - ($left: PgSelectSingleStep) => { - const $rights = rightResource.find(); - - const leftConditions: SQL[] = []; - for ( - let i = 0; - i < leftAttributeCount; - i++ - ) { - leftConditions.push( - sql`${junctionAlias}.${sql.identifier( - leftJunctionAttributeNames[i] - )} = ${$rights.placeholder( - $left.get(leftTableAttributeNames[i]) - )}` - ); - } - - const rightJunctionAttributes = sql`${sql.join( - rightJunctionAttributeNames.map( - (n) => - sql`${junctionAlias}.${sql.identifier( - n - )}` - ), - ", " - )}`; - const rightTableAttribute = sql`(${sql.join( - rightTableAttributeNames.map( - (n) => - sql`${$rights.alias}.${sql.identifier( - n - )}` - ), - ", " - )})`; - const junctionSubquery = sql.indent`select ${rightJunctionAttributes} -from ${junctionFrom} ${junctionAlias} -where ${sql.join(leftConditions, "\nand ")}`; - - $rights.where( - sql`${rightTableAttribute} in (${junctionSubquery})` - ); - - return $rights; - }, - }) - ), - }, - - `Many-to-many relation field (${ - isConnection ? "connection" : "simple collection" - }) on ${ - Self.name - } type for ${leftRelationName} and ${rightRelationName}.` - ) - ); - } - - if ( - build.behavior.pgManyToManyMatches(relationship, "manyToMany") - ) { - if ( - build.behavior.pgManyToManyMatches( - relationship, - "connection" - ) - ) { - makeFields(true); - } - if ( - build.behavior.pgManyToManyMatches(relationship, "list") - ) { - makeFields(false); - } - } - return memo; - }), - Object.create(null) - ), - `Adding many-to-many relations for ${Self.name}` - ); + return extendFields(fields, build, context); }, }, },