├── .eslintrc.js ├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── ci │ └── docker-entrypoint-initdb.d │ └── 010-setup.sh ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── __tests__ ├── __snapshots__ │ └── queries.test.js.snap ├── helpers.js ├── integration │ └── schema │ │ ├── __snapshots__ │ │ ├── a.test.js.snap │ │ ├── b.test.js.snap │ │ ├── c.test.js.snap │ │ ├── d.test.js.snap │ │ ├── e.test.js.snap │ │ ├── f.test.js.snap │ │ ├── g.test.js.snap │ │ ├── p.ignoreIndexesFalse.test.js.snap │ │ ├── p.simpleCollectionsBoth.test.js.snap │ │ ├── p.test.js.snap │ │ └── t.test.js.snap │ │ ├── a.test.js │ │ ├── b.test.js │ │ ├── c.test.js │ │ ├── core.js │ │ ├── d.test.js │ │ ├── e.test.js │ │ ├── f.test.js │ │ ├── g.test.js │ │ ├── p.ignoreIndexesFalse.test.js │ │ ├── p.simpleCollectionsBoth.test.js │ │ ├── p.test.js │ │ └── t.test.js ├── queries.test.js └── schemas │ ├── a │ ├── data.sql │ ├── fixtures │ │ └── queries │ │ │ ├── edges.graphql │ │ │ └── nodes.graphql │ └── schema.sql │ ├── b │ ├── data.sql │ ├── fixtures │ │ └── queries │ │ │ ├── edges.graphql │ │ │ └── nodes.graphql │ └── schema.sql │ ├── c │ ├── data.sql │ ├── fixtures │ │ └── queries │ │ │ ├── edges.graphql │ │ │ └── nodes.graphql │ └── schema.sql │ ├── d │ ├── data.sql │ ├── fixtures │ │ └── queries │ │ │ ├── edges.graphql │ │ │ └── nodes.graphql │ └── schema.sql │ ├── e │ ├── data.sql │ ├── fixtures │ │ └── queries │ │ │ └── multiple-tags-fields.graphql │ └── schema.sql │ ├── f │ ├── data.sql │ ├── fixtures │ │ └── queries │ │ │ └── f.graphql │ └── schema.sql │ ├── g │ ├── data.sql │ └── schema.sql │ ├── p │ ├── data.sql │ ├── fixtures │ │ └── queries │ │ │ ├── edge-fields.graphql │ │ │ └── many-to-many.graphql │ └── schema.sql │ └── t │ ├── data.sql │ ├── fixtures │ └── queries │ │ ├── edge-fields.graphql │ │ └── many-to-many.graphql │ └── schema.sql ├── index.d.ts ├── index.js ├── package.json ├── scripts └── test ├── src ├── PgManyToManyRelationEdgeColumnsPlugin.js ├── PgManyToManyRelationEdgeTablePlugin.js ├── PgManyToManyRelationInflectionPlugin.js ├── PgManyToManyRelationPlugin.js ├── createManyToManyConnectionType.js └── manyToManyRelationships.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | es6: true, 5 | "jest/globals": true, 6 | }, 7 | parserOptions: { 8 | ecmaVersion: 9, 9 | }, 10 | plugins: ["jest"], 11 | extends: [ 12 | "eslint:recommended", 13 | "plugin:jest/recommended", 14 | "plugin:prettier/recommended", 15 | ], 16 | rules: { 17 | "jest/expect-expect": ["off"], 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: push 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | 9 | env: 10 | TERM: xterm 11 | FORCE_COLOR: 1 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Use Node.js 18 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 18 21 | 22 | - name: Install 23 | run: yarn --immutable --ignore-engines 24 | 25 | - name: Check Code Format 26 | run: yarn format:check 27 | 28 | - name: Lint Code 29 | run: yarn lint 30 | 31 | test: 32 | runs-on: ubuntu-latest 33 | 34 | env: 35 | CI: true 36 | PGVERSION: ${{ matrix.postgres-version}} 37 | TEST_DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/graphile_test 38 | TERM: xterm 39 | FORCE_COLOR: 1 40 | 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | postgres-version: 45 | - 11 46 | - 12 47 | - 13 48 | - 14 49 | - 15 50 | node-version: 51 | - 14 52 | - 16 53 | - 18 54 | 55 | name: test (node:${{ matrix.node-version }}, postgres:${{ matrix.postgres-version }}) 56 | 57 | services: 58 | postgres: 59 | image: postgres:${{ matrix.postgres-version }} 60 | env: 61 | POSTGRES_USER: postgres 62 | POSTGRES_PASSWORD: postgres 63 | POSTGRES_DB: postgres 64 | ports: 65 | - "0.0.0.0:5432:5432" 66 | # needed because the postgres container does not provide a healthcheck 67 | options: 68 | --health-cmd pg_isready --health-interval 10s --health-timeout 5s 69 | --health-retries 5 --name postgres 70 | 71 | steps: 72 | - name: Checkout 73 | uses: actions/checkout@v3 74 | 75 | - name: Use Node.js ${{ matrix.node-version }} 76 | uses: actions/setup-node@v3 77 | with: 78 | node-version: ${{ matrix.node-version }} 79 | 80 | - name: Configure PostgreSQL 81 | run: | 82 | cat .github/workflows/ci/docker-entrypoint-initdb.d/010-setup.sh | docker exec -i postgres bash 83 | 84 | - name: Install 85 | run: yarn --immutable --ignore-engines 86 | 87 | - name: Test 88 | run: yarn test 89 | -------------------------------------------------------------------------------- /.github/workflows/ci/docker-entrypoint-initdb.d/010-setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | createdb graphile_test -U postgres 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5" 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Graphile Contrib 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @graphile-contrib/pg-many-to-many 2 | 3 | [![Package on npm](https://img.shields.io/npm/v/@graphile-contrib/pg-many-to-many.svg)](https://www.npmjs.com/package/@graphile-contrib/pg-many-to-many) 4 | 5 | This Graphile Engine plugin adds connection fields for many-to-many relations. 6 | 7 | > Requires `postgraphile@^4.5.0` or `graphile-build-pg@^4.5.0` 8 | 9 | Example: 10 | 11 | ```graphql 12 | { 13 | allPeople { 14 | nodes { 15 | personName 16 | # 👇 many-to-many relation 17 | teamsByTeamMemberPersonIdAndTeamId { 18 | nodes { 19 | teamName 20 | } 21 | } 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | ## Usage 28 | 29 | Append this plugin and the additional fields will be added to your schema. 30 | 31 | ### CLI 32 | 33 | ```bash 34 | yarn add postgraphile 35 | yarn add @graphile-contrib/pg-many-to-many 36 | npx postgraphile --append-plugins @graphile-contrib/pg-many-to-many 37 | ``` 38 | 39 | ### Library 40 | 41 | ```js 42 | const express = require("express"); 43 | const { postgraphile } = require("postgraphile"); 44 | const PgManyToManyPlugin = require("@graphile-contrib/pg-many-to-many"); 45 | 46 | const app = express(); 47 | 48 | app.use( 49 | postgraphile(process.env.DATABASE_URL, "app_public", { 50 | appendPlugins: [PgManyToManyPlugin], 51 | graphiql: true, 52 | }) 53 | ); 54 | 55 | app.listen(5000); 56 | ``` 57 | 58 | ## Excluding Fields 59 | 60 | To exclude certain many-to-many fields from appearing in your GraphQL schema, you can use `@omit manyToMany` [smart comments](https://www.graphile.org/postgraphile/smart-comments/) on constraints and tables. 61 | 62 | Here is an example of using a smart comment on a constraint: 63 | 64 | ``` 65 | create table p.foo ( 66 | id serial primary key, 67 | name text not null 68 | ); 69 | 70 | create table p.bar ( 71 | id serial primary key, 72 | name text not null 73 | ); 74 | 75 | create table p.qux ( 76 | foo_id int constraint qux_foo_id_fkey references p.foo (id), 77 | bar_id int constraint qux_bar_id_fkey references p.bar (id), 78 | primary key (foo_id, bar_id) 79 | ); 80 | 81 | -- `Foo` and `Bar` would normally have `barsBy...` and `foosBy...` fields, 82 | -- but this smart comment causes the constraint between `qux` and `bar` 83 | -- to be ignored, preventing the fields from being generated. 84 | comment on constraint qux_bar_id_fkey on p.qux is E'@omit manyToMany'; 85 | ``` 86 | 87 | Here is an example of using a smart comment on a table: 88 | 89 | ``` 90 | create table p.foo ( 91 | id serial primary key, 92 | name text not null 93 | ); 94 | 95 | create table p.bar ( 96 | id serial primary key, 97 | name text not null 98 | ); 99 | 100 | create table p.corge ( 101 | foo_id int constraint corge_foo_id_fkey references p.foo (id), 102 | bar_id int constraint corge_bar_id_fkey references p.bar (id), 103 | primary key (foo_id, bar_id) 104 | ); 105 | 106 | -- `Foo` and `Bar` would normally have `barsBy...` and `foosBy...` fields, 107 | -- but this smart comment causes `corge` to be excluded from consideration 108 | -- as a junction table, preventing the fields from being generated. 109 | comment on table p.corge is E'@omit manyToMany'; 110 | ``` 111 | 112 | ## Inflection 113 | 114 | To avoid naming conflicts, this plugin uses a verbose naming convention (e.g. `teamsByTeamMemberTeamId`), similar to how related fields are named by default in PostGraphile v4. You can override this by writing a custom inflector plugin or by using smart comments in your SQL schema. 115 | 116 | ### Inflector Plugin 117 | 118 | Writing a custom inflector plugin gives you full control over the GraphQL field names. Here is an example plugin that shortens the field names to just the table name (producing e.g. `teams`): 119 | 120 | > :warning: Warning: Simplifying the field names as shown below will lead to field name conflicts if your junction table has multiple foreign keys referencing the same table. You will need to customize the inflector function to resolve the conflicts. 121 | 122 | ```js 123 | const { makeAddInflectorsPlugin } = require("graphile-utils"); 124 | 125 | module.exports = makeAddInflectorsPlugin( 126 | { 127 | manyToManyRelationByKeys( 128 | _leftKeyAttributes, 129 | _junctionLeftKeyAttributes, 130 | _junctionRightKeyAttributes, 131 | _rightKeyAttributes, 132 | _junctionTable, 133 | rightTable, 134 | _junctionLeftConstraint, 135 | junctionRightConstraint 136 | ) { 137 | if (junctionRightConstraint.tags.manyToManyFieldName) { 138 | return junctionRightConstraint.tags.manyToManyFieldName; 139 | } 140 | return this.camelCase( 141 | `${this.pluralize(this._singularizedTableName(rightTable))}` 142 | ); 143 | }, 144 | manyToManyRelationByKeysSimple( 145 | _leftKeyAttributes, 146 | _junctionLeftKeyAttributes, 147 | _junctionRightKeyAttributes, 148 | _rightKeyAttributes, 149 | _junctionTable, 150 | rightTable, 151 | _junctionLeftConstraint, 152 | junctionRightConstraint 153 | ) { 154 | if (junctionRightConstraint.tags.manyToManySimpleFieldName) { 155 | return junctionRightConstraint.tags.manyToManySimpleFieldName; 156 | } 157 | return this.camelCase( 158 | `${this.pluralize(this._singularizedTableName(rightTable))}-list` 159 | ); 160 | }, 161 | }, 162 | true // Passing true here allows the plugin to overwrite existing inflectors. 163 | ); 164 | ``` 165 | 166 | For more information on custom inflector plugins, see the [makeAddInflectorsPlugin documentation](https://www.graphile.org/postgraphile/make-add-inflectors-plugin/). 167 | 168 | ### Smart Comments 169 | 170 | The `@manyToManyFieldName` and `@manyToManySimpleFieldName` smart comments allow you to override the field names generated by this plugin. 171 | 172 | For example, to rename the Connection field from `teamsByTeamMemberTeamId` to `teams`: 173 | 174 | ```sql 175 | comment on constraint membership_team_id_fkey on p.membership is E'@manyToManyFieldName teams'; 176 | ``` 177 | 178 | To rename both the Connection and simple collection fields (assuming simple collections are enabled): 179 | 180 | ```sql 181 | comment on constraint membership_team_id_fkey on p.membership is E'@manyToManyFieldName teams\n@manyToManySimpleFieldName teamsList'; 182 | ``` 183 | -------------------------------------------------------------------------------- /__tests__/helpers.js: -------------------------------------------------------------------------------- 1 | const pg = require("pg"); 2 | const fs = require("fs"); 3 | const util = require("util"); 4 | const path = require("path"); 5 | const pgConnectionString = require("pg-connection-string"); 6 | 7 | const readFile = util.promisify(fs.readFile); 8 | 9 | // This test suite can be flaky. Increase it’s timeout. 10 | jest.setTimeout(1000 * 20); 11 | 12 | const withPgClient = async (url, fn) => { 13 | if (!fn) { 14 | fn = url; 15 | url = process.env.TEST_DATABASE_URL; 16 | } 17 | const pgPool = new pg.Pool(pgConnectionString.parse(url)); 18 | let client; 19 | try { 20 | client = await pgPool.connect(); 21 | await client.query("begin"); 22 | await client.query("set local timezone to '+04:00'"); 23 | const result = await fn(client); 24 | await client.query("rollback"); 25 | return result; 26 | } finally { 27 | try { 28 | await client.release(); 29 | } catch (e) { 30 | console.error("Error releasing pgClient", e); // eslint-disable-line no-console 31 | } 32 | await pgPool.end(); 33 | } 34 | }; 35 | 36 | const withDbFromUrl = async (url, fn) => { 37 | return withPgClient(url, async (client) => { 38 | try { 39 | await client.query("BEGIN ISOLATION LEVEL SERIALIZABLE;"); 40 | return fn(client); 41 | } finally { 42 | await client.query("COMMIT;"); 43 | } 44 | }); 45 | }; 46 | 47 | const withRootDb = (fn) => withDbFromUrl(process.env.TEST_DATABASE_URL, fn); 48 | 49 | let prepopulatedDBKeepalive; 50 | 51 | const populateDatabase = async (client) => { 52 | const sqlSchemas = fs.readdirSync(path.resolve(__dirname, "schemas")); 53 | await Promise.all( 54 | sqlSchemas.map(async (sqlSchema) => { 55 | const sqlData = await readFile( 56 | path.resolve(__dirname, "schemas", sqlSchema, "data.sql"), 57 | "utf8" 58 | ); 59 | await client.query(sqlData); 60 | }) 61 | ); 62 | return {}; 63 | }; 64 | 65 | const withPrepopulatedDb = async (fn) => { 66 | if (!prepopulatedDBKeepalive) { 67 | throw new Error("You must call setup and teardown to use this"); 68 | } 69 | const { client, vars } = prepopulatedDBKeepalive; 70 | if (!vars) { 71 | throw new Error("No prepopulated vars"); 72 | } 73 | let err; 74 | try { 75 | await fn(client, vars); 76 | } catch (e) { 77 | err = e; 78 | } 79 | try { 80 | await client.query("ROLLBACK TO SAVEPOINT pristine;"); 81 | } catch (e) { 82 | err = err || e; 83 | console.error("ERROR ROLLING BACK", e.message); // eslint-disable-line no-console 84 | } 85 | if (err) { 86 | throw err; 87 | } 88 | }; 89 | 90 | withPrepopulatedDb.setup = (done) => { 91 | if (prepopulatedDBKeepalive) { 92 | throw new Error("There's already a prepopulated DB running"); 93 | } 94 | let res; 95 | let rej; 96 | prepopulatedDBKeepalive = new Promise((resolve, reject) => { 97 | res = resolve; 98 | rej = reject; 99 | }); 100 | prepopulatedDBKeepalive.resolve = res; 101 | prepopulatedDBKeepalive.reject = rej; 102 | withRootDb(async (client) => { 103 | prepopulatedDBKeepalive.client = client; 104 | try { 105 | prepopulatedDBKeepalive.vars = await populateDatabase(client); 106 | } catch (e) { 107 | console.error("FAILED TO PREPOPULATE DB!", e.message); // eslint-disable-line no-console 108 | return done(e); 109 | } 110 | await client.query("SAVEPOINT pristine;"); 111 | done(); 112 | return prepopulatedDBKeepalive; 113 | }); 114 | }; 115 | 116 | withPrepopulatedDb.teardown = () => { 117 | if (!prepopulatedDBKeepalive) { 118 | throw new Error("Cannot tear down null!"); 119 | } 120 | prepopulatedDBKeepalive.resolve(); // Release DB transaction 121 | prepopulatedDBKeepalive = null; 122 | }; 123 | 124 | exports.withRootDb = withRootDb; 125 | exports.withPrepopulatedDb = withPrepopulatedDb; 126 | exports.withPgClient = withPgClient; 127 | -------------------------------------------------------------------------------- /__tests__/integration/schema/__snapshots__/a.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`prints a schema using the 'a' database schema 1`] = ` 4 | "type Bar implements Node { 5 | barId: Int! 6 | barName: String! 7 | 8 | """Reads and enables pagination through a set of \`Foo\`.""" 9 | foosByJunctionJBarIdAndJFooId( 10 | """Read all values in the set after (below) this cursor.""" 11 | after: Cursor 12 | 13 | """Read all values in the set before (above) this cursor.""" 14 | before: Cursor 15 | 16 | """Only read the first \`n\` values of the set.""" 17 | first: Int 18 | 19 | """Only read the last \`n\` values of the set.""" 20 | last: Int 21 | 22 | """ 23 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 24 | based pagination. May not be used with \`last\`. 25 | """ 26 | offset: Int 27 | 28 | """The method to use when ordering \`Foo\`.""" 29 | orderBy: [FoosOrderBy!] = [PRIMARY_KEY_ASC] 30 | ): BarFoosByJunctionJBarIdAndJFooIdManyToManyConnection! 31 | 32 | """Reads and enables pagination through a set of \`Junction\`.""" 33 | junctionsByJBarId( 34 | """Read all values in the set after (below) this cursor.""" 35 | after: Cursor 36 | 37 | """Read all values in the set before (above) this cursor.""" 38 | before: Cursor 39 | 40 | """Only read the first \`n\` values of the set.""" 41 | first: Int 42 | 43 | """Only read the last \`n\` values of the set.""" 44 | last: Int 45 | 46 | """ 47 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 48 | based pagination. May not be used with \`last\`. 49 | """ 50 | offset: Int 51 | 52 | """The method to use when ordering \`Junction\`.""" 53 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 54 | ): JunctionsConnection! 55 | 56 | """ 57 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 58 | """ 59 | nodeId: ID! 60 | } 61 | 62 | """A connection to a list of \`Foo\` values, with data from \`Junction\`.""" 63 | type BarFoosByJunctionJBarIdAndJFooIdManyToManyConnection { 64 | """ 65 | A list of edges which contains the \`Foo\`, info from the \`Junction\`, and the cursor to aid in pagination. 66 | """ 67 | edges: [BarFoosByJunctionJBarIdAndJFooIdManyToManyEdge!]! 68 | 69 | """A list of \`Foo\` objects.""" 70 | nodes: [Foo]! 71 | 72 | """Information to aid in pagination.""" 73 | pageInfo: PageInfo! 74 | 75 | """The count of *all* \`Foo\` you could get from the connection.""" 76 | totalCount: Int! 77 | } 78 | 79 | """A \`Foo\` edge in the connection, with data from \`Junction\`.""" 80 | type BarFoosByJunctionJBarIdAndJFooIdManyToManyEdge { 81 | """A cursor for use in pagination.""" 82 | cursor: Cursor 83 | 84 | """The \`Foo\` at the end of the edge.""" 85 | node: Foo 86 | } 87 | 88 | """A connection to a list of \`Bar\` values.""" 89 | type BarsConnection { 90 | """ 91 | A list of edges which contains the \`Bar\` and cursor to aid in pagination. 92 | """ 93 | edges: [BarsEdge!]! 94 | 95 | """A list of \`Bar\` objects.""" 96 | nodes: [Bar]! 97 | 98 | """Information to aid in pagination.""" 99 | pageInfo: PageInfo! 100 | 101 | """The count of *all* \`Bar\` you could get from the connection.""" 102 | totalCount: Int! 103 | } 104 | 105 | """A \`Bar\` edge in the connection.""" 106 | type BarsEdge { 107 | """A cursor for use in pagination.""" 108 | cursor: Cursor 109 | 110 | """The \`Bar\` at the end of the edge.""" 111 | node: Bar 112 | } 113 | 114 | """Methods to use when ordering \`Bar\`.""" 115 | enum BarsOrderBy { 116 | BAR_ID_ASC 117 | BAR_ID_DESC 118 | BAR_NAME_ASC 119 | BAR_NAME_DESC 120 | NATURAL 121 | PRIMARY_KEY_ASC 122 | PRIMARY_KEY_DESC 123 | } 124 | 125 | """A location in a connection that can be used for resuming pagination.""" 126 | scalar Cursor 127 | 128 | type Foo implements Node { 129 | """Reads and enables pagination through a set of \`Bar\`.""" 130 | barsByJunctionJFooIdAndJBarId( 131 | """Read all values in the set after (below) this cursor.""" 132 | after: Cursor 133 | 134 | """Read all values in the set before (above) this cursor.""" 135 | before: Cursor 136 | 137 | """Only read the first \`n\` values of the set.""" 138 | first: Int 139 | 140 | """Only read the last \`n\` values of the set.""" 141 | last: Int 142 | 143 | """ 144 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 145 | based pagination. May not be used with \`last\`. 146 | """ 147 | offset: Int 148 | 149 | """The method to use when ordering \`Bar\`.""" 150 | orderBy: [BarsOrderBy!] = [PRIMARY_KEY_ASC] 151 | ): FooBarsByJunctionJFooIdAndJBarIdManyToManyConnection! 152 | fooId: Int! 153 | fooName: String! 154 | 155 | """Reads and enables pagination through a set of \`Junction\`.""" 156 | junctionsByJFooId( 157 | """Read all values in the set after (below) this cursor.""" 158 | after: Cursor 159 | 160 | """Read all values in the set before (above) this cursor.""" 161 | before: Cursor 162 | 163 | """Only read the first \`n\` values of the set.""" 164 | first: Int 165 | 166 | """Only read the last \`n\` values of the set.""" 167 | last: Int 168 | 169 | """ 170 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 171 | based pagination. May not be used with \`last\`. 172 | """ 173 | offset: Int 174 | 175 | """The method to use when ordering \`Junction\`.""" 176 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 177 | ): JunctionsConnection! 178 | 179 | """ 180 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 181 | """ 182 | nodeId: ID! 183 | } 184 | 185 | """A connection to a list of \`Bar\` values, with data from \`Junction\`.""" 186 | type FooBarsByJunctionJFooIdAndJBarIdManyToManyConnection { 187 | """ 188 | A list of edges which contains the \`Bar\`, info from the \`Junction\`, and the cursor to aid in pagination. 189 | """ 190 | edges: [FooBarsByJunctionJFooIdAndJBarIdManyToManyEdge!]! 191 | 192 | """A list of \`Bar\` objects.""" 193 | nodes: [Bar]! 194 | 195 | """Information to aid in pagination.""" 196 | pageInfo: PageInfo! 197 | 198 | """The count of *all* \`Bar\` you could get from the connection.""" 199 | totalCount: Int! 200 | } 201 | 202 | """A \`Bar\` edge in the connection, with data from \`Junction\`.""" 203 | type FooBarsByJunctionJFooIdAndJBarIdManyToManyEdge { 204 | """A cursor for use in pagination.""" 205 | cursor: Cursor 206 | 207 | """The \`Bar\` at the end of the edge.""" 208 | node: Bar 209 | } 210 | 211 | """A connection to a list of \`Foo\` values.""" 212 | type FoosConnection { 213 | """ 214 | A list of edges which contains the \`Foo\` and cursor to aid in pagination. 215 | """ 216 | edges: [FoosEdge!]! 217 | 218 | """A list of \`Foo\` objects.""" 219 | nodes: [Foo]! 220 | 221 | """Information to aid in pagination.""" 222 | pageInfo: PageInfo! 223 | 224 | """The count of *all* \`Foo\` you could get from the connection.""" 225 | totalCount: Int! 226 | } 227 | 228 | """A \`Foo\` edge in the connection.""" 229 | type FoosEdge { 230 | """A cursor for use in pagination.""" 231 | cursor: Cursor 232 | 233 | """The \`Foo\` at the end of the edge.""" 234 | node: Foo 235 | } 236 | 237 | """Methods to use when ordering \`Foo\`.""" 238 | enum FoosOrderBy { 239 | FOO_ID_ASC 240 | FOO_ID_DESC 241 | FOO_NAME_ASC 242 | FOO_NAME_DESC 243 | NATURAL 244 | PRIMARY_KEY_ASC 245 | PRIMARY_KEY_DESC 246 | } 247 | 248 | type Junction implements Node { 249 | """Reads a single \`Bar\` that is related to this \`Junction\`.""" 250 | barByJBarId: Bar 251 | 252 | """Reads a single \`Foo\` that is related to this \`Junction\`.""" 253 | fooByJFooId: Foo 254 | jBarId: Int! 255 | jFooId: Int! 256 | 257 | """ 258 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 259 | """ 260 | nodeId: ID! 261 | } 262 | 263 | """A connection to a list of \`Junction\` values.""" 264 | type JunctionsConnection { 265 | """ 266 | A list of edges which contains the \`Junction\` and cursor to aid in pagination. 267 | """ 268 | edges: [JunctionsEdge!]! 269 | 270 | """A list of \`Junction\` objects.""" 271 | nodes: [Junction]! 272 | 273 | """Information to aid in pagination.""" 274 | pageInfo: PageInfo! 275 | 276 | """The count of *all* \`Junction\` you could get from the connection.""" 277 | totalCount: Int! 278 | } 279 | 280 | """A \`Junction\` edge in the connection.""" 281 | type JunctionsEdge { 282 | """A cursor for use in pagination.""" 283 | cursor: Cursor 284 | 285 | """The \`Junction\` at the end of the edge.""" 286 | node: Junction 287 | } 288 | 289 | """Methods to use when ordering \`Junction\`.""" 290 | enum JunctionsOrderBy { 291 | J_BAR_ID_ASC 292 | J_BAR_ID_DESC 293 | J_FOO_ID_ASC 294 | J_FOO_ID_DESC 295 | NATURAL 296 | PRIMARY_KEY_ASC 297 | PRIMARY_KEY_DESC 298 | } 299 | 300 | """An object with a globally unique \`ID\`.""" 301 | interface Node { 302 | """ 303 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 304 | """ 305 | nodeId: ID! 306 | } 307 | 308 | """Information about pagination in a connection.""" 309 | type PageInfo { 310 | """When paginating forwards, the cursor to continue.""" 311 | endCursor: Cursor 312 | 313 | """When paginating forwards, are there more items?""" 314 | hasNextPage: Boolean! 315 | 316 | """When paginating backwards, are there more items?""" 317 | hasPreviousPage: Boolean! 318 | 319 | """When paginating backwards, the cursor to continue.""" 320 | startCursor: Cursor 321 | } 322 | 323 | """The root query type which gives access points into the data universe.""" 324 | type Query implements Node { 325 | """Reads and enables pagination through a set of \`Bar\`.""" 326 | allBars( 327 | """Read all values in the set after (below) this cursor.""" 328 | after: Cursor 329 | 330 | """Read all values in the set before (above) this cursor.""" 331 | before: Cursor 332 | 333 | """Only read the first \`n\` values of the set.""" 334 | first: Int 335 | 336 | """Only read the last \`n\` values of the set.""" 337 | last: Int 338 | 339 | """ 340 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 341 | based pagination. May not be used with \`last\`. 342 | """ 343 | offset: Int 344 | 345 | """The method to use when ordering \`Bar\`.""" 346 | orderBy: [BarsOrderBy!] = [PRIMARY_KEY_ASC] 347 | ): BarsConnection 348 | 349 | """Reads and enables pagination through a set of \`Foo\`.""" 350 | allFoos( 351 | """Read all values in the set after (below) this cursor.""" 352 | after: Cursor 353 | 354 | """Read all values in the set before (above) this cursor.""" 355 | before: Cursor 356 | 357 | """Only read the first \`n\` values of the set.""" 358 | first: Int 359 | 360 | """Only read the last \`n\` values of the set.""" 361 | last: Int 362 | 363 | """ 364 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 365 | based pagination. May not be used with \`last\`. 366 | """ 367 | offset: Int 368 | 369 | """The method to use when ordering \`Foo\`.""" 370 | orderBy: [FoosOrderBy!] = [PRIMARY_KEY_ASC] 371 | ): FoosConnection 372 | 373 | """Reads and enables pagination through a set of \`Junction\`.""" 374 | allJunctions( 375 | """Read all values in the set after (below) this cursor.""" 376 | after: Cursor 377 | 378 | """Read all values in the set before (above) this cursor.""" 379 | before: Cursor 380 | 381 | """Only read the first \`n\` values of the set.""" 382 | first: Int 383 | 384 | """Only read the last \`n\` values of the set.""" 385 | last: Int 386 | 387 | """ 388 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 389 | based pagination. May not be used with \`last\`. 390 | """ 391 | offset: Int 392 | 393 | """The method to use when ordering \`Junction\`.""" 394 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 395 | ): JunctionsConnection 396 | 397 | """Reads a single \`Bar\` using its globally unique \`ID\`.""" 398 | bar( 399 | """The globally unique \`ID\` to be used in selecting a single \`Bar\`.""" 400 | nodeId: ID! 401 | ): Bar 402 | barByBarId(barId: Int!): Bar 403 | 404 | """Reads a single \`Foo\` using its globally unique \`ID\`.""" 405 | foo( 406 | """The globally unique \`ID\` to be used in selecting a single \`Foo\`.""" 407 | nodeId: ID! 408 | ): Foo 409 | fooByFooId(fooId: Int!): Foo 410 | 411 | """Reads a single \`Junction\` using its globally unique \`ID\`.""" 412 | junction( 413 | """The globally unique \`ID\` to be used in selecting a single \`Junction\`.""" 414 | nodeId: ID! 415 | ): Junction 416 | junctionByJFooIdAndJBarId(jBarId: Int!, jFooId: Int!): Junction 417 | 418 | """Fetches an object given its globally unique \`ID\`.""" 419 | node( 420 | """The globally unique \`ID\`.""" 421 | nodeId: ID! 422 | ): Node 423 | 424 | """ 425 | The root query type must be a \`Node\` to work well with Relay 1 mutations. This just resolves to \`query\`. 426 | """ 427 | nodeId: ID! 428 | 429 | """ 430 | Exposes the root query type nested one level down. This is helpful for Relay 1 431 | which can only query top level fields if they are in a particular form. 432 | """ 433 | query: Query! 434 | } 435 | " 436 | `; 437 | -------------------------------------------------------------------------------- /__tests__/integration/schema/__snapshots__/b.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`prints a schema using the 'b' database schema 1`] = ` 4 | "type Bar implements Node { 5 | barId: Int! 6 | barName: String! 7 | 8 | """Reads and enables pagination through a set of \`Foo\`.""" 9 | foosByJunctionJBarIdAndJFooId( 10 | """Read all values in the set after (below) this cursor.""" 11 | after: Cursor 12 | 13 | """Read all values in the set before (above) this cursor.""" 14 | before: Cursor 15 | 16 | """Only read the first \`n\` values of the set.""" 17 | first: Int 18 | 19 | """Only read the last \`n\` values of the set.""" 20 | last: Int 21 | 22 | """ 23 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 24 | based pagination. May not be used with \`last\`. 25 | """ 26 | offset: Int 27 | 28 | """The method to use when ordering \`Foo\`.""" 29 | orderBy: [FoosOrderBy!] = [PRIMARY_KEY_ASC] 30 | ): BarFoosByJunctionJBarIdAndJFooIdManyToManyConnection! 31 | 32 | """Reads and enables pagination through a set of \`Junction\`.""" 33 | junctionsByJBarId( 34 | """Read all values in the set after (below) this cursor.""" 35 | after: Cursor 36 | 37 | """Read all values in the set before (above) this cursor.""" 38 | before: Cursor 39 | 40 | """Only read the first \`n\` values of the set.""" 41 | first: Int 42 | 43 | """Only read the last \`n\` values of the set.""" 44 | last: Int 45 | 46 | """ 47 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 48 | based pagination. May not be used with \`last\`. 49 | """ 50 | offset: Int 51 | 52 | """The method to use when ordering \`Junction\`.""" 53 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 54 | ): JunctionsConnection! 55 | 56 | """ 57 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 58 | """ 59 | nodeId: ID! 60 | } 61 | 62 | """A connection to a list of \`Foo\` values, with data from \`Junction\`.""" 63 | type BarFoosByJunctionJBarIdAndJFooIdManyToManyConnection { 64 | """ 65 | A list of edges which contains the \`Foo\`, info from the \`Junction\`, and the cursor to aid in pagination. 66 | """ 67 | edges: [BarFoosByJunctionJBarIdAndJFooIdManyToManyEdge!]! 68 | 69 | """A list of \`Foo\` objects.""" 70 | nodes: [Foo]! 71 | 72 | """Information to aid in pagination.""" 73 | pageInfo: PageInfo! 74 | 75 | """The count of *all* \`Foo\` you could get from the connection.""" 76 | totalCount: Int! 77 | } 78 | 79 | """A \`Foo\` edge in the connection, with data from \`Junction\`.""" 80 | type BarFoosByJunctionJBarIdAndJFooIdManyToManyEdge { 81 | createdAt: Datetime! 82 | 83 | """A cursor for use in pagination.""" 84 | cursor: Cursor 85 | 86 | """The \`Foo\` at the end of the edge.""" 87 | node: Foo 88 | } 89 | 90 | """A connection to a list of \`Bar\` values.""" 91 | type BarsConnection { 92 | """ 93 | A list of edges which contains the \`Bar\` and cursor to aid in pagination. 94 | """ 95 | edges: [BarsEdge!]! 96 | 97 | """A list of \`Bar\` objects.""" 98 | nodes: [Bar]! 99 | 100 | """Information to aid in pagination.""" 101 | pageInfo: PageInfo! 102 | 103 | """The count of *all* \`Bar\` you could get from the connection.""" 104 | totalCount: Int! 105 | } 106 | 107 | """A \`Bar\` edge in the connection.""" 108 | type BarsEdge { 109 | """A cursor for use in pagination.""" 110 | cursor: Cursor 111 | 112 | """The \`Bar\` at the end of the edge.""" 113 | node: Bar 114 | } 115 | 116 | """Methods to use when ordering \`Bar\`.""" 117 | enum BarsOrderBy { 118 | BAR_ID_ASC 119 | BAR_ID_DESC 120 | BAR_NAME_ASC 121 | BAR_NAME_DESC 122 | NATURAL 123 | PRIMARY_KEY_ASC 124 | PRIMARY_KEY_DESC 125 | } 126 | 127 | """A location in a connection that can be used for resuming pagination.""" 128 | scalar Cursor 129 | 130 | """ 131 | A point in time as described by the [ISO 132 | 8601](https://en.wikipedia.org/wiki/ISO_8601) standard. May or may not include a timezone. 133 | """ 134 | scalar Datetime 135 | 136 | type Foo implements Node { 137 | """Reads and enables pagination through a set of \`Bar\`.""" 138 | barsByJunctionJFooIdAndJBarId( 139 | """Read all values in the set after (below) this cursor.""" 140 | after: Cursor 141 | 142 | """Read all values in the set before (above) this cursor.""" 143 | before: Cursor 144 | 145 | """Only read the first \`n\` values of the set.""" 146 | first: Int 147 | 148 | """Only read the last \`n\` values of the set.""" 149 | last: Int 150 | 151 | """ 152 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 153 | based pagination. May not be used with \`last\`. 154 | """ 155 | offset: Int 156 | 157 | """The method to use when ordering \`Bar\`.""" 158 | orderBy: [BarsOrderBy!] = [PRIMARY_KEY_ASC] 159 | ): FooBarsByJunctionJFooIdAndJBarIdManyToManyConnection! 160 | fooId: Int! 161 | fooName: String! 162 | 163 | """Reads and enables pagination through a set of \`Junction\`.""" 164 | junctionsByJFooId( 165 | """Read all values in the set after (below) this cursor.""" 166 | after: Cursor 167 | 168 | """Read all values in the set before (above) this cursor.""" 169 | before: Cursor 170 | 171 | """Only read the first \`n\` values of the set.""" 172 | first: Int 173 | 174 | """Only read the last \`n\` values of the set.""" 175 | last: Int 176 | 177 | """ 178 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 179 | based pagination. May not be used with \`last\`. 180 | """ 181 | offset: Int 182 | 183 | """The method to use when ordering \`Junction\`.""" 184 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 185 | ): JunctionsConnection! 186 | 187 | """ 188 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 189 | """ 190 | nodeId: ID! 191 | } 192 | 193 | """A connection to a list of \`Bar\` values, with data from \`Junction\`.""" 194 | type FooBarsByJunctionJFooIdAndJBarIdManyToManyConnection { 195 | """ 196 | A list of edges which contains the \`Bar\`, info from the \`Junction\`, and the cursor to aid in pagination. 197 | """ 198 | edges: [FooBarsByJunctionJFooIdAndJBarIdManyToManyEdge!]! 199 | 200 | """A list of \`Bar\` objects.""" 201 | nodes: [Bar]! 202 | 203 | """Information to aid in pagination.""" 204 | pageInfo: PageInfo! 205 | 206 | """The count of *all* \`Bar\` you could get from the connection.""" 207 | totalCount: Int! 208 | } 209 | 210 | """A \`Bar\` edge in the connection, with data from \`Junction\`.""" 211 | type FooBarsByJunctionJFooIdAndJBarIdManyToManyEdge { 212 | createdAt: Datetime! 213 | 214 | """A cursor for use in pagination.""" 215 | cursor: Cursor 216 | 217 | """The \`Bar\` at the end of the edge.""" 218 | node: Bar 219 | } 220 | 221 | """A connection to a list of \`Foo\` values.""" 222 | type FoosConnection { 223 | """ 224 | A list of edges which contains the \`Foo\` and cursor to aid in pagination. 225 | """ 226 | edges: [FoosEdge!]! 227 | 228 | """A list of \`Foo\` objects.""" 229 | nodes: [Foo]! 230 | 231 | """Information to aid in pagination.""" 232 | pageInfo: PageInfo! 233 | 234 | """The count of *all* \`Foo\` you could get from the connection.""" 235 | totalCount: Int! 236 | } 237 | 238 | """A \`Foo\` edge in the connection.""" 239 | type FoosEdge { 240 | """A cursor for use in pagination.""" 241 | cursor: Cursor 242 | 243 | """The \`Foo\` at the end of the edge.""" 244 | node: Foo 245 | } 246 | 247 | """Methods to use when ordering \`Foo\`.""" 248 | enum FoosOrderBy { 249 | FOO_ID_ASC 250 | FOO_ID_DESC 251 | FOO_NAME_ASC 252 | FOO_NAME_DESC 253 | NATURAL 254 | PRIMARY_KEY_ASC 255 | PRIMARY_KEY_DESC 256 | } 257 | 258 | type Junction implements Node { 259 | """Reads a single \`Bar\` that is related to this \`Junction\`.""" 260 | barByJBarId: Bar 261 | createdAt: Datetime! 262 | 263 | """Reads a single \`Foo\` that is related to this \`Junction\`.""" 264 | fooByJFooId: Foo 265 | jBarId: Int! 266 | jFooId: Int! 267 | 268 | """ 269 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 270 | """ 271 | nodeId: ID! 272 | } 273 | 274 | """A connection to a list of \`Junction\` values.""" 275 | type JunctionsConnection { 276 | """ 277 | A list of edges which contains the \`Junction\` and cursor to aid in pagination. 278 | """ 279 | edges: [JunctionsEdge!]! 280 | 281 | """A list of \`Junction\` objects.""" 282 | nodes: [Junction]! 283 | 284 | """Information to aid in pagination.""" 285 | pageInfo: PageInfo! 286 | 287 | """The count of *all* \`Junction\` you could get from the connection.""" 288 | totalCount: Int! 289 | } 290 | 291 | """A \`Junction\` edge in the connection.""" 292 | type JunctionsEdge { 293 | """A cursor for use in pagination.""" 294 | cursor: Cursor 295 | 296 | """The \`Junction\` at the end of the edge.""" 297 | node: Junction 298 | } 299 | 300 | """Methods to use when ordering \`Junction\`.""" 301 | enum JunctionsOrderBy { 302 | CREATED_AT_ASC 303 | CREATED_AT_DESC 304 | J_BAR_ID_ASC 305 | J_BAR_ID_DESC 306 | J_FOO_ID_ASC 307 | J_FOO_ID_DESC 308 | NATURAL 309 | PRIMARY_KEY_ASC 310 | PRIMARY_KEY_DESC 311 | } 312 | 313 | """An object with a globally unique \`ID\`.""" 314 | interface Node { 315 | """ 316 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 317 | """ 318 | nodeId: ID! 319 | } 320 | 321 | """Information about pagination in a connection.""" 322 | type PageInfo { 323 | """When paginating forwards, the cursor to continue.""" 324 | endCursor: Cursor 325 | 326 | """When paginating forwards, are there more items?""" 327 | hasNextPage: Boolean! 328 | 329 | """When paginating backwards, are there more items?""" 330 | hasPreviousPage: Boolean! 331 | 332 | """When paginating backwards, the cursor to continue.""" 333 | startCursor: Cursor 334 | } 335 | 336 | """The root query type which gives access points into the data universe.""" 337 | type Query implements Node { 338 | """Reads and enables pagination through a set of \`Bar\`.""" 339 | allBars( 340 | """Read all values in the set after (below) this cursor.""" 341 | after: Cursor 342 | 343 | """Read all values in the set before (above) this cursor.""" 344 | before: Cursor 345 | 346 | """Only read the first \`n\` values of the set.""" 347 | first: Int 348 | 349 | """Only read the last \`n\` values of the set.""" 350 | last: Int 351 | 352 | """ 353 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 354 | based pagination. May not be used with \`last\`. 355 | """ 356 | offset: Int 357 | 358 | """The method to use when ordering \`Bar\`.""" 359 | orderBy: [BarsOrderBy!] = [PRIMARY_KEY_ASC] 360 | ): BarsConnection 361 | 362 | """Reads and enables pagination through a set of \`Foo\`.""" 363 | allFoos( 364 | """Read all values in the set after (below) this cursor.""" 365 | after: Cursor 366 | 367 | """Read all values in the set before (above) this cursor.""" 368 | before: Cursor 369 | 370 | """Only read the first \`n\` values of the set.""" 371 | first: Int 372 | 373 | """Only read the last \`n\` values of the set.""" 374 | last: Int 375 | 376 | """ 377 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 378 | based pagination. May not be used with \`last\`. 379 | """ 380 | offset: Int 381 | 382 | """The method to use when ordering \`Foo\`.""" 383 | orderBy: [FoosOrderBy!] = [PRIMARY_KEY_ASC] 384 | ): FoosConnection 385 | 386 | """Reads and enables pagination through a set of \`Junction\`.""" 387 | allJunctions( 388 | """Read all values in the set after (below) this cursor.""" 389 | after: Cursor 390 | 391 | """Read all values in the set before (above) this cursor.""" 392 | before: Cursor 393 | 394 | """Only read the first \`n\` values of the set.""" 395 | first: Int 396 | 397 | """Only read the last \`n\` values of the set.""" 398 | last: Int 399 | 400 | """ 401 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 402 | based pagination. May not be used with \`last\`. 403 | """ 404 | offset: Int 405 | 406 | """The method to use when ordering \`Junction\`.""" 407 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 408 | ): JunctionsConnection 409 | 410 | """Reads a single \`Bar\` using its globally unique \`ID\`.""" 411 | bar( 412 | """The globally unique \`ID\` to be used in selecting a single \`Bar\`.""" 413 | nodeId: ID! 414 | ): Bar 415 | barByBarId(barId: Int!): Bar 416 | 417 | """Reads a single \`Foo\` using its globally unique \`ID\`.""" 418 | foo( 419 | """The globally unique \`ID\` to be used in selecting a single \`Foo\`.""" 420 | nodeId: ID! 421 | ): Foo 422 | fooByFooId(fooId: Int!): Foo 423 | 424 | """Reads a single \`Junction\` using its globally unique \`ID\`.""" 425 | junction( 426 | """The globally unique \`ID\` to be used in selecting a single \`Junction\`.""" 427 | nodeId: ID! 428 | ): Junction 429 | junctionByJFooIdAndJBarId(jBarId: Int!, jFooId: Int!): Junction 430 | 431 | """Fetches an object given its globally unique \`ID\`.""" 432 | node( 433 | """The globally unique \`ID\`.""" 434 | nodeId: ID! 435 | ): Node 436 | 437 | """ 438 | The root query type must be a \`Node\` to work well with Relay 1 mutations. This just resolves to \`query\`. 439 | """ 440 | nodeId: ID! 441 | 442 | """ 443 | Exposes the root query type nested one level down. This is helpful for Relay 1 444 | which can only query top level fields if they are in a particular form. 445 | """ 446 | query: Query! 447 | } 448 | " 449 | `; 450 | -------------------------------------------------------------------------------- /__tests__/integration/schema/__snapshots__/c.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`prints a schema using the 'c' database schema 1`] = ` 4 | "type Bar implements Node { 5 | barId: Int! 6 | barName: String! 7 | 8 | """Reads and enables pagination through a set of \`Foo\`.""" 9 | foosByJunctionJBarIdAndJFooId( 10 | """Read all values in the set after (below) this cursor.""" 11 | after: Cursor 12 | 13 | """Read all values in the set before (above) this cursor.""" 14 | before: Cursor 15 | 16 | """Only read the first \`n\` values of the set.""" 17 | first: Int 18 | 19 | """Only read the last \`n\` values of the set.""" 20 | last: Int 21 | 22 | """ 23 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 24 | based pagination. May not be used with \`last\`. 25 | """ 26 | offset: Int 27 | 28 | """The method to use when ordering \`Foo\`.""" 29 | orderBy: [FoosOrderBy!] = [PRIMARY_KEY_ASC] 30 | ): BarFoosByJunctionJBarIdAndJFooIdManyToManyConnection! 31 | 32 | """Reads and enables pagination through a set of \`Junction\`.""" 33 | junctionsByJBarId( 34 | """Read all values in the set after (below) this cursor.""" 35 | after: Cursor 36 | 37 | """Read all values in the set before (above) this cursor.""" 38 | before: Cursor 39 | 40 | """Only read the first \`n\` values of the set.""" 41 | first: Int 42 | 43 | """Only read the last \`n\` values of the set.""" 44 | last: Int 45 | 46 | """ 47 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 48 | based pagination. May not be used with \`last\`. 49 | """ 50 | offset: Int 51 | 52 | """The method to use when ordering \`Junction\`.""" 53 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 54 | ): JunctionsConnection! 55 | 56 | """ 57 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 58 | """ 59 | nodeId: ID! 60 | } 61 | 62 | """A connection to a list of \`Foo\` values, with data from \`Junction\`.""" 63 | type BarFoosByJunctionJBarIdAndJFooIdManyToManyConnection { 64 | """ 65 | A list of edges which contains the \`Foo\`, info from the \`Junction\`, and the cursor to aid in pagination. 66 | """ 67 | edges: [BarFoosByJunctionJBarIdAndJFooIdManyToManyEdge!]! 68 | 69 | """A list of \`Foo\` objects.""" 70 | nodes: [Foo]! 71 | 72 | """Information to aid in pagination.""" 73 | pageInfo: PageInfo! 74 | 75 | """The count of *all* \`Foo\` you could get from the connection.""" 76 | totalCount: Int! 77 | } 78 | 79 | """A \`Foo\` edge in the connection, with data from \`Junction\`.""" 80 | type BarFoosByJunctionJBarIdAndJFooIdManyToManyEdge { 81 | """A cursor for use in pagination.""" 82 | cursor: Cursor 83 | 84 | """Reads and enables pagination through a set of \`Junction\`.""" 85 | junctionsByJFooId( 86 | """Read all values in the set after (below) this cursor.""" 87 | after: Cursor 88 | 89 | """Read all values in the set before (above) this cursor.""" 90 | before: Cursor 91 | 92 | """Only read the first \`n\` values of the set.""" 93 | first: Int 94 | 95 | """Only read the last \`n\` values of the set.""" 96 | last: Int 97 | 98 | """ 99 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 100 | based pagination. May not be used with \`last\`. 101 | """ 102 | offset: Int 103 | 104 | """The method to use when ordering \`Junction\`.""" 105 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 106 | ): JunctionsConnection! 107 | 108 | """The \`Foo\` at the end of the edge.""" 109 | node: Foo 110 | } 111 | 112 | """A connection to a list of \`Bar\` values.""" 113 | type BarsConnection { 114 | """ 115 | A list of edges which contains the \`Bar\` and cursor to aid in pagination. 116 | """ 117 | edges: [BarsEdge!]! 118 | 119 | """A list of \`Bar\` objects.""" 120 | nodes: [Bar]! 121 | 122 | """Information to aid in pagination.""" 123 | pageInfo: PageInfo! 124 | 125 | """The count of *all* \`Bar\` you could get from the connection.""" 126 | totalCount: Int! 127 | } 128 | 129 | """A \`Bar\` edge in the connection.""" 130 | type BarsEdge { 131 | """A cursor for use in pagination.""" 132 | cursor: Cursor 133 | 134 | """The \`Bar\` at the end of the edge.""" 135 | node: Bar 136 | } 137 | 138 | """Methods to use when ordering \`Bar\`.""" 139 | enum BarsOrderBy { 140 | BAR_ID_ASC 141 | BAR_ID_DESC 142 | BAR_NAME_ASC 143 | BAR_NAME_DESC 144 | NATURAL 145 | PRIMARY_KEY_ASC 146 | PRIMARY_KEY_DESC 147 | } 148 | 149 | """A location in a connection that can be used for resuming pagination.""" 150 | scalar Cursor 151 | 152 | type Foo implements Node { 153 | """Reads and enables pagination through a set of \`Bar\`.""" 154 | barsByJunctionJFooIdAndJBarId( 155 | """Read all values in the set after (below) this cursor.""" 156 | after: Cursor 157 | 158 | """Read all values in the set before (above) this cursor.""" 159 | before: Cursor 160 | 161 | """Only read the first \`n\` values of the set.""" 162 | first: Int 163 | 164 | """Only read the last \`n\` values of the set.""" 165 | last: Int 166 | 167 | """ 168 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 169 | based pagination. May not be used with \`last\`. 170 | """ 171 | offset: Int 172 | 173 | """The method to use when ordering \`Bar\`.""" 174 | orderBy: [BarsOrderBy!] = [PRIMARY_KEY_ASC] 175 | ): FooBarsByJunctionJFooIdAndJBarIdManyToManyConnection! 176 | fooId: Int! 177 | fooName: String! 178 | 179 | """Reads and enables pagination through a set of \`Junction\`.""" 180 | junctionsByJFooId( 181 | """Read all values in the set after (below) this cursor.""" 182 | after: Cursor 183 | 184 | """Read all values in the set before (above) this cursor.""" 185 | before: Cursor 186 | 187 | """Only read the first \`n\` values of the set.""" 188 | first: Int 189 | 190 | """Only read the last \`n\` values of the set.""" 191 | last: Int 192 | 193 | """ 194 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 195 | based pagination. May not be used with \`last\`. 196 | """ 197 | offset: Int 198 | 199 | """The method to use when ordering \`Junction\`.""" 200 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 201 | ): JunctionsConnection! 202 | 203 | """ 204 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 205 | """ 206 | nodeId: ID! 207 | } 208 | 209 | """A connection to a list of \`Bar\` values, with data from \`Junction\`.""" 210 | type FooBarsByJunctionJFooIdAndJBarIdManyToManyConnection { 211 | """ 212 | A list of edges which contains the \`Bar\`, info from the \`Junction\`, and the cursor to aid in pagination. 213 | """ 214 | edges: [FooBarsByJunctionJFooIdAndJBarIdManyToManyEdge!]! 215 | 216 | """A list of \`Bar\` objects.""" 217 | nodes: [Bar]! 218 | 219 | """Information to aid in pagination.""" 220 | pageInfo: PageInfo! 221 | 222 | """The count of *all* \`Bar\` you could get from the connection.""" 223 | totalCount: Int! 224 | } 225 | 226 | """A \`Bar\` edge in the connection, with data from \`Junction\`.""" 227 | type FooBarsByJunctionJFooIdAndJBarIdManyToManyEdge { 228 | """A cursor for use in pagination.""" 229 | cursor: Cursor 230 | 231 | """Reads and enables pagination through a set of \`Junction\`.""" 232 | junctionsByJBarId( 233 | """Read all values in the set after (below) this cursor.""" 234 | after: Cursor 235 | 236 | """Read all values in the set before (above) this cursor.""" 237 | before: Cursor 238 | 239 | """Only read the first \`n\` values of the set.""" 240 | first: Int 241 | 242 | """Only read the last \`n\` values of the set.""" 243 | last: Int 244 | 245 | """ 246 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 247 | based pagination. May not be used with \`last\`. 248 | """ 249 | offset: Int 250 | 251 | """The method to use when ordering \`Junction\`.""" 252 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 253 | ): JunctionsConnection! 254 | 255 | """The \`Bar\` at the end of the edge.""" 256 | node: Bar 257 | } 258 | 259 | """A connection to a list of \`Foo\` values.""" 260 | type FoosConnection { 261 | """ 262 | A list of edges which contains the \`Foo\` and cursor to aid in pagination. 263 | """ 264 | edges: [FoosEdge!]! 265 | 266 | """A list of \`Foo\` objects.""" 267 | nodes: [Foo]! 268 | 269 | """Information to aid in pagination.""" 270 | pageInfo: PageInfo! 271 | 272 | """The count of *all* \`Foo\` you could get from the connection.""" 273 | totalCount: Int! 274 | } 275 | 276 | """A \`Foo\` edge in the connection.""" 277 | type FoosEdge { 278 | """A cursor for use in pagination.""" 279 | cursor: Cursor 280 | 281 | """The \`Foo\` at the end of the edge.""" 282 | node: Foo 283 | } 284 | 285 | """Methods to use when ordering \`Foo\`.""" 286 | enum FoosOrderBy { 287 | FOO_ID_ASC 288 | FOO_ID_DESC 289 | FOO_NAME_ASC 290 | FOO_NAME_DESC 291 | NATURAL 292 | PRIMARY_KEY_ASC 293 | PRIMARY_KEY_DESC 294 | } 295 | 296 | type Junction implements Node { 297 | """Reads a single \`Bar\` that is related to this \`Junction\`.""" 298 | barByJBarId: Bar 299 | 300 | """Reads a single \`Foo\` that is related to this \`Junction\`.""" 301 | fooByJFooId: Foo 302 | id: Int! 303 | jBarId: Int 304 | jFooId: Int 305 | 306 | """ 307 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 308 | """ 309 | nodeId: ID! 310 | } 311 | 312 | """A connection to a list of \`Junction\` values.""" 313 | type JunctionsConnection { 314 | """ 315 | A list of edges which contains the \`Junction\` and cursor to aid in pagination. 316 | """ 317 | edges: [JunctionsEdge!]! 318 | 319 | """A list of \`Junction\` objects.""" 320 | nodes: [Junction]! 321 | 322 | """Information to aid in pagination.""" 323 | pageInfo: PageInfo! 324 | 325 | """The count of *all* \`Junction\` you could get from the connection.""" 326 | totalCount: Int! 327 | } 328 | 329 | """A \`Junction\` edge in the connection.""" 330 | type JunctionsEdge { 331 | """A cursor for use in pagination.""" 332 | cursor: Cursor 333 | 334 | """The \`Junction\` at the end of the edge.""" 335 | node: Junction 336 | } 337 | 338 | """Methods to use when ordering \`Junction\`.""" 339 | enum JunctionsOrderBy { 340 | ID_ASC 341 | ID_DESC 342 | J_BAR_ID_ASC 343 | J_BAR_ID_DESC 344 | J_FOO_ID_ASC 345 | J_FOO_ID_DESC 346 | NATURAL 347 | PRIMARY_KEY_ASC 348 | PRIMARY_KEY_DESC 349 | } 350 | 351 | """An object with a globally unique \`ID\`.""" 352 | interface Node { 353 | """ 354 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 355 | """ 356 | nodeId: ID! 357 | } 358 | 359 | """Information about pagination in a connection.""" 360 | type PageInfo { 361 | """When paginating forwards, the cursor to continue.""" 362 | endCursor: Cursor 363 | 364 | """When paginating forwards, are there more items?""" 365 | hasNextPage: Boolean! 366 | 367 | """When paginating backwards, are there more items?""" 368 | hasPreviousPage: Boolean! 369 | 370 | """When paginating backwards, the cursor to continue.""" 371 | startCursor: Cursor 372 | } 373 | 374 | """The root query type which gives access points into the data universe.""" 375 | type Query implements Node { 376 | """Reads and enables pagination through a set of \`Bar\`.""" 377 | allBars( 378 | """Read all values in the set after (below) this cursor.""" 379 | after: Cursor 380 | 381 | """Read all values in the set before (above) this cursor.""" 382 | before: Cursor 383 | 384 | """Only read the first \`n\` values of the set.""" 385 | first: Int 386 | 387 | """Only read the last \`n\` values of the set.""" 388 | last: Int 389 | 390 | """ 391 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 392 | based pagination. May not be used with \`last\`. 393 | """ 394 | offset: Int 395 | 396 | """The method to use when ordering \`Bar\`.""" 397 | orderBy: [BarsOrderBy!] = [PRIMARY_KEY_ASC] 398 | ): BarsConnection 399 | 400 | """Reads and enables pagination through a set of \`Foo\`.""" 401 | allFoos( 402 | """Read all values in the set after (below) this cursor.""" 403 | after: Cursor 404 | 405 | """Read all values in the set before (above) this cursor.""" 406 | before: Cursor 407 | 408 | """Only read the first \`n\` values of the set.""" 409 | first: Int 410 | 411 | """Only read the last \`n\` values of the set.""" 412 | last: Int 413 | 414 | """ 415 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 416 | based pagination. May not be used with \`last\`. 417 | """ 418 | offset: Int 419 | 420 | """The method to use when ordering \`Foo\`.""" 421 | orderBy: [FoosOrderBy!] = [PRIMARY_KEY_ASC] 422 | ): FoosConnection 423 | 424 | """Reads and enables pagination through a set of \`Junction\`.""" 425 | allJunctions( 426 | """Read all values in the set after (below) this cursor.""" 427 | after: Cursor 428 | 429 | """Read all values in the set before (above) this cursor.""" 430 | before: Cursor 431 | 432 | """Only read the first \`n\` values of the set.""" 433 | first: Int 434 | 435 | """Only read the last \`n\` values of the set.""" 436 | last: Int 437 | 438 | """ 439 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 440 | based pagination. May not be used with \`last\`. 441 | """ 442 | offset: Int 443 | 444 | """The method to use when ordering \`Junction\`.""" 445 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 446 | ): JunctionsConnection 447 | 448 | """Reads a single \`Bar\` using its globally unique \`ID\`.""" 449 | bar( 450 | """The globally unique \`ID\` to be used in selecting a single \`Bar\`.""" 451 | nodeId: ID! 452 | ): Bar 453 | barByBarId(barId: Int!): Bar 454 | 455 | """Reads a single \`Foo\` using its globally unique \`ID\`.""" 456 | foo( 457 | """The globally unique \`ID\` to be used in selecting a single \`Foo\`.""" 458 | nodeId: ID! 459 | ): Foo 460 | fooByFooId(fooId: Int!): Foo 461 | 462 | """Reads a single \`Junction\` using its globally unique \`ID\`.""" 463 | junction( 464 | """The globally unique \`ID\` to be used in selecting a single \`Junction\`.""" 465 | nodeId: ID! 466 | ): Junction 467 | junctionById(id: Int!): Junction 468 | 469 | """Fetches an object given its globally unique \`ID\`.""" 470 | node( 471 | """The globally unique \`ID\`.""" 472 | nodeId: ID! 473 | ): Node 474 | 475 | """ 476 | The root query type must be a \`Node\` to work well with Relay 1 mutations. This just resolves to \`query\`. 477 | """ 478 | nodeId: ID! 479 | 480 | """ 481 | Exposes the root query type nested one level down. This is helpful for Relay 1 482 | which can only query top level fields if they are in a particular form. 483 | """ 484 | query: Query! 485 | } 486 | " 487 | `; 488 | -------------------------------------------------------------------------------- /__tests__/integration/schema/__snapshots__/d.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`prints a schema using the 'd' database schema 1`] = ` 4 | "type Bar implements Node { 5 | barId: Int! 6 | barName: String! 7 | 8 | """Reads and enables pagination through a set of \`Foo\`.""" 9 | foosByJunctionJBarIdAndJFooId( 10 | """Read all values in the set after (below) this cursor.""" 11 | after: Cursor 12 | 13 | """Read all values in the set before (above) this cursor.""" 14 | before: Cursor 15 | 16 | """Only read the first \`n\` values of the set.""" 17 | first: Int 18 | 19 | """Only read the last \`n\` values of the set.""" 20 | last: Int 21 | 22 | """ 23 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 24 | based pagination. May not be used with \`last\`. 25 | """ 26 | offset: Int 27 | 28 | """The method to use when ordering \`Foo\`.""" 29 | orderBy: [FoosOrderBy!] = [PRIMARY_KEY_ASC] 30 | ): BarFoosByJunctionJBarIdAndJFooIdManyToManyConnection! 31 | 32 | """Reads and enables pagination through a set of \`Junction\`.""" 33 | junctionsByJBarId( 34 | """Read all values in the set after (below) this cursor.""" 35 | after: Cursor 36 | 37 | """Read all values in the set before (above) this cursor.""" 38 | before: Cursor 39 | 40 | """Only read the first \`n\` values of the set.""" 41 | first: Int 42 | 43 | """Only read the last \`n\` values of the set.""" 44 | last: Int 45 | 46 | """ 47 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 48 | based pagination. May not be used with \`last\`. 49 | """ 50 | offset: Int 51 | 52 | """The method to use when ordering \`Junction\`.""" 53 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 54 | ): JunctionsConnection! 55 | 56 | """ 57 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 58 | """ 59 | nodeId: ID! 60 | } 61 | 62 | """A connection to a list of \`Foo\` values, with data from \`Junction\`.""" 63 | type BarFoosByJunctionJBarIdAndJFooIdManyToManyConnection { 64 | """ 65 | A list of edges which contains the \`Foo\`, info from the \`Junction\`, and the cursor to aid in pagination. 66 | """ 67 | edges: [BarFoosByJunctionJBarIdAndJFooIdManyToManyEdge!]! 68 | 69 | """A list of \`Foo\` objects.""" 70 | nodes: [Foo]! 71 | 72 | """Information to aid in pagination.""" 73 | pageInfo: PageInfo! 74 | 75 | """The count of *all* \`Foo\` you could get from the connection.""" 76 | totalCount: Int! 77 | } 78 | 79 | """A \`Foo\` edge in the connection, with data from \`Junction\`.""" 80 | type BarFoosByJunctionJBarIdAndJFooIdManyToManyEdge { 81 | """A cursor for use in pagination.""" 82 | cursor: Cursor 83 | 84 | """Reads and enables pagination through a set of \`Junction\`.""" 85 | junctionsByJFooId( 86 | """Read all values in the set after (below) this cursor.""" 87 | after: Cursor 88 | 89 | """Read all values in the set before (above) this cursor.""" 90 | before: Cursor 91 | 92 | """Only read the first \`n\` values of the set.""" 93 | first: Int 94 | 95 | """Only read the last \`n\` values of the set.""" 96 | last: Int 97 | 98 | """ 99 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 100 | based pagination. May not be used with \`last\`. 101 | """ 102 | offset: Int 103 | 104 | """The method to use when ordering \`Junction\`.""" 105 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 106 | ): JunctionsConnection! 107 | 108 | """The \`Foo\` at the end of the edge.""" 109 | node: Foo 110 | } 111 | 112 | """A connection to a list of \`Bar\` values.""" 113 | type BarsConnection { 114 | """ 115 | A list of edges which contains the \`Bar\` and cursor to aid in pagination. 116 | """ 117 | edges: [BarsEdge!]! 118 | 119 | """A list of \`Bar\` objects.""" 120 | nodes: [Bar]! 121 | 122 | """Information to aid in pagination.""" 123 | pageInfo: PageInfo! 124 | 125 | """The count of *all* \`Bar\` you could get from the connection.""" 126 | totalCount: Int! 127 | } 128 | 129 | """A \`Bar\` edge in the connection.""" 130 | type BarsEdge { 131 | """A cursor for use in pagination.""" 132 | cursor: Cursor 133 | 134 | """The \`Bar\` at the end of the edge.""" 135 | node: Bar 136 | } 137 | 138 | """Methods to use when ordering \`Bar\`.""" 139 | enum BarsOrderBy { 140 | BAR_ID_ASC 141 | BAR_ID_DESC 142 | BAR_NAME_ASC 143 | BAR_NAME_DESC 144 | NATURAL 145 | PRIMARY_KEY_ASC 146 | PRIMARY_KEY_DESC 147 | } 148 | 149 | """A location in a connection that can be used for resuming pagination.""" 150 | scalar Cursor 151 | 152 | """ 153 | A point in time as described by the [ISO 154 | 8601](https://en.wikipedia.org/wiki/ISO_8601) standard. May or may not include a timezone. 155 | """ 156 | scalar Datetime 157 | 158 | type Foo implements Node { 159 | """Reads and enables pagination through a set of \`Bar\`.""" 160 | barsByJunctionJFooIdAndJBarId( 161 | """Read all values in the set after (below) this cursor.""" 162 | after: Cursor 163 | 164 | """Read all values in the set before (above) this cursor.""" 165 | before: Cursor 166 | 167 | """Only read the first \`n\` values of the set.""" 168 | first: Int 169 | 170 | """Only read the last \`n\` values of the set.""" 171 | last: Int 172 | 173 | """ 174 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 175 | based pagination. May not be used with \`last\`. 176 | """ 177 | offset: Int 178 | 179 | """The method to use when ordering \`Bar\`.""" 180 | orderBy: [BarsOrderBy!] = [PRIMARY_KEY_ASC] 181 | ): FooBarsByJunctionJFooIdAndJBarIdManyToManyConnection! 182 | fooId: Int! 183 | fooName: String! 184 | 185 | """Reads and enables pagination through a set of \`Junction\`.""" 186 | junctionsByJFooId( 187 | """Read all values in the set after (below) this cursor.""" 188 | after: Cursor 189 | 190 | """Read all values in the set before (above) this cursor.""" 191 | before: Cursor 192 | 193 | """Only read the first \`n\` values of the set.""" 194 | first: Int 195 | 196 | """Only read the last \`n\` values of the set.""" 197 | last: Int 198 | 199 | """ 200 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 201 | based pagination. May not be used with \`last\`. 202 | """ 203 | offset: Int 204 | 205 | """The method to use when ordering \`Junction\`.""" 206 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 207 | ): JunctionsConnection! 208 | 209 | """ 210 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 211 | """ 212 | nodeId: ID! 213 | } 214 | 215 | """A connection to a list of \`Bar\` values, with data from \`Junction\`.""" 216 | type FooBarsByJunctionJFooIdAndJBarIdManyToManyConnection { 217 | """ 218 | A list of edges which contains the \`Bar\`, info from the \`Junction\`, and the cursor to aid in pagination. 219 | """ 220 | edges: [FooBarsByJunctionJFooIdAndJBarIdManyToManyEdge!]! 221 | 222 | """A list of \`Bar\` objects.""" 223 | nodes: [Bar]! 224 | 225 | """Information to aid in pagination.""" 226 | pageInfo: PageInfo! 227 | 228 | """The count of *all* \`Bar\` you could get from the connection.""" 229 | totalCount: Int! 230 | } 231 | 232 | """A \`Bar\` edge in the connection, with data from \`Junction\`.""" 233 | type FooBarsByJunctionJFooIdAndJBarIdManyToManyEdge { 234 | """A cursor for use in pagination.""" 235 | cursor: Cursor 236 | 237 | """Reads and enables pagination through a set of \`Junction\`.""" 238 | junctionsByJBarId( 239 | """Read all values in the set after (below) this cursor.""" 240 | after: Cursor 241 | 242 | """Read all values in the set before (above) this cursor.""" 243 | before: Cursor 244 | 245 | """Only read the first \`n\` values of the set.""" 246 | first: Int 247 | 248 | """Only read the last \`n\` values of the set.""" 249 | last: Int 250 | 251 | """ 252 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 253 | based pagination. May not be used with \`last\`. 254 | """ 255 | offset: Int 256 | 257 | """The method to use when ordering \`Junction\`.""" 258 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 259 | ): JunctionsConnection! 260 | 261 | """The \`Bar\` at the end of the edge.""" 262 | node: Bar 263 | } 264 | 265 | """A connection to a list of \`Foo\` values.""" 266 | type FoosConnection { 267 | """ 268 | A list of edges which contains the \`Foo\` and cursor to aid in pagination. 269 | """ 270 | edges: [FoosEdge!]! 271 | 272 | """A list of \`Foo\` objects.""" 273 | nodes: [Foo]! 274 | 275 | """Information to aid in pagination.""" 276 | pageInfo: PageInfo! 277 | 278 | """The count of *all* \`Foo\` you could get from the connection.""" 279 | totalCount: Int! 280 | } 281 | 282 | """A \`Foo\` edge in the connection.""" 283 | type FoosEdge { 284 | """A cursor for use in pagination.""" 285 | cursor: Cursor 286 | 287 | """The \`Foo\` at the end of the edge.""" 288 | node: Foo 289 | } 290 | 291 | """Methods to use when ordering \`Foo\`.""" 292 | enum FoosOrderBy { 293 | FOO_ID_ASC 294 | FOO_ID_DESC 295 | FOO_NAME_ASC 296 | FOO_NAME_DESC 297 | NATURAL 298 | PRIMARY_KEY_ASC 299 | PRIMARY_KEY_DESC 300 | } 301 | 302 | type Junction implements Node { 303 | """Reads a single \`Bar\` that is related to this \`Junction\`.""" 304 | barByJBarId: Bar 305 | createdAt: Datetime! 306 | 307 | """Reads a single \`Foo\` that is related to this \`Junction\`.""" 308 | fooByJFooId: Foo 309 | id: Int! 310 | jBarId: Int 311 | jFooId: Int 312 | 313 | """ 314 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 315 | """ 316 | nodeId: ID! 317 | } 318 | 319 | """A connection to a list of \`Junction\` values.""" 320 | type JunctionsConnection { 321 | """ 322 | A list of edges which contains the \`Junction\` and cursor to aid in pagination. 323 | """ 324 | edges: [JunctionsEdge!]! 325 | 326 | """A list of \`Junction\` objects.""" 327 | nodes: [Junction]! 328 | 329 | """Information to aid in pagination.""" 330 | pageInfo: PageInfo! 331 | 332 | """The count of *all* \`Junction\` you could get from the connection.""" 333 | totalCount: Int! 334 | } 335 | 336 | """A \`Junction\` edge in the connection.""" 337 | type JunctionsEdge { 338 | """A cursor for use in pagination.""" 339 | cursor: Cursor 340 | 341 | """The \`Junction\` at the end of the edge.""" 342 | node: Junction 343 | } 344 | 345 | """Methods to use when ordering \`Junction\`.""" 346 | enum JunctionsOrderBy { 347 | CREATED_AT_ASC 348 | CREATED_AT_DESC 349 | ID_ASC 350 | ID_DESC 351 | J_BAR_ID_ASC 352 | J_BAR_ID_DESC 353 | J_FOO_ID_ASC 354 | J_FOO_ID_DESC 355 | NATURAL 356 | PRIMARY_KEY_ASC 357 | PRIMARY_KEY_DESC 358 | } 359 | 360 | """An object with a globally unique \`ID\`.""" 361 | interface Node { 362 | """ 363 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 364 | """ 365 | nodeId: ID! 366 | } 367 | 368 | """Information about pagination in a connection.""" 369 | type PageInfo { 370 | """When paginating forwards, the cursor to continue.""" 371 | endCursor: Cursor 372 | 373 | """When paginating forwards, are there more items?""" 374 | hasNextPage: Boolean! 375 | 376 | """When paginating backwards, are there more items?""" 377 | hasPreviousPage: Boolean! 378 | 379 | """When paginating backwards, the cursor to continue.""" 380 | startCursor: Cursor 381 | } 382 | 383 | """The root query type which gives access points into the data universe.""" 384 | type Query implements Node { 385 | """Reads and enables pagination through a set of \`Bar\`.""" 386 | allBars( 387 | """Read all values in the set after (below) this cursor.""" 388 | after: Cursor 389 | 390 | """Read all values in the set before (above) this cursor.""" 391 | before: Cursor 392 | 393 | """Only read the first \`n\` values of the set.""" 394 | first: Int 395 | 396 | """Only read the last \`n\` values of the set.""" 397 | last: Int 398 | 399 | """ 400 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 401 | based pagination. May not be used with \`last\`. 402 | """ 403 | offset: Int 404 | 405 | """The method to use when ordering \`Bar\`.""" 406 | orderBy: [BarsOrderBy!] = [PRIMARY_KEY_ASC] 407 | ): BarsConnection 408 | 409 | """Reads and enables pagination through a set of \`Foo\`.""" 410 | allFoos( 411 | """Read all values in the set after (below) this cursor.""" 412 | after: Cursor 413 | 414 | """Read all values in the set before (above) this cursor.""" 415 | before: Cursor 416 | 417 | """Only read the first \`n\` values of the set.""" 418 | first: Int 419 | 420 | """Only read the last \`n\` values of the set.""" 421 | last: Int 422 | 423 | """ 424 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 425 | based pagination. May not be used with \`last\`. 426 | """ 427 | offset: Int 428 | 429 | """The method to use when ordering \`Foo\`.""" 430 | orderBy: [FoosOrderBy!] = [PRIMARY_KEY_ASC] 431 | ): FoosConnection 432 | 433 | """Reads and enables pagination through a set of \`Junction\`.""" 434 | allJunctions( 435 | """Read all values in the set after (below) this cursor.""" 436 | after: Cursor 437 | 438 | """Read all values in the set before (above) this cursor.""" 439 | before: Cursor 440 | 441 | """Only read the first \`n\` values of the set.""" 442 | first: Int 443 | 444 | """Only read the last \`n\` values of the set.""" 445 | last: Int 446 | 447 | """ 448 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 449 | based pagination. May not be used with \`last\`. 450 | """ 451 | offset: Int 452 | 453 | """The method to use when ordering \`Junction\`.""" 454 | orderBy: [JunctionsOrderBy!] = [PRIMARY_KEY_ASC] 455 | ): JunctionsConnection 456 | 457 | """Reads a single \`Bar\` using its globally unique \`ID\`.""" 458 | bar( 459 | """The globally unique \`ID\` to be used in selecting a single \`Bar\`.""" 460 | nodeId: ID! 461 | ): Bar 462 | barByBarId(barId: Int!): Bar 463 | 464 | """Reads a single \`Foo\` using its globally unique \`ID\`.""" 465 | foo( 466 | """The globally unique \`ID\` to be used in selecting a single \`Foo\`.""" 467 | nodeId: ID! 468 | ): Foo 469 | fooByFooId(fooId: Int!): Foo 470 | 471 | """Reads a single \`Junction\` using its globally unique \`ID\`.""" 472 | junction( 473 | """The globally unique \`ID\` to be used in selecting a single \`Junction\`.""" 474 | nodeId: ID! 475 | ): Junction 476 | junctionById(id: Int!): Junction 477 | 478 | """Fetches an object given its globally unique \`ID\`.""" 479 | node( 480 | """The globally unique \`ID\`.""" 481 | nodeId: ID! 482 | ): Node 483 | 484 | """ 485 | The root query type must be a \`Node\` to work well with Relay 1 mutations. This just resolves to \`query\`. 486 | """ 487 | nodeId: ID! 488 | 489 | """ 490 | Exposes the root query type nested one level down. This is helpful for Relay 1 491 | which can only query top level fields if they are in a particular form. 492 | """ 493 | query: Query! 494 | } 495 | " 496 | `; 497 | -------------------------------------------------------------------------------- /__tests__/integration/schema/__snapshots__/f.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`prints a schema with the many-to-many plugin 1`] = ` 4 | """"A location in a connection that can be used for resuming pagination.""" 5 | scalar Cursor 6 | 7 | type Junction implements Node { 8 | followerId: Int! 9 | followingId: Int! 10 | 11 | """ 12 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 13 | """ 14 | nodeId: ID! 15 | 16 | """Reads a single \`Person\` that is related to this \`Junction\`.""" 17 | personByFollowerId: Person 18 | 19 | """Reads a single \`Person\` that is related to this \`Junction\`.""" 20 | personByFollowingId: Person 21 | } 22 | 23 | """An object with a globally unique \`ID\`.""" 24 | interface Node { 25 | """ 26 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 27 | """ 28 | nodeId: ID! 29 | } 30 | 31 | """Information about pagination in a connection.""" 32 | type PageInfo { 33 | """When paginating forwards, the cursor to continue.""" 34 | endCursor: Cursor 35 | 36 | """When paginating forwards, are there more items?""" 37 | hasNextPage: Boolean! 38 | 39 | """When paginating backwards, are there more items?""" 40 | hasPreviousPage: Boolean! 41 | 42 | """When paginating backwards, the cursor to continue.""" 43 | startCursor: Cursor 44 | } 45 | 46 | """A connection to a list of \`Person\` values.""" 47 | type PeopleConnection { 48 | """ 49 | A list of edges which contains the \`Person\` and cursor to aid in pagination. 50 | """ 51 | edges: [PeopleEdge!]! 52 | 53 | """A list of \`Person\` objects.""" 54 | nodes: [Person]! 55 | 56 | """Information to aid in pagination.""" 57 | pageInfo: PageInfo! 58 | 59 | """The count of *all* \`Person\` you could get from the connection.""" 60 | totalCount: Int! 61 | } 62 | 63 | """A \`Person\` edge in the connection.""" 64 | type PeopleEdge { 65 | """A cursor for use in pagination.""" 66 | cursor: Cursor 67 | 68 | """The \`Person\` at the end of the edge.""" 69 | node: Person 70 | } 71 | 72 | """Methods to use when ordering \`Person\`.""" 73 | enum PeopleOrderBy { 74 | ID_ASC 75 | ID_DESC 76 | NAME_ASC 77 | NAME_DESC 78 | NATURAL 79 | PRIMARY_KEY_ASC 80 | PRIMARY_KEY_DESC 81 | } 82 | 83 | type Person implements Node { 84 | """Reads and enables pagination through a set of \`Person\`.""" 85 | followers( 86 | """Read all values in the set after (below) this cursor.""" 87 | after: Cursor 88 | 89 | """Read all values in the set before (above) this cursor.""" 90 | before: Cursor 91 | 92 | """ 93 | A condition to be used in determining which values should be returned by the collection. 94 | """ 95 | condition: PersonCondition 96 | 97 | """Only read the first \`n\` values of the set.""" 98 | first: Int 99 | 100 | """Only read the last \`n\` values of the set.""" 101 | last: Int 102 | 103 | """ 104 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 105 | based pagination. May not be used with \`last\`. 106 | """ 107 | offset: Int 108 | 109 | """The method to use when ordering \`Person\`.""" 110 | orderBy: [PeopleOrderBy!] = [PRIMARY_KEY_ASC] 111 | ): PersonFollowersManyToManyConnection! 112 | 113 | """Reads and enables pagination through a set of \`Person\`.""" 114 | following( 115 | """Read all values in the set after (below) this cursor.""" 116 | after: Cursor 117 | 118 | """Read all values in the set before (above) this cursor.""" 119 | before: Cursor 120 | 121 | """ 122 | A condition to be used in determining which values should be returned by the collection. 123 | """ 124 | condition: PersonCondition 125 | 126 | """Only read the first \`n\` values of the set.""" 127 | first: Int 128 | 129 | """Only read the last \`n\` values of the set.""" 130 | last: Int 131 | 132 | """ 133 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 134 | based pagination. May not be used with \`last\`. 135 | """ 136 | offset: Int 137 | 138 | """The method to use when ordering \`Person\`.""" 139 | orderBy: [PeopleOrderBy!] = [PRIMARY_KEY_ASC] 140 | ): PersonFollowingManyToManyConnection! 141 | id: Int! 142 | name: String 143 | 144 | """ 145 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 146 | """ 147 | nodeId: ID! 148 | } 149 | 150 | """ 151 | A condition to be used against \`Person\` object types. All fields are tested for equality and combined with a logical ‘and.’ 152 | """ 153 | input PersonCondition { 154 | """Checks for equality with the object’s \`id\` field.""" 155 | id: Int 156 | 157 | """Checks for equality with the object’s \`name\` field.""" 158 | name: String 159 | } 160 | 161 | """A connection to a list of \`Person\` values, with data from \`Junction\`.""" 162 | type PersonFollowersManyToManyConnection { 163 | """ 164 | A list of edges which contains the \`Person\`, info from the \`Junction\`, and the cursor to aid in pagination. 165 | """ 166 | edges: [PersonFollowersManyToManyEdge!]! 167 | 168 | """A list of \`Person\` objects.""" 169 | nodes: [Person]! 170 | 171 | """Information to aid in pagination.""" 172 | pageInfo: PageInfo! 173 | 174 | """The count of *all* \`Person\` you could get from the connection.""" 175 | totalCount: Int! 176 | } 177 | 178 | """A \`Person\` edge in the connection, with data from \`Junction\`.""" 179 | type PersonFollowersManyToManyEdge { 180 | """A cursor for use in pagination.""" 181 | cursor: Cursor 182 | 183 | """The \`Person\` at the end of the edge.""" 184 | node: Person 185 | } 186 | 187 | """A connection to a list of \`Person\` values, with data from \`Junction\`.""" 188 | type PersonFollowingManyToManyConnection { 189 | """ 190 | A list of edges which contains the \`Person\`, info from the \`Junction\`, and the cursor to aid in pagination. 191 | """ 192 | edges: [PersonFollowingManyToManyEdge!]! 193 | 194 | """A list of \`Person\` objects.""" 195 | nodes: [Person]! 196 | 197 | """Information to aid in pagination.""" 198 | pageInfo: PageInfo! 199 | 200 | """The count of *all* \`Person\` you could get from the connection.""" 201 | totalCount: Int! 202 | } 203 | 204 | """A \`Person\` edge in the connection, with data from \`Junction\`.""" 205 | type PersonFollowingManyToManyEdge { 206 | """A cursor for use in pagination.""" 207 | cursor: Cursor 208 | 209 | """The \`Person\` at the end of the edge.""" 210 | node: Person 211 | } 212 | 213 | """The root query type which gives access points into the data universe.""" 214 | type Query implements Node { 215 | """Reads and enables pagination through a set of \`Person\`.""" 216 | allPeople( 217 | """Read all values in the set after (below) this cursor.""" 218 | after: Cursor 219 | 220 | """Read all values in the set before (above) this cursor.""" 221 | before: Cursor 222 | 223 | """ 224 | A condition to be used in determining which values should be returned by the collection. 225 | """ 226 | condition: PersonCondition 227 | 228 | """Only read the first \`n\` values of the set.""" 229 | first: Int 230 | 231 | """Only read the last \`n\` values of the set.""" 232 | last: Int 233 | 234 | """ 235 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 236 | based pagination. May not be used with \`last\`. 237 | """ 238 | offset: Int 239 | 240 | """The method to use when ordering \`Person\`.""" 241 | orderBy: [PeopleOrderBy!] = [PRIMARY_KEY_ASC] 242 | ): PeopleConnection 243 | 244 | """Reads a single \`Junction\` using its globally unique \`ID\`.""" 245 | junction( 246 | """The globally unique \`ID\` to be used in selecting a single \`Junction\`.""" 247 | nodeId: ID! 248 | ): Junction 249 | junctionByFollowerIdAndFollowingId(followerId: Int!, followingId: Int!): Junction 250 | 251 | """Fetches an object given its globally unique \`ID\`.""" 252 | node( 253 | """The globally unique \`ID\`.""" 254 | nodeId: ID! 255 | ): Node 256 | 257 | """ 258 | The root query type must be a \`Node\` to work well with Relay 1 mutations. This just resolves to \`query\`. 259 | """ 260 | nodeId: ID! 261 | 262 | """Reads a single \`Person\` using its globally unique \`ID\`.""" 263 | person( 264 | """The globally unique \`ID\` to be used in selecting a single \`Person\`.""" 265 | nodeId: ID! 266 | ): Person 267 | personById(id: Int!): Person 268 | 269 | """ 270 | Exposes the root query type nested one level down. This is helpful for Relay 1 271 | which can only query top level fields if they are in a particular form. 272 | """ 273 | query: Query! 274 | } 275 | " 276 | `; 277 | -------------------------------------------------------------------------------- /__tests__/integration/schema/__snapshots__/g.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`prints a schema with the many-to-many plugin 1`] = ` 4 | """"A location in a connection that can be used for resuming pagination.""" 5 | scalar Cursor 6 | 7 | """ 8 | A point in time as described by the [ISO 9 | 8601](https://en.wikipedia.org/wiki/ISO_8601) standard. May or may not include a timezone. 10 | """ 11 | scalar Datetime 12 | 13 | """An object with a globally unique \`ID\`.""" 14 | interface Node { 15 | """ 16 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 17 | """ 18 | nodeId: ID! 19 | } 20 | 21 | """Information about pagination in a connection.""" 22 | type PageInfo { 23 | """When paginating forwards, the cursor to continue.""" 24 | endCursor: Cursor 25 | 26 | """When paginating forwards, are there more items?""" 27 | hasNextPage: Boolean! 28 | 29 | """When paginating backwards, are there more items?""" 30 | hasPreviousPage: Boolean! 31 | 32 | """When paginating backwards, the cursor to continue.""" 33 | startCursor: Cursor 34 | } 35 | 36 | type Post implements Node { 37 | """Reads and enables pagination through a set of \`User\`.""" 38 | authors( 39 | """Read all values in the set after (below) this cursor.""" 40 | after: Cursor 41 | 42 | """Read all values in the set before (above) this cursor.""" 43 | before: Cursor 44 | 45 | """ 46 | A condition to be used in determining which values should be returned by the collection. 47 | """ 48 | condition: UserCondition 49 | 50 | """Only read the first \`n\` values of the set.""" 51 | first: Int 52 | 53 | """Only read the last \`n\` values of the set.""" 54 | last: Int 55 | 56 | """ 57 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 58 | based pagination. May not be used with \`last\`. 59 | """ 60 | offset: Int 61 | 62 | """The method to use when ordering \`User\`.""" 63 | orderBy: [UsersOrderBy!] = [PRIMARY_KEY_ASC] 64 | ): PostAuthorsManyToManyConnection! 65 | content: String 66 | createdAt: Datetime! 67 | id: UUID! 68 | 69 | """ 70 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 71 | """ 72 | nodeId: ID! 73 | title: String! 74 | } 75 | 76 | type PostAuthor implements Node { 77 | createdAt: Datetime! 78 | id: UUID! 79 | 80 | """ 81 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 82 | """ 83 | nodeId: ID! 84 | 85 | """Reads a single \`Post\` that is related to this \`PostAuthor\`.""" 86 | postByPostId: Post 87 | postId: UUID! 88 | 89 | """Reads a single \`User\` that is related to this \`PostAuthor\`.""" 90 | userByUserId: User 91 | userId: UUID! 92 | } 93 | 94 | """ 95 | A condition to be used against \`PostAuthor\` object types. All fields are tested 96 | for equality and combined with a logical ‘and.’ 97 | """ 98 | input PostAuthorCondition { 99 | """Checks for equality with the object’s \`createdAt\` field.""" 100 | createdAt: Datetime 101 | 102 | """Checks for equality with the object’s \`id\` field.""" 103 | id: UUID 104 | 105 | """Checks for equality with the object’s \`postId\` field.""" 106 | postId: UUID 107 | 108 | """Checks for equality with the object’s \`userId\` field.""" 109 | userId: UUID 110 | } 111 | 112 | """A connection to a list of \`PostAuthor\` values.""" 113 | type PostAuthorsConnection { 114 | """ 115 | A list of edges which contains the \`PostAuthor\` and cursor to aid in pagination. 116 | """ 117 | edges: [PostAuthorsEdge!]! 118 | 119 | """A list of \`PostAuthor\` objects.""" 120 | nodes: [PostAuthor]! 121 | 122 | """Information to aid in pagination.""" 123 | pageInfo: PageInfo! 124 | 125 | """The count of *all* \`PostAuthor\` you could get from the connection.""" 126 | totalCount: Int! 127 | } 128 | 129 | """A \`PostAuthor\` edge in the connection.""" 130 | type PostAuthorsEdge { 131 | """A cursor for use in pagination.""" 132 | cursor: Cursor 133 | 134 | """The \`PostAuthor\` at the end of the edge.""" 135 | node: PostAuthor 136 | } 137 | 138 | """A connection to a list of \`User\` values, with data from \`PostAuthor\`.""" 139 | type PostAuthorsManyToManyConnection { 140 | """ 141 | A list of edges which contains the \`User\`, info from the \`PostAuthor\`, and the cursor to aid in pagination. 142 | """ 143 | edges: [PostAuthorsManyToManyEdge!]! 144 | 145 | """A list of \`User\` objects.""" 146 | nodes: [User]! 147 | 148 | """Information to aid in pagination.""" 149 | pageInfo: PageInfo! 150 | 151 | """The count of *all* \`User\` you could get from the connection.""" 152 | totalCount: Int! 153 | } 154 | 155 | """A \`User\` edge in the connection, with data from \`PostAuthor\`.""" 156 | type PostAuthorsManyToManyEdge { 157 | """A cursor for use in pagination.""" 158 | cursor: Cursor 159 | 160 | """The \`User\` at the end of the edge.""" 161 | node: User 162 | 163 | """Reads and enables pagination through a set of \`PostAuthor\`.""" 164 | postAuthorsByUserId( 165 | """Read all values in the set after (below) this cursor.""" 166 | after: Cursor 167 | 168 | """Read all values in the set before (above) this cursor.""" 169 | before: Cursor 170 | 171 | """ 172 | A condition to be used in determining which values should be returned by the collection. 173 | """ 174 | condition: PostAuthorCondition 175 | 176 | """Only read the first \`n\` values of the set.""" 177 | first: Int 178 | 179 | """Only read the last \`n\` values of the set.""" 180 | last: Int 181 | 182 | """ 183 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 184 | based pagination. May not be used with \`last\`. 185 | """ 186 | offset: Int 187 | 188 | """The method to use when ordering \`PostAuthor\`.""" 189 | orderBy: [PostAuthorsOrderBy!] = [PRIMARY_KEY_ASC] 190 | ): PostAuthorsConnection! 191 | } 192 | 193 | """Methods to use when ordering \`PostAuthor\`.""" 194 | enum PostAuthorsOrderBy { 195 | CREATED_AT_ASC 196 | CREATED_AT_DESC 197 | ID_ASC 198 | ID_DESC 199 | NATURAL 200 | POST_ID_ASC 201 | POST_ID_DESC 202 | PRIMARY_KEY_ASC 203 | PRIMARY_KEY_DESC 204 | USER_ID_ASC 205 | USER_ID_DESC 206 | } 207 | 208 | """ 209 | A condition to be used against \`Post\` object types. All fields are tested for equality and combined with a logical ‘and.’ 210 | """ 211 | input PostCondition { 212 | """Checks for equality with the object’s \`content\` field.""" 213 | content: String 214 | 215 | """Checks for equality with the object’s \`createdAt\` field.""" 216 | createdAt: Datetime 217 | 218 | """Checks for equality with the object’s \`id\` field.""" 219 | id: UUID 220 | 221 | """Checks for equality with the object’s \`title\` field.""" 222 | title: String 223 | } 224 | 225 | """A connection to a list of \`Post\` values.""" 226 | type PostsConnection { 227 | """ 228 | A list of edges which contains the \`Post\` and cursor to aid in pagination. 229 | """ 230 | edges: [PostsEdge!]! 231 | 232 | """A list of \`Post\` objects.""" 233 | nodes: [Post]! 234 | 235 | """Information to aid in pagination.""" 236 | pageInfo: PageInfo! 237 | 238 | """The count of *all* \`Post\` you could get from the connection.""" 239 | totalCount: Int! 240 | } 241 | 242 | """A \`Post\` edge in the connection.""" 243 | type PostsEdge { 244 | """A cursor for use in pagination.""" 245 | cursor: Cursor 246 | 247 | """The \`Post\` at the end of the edge.""" 248 | node: Post 249 | } 250 | 251 | """Methods to use when ordering \`Post\`.""" 252 | enum PostsOrderBy { 253 | CONTENT_ASC 254 | CONTENT_DESC 255 | CREATED_AT_ASC 256 | CREATED_AT_DESC 257 | ID_ASC 258 | ID_DESC 259 | NATURAL 260 | PRIMARY_KEY_ASC 261 | PRIMARY_KEY_DESC 262 | TITLE_ASC 263 | TITLE_DESC 264 | } 265 | 266 | """The root query type which gives access points into the data universe.""" 267 | type Query implements Node { 268 | """Reads and enables pagination through a set of \`PostAuthor\`.""" 269 | allPostAuthors( 270 | """Read all values in the set after (below) this cursor.""" 271 | after: Cursor 272 | 273 | """Read all values in the set before (above) this cursor.""" 274 | before: Cursor 275 | 276 | """ 277 | A condition to be used in determining which values should be returned by the collection. 278 | """ 279 | condition: PostAuthorCondition 280 | 281 | """Only read the first \`n\` values of the set.""" 282 | first: Int 283 | 284 | """Only read the last \`n\` values of the set.""" 285 | last: Int 286 | 287 | """ 288 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 289 | based pagination. May not be used with \`last\`. 290 | """ 291 | offset: Int 292 | 293 | """The method to use when ordering \`PostAuthor\`.""" 294 | orderBy: [PostAuthorsOrderBy!] = [PRIMARY_KEY_ASC] 295 | ): PostAuthorsConnection 296 | 297 | """Reads and enables pagination through a set of \`Post\`.""" 298 | allPosts( 299 | """Read all values in the set after (below) this cursor.""" 300 | after: Cursor 301 | 302 | """Read all values in the set before (above) this cursor.""" 303 | before: Cursor 304 | 305 | """ 306 | A condition to be used in determining which values should be returned by the collection. 307 | """ 308 | condition: PostCondition 309 | 310 | """Only read the first \`n\` values of the set.""" 311 | first: Int 312 | 313 | """Only read the last \`n\` values of the set.""" 314 | last: Int 315 | 316 | """ 317 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 318 | based pagination. May not be used with \`last\`. 319 | """ 320 | offset: Int 321 | 322 | """The method to use when ordering \`Post\`.""" 323 | orderBy: [PostsOrderBy!] = [PRIMARY_KEY_ASC] 324 | ): PostsConnection 325 | 326 | """Reads and enables pagination through a set of \`User\`.""" 327 | allUsers( 328 | """Read all values in the set after (below) this cursor.""" 329 | after: Cursor 330 | 331 | """Read all values in the set before (above) this cursor.""" 332 | before: Cursor 333 | 334 | """ 335 | A condition to be used in determining which values should be returned by the collection. 336 | """ 337 | condition: UserCondition 338 | 339 | """Only read the first \`n\` values of the set.""" 340 | first: Int 341 | 342 | """Only read the last \`n\` values of the set.""" 343 | last: Int 344 | 345 | """ 346 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 347 | based pagination. May not be used with \`last\`. 348 | """ 349 | offset: Int 350 | 351 | """The method to use when ordering \`User\`.""" 352 | orderBy: [UsersOrderBy!] = [PRIMARY_KEY_ASC] 353 | ): UsersConnection 354 | 355 | """Fetches an object given its globally unique \`ID\`.""" 356 | node( 357 | """The globally unique \`ID\`.""" 358 | nodeId: ID! 359 | ): Node 360 | 361 | """ 362 | The root query type must be a \`Node\` to work well with Relay 1 mutations. This just resolves to \`query\`. 363 | """ 364 | nodeId: ID! 365 | 366 | """Reads a single \`Post\` using its globally unique \`ID\`.""" 367 | post( 368 | """The globally unique \`ID\` to be used in selecting a single \`Post\`.""" 369 | nodeId: ID! 370 | ): Post 371 | 372 | """Reads a single \`PostAuthor\` using its globally unique \`ID\`.""" 373 | postAuthor( 374 | """ 375 | The globally unique \`ID\` to be used in selecting a single \`PostAuthor\`. 376 | """ 377 | nodeId: ID! 378 | ): PostAuthor 379 | postAuthorById(id: UUID!): PostAuthor 380 | postById(id: UUID!): Post 381 | 382 | """ 383 | Exposes the root query type nested one level down. This is helpful for Relay 1 384 | which can only query top level fields if they are in a particular form. 385 | """ 386 | query: Query! 387 | 388 | """Reads a single \`User\` using its globally unique \`ID\`.""" 389 | user( 390 | """The globally unique \`ID\` to be used in selecting a single \`User\`.""" 391 | nodeId: ID! 392 | ): User 393 | userById(id: UUID!): User 394 | } 395 | 396 | """ 397 | A universally unique identifier as defined by [RFC 4122](https://tools.ietf.org/html/rfc4122). 398 | """ 399 | scalar UUID 400 | 401 | type User implements Node { 402 | id: UUID! 403 | 404 | """ 405 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 406 | """ 407 | nodeId: ID! 408 | 409 | """Reads and enables pagination through a set of \`Post\`.""" 410 | postsByPostAuthorUserIdAndPostId( 411 | """Read all values in the set after (below) this cursor.""" 412 | after: Cursor 413 | 414 | """Read all values in the set before (above) this cursor.""" 415 | before: Cursor 416 | 417 | """ 418 | A condition to be used in determining which values should be returned by the collection. 419 | """ 420 | condition: PostCondition 421 | 422 | """Only read the first \`n\` values of the set.""" 423 | first: Int 424 | 425 | """Only read the last \`n\` values of the set.""" 426 | last: Int 427 | 428 | """ 429 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 430 | based pagination. May not be used with \`last\`. 431 | """ 432 | offset: Int 433 | 434 | """The method to use when ordering \`Post\`.""" 435 | orderBy: [PostsOrderBy!] = [PRIMARY_KEY_ASC] 436 | ): UserPostsByPostAuthorUserIdAndPostIdManyToManyConnection! 437 | } 438 | 439 | """ 440 | A condition to be used against \`User\` object types. All fields are tested for equality and combined with a logical ‘and.’ 441 | """ 442 | input UserCondition { 443 | """Checks for equality with the object’s \`id\` field.""" 444 | id: UUID 445 | } 446 | 447 | """A connection to a list of \`Post\` values, with data from \`PostAuthor\`.""" 448 | type UserPostsByPostAuthorUserIdAndPostIdManyToManyConnection { 449 | """ 450 | A list of edges which contains the \`Post\`, info from the \`PostAuthor\`, and the cursor to aid in pagination. 451 | """ 452 | edges: [UserPostsByPostAuthorUserIdAndPostIdManyToManyEdge!]! 453 | 454 | """A list of \`Post\` objects.""" 455 | nodes: [Post]! 456 | 457 | """Information to aid in pagination.""" 458 | pageInfo: PageInfo! 459 | 460 | """The count of *all* \`Post\` you could get from the connection.""" 461 | totalCount: Int! 462 | } 463 | 464 | """A \`Post\` edge in the connection, with data from \`PostAuthor\`.""" 465 | type UserPostsByPostAuthorUserIdAndPostIdManyToManyEdge { 466 | """A cursor for use in pagination.""" 467 | cursor: Cursor 468 | 469 | """The \`Post\` at the end of the edge.""" 470 | node: Post 471 | 472 | """Reads and enables pagination through a set of \`PostAuthor\`.""" 473 | postAuthorsByPostId( 474 | """Read all values in the set after (below) this cursor.""" 475 | after: Cursor 476 | 477 | """Read all values in the set before (above) this cursor.""" 478 | before: Cursor 479 | 480 | """ 481 | A condition to be used in determining which values should be returned by the collection. 482 | """ 483 | condition: PostAuthorCondition 484 | 485 | """Only read the first \`n\` values of the set.""" 486 | first: Int 487 | 488 | """Only read the last \`n\` values of the set.""" 489 | last: Int 490 | 491 | """ 492 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 493 | based pagination. May not be used with \`last\`. 494 | """ 495 | offset: Int 496 | 497 | """The method to use when ordering \`PostAuthor\`.""" 498 | orderBy: [PostAuthorsOrderBy!] = [PRIMARY_KEY_ASC] 499 | ): PostAuthorsConnection! 500 | } 501 | 502 | """A connection to a list of \`User\` values.""" 503 | type UsersConnection { 504 | """ 505 | A list of edges which contains the \`User\` and cursor to aid in pagination. 506 | """ 507 | edges: [UsersEdge!]! 508 | 509 | """A list of \`User\` objects.""" 510 | nodes: [User]! 511 | 512 | """Information to aid in pagination.""" 513 | pageInfo: PageInfo! 514 | 515 | """The count of *all* \`User\` you could get from the connection.""" 516 | totalCount: Int! 517 | } 518 | 519 | """A \`User\` edge in the connection.""" 520 | type UsersEdge { 521 | """A cursor for use in pagination.""" 522 | cursor: Cursor 523 | 524 | """The \`User\` at the end of the edge.""" 525 | node: User 526 | } 527 | 528 | """Methods to use when ordering \`User\`.""" 529 | enum UsersOrderBy { 530 | ID_ASC 531 | ID_DESC 532 | NATURAL 533 | PRIMARY_KEY_ASC 534 | PRIMARY_KEY_DESC 535 | } 536 | " 537 | `; 538 | -------------------------------------------------------------------------------- /__tests__/integration/schema/__snapshots__/p.ignoreIndexesFalse.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`prints a schema with \`ignoreIndexes: false\` 1`] = ` 4 | "type Bar implements Node { 5 | id: Int! 6 | name: String! 7 | 8 | """ 9 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 10 | """ 11 | nodeId: ID! 12 | } 13 | 14 | """ 15 | A condition to be used against \`Bar\` object types. All fields are tested for equality and combined with a logical ‘and.’ 16 | """ 17 | input BarCondition { 18 | """Checks for equality with the object’s \`id\` field.""" 19 | id: Int 20 | } 21 | 22 | """A connection to a list of \`Bar\` values.""" 23 | type BarsConnection { 24 | """ 25 | A list of edges which contains the \`Bar\` and cursor to aid in pagination. 26 | """ 27 | edges: [BarsEdge!]! 28 | 29 | """A list of \`Bar\` objects.""" 30 | nodes: [Bar]! 31 | 32 | """Information to aid in pagination.""" 33 | pageInfo: PageInfo! 34 | 35 | """The count of *all* \`Bar\` you could get from the connection.""" 36 | totalCount: Int! 37 | } 38 | 39 | """A \`Bar\` edge in the connection.""" 40 | type BarsEdge { 41 | """A cursor for use in pagination.""" 42 | cursor: Cursor 43 | 44 | """The \`Bar\` at the end of the edge.""" 45 | node: Bar 46 | } 47 | 48 | """Methods to use when ordering \`Bar\`.""" 49 | enum BarsOrderBy { 50 | ID_ASC 51 | ID_DESC 52 | NATURAL 53 | PRIMARY_KEY_ASC 54 | PRIMARY_KEY_DESC 55 | } 56 | 57 | type Baz implements Node { 58 | barId: Int! 59 | 60 | """Reads a single \`Foo\` that is related to this \`Baz\`.""" 61 | fooByFooId: Foo 62 | fooId: Int! 63 | 64 | """ 65 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 66 | """ 67 | nodeId: ID! 68 | } 69 | 70 | """ 71 | A condition to be used against \`Baz\` object types. All fields are tested for equality and combined with a logical ‘and.’ 72 | """ 73 | input BazCondition { 74 | """Checks for equality with the object’s \`fooId\` field.""" 75 | fooId: Int 76 | } 77 | 78 | """A connection to a list of \`Baz\` values.""" 79 | type BazsConnection { 80 | """ 81 | A list of edges which contains the \`Baz\` and cursor to aid in pagination. 82 | """ 83 | edges: [BazsEdge!]! 84 | 85 | """A list of \`Baz\` objects.""" 86 | nodes: [Baz]! 87 | 88 | """Information to aid in pagination.""" 89 | pageInfo: PageInfo! 90 | 91 | """The count of *all* \`Baz\` you could get from the connection.""" 92 | totalCount: Int! 93 | } 94 | 95 | """A \`Baz\` edge in the connection.""" 96 | type BazsEdge { 97 | """A cursor for use in pagination.""" 98 | cursor: Cursor 99 | 100 | """The \`Baz\` at the end of the edge.""" 101 | node: Baz 102 | } 103 | 104 | """Methods to use when ordering \`Baz\`.""" 105 | enum BazsOrderBy { 106 | FOO_ID_ASC 107 | FOO_ID_DESC 108 | NATURAL 109 | PRIMARY_KEY_ASC 110 | PRIMARY_KEY_DESC 111 | } 112 | 113 | type Corge implements Node { 114 | barId: Int! 115 | 116 | """Reads a single \`Foo\` that is related to this \`Corge\`.""" 117 | fooByFooId: Foo 118 | fooId: Int! 119 | 120 | """ 121 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 122 | """ 123 | nodeId: ID! 124 | } 125 | 126 | """ 127 | A condition to be used against \`Corge\` object types. All fields are tested for equality and combined with a logical ‘and.’ 128 | """ 129 | input CorgeCondition { 130 | """Checks for equality with the object’s \`fooId\` field.""" 131 | fooId: Int 132 | } 133 | 134 | """A connection to a list of \`Corge\` values.""" 135 | type CorgesConnection { 136 | """ 137 | A list of edges which contains the \`Corge\` and cursor to aid in pagination. 138 | """ 139 | edges: [CorgesEdge!]! 140 | 141 | """A list of \`Corge\` objects.""" 142 | nodes: [Corge]! 143 | 144 | """Information to aid in pagination.""" 145 | pageInfo: PageInfo! 146 | 147 | """The count of *all* \`Corge\` you could get from the connection.""" 148 | totalCount: Int! 149 | } 150 | 151 | """A \`Corge\` edge in the connection.""" 152 | type CorgesEdge { 153 | """A cursor for use in pagination.""" 154 | cursor: Cursor 155 | 156 | """The \`Corge\` at the end of the edge.""" 157 | node: Corge 158 | } 159 | 160 | """Methods to use when ordering \`Corge\`.""" 161 | enum CorgesOrderBy { 162 | FOO_ID_ASC 163 | FOO_ID_DESC 164 | NATURAL 165 | PRIMARY_KEY_ASC 166 | PRIMARY_KEY_DESC 167 | } 168 | 169 | """A location in a connection that can be used for resuming pagination.""" 170 | scalar Cursor 171 | 172 | """ 173 | A point in time as described by the [ISO 174 | 8601](https://en.wikipedia.org/wiki/ISO_8601) standard. May or may not include a timezone. 175 | """ 176 | scalar Datetime 177 | 178 | type Foo implements Node { 179 | """Reads and enables pagination through a set of \`Baz\`.""" 180 | bazsByFooId( 181 | """Read all values in the set after (below) this cursor.""" 182 | after: Cursor 183 | 184 | """Read all values in the set before (above) this cursor.""" 185 | before: Cursor 186 | 187 | """ 188 | A condition to be used in determining which values should be returned by the collection. 189 | """ 190 | condition: BazCondition 191 | 192 | """Only read the first \`n\` values of the set.""" 193 | first: Int 194 | 195 | """Only read the last \`n\` values of the set.""" 196 | last: Int 197 | 198 | """ 199 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 200 | based pagination. May not be used with \`last\`. 201 | """ 202 | offset: Int 203 | 204 | """The method to use when ordering \`Baz\`.""" 205 | orderBy: [BazsOrderBy!] = [PRIMARY_KEY_ASC] 206 | ): BazsConnection! 207 | 208 | """Reads and enables pagination through a set of \`Corge\`.""" 209 | corgesByFooId( 210 | """Read all values in the set after (below) this cursor.""" 211 | after: Cursor 212 | 213 | """Read all values in the set before (above) this cursor.""" 214 | before: Cursor 215 | 216 | """ 217 | A condition to be used in determining which values should be returned by the collection. 218 | """ 219 | condition: CorgeCondition 220 | 221 | """Only read the first \`n\` values of the set.""" 222 | first: Int 223 | 224 | """Only read the last \`n\` values of the set.""" 225 | last: Int 226 | 227 | """ 228 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 229 | based pagination. May not be used with \`last\`. 230 | """ 231 | offset: Int 232 | 233 | """The method to use when ordering \`Corge\`.""" 234 | orderBy: [CorgesOrderBy!] = [PRIMARY_KEY_ASC] 235 | ): CorgesConnection! 236 | id: Int! 237 | name: String! 238 | 239 | """ 240 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 241 | """ 242 | nodeId: ID! 243 | 244 | """Reads and enables pagination through a set of \`Qux\`.""" 245 | quxesByFooId( 246 | """Read all values in the set after (below) this cursor.""" 247 | after: Cursor 248 | 249 | """Read all values in the set before (above) this cursor.""" 250 | before: Cursor 251 | 252 | """ 253 | A condition to be used in determining which values should be returned by the collection. 254 | """ 255 | condition: QuxCondition 256 | 257 | """Only read the first \`n\` values of the set.""" 258 | first: Int 259 | 260 | """Only read the last \`n\` values of the set.""" 261 | last: Int 262 | 263 | """ 264 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 265 | based pagination. May not be used with \`last\`. 266 | """ 267 | offset: Int 268 | 269 | """The method to use when ordering \`Qux\`.""" 270 | orderBy: [QuxesOrderBy!] = [PRIMARY_KEY_ASC] 271 | ): QuxesConnection! 272 | } 273 | 274 | """ 275 | A condition to be used against \`Foo\` object types. All fields are tested for equality and combined with a logical ‘and.’ 276 | """ 277 | input FooCondition { 278 | """Checks for equality with the object’s \`id\` field.""" 279 | id: Int 280 | } 281 | 282 | """A connection to a list of \`Foo\` values.""" 283 | type FoosConnection { 284 | """ 285 | A list of edges which contains the \`Foo\` and cursor to aid in pagination. 286 | """ 287 | edges: [FoosEdge!]! 288 | 289 | """A list of \`Foo\` objects.""" 290 | nodes: [Foo]! 291 | 292 | """Information to aid in pagination.""" 293 | pageInfo: PageInfo! 294 | 295 | """The count of *all* \`Foo\` you could get from the connection.""" 296 | totalCount: Int! 297 | } 298 | 299 | """A \`Foo\` edge in the connection.""" 300 | type FoosEdge { 301 | """A cursor for use in pagination.""" 302 | cursor: Cursor 303 | 304 | """The \`Foo\` at the end of the edge.""" 305 | node: Foo 306 | } 307 | 308 | """Methods to use when ordering \`Foo\`.""" 309 | enum FoosOrderBy { 310 | ID_ASC 311 | ID_DESC 312 | NATURAL 313 | PRIMARY_KEY_ASC 314 | PRIMARY_KEY_DESC 315 | } 316 | 317 | type Membership implements Node { 318 | createdAt: Datetime! 319 | 320 | """ 321 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 322 | """ 323 | nodeId: ID! 324 | 325 | """Reads a single \`Person\` that is related to this \`Membership\`.""" 326 | personByPersonId: Person 327 | personId: Int! 328 | teamId: Int! 329 | } 330 | 331 | """ 332 | A condition to be used against \`Membership\` object types. All fields are tested 333 | for equality and combined with a logical ‘and.’ 334 | """ 335 | input MembershipCondition { 336 | """Checks for equality with the object’s \`personId\` field.""" 337 | personId: Int 338 | } 339 | 340 | """A connection to a list of \`Membership\` values.""" 341 | type MembershipsConnection { 342 | """ 343 | A list of edges which contains the \`Membership\` and cursor to aid in pagination. 344 | """ 345 | edges: [MembershipsEdge!]! 346 | 347 | """A list of \`Membership\` objects.""" 348 | nodes: [Membership]! 349 | 350 | """Information to aid in pagination.""" 351 | pageInfo: PageInfo! 352 | 353 | """The count of *all* \`Membership\` you could get from the connection.""" 354 | totalCount: Int! 355 | } 356 | 357 | """A \`Membership\` edge in the connection.""" 358 | type MembershipsEdge { 359 | """A cursor for use in pagination.""" 360 | cursor: Cursor 361 | 362 | """The \`Membership\` at the end of the edge.""" 363 | node: Membership 364 | } 365 | 366 | """Methods to use when ordering \`Membership\`.""" 367 | enum MembershipsOrderBy { 368 | NATURAL 369 | PERSON_ID_ASC 370 | PERSON_ID_DESC 371 | PRIMARY_KEY_ASC 372 | PRIMARY_KEY_DESC 373 | } 374 | 375 | """An object with a globally unique \`ID\`.""" 376 | interface Node { 377 | """ 378 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 379 | """ 380 | nodeId: ID! 381 | } 382 | 383 | """Information about pagination in a connection.""" 384 | type PageInfo { 385 | """When paginating forwards, the cursor to continue.""" 386 | endCursor: Cursor 387 | 388 | """When paginating forwards, are there more items?""" 389 | hasNextPage: Boolean! 390 | 391 | """When paginating backwards, are there more items?""" 392 | hasPreviousPage: Boolean! 393 | 394 | """When paginating backwards, the cursor to continue.""" 395 | startCursor: Cursor 396 | } 397 | 398 | """A connection to a list of \`Person\` values.""" 399 | type PeopleConnection { 400 | """ 401 | A list of edges which contains the \`Person\` and cursor to aid in pagination. 402 | """ 403 | edges: [PeopleEdge!]! 404 | 405 | """A list of \`Person\` objects.""" 406 | nodes: [Person]! 407 | 408 | """Information to aid in pagination.""" 409 | pageInfo: PageInfo! 410 | 411 | """The count of *all* \`Person\` you could get from the connection.""" 412 | totalCount: Int! 413 | } 414 | 415 | """A \`Person\` edge in the connection.""" 416 | type PeopleEdge { 417 | """A cursor for use in pagination.""" 418 | cursor: Cursor 419 | 420 | """The \`Person\` at the end of the edge.""" 421 | node: Person 422 | } 423 | 424 | """Methods to use when ordering \`Person\`.""" 425 | enum PeopleOrderBy { 426 | ID_ASC 427 | ID_DESC 428 | NATURAL 429 | PRIMARY_KEY_ASC 430 | PRIMARY_KEY_DESC 431 | } 432 | 433 | type Person implements Node { 434 | id: Int! 435 | 436 | """Reads and enables pagination through a set of \`Membership\`.""" 437 | membershipsByPersonId( 438 | """Read all values in the set after (below) this cursor.""" 439 | after: Cursor 440 | 441 | """Read all values in the set before (above) this cursor.""" 442 | before: Cursor 443 | 444 | """ 445 | A condition to be used in determining which values should be returned by the collection. 446 | """ 447 | condition: MembershipCondition 448 | 449 | """Only read the first \`n\` values of the set.""" 450 | first: Int 451 | 452 | """Only read the last \`n\` values of the set.""" 453 | last: Int 454 | 455 | """ 456 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 457 | based pagination. May not be used with \`last\`. 458 | """ 459 | offset: Int 460 | 461 | """The method to use when ordering \`Membership\`.""" 462 | orderBy: [MembershipsOrderBy!] = [PRIMARY_KEY_ASC] 463 | ): MembershipsConnection! 464 | 465 | """ 466 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 467 | """ 468 | nodeId: ID! 469 | personName: String! 470 | } 471 | 472 | """ 473 | A condition to be used against \`Person\` object types. All fields are tested for equality and combined with a logical ‘and.’ 474 | """ 475 | input PersonCondition { 476 | """Checks for equality with the object’s \`id\` field.""" 477 | id: Int 478 | } 479 | 480 | """The root query type which gives access points into the data universe.""" 481 | type Query implements Node { 482 | """Reads and enables pagination through a set of \`Bar\`.""" 483 | allBars( 484 | """Read all values in the set after (below) this cursor.""" 485 | after: Cursor 486 | 487 | """Read all values in the set before (above) this cursor.""" 488 | before: Cursor 489 | 490 | """ 491 | A condition to be used in determining which values should be returned by the collection. 492 | """ 493 | condition: BarCondition 494 | 495 | """Only read the first \`n\` values of the set.""" 496 | first: Int 497 | 498 | """Only read the last \`n\` values of the set.""" 499 | last: Int 500 | 501 | """ 502 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 503 | based pagination. May not be used with \`last\`. 504 | """ 505 | offset: Int 506 | 507 | """The method to use when ordering \`Bar\`.""" 508 | orderBy: [BarsOrderBy!] = [PRIMARY_KEY_ASC] 509 | ): BarsConnection 510 | 511 | """Reads and enables pagination through a set of \`Baz\`.""" 512 | allBazs( 513 | """Read all values in the set after (below) this cursor.""" 514 | after: Cursor 515 | 516 | """Read all values in the set before (above) this cursor.""" 517 | before: Cursor 518 | 519 | """ 520 | A condition to be used in determining which values should be returned by the collection. 521 | """ 522 | condition: BazCondition 523 | 524 | """Only read the first \`n\` values of the set.""" 525 | first: Int 526 | 527 | """Only read the last \`n\` values of the set.""" 528 | last: Int 529 | 530 | """ 531 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 532 | based pagination. May not be used with \`last\`. 533 | """ 534 | offset: Int 535 | 536 | """The method to use when ordering \`Baz\`.""" 537 | orderBy: [BazsOrderBy!] = [PRIMARY_KEY_ASC] 538 | ): BazsConnection 539 | 540 | """Reads and enables pagination through a set of \`Foo\`.""" 541 | allFoos( 542 | """Read all values in the set after (below) this cursor.""" 543 | after: Cursor 544 | 545 | """Read all values in the set before (above) this cursor.""" 546 | before: Cursor 547 | 548 | """ 549 | A condition to be used in determining which values should be returned by the collection. 550 | """ 551 | condition: FooCondition 552 | 553 | """Only read the first \`n\` values of the set.""" 554 | first: Int 555 | 556 | """Only read the last \`n\` values of the set.""" 557 | last: Int 558 | 559 | """ 560 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 561 | based pagination. May not be used with \`last\`. 562 | """ 563 | offset: Int 564 | 565 | """The method to use when ordering \`Foo\`.""" 566 | orderBy: [FoosOrderBy!] = [PRIMARY_KEY_ASC] 567 | ): FoosConnection 568 | 569 | """Reads and enables pagination through a set of \`Membership\`.""" 570 | allMemberships( 571 | """Read all values in the set after (below) this cursor.""" 572 | after: Cursor 573 | 574 | """Read all values in the set before (above) this cursor.""" 575 | before: Cursor 576 | 577 | """ 578 | A condition to be used in determining which values should be returned by the collection. 579 | """ 580 | condition: MembershipCondition 581 | 582 | """Only read the first \`n\` values of the set.""" 583 | first: Int 584 | 585 | """Only read the last \`n\` values of the set.""" 586 | last: Int 587 | 588 | """ 589 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 590 | based pagination. May not be used with \`last\`. 591 | """ 592 | offset: Int 593 | 594 | """The method to use when ordering \`Membership\`.""" 595 | orderBy: [MembershipsOrderBy!] = [PRIMARY_KEY_ASC] 596 | ): MembershipsConnection 597 | 598 | """Reads and enables pagination through a set of \`Person\`.""" 599 | allPeople( 600 | """Read all values in the set after (below) this cursor.""" 601 | after: Cursor 602 | 603 | """Read all values in the set before (above) this cursor.""" 604 | before: Cursor 605 | 606 | """ 607 | A condition to be used in determining which values should be returned by the collection. 608 | """ 609 | condition: PersonCondition 610 | 611 | """Only read the first \`n\` values of the set.""" 612 | first: Int 613 | 614 | """Only read the last \`n\` values of the set.""" 615 | last: Int 616 | 617 | """ 618 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 619 | based pagination. May not be used with \`last\`. 620 | """ 621 | offset: Int 622 | 623 | """The method to use when ordering \`Person\`.""" 624 | orderBy: [PeopleOrderBy!] = [PRIMARY_KEY_ASC] 625 | ): PeopleConnection 626 | 627 | """Reads and enables pagination through a set of \`Team\`.""" 628 | allTeams( 629 | """Read all values in the set after (below) this cursor.""" 630 | after: Cursor 631 | 632 | """Read all values in the set before (above) this cursor.""" 633 | before: Cursor 634 | 635 | """ 636 | A condition to be used in determining which values should be returned by the collection. 637 | """ 638 | condition: TeamCondition 639 | 640 | """Only read the first \`n\` values of the set.""" 641 | first: Int 642 | 643 | """Only read the last \`n\` values of the set.""" 644 | last: Int 645 | 646 | """ 647 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 648 | based pagination. May not be used with \`last\`. 649 | """ 650 | offset: Int 651 | 652 | """The method to use when ordering \`Team\`.""" 653 | orderBy: [TeamsOrderBy!] = [PRIMARY_KEY_ASC] 654 | ): TeamsConnection 655 | 656 | """Reads a single \`Bar\` using its globally unique \`ID\`.""" 657 | bar( 658 | """The globally unique \`ID\` to be used in selecting a single \`Bar\`.""" 659 | nodeId: ID! 660 | ): Bar 661 | barById(id: Int!): Bar 662 | 663 | """Reads a single \`Baz\` using its globally unique \`ID\`.""" 664 | baz( 665 | """The globally unique \`ID\` to be used in selecting a single \`Baz\`.""" 666 | nodeId: ID! 667 | ): Baz 668 | bazByFooIdAndBarId(barId: Int!, fooId: Int!): Baz 669 | 670 | """Reads a single \`Corge\` using its globally unique \`ID\`.""" 671 | corge( 672 | """The globally unique \`ID\` to be used in selecting a single \`Corge\`.""" 673 | nodeId: ID! 674 | ): Corge 675 | corgeByFooIdAndBarId(barId: Int!, fooId: Int!): Corge 676 | 677 | """Reads a single \`Foo\` using its globally unique \`ID\`.""" 678 | foo( 679 | """The globally unique \`ID\` to be used in selecting a single \`Foo\`.""" 680 | nodeId: ID! 681 | ): Foo 682 | fooById(id: Int!): Foo 683 | 684 | """Reads a single \`Membership\` using its globally unique \`ID\`.""" 685 | membership( 686 | """ 687 | The globally unique \`ID\` to be used in selecting a single \`Membership\`. 688 | """ 689 | nodeId: ID! 690 | ): Membership 691 | membershipByPersonIdAndTeamId(personId: Int!, teamId: Int!): Membership 692 | 693 | """Fetches an object given its globally unique \`ID\`.""" 694 | node( 695 | """The globally unique \`ID\`.""" 696 | nodeId: ID! 697 | ): Node 698 | 699 | """ 700 | The root query type must be a \`Node\` to work well with Relay 1 mutations. This just resolves to \`query\`. 701 | """ 702 | nodeId: ID! 703 | 704 | """Reads a single \`Person\` using its globally unique \`ID\`.""" 705 | person( 706 | """The globally unique \`ID\` to be used in selecting a single \`Person\`.""" 707 | nodeId: ID! 708 | ): Person 709 | personById(id: Int!): Person 710 | 711 | """ 712 | Exposes the root query type nested one level down. This is helpful for Relay 1 713 | which can only query top level fields if they are in a particular form. 714 | """ 715 | query: Query! 716 | 717 | """Reads a single \`Qux\` using its globally unique \`ID\`.""" 718 | qux( 719 | """The globally unique \`ID\` to be used in selecting a single \`Qux\`.""" 720 | nodeId: ID! 721 | ): Qux 722 | quxByFooIdAndBarId(barId: Int!, fooId: Int!): Qux 723 | 724 | """Reads a single \`Team\` using its globally unique \`ID\`.""" 725 | team( 726 | """The globally unique \`ID\` to be used in selecting a single \`Team\`.""" 727 | nodeId: ID! 728 | ): Team 729 | teamById(id: Int!): Team 730 | } 731 | 732 | type Qux implements Node { 733 | barId: Int! 734 | 735 | """Reads a single \`Foo\` that is related to this \`Qux\`.""" 736 | fooByFooId: Foo 737 | fooId: Int! 738 | 739 | """ 740 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 741 | """ 742 | nodeId: ID! 743 | } 744 | 745 | """ 746 | A condition to be used against \`Qux\` object types. All fields are tested for equality and combined with a logical ‘and.’ 747 | """ 748 | input QuxCondition { 749 | """Checks for equality with the object’s \`fooId\` field.""" 750 | fooId: Int 751 | } 752 | 753 | """A connection to a list of \`Qux\` values.""" 754 | type QuxesConnection { 755 | """ 756 | A list of edges which contains the \`Qux\` and cursor to aid in pagination. 757 | """ 758 | edges: [QuxesEdge!]! 759 | 760 | """A list of \`Qux\` objects.""" 761 | nodes: [Qux]! 762 | 763 | """Information to aid in pagination.""" 764 | pageInfo: PageInfo! 765 | 766 | """The count of *all* \`Qux\` you could get from the connection.""" 767 | totalCount: Int! 768 | } 769 | 770 | """A \`Qux\` edge in the connection.""" 771 | type QuxesEdge { 772 | """A cursor for use in pagination.""" 773 | cursor: Cursor 774 | 775 | """The \`Qux\` at the end of the edge.""" 776 | node: Qux 777 | } 778 | 779 | """Methods to use when ordering \`Qux\`.""" 780 | enum QuxesOrderBy { 781 | FOO_ID_ASC 782 | FOO_ID_DESC 783 | NATURAL 784 | PRIMARY_KEY_ASC 785 | PRIMARY_KEY_DESC 786 | } 787 | 788 | type Team implements Node { 789 | id: Int! 790 | 791 | """ 792 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 793 | """ 794 | nodeId: ID! 795 | teamName: String! 796 | } 797 | 798 | """ 799 | A condition to be used against \`Team\` object types. All fields are tested for equality and combined with a logical ‘and.’ 800 | """ 801 | input TeamCondition { 802 | """Checks for equality with the object’s \`id\` field.""" 803 | id: Int 804 | } 805 | 806 | """A connection to a list of \`Team\` values.""" 807 | type TeamsConnection { 808 | """ 809 | A list of edges which contains the \`Team\` and cursor to aid in pagination. 810 | """ 811 | edges: [TeamsEdge!]! 812 | 813 | """A list of \`Team\` objects.""" 814 | nodes: [Team]! 815 | 816 | """Information to aid in pagination.""" 817 | pageInfo: PageInfo! 818 | 819 | """The count of *all* \`Team\` you could get from the connection.""" 820 | totalCount: Int! 821 | } 822 | 823 | """A \`Team\` edge in the connection.""" 824 | type TeamsEdge { 825 | """A cursor for use in pagination.""" 826 | cursor: Cursor 827 | 828 | """The \`Team\` at the end of the edge.""" 829 | node: Team 830 | } 831 | 832 | """Methods to use when ordering \`Team\`.""" 833 | enum TeamsOrderBy { 834 | ID_ASC 835 | ID_DESC 836 | NATURAL 837 | PRIMARY_KEY_ASC 838 | PRIMARY_KEY_DESC 839 | } 840 | " 841 | `; 842 | -------------------------------------------------------------------------------- /__tests__/integration/schema/__snapshots__/t.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`prints a schema with the many-to-many plugin 1`] = ` 4 | """"A location in a connection that can be used for resuming pagination.""" 5 | scalar Cursor 6 | 7 | """ 8 | A point in time as described by the [ISO 9 | 8601](https://en.wikipedia.org/wiki/ISO_8601) standard. May or may not include a timezone. 10 | """ 11 | scalar Datetime 12 | 13 | type Membership implements Node { 14 | endAt: Datetime 15 | id: Int! 16 | 17 | """ 18 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 19 | """ 20 | nodeId: ID! 21 | 22 | """Reads a single \`Person\` that is related to this \`Membership\`.""" 23 | personByPersonId: Person 24 | personId: Int! 25 | startAt: Datetime! 26 | 27 | """Reads a single \`Team\` that is related to this \`Membership\`.""" 28 | teamByTeamId: Team 29 | teamId: Int! 30 | } 31 | 32 | """ 33 | A condition to be used against \`Membership\` object types. All fields are tested 34 | for equality and combined with a logical ‘and.’ 35 | """ 36 | input MembershipCondition { 37 | """Checks for equality with the object’s \`endAt\` field.""" 38 | endAt: Datetime 39 | 40 | """Checks for equality with the object’s \`id\` field.""" 41 | id: Int 42 | 43 | """Checks for equality with the object’s \`personId\` field.""" 44 | personId: Int 45 | 46 | """Checks for equality with the object’s \`startAt\` field.""" 47 | startAt: Datetime 48 | 49 | """Checks for equality with the object’s \`teamId\` field.""" 50 | teamId: Int 51 | } 52 | 53 | """A connection to a list of \`Membership\` values.""" 54 | type MembershipsConnection { 55 | """ 56 | A list of edges which contains the \`Membership\` and cursor to aid in pagination. 57 | """ 58 | edges: [MembershipsEdge!]! 59 | 60 | """A list of \`Membership\` objects.""" 61 | nodes: [Membership]! 62 | 63 | """Information to aid in pagination.""" 64 | pageInfo: PageInfo! 65 | 66 | """The count of *all* \`Membership\` you could get from the connection.""" 67 | totalCount: Int! 68 | } 69 | 70 | """A \`Membership\` edge in the connection.""" 71 | type MembershipsEdge { 72 | """A cursor for use in pagination.""" 73 | cursor: Cursor 74 | 75 | """The \`Membership\` at the end of the edge.""" 76 | node: Membership 77 | } 78 | 79 | """Methods to use when ordering \`Membership\`.""" 80 | enum MembershipsOrderBy { 81 | END_AT_ASC 82 | END_AT_DESC 83 | ID_ASC 84 | ID_DESC 85 | NATURAL 86 | PERSON_ID_ASC 87 | PERSON_ID_DESC 88 | PRIMARY_KEY_ASC 89 | PRIMARY_KEY_DESC 90 | START_AT_ASC 91 | START_AT_DESC 92 | TEAM_ID_ASC 93 | TEAM_ID_DESC 94 | } 95 | 96 | """An object with a globally unique \`ID\`.""" 97 | interface Node { 98 | """ 99 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 100 | """ 101 | nodeId: ID! 102 | } 103 | 104 | """Information about pagination in a connection.""" 105 | type PageInfo { 106 | """When paginating forwards, the cursor to continue.""" 107 | endCursor: Cursor 108 | 109 | """When paginating forwards, are there more items?""" 110 | hasNextPage: Boolean! 111 | 112 | """When paginating backwards, are there more items?""" 113 | hasPreviousPage: Boolean! 114 | 115 | """When paginating backwards, the cursor to continue.""" 116 | startCursor: Cursor 117 | } 118 | 119 | """A connection to a list of \`Person\` values.""" 120 | type PeopleConnection { 121 | """ 122 | A list of edges which contains the \`Person\` and cursor to aid in pagination. 123 | """ 124 | edges: [PeopleEdge!]! 125 | 126 | """A list of \`Person\` objects.""" 127 | nodes: [Person]! 128 | 129 | """Information to aid in pagination.""" 130 | pageInfo: PageInfo! 131 | 132 | """The count of *all* \`Person\` you could get from the connection.""" 133 | totalCount: Int! 134 | } 135 | 136 | """A \`Person\` edge in the connection.""" 137 | type PeopleEdge { 138 | """A cursor for use in pagination.""" 139 | cursor: Cursor 140 | 141 | """The \`Person\` at the end of the edge.""" 142 | node: Person 143 | } 144 | 145 | """Methods to use when ordering \`Person\`.""" 146 | enum PeopleOrderBy { 147 | ID_ASC 148 | ID_DESC 149 | NATURAL 150 | PERSON_NAME_ASC 151 | PERSON_NAME_DESC 152 | PRIMARY_KEY_ASC 153 | PRIMARY_KEY_DESC 154 | } 155 | 156 | type Person implements Node { 157 | id: Int! 158 | 159 | """Reads and enables pagination through a set of \`Membership\`.""" 160 | membershipsByPersonId( 161 | """Read all values in the set after (below) this cursor.""" 162 | after: Cursor 163 | 164 | """Read all values in the set before (above) this cursor.""" 165 | before: Cursor 166 | 167 | """ 168 | A condition to be used in determining which values should be returned by the collection. 169 | """ 170 | condition: MembershipCondition 171 | 172 | """Only read the first \`n\` values of the set.""" 173 | first: Int 174 | 175 | """Only read the last \`n\` values of the set.""" 176 | last: Int 177 | 178 | """ 179 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 180 | based pagination. May not be used with \`last\`. 181 | """ 182 | offset: Int 183 | 184 | """The method to use when ordering \`Membership\`.""" 185 | orderBy: [MembershipsOrderBy!] = [PRIMARY_KEY_ASC] 186 | ): MembershipsConnection! 187 | 188 | """ 189 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 190 | """ 191 | nodeId: ID! 192 | personName: String! 193 | 194 | """Reads and enables pagination through a set of \`Team\`.""" 195 | teamsByMembershipPersonIdAndTeamId( 196 | """Read all values in the set after (below) this cursor.""" 197 | after: Cursor 198 | 199 | """Read all values in the set before (above) this cursor.""" 200 | before: Cursor 201 | 202 | """ 203 | A condition to be used in determining which values should be returned by the collection. 204 | """ 205 | condition: TeamCondition 206 | 207 | """Only read the first \`n\` values of the set.""" 208 | first: Int 209 | 210 | """Only read the last \`n\` values of the set.""" 211 | last: Int 212 | 213 | """ 214 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 215 | based pagination. May not be used with \`last\`. 216 | """ 217 | offset: Int 218 | 219 | """The method to use when ordering \`Team\`.""" 220 | orderBy: [TeamsOrderBy!] = [PRIMARY_KEY_ASC] 221 | ): PersonTeamsByMembershipPersonIdAndTeamIdManyToManyConnection! 222 | } 223 | 224 | """ 225 | A condition to be used against \`Person\` object types. All fields are tested for equality and combined with a logical ‘and.’ 226 | """ 227 | input PersonCondition { 228 | """Checks for equality with the object’s \`id\` field.""" 229 | id: Int 230 | 231 | """Checks for equality with the object’s \`personName\` field.""" 232 | personName: String 233 | } 234 | 235 | """A connection to a list of \`Team\` values, with data from \`Membership\`.""" 236 | type PersonTeamsByMembershipPersonIdAndTeamIdManyToManyConnection { 237 | """ 238 | A list of edges which contains the \`Team\`, info from the \`Membership\`, and the cursor to aid in pagination. 239 | """ 240 | edges: [PersonTeamsByMembershipPersonIdAndTeamIdManyToManyEdge!]! 241 | 242 | """A list of \`Team\` objects.""" 243 | nodes: [Team]! 244 | 245 | """Information to aid in pagination.""" 246 | pageInfo: PageInfo! 247 | 248 | """The count of *all* \`Team\` you could get from the connection.""" 249 | totalCount: Int! 250 | } 251 | 252 | """A \`Team\` edge in the connection, with data from \`Membership\`.""" 253 | type PersonTeamsByMembershipPersonIdAndTeamIdManyToManyEdge { 254 | """A cursor for use in pagination.""" 255 | cursor: Cursor 256 | 257 | """Reads and enables pagination through a set of \`Membership\`.""" 258 | membershipsByTeamId( 259 | """Read all values in the set after (below) this cursor.""" 260 | after: Cursor 261 | 262 | """Read all values in the set before (above) this cursor.""" 263 | before: Cursor 264 | 265 | """ 266 | A condition to be used in determining which values should be returned by the collection. 267 | """ 268 | condition: MembershipCondition 269 | 270 | """Only read the first \`n\` values of the set.""" 271 | first: Int 272 | 273 | """Only read the last \`n\` values of the set.""" 274 | last: Int 275 | 276 | """ 277 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 278 | based pagination. May not be used with \`last\`. 279 | """ 280 | offset: Int 281 | 282 | """The method to use when ordering \`Membership\`.""" 283 | orderBy: [MembershipsOrderBy!] = [PRIMARY_KEY_ASC] 284 | ): MembershipsConnection! 285 | 286 | """The \`Team\` at the end of the edge.""" 287 | node: Team 288 | } 289 | 290 | """The root query type which gives access points into the data universe.""" 291 | type Query implements Node { 292 | """Reads and enables pagination through a set of \`Membership\`.""" 293 | allMemberships( 294 | """Read all values in the set after (below) this cursor.""" 295 | after: Cursor 296 | 297 | """Read all values in the set before (above) this cursor.""" 298 | before: Cursor 299 | 300 | """ 301 | A condition to be used in determining which values should be returned by the collection. 302 | """ 303 | condition: MembershipCondition 304 | 305 | """Only read the first \`n\` values of the set.""" 306 | first: Int 307 | 308 | """Only read the last \`n\` values of the set.""" 309 | last: Int 310 | 311 | """ 312 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 313 | based pagination. May not be used with \`last\`. 314 | """ 315 | offset: Int 316 | 317 | """The method to use when ordering \`Membership\`.""" 318 | orderBy: [MembershipsOrderBy!] = [PRIMARY_KEY_ASC] 319 | ): MembershipsConnection 320 | 321 | """Reads and enables pagination through a set of \`Person\`.""" 322 | allPeople( 323 | """Read all values in the set after (below) this cursor.""" 324 | after: Cursor 325 | 326 | """Read all values in the set before (above) this cursor.""" 327 | before: Cursor 328 | 329 | """ 330 | A condition to be used in determining which values should be returned by the collection. 331 | """ 332 | condition: PersonCondition 333 | 334 | """Only read the first \`n\` values of the set.""" 335 | first: Int 336 | 337 | """Only read the last \`n\` values of the set.""" 338 | last: Int 339 | 340 | """ 341 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 342 | based pagination. May not be used with \`last\`. 343 | """ 344 | offset: Int 345 | 346 | """The method to use when ordering \`Person\`.""" 347 | orderBy: [PeopleOrderBy!] = [PRIMARY_KEY_ASC] 348 | ): PeopleConnection 349 | 350 | """Reads and enables pagination through a set of \`Team\`.""" 351 | allTeams( 352 | """Read all values in the set after (below) this cursor.""" 353 | after: Cursor 354 | 355 | """Read all values in the set before (above) this cursor.""" 356 | before: Cursor 357 | 358 | """ 359 | A condition to be used in determining which values should be returned by the collection. 360 | """ 361 | condition: TeamCondition 362 | 363 | """Only read the first \`n\` values of the set.""" 364 | first: Int 365 | 366 | """Only read the last \`n\` values of the set.""" 367 | last: Int 368 | 369 | """ 370 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 371 | based pagination. May not be used with \`last\`. 372 | """ 373 | offset: Int 374 | 375 | """The method to use when ordering \`Team\`.""" 376 | orderBy: [TeamsOrderBy!] = [PRIMARY_KEY_ASC] 377 | ): TeamsConnection 378 | 379 | """Reads a single \`Membership\` using its globally unique \`ID\`.""" 380 | membership( 381 | """ 382 | The globally unique \`ID\` to be used in selecting a single \`Membership\`. 383 | """ 384 | nodeId: ID! 385 | ): Membership 386 | membershipById(id: Int!): Membership 387 | 388 | """Fetches an object given its globally unique \`ID\`.""" 389 | node( 390 | """The globally unique \`ID\`.""" 391 | nodeId: ID! 392 | ): Node 393 | 394 | """ 395 | The root query type must be a \`Node\` to work well with Relay 1 mutations. This just resolves to \`query\`. 396 | """ 397 | nodeId: ID! 398 | 399 | """Reads a single \`Person\` using its globally unique \`ID\`.""" 400 | person( 401 | """The globally unique \`ID\` to be used in selecting a single \`Person\`.""" 402 | nodeId: ID! 403 | ): Person 404 | personById(id: Int!): Person 405 | 406 | """ 407 | Exposes the root query type nested one level down. This is helpful for Relay 1 408 | which can only query top level fields if they are in a particular form. 409 | """ 410 | query: Query! 411 | 412 | """Reads a single \`Team\` using its globally unique \`ID\`.""" 413 | team( 414 | """The globally unique \`ID\` to be used in selecting a single \`Team\`.""" 415 | nodeId: ID! 416 | ): Team 417 | teamById(id: Int!): Team 418 | } 419 | 420 | type Team implements Node { 421 | id: Int! 422 | 423 | """Reads and enables pagination through a set of \`Person\`.""" 424 | members( 425 | """Read all values in the set after (below) this cursor.""" 426 | after: Cursor 427 | 428 | """Read all values in the set before (above) this cursor.""" 429 | before: Cursor 430 | 431 | """ 432 | A condition to be used in determining which values should be returned by the collection. 433 | """ 434 | condition: PersonCondition 435 | 436 | """Only read the first \`n\` values of the set.""" 437 | first: Int 438 | 439 | """Only read the last \`n\` values of the set.""" 440 | last: Int 441 | 442 | """ 443 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 444 | based pagination. May not be used with \`last\`. 445 | """ 446 | offset: Int 447 | 448 | """The method to use when ordering \`Person\`.""" 449 | orderBy: [PeopleOrderBy!] = [PRIMARY_KEY_ASC] 450 | ): TeamMembersManyToManyConnection! 451 | 452 | """Reads and enables pagination through a set of \`Membership\`.""" 453 | membershipsByTeamId( 454 | """Read all values in the set after (below) this cursor.""" 455 | after: Cursor 456 | 457 | """Read all values in the set before (above) this cursor.""" 458 | before: Cursor 459 | 460 | """ 461 | A condition to be used in determining which values should be returned by the collection. 462 | """ 463 | condition: MembershipCondition 464 | 465 | """Only read the first \`n\` values of the set.""" 466 | first: Int 467 | 468 | """Only read the last \`n\` values of the set.""" 469 | last: Int 470 | 471 | """ 472 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 473 | based pagination. May not be used with \`last\`. 474 | """ 475 | offset: Int 476 | 477 | """The method to use when ordering \`Membership\`.""" 478 | orderBy: [MembershipsOrderBy!] = [PRIMARY_KEY_ASC] 479 | ): MembershipsConnection! 480 | 481 | """ 482 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 483 | """ 484 | nodeId: ID! 485 | teamName: String! 486 | } 487 | 488 | """ 489 | A condition to be used against \`Team\` object types. All fields are tested for equality and combined with a logical ‘and.’ 490 | """ 491 | input TeamCondition { 492 | """Checks for equality with the object’s \`id\` field.""" 493 | id: Int 494 | 495 | """Checks for equality with the object’s \`teamName\` field.""" 496 | teamName: String 497 | } 498 | 499 | """ 500 | A connection to a list of \`Person\` values, with data from \`Membership\`. 501 | """ 502 | type TeamMembersManyToManyConnection { 503 | """ 504 | A list of edges which contains the \`Person\`, info from the \`Membership\`, and the cursor to aid in pagination. 505 | """ 506 | edges: [TeamMembersManyToManyEdge!]! 507 | 508 | """A list of \`Person\` objects.""" 509 | nodes: [Person]! 510 | 511 | """Information to aid in pagination.""" 512 | pageInfo: PageInfo! 513 | 514 | """The count of *all* \`Person\` you could get from the connection.""" 515 | totalCount: Int! 516 | } 517 | 518 | """A \`Person\` edge in the connection, with data from \`Membership\`.""" 519 | type TeamMembersManyToManyEdge { 520 | """A cursor for use in pagination.""" 521 | cursor: Cursor 522 | 523 | """Reads and enables pagination through a set of \`Membership\`.""" 524 | membershipsByPersonId( 525 | """Read all values in the set after (below) this cursor.""" 526 | after: Cursor 527 | 528 | """Read all values in the set before (above) this cursor.""" 529 | before: Cursor 530 | 531 | """ 532 | A condition to be used in determining which values should be returned by the collection. 533 | """ 534 | condition: MembershipCondition 535 | 536 | """Only read the first \`n\` values of the set.""" 537 | first: Int 538 | 539 | """Only read the last \`n\` values of the set.""" 540 | last: Int 541 | 542 | """ 543 | Skip the first \`n\` values from our \`after\` cursor, an alternative to cursor 544 | based pagination. May not be used with \`last\`. 545 | """ 546 | offset: Int 547 | 548 | """The method to use when ordering \`Membership\`.""" 549 | orderBy: [MembershipsOrderBy!] = [PRIMARY_KEY_ASC] 550 | ): MembershipsConnection! 551 | 552 | """The \`Person\` at the end of the edge.""" 553 | node: Person 554 | } 555 | 556 | """A connection to a list of \`Team\` values.""" 557 | type TeamsConnection { 558 | """ 559 | A list of edges which contains the \`Team\` and cursor to aid in pagination. 560 | """ 561 | edges: [TeamsEdge!]! 562 | 563 | """A list of \`Team\` objects.""" 564 | nodes: [Team]! 565 | 566 | """Information to aid in pagination.""" 567 | pageInfo: PageInfo! 568 | 569 | """The count of *all* \`Team\` you could get from the connection.""" 570 | totalCount: Int! 571 | } 572 | 573 | """A \`Team\` edge in the connection.""" 574 | type TeamsEdge { 575 | """A cursor for use in pagination.""" 576 | cursor: Cursor 577 | 578 | """The \`Team\` at the end of the edge.""" 579 | node: Team 580 | } 581 | 582 | """Methods to use when ordering \`Team\`.""" 583 | enum TeamsOrderBy { 584 | ID_ASC 585 | ID_DESC 586 | NATURAL 587 | PRIMARY_KEY_ASC 588 | PRIMARY_KEY_DESC 589 | TEAM_NAME_ASC 590 | TEAM_NAME_DESC 591 | } 592 | " 593 | `; 594 | -------------------------------------------------------------------------------- /__tests__/integration/schema/a.test.js: -------------------------------------------------------------------------------- 1 | const core = require("./core"); 2 | 3 | test( 4 | "prints a schema using the 'a' database schema", 5 | core.test(["a"], { 6 | skipPlugins: [require("graphile-build-pg").PgConnectionArgCondition], 7 | appendPlugins: [require("../../../index.js")], 8 | disableDefaultMutations: true, 9 | legacyRelations: "omit", 10 | }) 11 | ); 12 | -------------------------------------------------------------------------------- /__tests__/integration/schema/b.test.js: -------------------------------------------------------------------------------- 1 | const core = require("./core"); 2 | 3 | test( 4 | "prints a schema using the 'b' database schema", 5 | core.test(["b"], { 6 | skipPlugins: [require("graphile-build-pg").PgConnectionArgCondition], 7 | appendPlugins: [require("../../../index.js")], 8 | disableDefaultMutations: true, 9 | legacyRelations: "omit", 10 | }) 11 | ); 12 | -------------------------------------------------------------------------------- /__tests__/integration/schema/c.test.js: -------------------------------------------------------------------------------- 1 | const core = require("./core"); 2 | 3 | test( 4 | "prints a schema using the 'c' database schema", 5 | core.test(["c"], { 6 | skipPlugins: [require("graphile-build-pg").PgConnectionArgCondition], 7 | appendPlugins: [require("../../../index.js")], 8 | disableDefaultMutations: true, 9 | legacyRelations: "omit", 10 | }) 11 | ); 12 | -------------------------------------------------------------------------------- /__tests__/integration/schema/core.js: -------------------------------------------------------------------------------- 1 | const { withPgClient } = require("../../helpers"); 2 | const { createPostGraphileSchema } = require("postgraphile-core"); 3 | const { parse, buildASTSchema } = require("graphql"); 4 | const { lexicographicSortSchema, printSchema } = require("graphql/utilities"); 5 | 6 | exports.test = (schemas, options, setup) => () => 7 | withPgClient(async (client) => { 8 | if (setup) { 9 | if (typeof setup === "function") { 10 | await setup(client); 11 | } else { 12 | await client.query(setup); 13 | } 14 | } 15 | const schema = await createPostGraphileSchema(client, schemas, options); 16 | expect(printSchemaOrdered(schema)).toMatchSnapshot(); 17 | }); 18 | 19 | function printSchemaOrdered(originalSchema) { 20 | // Clone schema so we don't damage anything 21 | const schema = buildASTSchema(parse(printSchema(originalSchema))); 22 | 23 | return printSchema(lexicographicSortSchema(schema)); 24 | } 25 | -------------------------------------------------------------------------------- /__tests__/integration/schema/d.test.js: -------------------------------------------------------------------------------- 1 | const core = require("./core"); 2 | 3 | test( 4 | "prints a schema using the 'd' database schema", 5 | core.test(["d"], { 6 | skipPlugins: [require("graphile-build-pg").PgConnectionArgCondition], 7 | appendPlugins: [require("../../../index.js")], 8 | disableDefaultMutations: true, 9 | legacyRelations: "omit", 10 | }) 11 | ); 12 | -------------------------------------------------------------------------------- /__tests__/integration/schema/e.test.js: -------------------------------------------------------------------------------- 1 | const core = require("./core"); 2 | 3 | test( 4 | "prints a schema using the 'e' database schema", 5 | core.test(["e"], { 6 | appendPlugins: [require("../../../index.js")], 7 | disableDefaultMutations: true, 8 | setofFunctionsContainNulls: false, 9 | }) 10 | ); 11 | -------------------------------------------------------------------------------- /__tests__/integration/schema/f.test.js: -------------------------------------------------------------------------------- 1 | const core = require("./core"); 2 | 3 | test( 4 | "prints a schema with the many-to-many plugin", 5 | core.test(["f"], { 6 | appendPlugins: [require("../../../index.js")], 7 | disableDefaultMutations: true, 8 | legacyRelations: "omit", 9 | }) 10 | ); 11 | -------------------------------------------------------------------------------- /__tests__/integration/schema/g.test.js: -------------------------------------------------------------------------------- 1 | const core = require("./core"); 2 | 3 | test( 4 | "prints a schema with the many-to-many plugin", 5 | core.test(["g"], { 6 | appendPlugins: [require("../../../index.js")], 7 | disableDefaultMutations: true, 8 | legacyRelations: "omit", 9 | }) 10 | ); 11 | -------------------------------------------------------------------------------- /__tests__/integration/schema/p.ignoreIndexesFalse.test.js: -------------------------------------------------------------------------------- 1 | const core = require("./core"); 2 | 3 | test( 4 | "prints a schema with `ignoreIndexes: false`", 5 | core.test(["p"], { 6 | appendPlugins: [require("../../../index.js")], 7 | disableDefaultMutations: true, 8 | legacyRelations: "omit", 9 | ignoreIndexes: false, 10 | }) 11 | ); 12 | -------------------------------------------------------------------------------- /__tests__/integration/schema/p.simpleCollectionsBoth.test.js: -------------------------------------------------------------------------------- 1 | const core = require("./core"); 2 | 3 | test( 4 | "prints a schema with the many-to-many plugin", 5 | core.test(["p"], { 6 | appendPlugins: [require("../../../index.js")], 7 | disableDefaultMutations: true, 8 | legacyRelations: "omit", 9 | simpleCollections: "both", 10 | }) 11 | ); 12 | -------------------------------------------------------------------------------- /__tests__/integration/schema/p.test.js: -------------------------------------------------------------------------------- 1 | const core = require("./core"); 2 | 3 | test( 4 | "prints a schema with the many-to-many plugin", 5 | core.test(["p"], { 6 | appendPlugins: [require("../../../index.js")], 7 | disableDefaultMutations: true, 8 | legacyRelations: "omit", 9 | }) 10 | ); 11 | -------------------------------------------------------------------------------- /__tests__/integration/schema/t.test.js: -------------------------------------------------------------------------------- 1 | const core = require("./core"); 2 | 3 | test( 4 | "prints a schema with the many-to-many plugin", 5 | core.test(["t"], { 6 | appendPlugins: [require("../../../index.js")], 7 | disableDefaultMutations: true, 8 | legacyRelations: "omit", 9 | }) 10 | ); 11 | -------------------------------------------------------------------------------- /__tests__/queries.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const fs = require("fs"); 3 | const util = require("util"); 4 | const path = require("path"); 5 | const { graphql } = require("graphql"); 6 | const { withPgClient } = require("./helpers"); 7 | const { createPostGraphileSchema } = require("postgraphile-core"); 8 | const { printSchema } = require("graphql/utilities"); 9 | const debug = require("debug")("graphile-build:schema"); 10 | 11 | const readFile = util.promisify(fs.readFile); 12 | 13 | const getSqlSchemas = () => fs.readdirSync(path.resolve(__dirname, "schemas")); 14 | const getFixturesForSqlSchema = (sqlSchema) => 15 | fs.existsSync( 16 | path.resolve(__dirname, "schemas", sqlSchema, "fixtures", "queries") 17 | ) 18 | ? fs.readdirSync( 19 | path.resolve(__dirname, "schemas", sqlSchema, "fixtures", "queries") 20 | ) 21 | : []; 22 | const readFixtureForSqlSchema = (sqlSchema, fixture) => 23 | readFile( 24 | path.resolve( 25 | __dirname, 26 | "schemas", 27 | sqlSchema, 28 | "fixtures", 29 | "queries", 30 | fixture 31 | ), 32 | "utf8" 33 | ); 34 | 35 | const queryResult = async (sqlSchema, fixture) => { 36 | return await withPgClient(async (pgClient) => { 37 | const data = await readFile( 38 | path.resolve(__dirname, "schemas", sqlSchema, "data.sql"), 39 | "utf8" 40 | ); 41 | await pgClient.query(data); 42 | const PgManyToManyPlugin = require("../index"); 43 | const gqlSchema = await createPostGraphileSchema(pgClient, [sqlSchema], { 44 | appendPlugins: [PgManyToManyPlugin], 45 | }); 46 | debug(`${sqlSchema}: ${printSchema(gqlSchema)}`); 47 | const query = await readFixtureForSqlSchema(sqlSchema, fixture); 48 | return await graphql(gqlSchema, query, null, { 49 | pgClient: pgClient, 50 | }); 51 | }); 52 | }; 53 | 54 | const sqlSchemas = getSqlSchemas(); 55 | describe.each(sqlSchemas)("schema=%s", (sqlSchema) => { 56 | const fixtures = getFixturesForSqlSchema(sqlSchema); 57 | if (fixtures.length > 0) { 58 | test.each(fixtures)("query=%s", async (fixture) => { 59 | const result = await queryResult(sqlSchema, fixture); 60 | if (result.errors) { 61 | console.log(result.errors.map((e) => e.originalError)); 62 | } 63 | expect(result).toMatchSnapshot(); 64 | }); 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /__tests__/schemas/a/data.sql: -------------------------------------------------------------------------------- 1 | insert into a.foo (foo_id, foo_name) values 2 | (1, 'Foo 1'), 3 | (2, 'Foo 2'), 4 | (3, 'Foo 3'); 5 | 6 | insert into a.bar (bar_id, bar_name) values 7 | (11, 'Bar 11'), 8 | (12, 'Bar 12'), 9 | (13, 'Bar 13'); 10 | 11 | insert into a.junction (j_foo_id, j_bar_id) values 12 | (1, 11), 13 | (1, 12), 14 | (2, 11); -------------------------------------------------------------------------------- /__tests__/schemas/a/fixtures/queries/edges.graphql: -------------------------------------------------------------------------------- 1 | { 2 | allFoos { 3 | nodes { 4 | fooId 5 | barsByJunctionJFooIdAndJBarId { 6 | edges { 7 | cursor 8 | node { 9 | nodeId 10 | barId 11 | barName 12 | } 13 | } 14 | pageInfo { 15 | startCursor 16 | endCursor 17 | } 18 | totalCount 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /__tests__/schemas/a/fixtures/queries/nodes.graphql: -------------------------------------------------------------------------------- 1 | { 2 | allFoos { 3 | nodes { 4 | fooId 5 | barsByJunctionJFooIdAndJBarId { 6 | nodes { 7 | nodeId 8 | barId 9 | barName 10 | } 11 | pageInfo { 12 | startCursor 13 | endCursor 14 | } 15 | totalCount 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /__tests__/schemas/a/schema.sql: -------------------------------------------------------------------------------- 1 | -- Scenario: 2 | -- * One junction table record per unique pair of nodes (enforced by PK constraint) 3 | -- * No additional junction table columns 4 | 5 | drop schema if exists a cascade; 6 | create schema a; 7 | 8 | create table a.foo ( 9 | foo_id integer primary key, 10 | foo_name text not null 11 | ); 12 | 13 | create table a.bar ( 14 | bar_id integer primary key, 15 | bar_name text not null 16 | ); 17 | 18 | create table a.junction ( 19 | j_foo_id integer references a.foo (foo_id), 20 | j_bar_id integer references a.bar (bar_id), 21 | primary key (j_foo_id, j_bar_id) 22 | ); -------------------------------------------------------------------------------- /__tests__/schemas/b/data.sql: -------------------------------------------------------------------------------- 1 | insert into b.foo (foo_id, foo_name) values 2 | (1, 'Foo 1'), 3 | (2, 'Foo 2'), 4 | (3, 'Foo 3'); 5 | 6 | insert into b.bar (bar_id, bar_name) values 7 | (11, 'Bar 11'), 8 | (12, 'Bar 12'), 9 | (13, 'Bar 13'); 10 | 11 | insert into b.junction (j_foo_id, j_bar_id, created_at) values 12 | (1, 11, '2018-01-01T12:00:00Z'), 13 | (1, 12, '2018-01-02T12:00:00Z'), 14 | (2, 11, '2018-01-03T12:00:00Z'); -------------------------------------------------------------------------------- /__tests__/schemas/b/fixtures/queries/edges.graphql: -------------------------------------------------------------------------------- 1 | { 2 | allFoos { 3 | nodes { 4 | fooId 5 | barsByJunctionJFooIdAndJBarId { 6 | edges { 7 | createdAt 8 | cursor 9 | node { 10 | nodeId 11 | barId 12 | barName 13 | } 14 | } 15 | pageInfo { 16 | startCursor 17 | endCursor 18 | } 19 | totalCount 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /__tests__/schemas/b/fixtures/queries/nodes.graphql: -------------------------------------------------------------------------------- 1 | { 2 | allFoos { 3 | nodes { 4 | fooId 5 | barsByJunctionJFooIdAndJBarId { 6 | nodes { 7 | nodeId 8 | barId 9 | barName 10 | } 11 | pageInfo { 12 | startCursor 13 | endCursor 14 | } 15 | totalCount 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /__tests__/schemas/b/schema.sql: -------------------------------------------------------------------------------- 1 | -- Scenario: 2 | -- * One junction table record per unique pair of nodes (enforced by compound PK constraint) 3 | -- * Additional junction table column (created_at) 4 | 5 | drop schema if exists b cascade; 6 | create schema b; 7 | 8 | create table b.foo ( 9 | foo_id integer primary key, 10 | foo_name text not null 11 | ); 12 | 13 | create table b.bar ( 14 | bar_id integer primary key, 15 | bar_name text not null 16 | ); 17 | 18 | create table b.junction ( 19 | j_foo_id integer references b.foo (foo_id), 20 | j_bar_id integer references b.bar (bar_id), 21 | created_at timestamptz not null, 22 | primary key (j_foo_id, j_bar_id) 23 | ); -------------------------------------------------------------------------------- /__tests__/schemas/c/data.sql: -------------------------------------------------------------------------------- 1 | insert into c.foo (foo_id, foo_name) values 2 | (1, 'Foo 1'), 3 | (2, 'Foo 2'), 4 | (3, 'Foo 3'); 5 | 6 | insert into c.bar (bar_id, bar_name) values 7 | (11, 'Bar 11'), 8 | (12, 'Bar 12'), 9 | (13, 'Bar 13'); 10 | 11 | insert into c.junction (j_foo_id, j_bar_id) values 12 | (1, 11), 13 | (1, 12), 14 | (1, 12), 15 | (2, 11); -------------------------------------------------------------------------------- /__tests__/schemas/c/fixtures/queries/edges.graphql: -------------------------------------------------------------------------------- 1 | { 2 | allFoos { 3 | nodes { 4 | fooId 5 | barsByJunctionJFooIdAndJBarId { 6 | edges { 7 | cursor 8 | junctionsByJBarId { 9 | edges { 10 | cursor 11 | node { 12 | nodeId 13 | jFooId 14 | jBarId 15 | } 16 | } 17 | pageInfo { 18 | startCursor 19 | endCursor 20 | } 21 | totalCount 22 | } 23 | node { 24 | nodeId 25 | barId 26 | barName 27 | } 28 | } 29 | pageInfo { 30 | startCursor 31 | endCursor 32 | } 33 | totalCount 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /__tests__/schemas/c/fixtures/queries/nodes.graphql: -------------------------------------------------------------------------------- 1 | { 2 | allFoos { 3 | nodes { 4 | fooId 5 | barsByJunctionJFooIdAndJBarId { 6 | nodes { 7 | nodeId 8 | barId 9 | barName 10 | } 11 | pageInfo { 12 | startCursor 13 | endCursor 14 | } 15 | totalCount 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /__tests__/schemas/c/schema.sql: -------------------------------------------------------------------------------- 1 | -- Scenario: 2 | -- * Multiple junction table records per unique pair of nodes 3 | -- * No additional junction table columns 4 | 5 | drop schema if exists c cascade; 6 | create schema c; 7 | 8 | create table c.foo ( 9 | foo_id integer primary key, 10 | foo_name text not null 11 | ); 12 | 13 | create table c.bar ( 14 | bar_id integer primary key, 15 | bar_name text not null 16 | ); 17 | 18 | create table c.junction ( 19 | id serial primary key, 20 | j_foo_id integer references c.foo (foo_id), 21 | j_bar_id integer references c.bar (bar_id) 22 | ); -------------------------------------------------------------------------------- /__tests__/schemas/d/data.sql: -------------------------------------------------------------------------------- 1 | insert into d.foo (foo_id, foo_name) values 2 | (1, 'Foo 1'), 3 | (2, 'Foo 2'), 4 | (3, 'Foo 3'); 5 | 6 | insert into d.bar (bar_id, bar_name) values 7 | (11, 'Bar 11'), 8 | (12, 'Bar 12'), 9 | (13, 'Bar 13'); 10 | 11 | insert into d.junction (j_foo_id, j_bar_id, created_at) values 12 | (1, 11, '2018-01-01T12:00:00Z'), 13 | (1, 12, '2018-01-02T12:00:00Z'), 14 | (1, 12, '2018-01-04T12:00:00Z'), 15 | (2, 11, '2018-01-03T12:00:00Z'); -------------------------------------------------------------------------------- /__tests__/schemas/d/fixtures/queries/edges.graphql: -------------------------------------------------------------------------------- 1 | { 2 | allFoos { 3 | nodes { 4 | fooId 5 | barsByJunctionJFooIdAndJBarId { 6 | edges { 7 | cursor 8 | junctionsByJBarId { 9 | edges { 10 | cursor 11 | node { 12 | nodeId 13 | jFooId 14 | jBarId 15 | createdAt 16 | } 17 | } 18 | pageInfo { 19 | startCursor 20 | endCursor 21 | } 22 | totalCount 23 | } 24 | node { 25 | nodeId 26 | barId 27 | barName 28 | } 29 | } 30 | pageInfo { 31 | startCursor 32 | endCursor 33 | } 34 | totalCount 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /__tests__/schemas/d/fixtures/queries/nodes.graphql: -------------------------------------------------------------------------------- 1 | { 2 | allFoos { 3 | nodes { 4 | fooId 5 | barsByJunctionJFooIdAndJBarId { 6 | nodes { 7 | nodeId 8 | barId 9 | barName 10 | } 11 | pageInfo { 12 | startCursor 13 | endCursor 14 | } 15 | totalCount 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /__tests__/schemas/d/schema.sql: -------------------------------------------------------------------------------- 1 | -- Scenario: 2 | -- * Multiple junction table records per unique pair of nodes 3 | -- * Additional junction table column (created_at) 4 | 5 | drop schema if exists d cascade; 6 | create schema d; 7 | 8 | create table d.foo ( 9 | foo_id integer primary key, 10 | foo_name text not null 11 | ); 12 | 13 | create table d.bar ( 14 | bar_id integer primary key, 15 | bar_name text not null 16 | ); 17 | 18 | create table d.junction ( 19 | id serial primary key, 20 | j_foo_id integer references d.foo (foo_id), 21 | j_bar_id integer references d.bar (bar_id), 22 | created_at timestamptz not null 23 | ); -------------------------------------------------------------------------------- /__tests__/schemas/e/data.sql: -------------------------------------------------------------------------------- 1 | insert into e.person (id, person_name) values 2 | (1, 'Alice'), 3 | (2, 'Bob'), 4 | (3, 'Carol'); 5 | 6 | insert into e.team (id, team_name) values 7 | (1, 'Development'), 8 | (2, 'Sales'), 9 | (3, 'Marketing'); 10 | 11 | insert into e.tag (id, tag_name) values 12 | (1, 'high-performing'), 13 | (2, 'awesome'), 14 | (3, 'strange'); 15 | 16 | insert into e.membership (person_id, team_id) values 17 | (1, 1), 18 | (2, 2), 19 | (3, 3); 20 | 21 | insert into e.person_tag_junction (person_id, tag_id) values 22 | (1, 2), 23 | (2, 3), 24 | (3, 1); 25 | 26 | insert into e.team_tag_junction (team_id, tag_id) values 27 | (1, 3), 28 | (2, 1), 29 | (3, 2); 30 | -------------------------------------------------------------------------------- /__tests__/schemas/e/fixtures/queries/multiple-tags-fields.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | allPeople { 3 | nodes { 4 | personName 5 | tags { 6 | nodes { 7 | tagName 8 | } 9 | } 10 | teams { 11 | nodes { 12 | teamName 13 | tags { 14 | nodes { 15 | tagName 16 | } 17 | } 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /__tests__/schemas/e/schema.sql: -------------------------------------------------------------------------------- 1 | -- Scenario: 2 | -- * multiple junction tables, reusing names 3 | 4 | drop schema if exists e cascade; 5 | 6 | create schema e; 7 | 8 | create table e.person ( 9 | id serial primary key, 10 | person_name text not null 11 | ); 12 | 13 | create table e.team ( 14 | id serial primary key, 15 | team_name text not null 16 | ); 17 | 18 | create table e.tag ( 19 | id serial primary key, 20 | tag_name text not null 21 | ); 22 | 23 | create table e.membership ( 24 | person_id int not null constraint membership_person_id_fkey references e.person (id), 25 | team_id int not null constraint membership_team_id_fkey references e.team (id), 26 | primary key (person_id, team_id) 27 | ); 28 | comment on constraint membership_person_id_fkey on e.membership is 29 | E'@manyToManyFieldName people\n@manyToManySimpleFieldName peopleList'; 30 | comment on constraint membership_team_id_fkey on e.membership is 31 | E'@manyToManyFieldName teams\n@manyToManySimpleFieldName teamsList'; 32 | 33 | create table e.person_tag_junction ( 34 | person_id int not null constraint person_tag_junction_person_id_fkey references e.person (id), 35 | tag_id int not null constraint person_tag_junction_tag_id_fkey references e.tag (id), 36 | primary key (person_id, tag_id) 37 | ); 38 | comment on constraint person_tag_junction_person_id_fkey on e.person_tag_junction is 39 | E'@manyToManyFieldName people\n@manyToManySimpleFieldName peopleList'; 40 | comment on constraint person_tag_junction_tag_id_fkey on e.person_tag_junction is 41 | E'@manyToManyFieldName tags\n@manyToManySimpleFieldName tagsList'; 42 | 43 | create table e.team_tag_junction ( 44 | team_id int not null constraint team_tag_junction_team_id_fkey references e.team (id), 45 | tag_id int not null constraint team_tag_junction_tag_id_fkey references e.tag (id), 46 | primary key (team_id, tag_id) 47 | ); 48 | comment on constraint team_tag_junction_team_id_fkey on e.team_tag_junction is 49 | E'@manyToManyFieldName teams\n@manyToManySimpleFieldName teamsList'; 50 | comment on constraint team_tag_junction_tag_id_fkey on e.team_tag_junction is 51 | E'@manyToManyFieldName tags\n@manyToManySimpleFieldName tagsList'; 52 | -------------------------------------------------------------------------------- /__tests__/schemas/f/data.sql: -------------------------------------------------------------------------------- 1 | insert into f.person (id, name) values 2 | (1, 'Alice'), 3 | (2, 'Bob'), 4 | (3, 'Eve'); 5 | 6 | insert into f.junction (follower_id, following_id) values 7 | (2, 1), 8 | (3, 1), 9 | (3, 2); 10 | -------------------------------------------------------------------------------- /__tests__/schemas/f/fixtures/queries/f.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | allPeople { 3 | nodes { 4 | name 5 | followers { 6 | nodes { 7 | name 8 | } 9 | } 10 | following { 11 | nodes { 12 | name 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /__tests__/schemas/f/schema.sql: -------------------------------------------------------------------------------- 1 | drop schema if exists f cascade; 2 | 3 | create schema f; 4 | 5 | create table f.person ( 6 | id serial primary key, 7 | name text 8 | ); 9 | 10 | create table f.junction ( 11 | follower_id int constraint junction_follower_id_fkey references f.person (id), 12 | following_id int constraint junction_following_id_fkey references f.person (id), 13 | primary key (follower_id, following_id) 14 | ); 15 | 16 | comment on table f.junction is E'@omit all,many'; 17 | comment on constraint junction_follower_id_fkey on f.junction is E'@manyToManyFieldName followers'; 18 | comment on constraint junction_following_id_fkey on f.junction is E'@manyToManyFieldName following'; -------------------------------------------------------------------------------- /__tests__/schemas/g/data.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphile-contrib/pg-many-to-many/dace0691fa097faeeb9db3f48dbec92ee3b18228/__tests__/schemas/g/data.sql -------------------------------------------------------------------------------- /__tests__/schemas/g/schema.sql: -------------------------------------------------------------------------------- 1 | -- Scenario: 2 | -- Potentially conflicting Edge/Connection type names 3 | -- https://github.com/graphile-contrib/pg-many-to-many/issues/38 4 | 5 | drop schema if exists g cascade; 6 | create schema g; 7 | 8 | create extension if not exists "pgcrypto"; 9 | 10 | CREATE TABLE g.users ( 11 | id uuid DEFAULT gen_random_uuid() PRIMARY KEY 12 | ); 13 | 14 | CREATE TABLE g.posts ( 15 | id uuid DEFAULT gen_random_uuid() PRIMARY KEY, 16 | title text NOT NULL, 17 | content text, 18 | created_at timestamp with time zone DEFAULT now() NOT NULL 19 | ); 20 | 21 | CREATE TABLE g.post_authors ( 22 | id uuid DEFAULT gen_random_uuid() PRIMARY KEY, 23 | post_id uuid NOT NULL REFERENCES g.posts(id), 24 | user_id uuid NOT NULL REFERENCES g.users(id), 25 | created_at timestamp with time zone DEFAULT now() NOT NULL 26 | ); 27 | 28 | CREATE INDEX ON g.post_authors (post_id); 29 | CREATE INDEX ON g.post_authors (user_id); 30 | 31 | COMMENT ON TABLE g.post_authors IS E'@omit many'; 32 | 33 | COMMENT ON CONSTRAINT post_authors_user_id_fkey ON g.post_authors IS E'@manyToManyFieldName authors'; -------------------------------------------------------------------------------- /__tests__/schemas/p/data.sql: -------------------------------------------------------------------------------- 1 | insert into p.person (id, person_name) values 2 | (1, 'Person1'), 3 | (2, 'Person2'), 4 | (3, 'Person3'); 5 | 6 | insert into p.team (id, team_name) values 7 | (1, 'Team1'), 8 | (2, 'Team2'), 9 | (3, 'Team3'); 10 | 11 | insert into p.membership (person_id, team_id, created_at) values 12 | (1, 1, '2018-01-01T12:00:00Z'), 13 | (1, 2, '2018-01-02T12:00:00Z'), 14 | (2, 1, '2018-01-03T12:00:00Z'); 15 | 16 | -- Person1: [Team1,Team2] 17 | -- Person2: [Team1] 18 | -- Person3: [] 19 | -- Team1: [Person1,Person2] 20 | -- Team2: [Person1] 21 | -- Team3: [] -------------------------------------------------------------------------------- /__tests__/schemas/p/fixtures/queries/edge-fields.graphql: -------------------------------------------------------------------------------- 1 | { 2 | allTeams { 3 | nodes { 4 | teamName 5 | members { 6 | edges { 7 | node { 8 | personName 9 | } 10 | createdAt 11 | } 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /__tests__/schemas/p/fixtures/queries/many-to-many.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | a: allPeople { 3 | nodes { 4 | personName 5 | teamsByMembershipPersonIdAndTeamId { 6 | nodes { 7 | teamName 8 | } 9 | } 10 | } 11 | } 12 | b: allTeams { 13 | nodes { 14 | teamName 15 | members { 16 | nodes { 17 | personName 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /__tests__/schemas/p/schema.sql: -------------------------------------------------------------------------------- 1 | drop schema if exists p cascade; 2 | 3 | create schema p; 4 | 5 | create table p.person ( 6 | id serial primary key, 7 | person_name text not null 8 | ); 9 | 10 | create table p.team ( 11 | id serial primary key, 12 | team_name text not null 13 | ); 14 | 15 | create table p.membership ( 16 | person_id int constraint membership_person_id_fkey references p.person (id), 17 | team_id int constraint membership_team_id_fkey references p.team (id), 18 | created_at timestamptz not null, 19 | primary key (person_id, team_id) 20 | ); 21 | 22 | comment on constraint membership_person_id_fkey on p.membership is E'@manyToManyFieldName members\n@manyToManySimpleFieldName membersList'; 23 | 24 | comment on constraint membership_team_id_fkey on p.membership is E'@simpleCollections omit'; 25 | 26 | create table p.foo ( 27 | id serial primary key, 28 | name text not null 29 | ); 30 | 31 | create table p.bar ( 32 | id serial primary key, 33 | name text not null 34 | ); 35 | 36 | create table p.baz ( 37 | foo_id int constraint baz_foo_id_fkey references p.foo (id), 38 | bar_id int constraint baz_bar_id_fkey references p.bar (id), 39 | primary key (foo_id, bar_id) 40 | ); 41 | 42 | comment on constraint baz_bar_id_fkey on p.baz is E'@omit'; 43 | 44 | create table p.qux ( 45 | foo_id int constraint qux_foo_id_fkey references p.foo (id), 46 | bar_id int constraint qux_bar_id_fkey references p.bar (id), 47 | primary key (foo_id, bar_id) 48 | ); 49 | 50 | comment on table p.qux is E'@omit all'; 51 | comment on constraint qux_bar_id_fkey on p.qux is E'@omit manyToMany'; 52 | 53 | create table p.corge ( 54 | foo_id int constraint corge_foo_id_fkey references p.foo (id), 55 | bar_id int constraint corge_bar_id_fkey references p.bar (id), 56 | primary key (foo_id, bar_id) 57 | ); 58 | 59 | comment on table p.corge is E'@omit all,manyToMany'; 60 | -------------------------------------------------------------------------------- /__tests__/schemas/t/data.sql: -------------------------------------------------------------------------------- 1 | insert into t.person (id, person_name) values 2 | (1, 'Person1'), 3 | (2, 'Person2'), 4 | (3, 'Person3'); 5 | 6 | insert into t.team (id, team_name) values 7 | (1, 'Team1'), 8 | (2, 'Team2'), 9 | (3, 'Team3'); 10 | 11 | insert into t.membership (person_id, team_id, start_at, end_at) values 12 | (1, 1, '2018-01-01T12:00:00Z', null), 13 | (1, 2, '2018-01-02T12:00:00Z', '2018-01-03T12:00:00Z'), 14 | (1, 2, '2018-01-04T12:00:00Z', '2018-01-05T12:00:00Z'), 15 | (2, 1, '2018-01-03T12:00:00Z', null); 16 | 17 | -- Person1: [Team1,Team2] 18 | -- Person2: [Team1] 19 | -- Person3: [] 20 | -- Team1: [Person1,Person2] 21 | -- Team2: [Person1] 22 | -- Team3: [] 23 | -------------------------------------------------------------------------------- /__tests__/schemas/t/fixtures/queries/edge-fields.graphql: -------------------------------------------------------------------------------- 1 | { 2 | allTeams { 3 | nodes { 4 | teamName 5 | members { 6 | edges { 7 | node { 8 | personName 9 | } 10 | membershipsByPersonId { 11 | nodes { 12 | startAt 13 | endAt 14 | } 15 | } 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /__tests__/schemas/t/fixtures/queries/many-to-many.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | a: allPeople { 3 | nodes { 4 | personName 5 | teamsByMembershipPersonIdAndTeamId { 6 | nodes { 7 | teamName 8 | } 9 | } 10 | } 11 | } 12 | b: allTeams { 13 | nodes { 14 | teamName 15 | members { 16 | nodes { 17 | personName 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /__tests__/schemas/t/schema.sql: -------------------------------------------------------------------------------- 1 | drop schema if exists t cascade; 2 | 3 | create schema t; 4 | create extension btree_gist with schema t; 5 | 6 | create table t.person ( 7 | id serial primary key, 8 | person_name text not null 9 | ); 10 | 11 | create table t.team ( 12 | id serial primary key, 13 | team_name text not null 14 | ); 15 | 16 | create table t.membership ( 17 | id serial primary key, 18 | person_id int not null constraint membership_person_id_fkey references t.person (id), 19 | team_id int not null constraint membership_team_id_fkey references t.team (id), 20 | start_at timestamptz not null, 21 | end_at timestamptz, 22 | constraint membership_unique_nonoverlapping exclude using gist ( 23 | person_id with =, 24 | team_id with =, 25 | tstzrange(start_at, end_at) with && 26 | ) 27 | ); 28 | 29 | create index on t.membership(person_id); 30 | create index on t.membership(team_id); 31 | 32 | comment on constraint membership_person_id_fkey on t.membership is E'@manyToManyFieldName members\n@manyToManySimpleFieldName membersList'; 33 | 34 | comment on constraint membership_team_id_fkey on t.membership is E'@simpleCollections omit'; 35 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from "graphile-build"; 2 | declare const PgManyToManyPlugin: Plugin; 3 | export default PgManyToManyPlugin; 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const pkg = require("./package.json"); 2 | const PgManyToManyRelationInflectionPlugin = require("./src/PgManyToManyRelationInflectionPlugin.js"); 3 | const PgManyToManyRelationPlugin = require("./src/PgManyToManyRelationPlugin.js"); 4 | const PgManyToManyRelationEdgeColumnsPlugin = require("./src/PgManyToManyRelationEdgeColumnsPlugin.js"); 5 | const PgManyToManyRelationEdgeTablePlugin = require("./src/PgManyToManyRelationEdgeTablePlugin.js"); 6 | 7 | function PgManyToManyPlugin(builder, options) { 8 | builder.hook("build", (build) => { 9 | // Check dependencies 10 | if (!build.versions) { 11 | throw new Error( 12 | `Plugin ${pkg.name}@${pkg.version} requires graphile-build@^4.1.0 in order to check dependencies (current version: ${build.graphileBuildVersion})` 13 | ); 14 | } 15 | const depends = (name, range) => { 16 | if (!build.hasVersion(name, range)) { 17 | throw new Error( 18 | `Plugin ${pkg.name}@${pkg.version} requires ${name}@${range} (${ 19 | build.versions[name] 20 | ? `current version: ${build.versions[name]}` 21 | : "not found" 22 | })` 23 | ); 24 | } 25 | }; 26 | depends("graphile-build-pg", "^4.5.0"); 27 | 28 | // Register this plugin 29 | build.versions = build.extend(build.versions, { [pkg.name]: pkg.version }); 30 | 31 | return build; 32 | }); 33 | 34 | PgManyToManyRelationInflectionPlugin(builder, options); 35 | PgManyToManyRelationPlugin(builder, options); 36 | PgManyToManyRelationEdgeColumnsPlugin(builder, options); 37 | PgManyToManyRelationEdgeTablePlugin(builder, options); 38 | } 39 | 40 | module.exports = PgManyToManyPlugin; 41 | // Hacks for TypeScript/Babel import 42 | module.exports.default = PgManyToManyPlugin; 43 | Object.defineProperty(module.exports, "__esModule", { value: true }); 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphile-contrib/pg-many-to-many", 3 | "version": "1.0.2", 4 | "description": "Add connection fields for many-to-many relations", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "format": "prettier --ignore-path ./.eslintignore", 9 | "format:all": "yarn format '**/*.{json,md,html,js,jsx,ts,tsx}'", 10 | "format:fix": "yarn format:all --write", 11 | "format:check": "yarn format:all --list-different", 12 | "lint": "eslint . --ext .js,.jsx", 13 | "test": "scripts/test" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/graphile-contrib/pg-many-to-many.git" 18 | }, 19 | "author": "Matt Bretl", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/graphile-contrib/pg-many-to-many/issues" 23 | }, 24 | "devDependencies": { 25 | "eslint": "8.28.0", 26 | "eslint-config-prettier": "8.5.0", 27 | "eslint-plugin-jest": "27.1.6", 28 | "eslint-plugin-prettier": "4.2.1", 29 | "graphql": "15.8.0", 30 | "jest": "29.3.1", 31 | "pg": "8.8.0", 32 | "postgraphile-core": "4.12.3", 33 | "prettier": "2.8.0" 34 | }, 35 | "jest": { 36 | "testRegex": "__tests__/.*\\.test\\.js$" 37 | }, 38 | "files": [ 39 | "src", 40 | "index.js", 41 | "index.d.ts" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ -x ".env" ]; then 5 | set -a 6 | . ./.env 7 | set +a 8 | fi; 9 | 10 | if [ "$TEST_DATABASE_URL" == "" ]; then 11 | echo "ERROR: No test database configured; aborting" 12 | echo 13 | echo "To resolve this, ensure environmental variable TEST_DATABASE_URL is set" 14 | exit 1; 15 | fi; 16 | 17 | # Import latest schemas (throw on error) 18 | for schema in $(ls __tests__/schemas); do 19 | psql -Xqv ON_ERROR_STOP=1 -f __tests__/schemas/${schema}/schema.sql "$TEST_DATABASE_URL" 20 | done 21 | echo "Database reset successfully ✅" 22 | 23 | # Now run the tests 24 | jest -i $@ 25 | -------------------------------------------------------------------------------- /src/PgManyToManyRelationEdgeColumnsPlugin.js: -------------------------------------------------------------------------------- 1 | module.exports = function PgManyToManyRelationEdgeColumnsPlugin(builder) { 2 | builder.hook( 3 | "GraphQLObjectType:fields", 4 | (fields, build, context) => { 5 | const { 6 | extend, 7 | pgGetGqlTypeByTypeIdAndModifier, 8 | pgSql: sql, 9 | pg2gql, 10 | graphql: { GraphQLString, GraphQLNonNull }, 11 | pgColumnFilter, 12 | inflection, 13 | pgOmit: omit, 14 | pgGetSelectValueForFieldAndTypeAndModifier: 15 | getSelectValueForFieldAndTypeAndModifier, 16 | describePgEntity, 17 | } = build; 18 | const { 19 | scope: { isPgManyToManyEdgeType, pgManyToManyRelationship }, 20 | fieldWithHooks, 21 | } = context; 22 | const nullableIf = (condition, Type) => 23 | condition ? Type : new GraphQLNonNull(Type); 24 | 25 | if (!isPgManyToManyEdgeType || !pgManyToManyRelationship) { 26 | return fields; 27 | } 28 | 29 | const { 30 | leftKeyAttributes, 31 | junctionTable, 32 | junctionLeftKeyAttributes, 33 | junctionRightKeyAttributes, 34 | rightKeyAttributes, 35 | allowsMultipleEdgesToNode, 36 | } = pgManyToManyRelationship; 37 | 38 | if (allowsMultipleEdgesToNode) { 39 | return fields; 40 | } 41 | 42 | return extend( 43 | fields, 44 | junctionTable.attributes.reduce((memo, attr) => { 45 | if (!pgColumnFilter(attr, build, context)) return memo; 46 | if (omit(attr, "read")) return memo; 47 | 48 | // Skip left and right key attributes 49 | if (junctionLeftKeyAttributes.map((a) => a.name).includes(attr.name)) 50 | return memo; 51 | if (junctionRightKeyAttributes.map((a) => a.name).includes(attr.name)) 52 | return memo; 53 | 54 | const fieldName = inflection.column(attr); 55 | memo = extend( 56 | memo, 57 | { 58 | [fieldName]: fieldWithHooks( 59 | fieldName, 60 | (fieldContext) => { 61 | const { type, typeModifier } = attr; 62 | const { addDataGenerator } = fieldContext; 63 | const ReturnType = 64 | pgGetGqlTypeByTypeIdAndModifier( 65 | attr.typeId, 66 | attr.typeModifier 67 | ) || GraphQLString; 68 | 69 | // Since we're ignoring multi-column keys, we can simplify here 70 | const leftKeyAttribute = leftKeyAttributes[0]; 71 | const junctionLeftKeyAttribute = junctionLeftKeyAttributes[0]; 72 | const junctionRightKeyAttribute = 73 | junctionRightKeyAttributes[0]; 74 | const rightKeyAttribute = rightKeyAttributes[0]; 75 | 76 | const sqlSelectFrom = sql.fragment`select ${sql.identifier( 77 | attr.name 78 | )} from ${sql.identifier( 79 | junctionTable.namespace.name, 80 | junctionTable.name 81 | )}`; 82 | 83 | addDataGenerator((parsedResolveInfoFragment) => { 84 | return { 85 | pgQuery: (queryBuilder) => { 86 | queryBuilder.select( 87 | getSelectValueForFieldAndTypeAndModifier( 88 | ReturnType, 89 | fieldContext, 90 | parsedResolveInfoFragment, 91 | sql.fragment`(${sqlSelectFrom} where ${sql.identifier( 92 | junctionRightKeyAttribute.name 93 | )} = ${queryBuilder.getTableAlias()}.${sql.identifier( 94 | rightKeyAttribute.name 95 | )} and ${sql.identifier( 96 | junctionLeftKeyAttribute.name 97 | )} = ${queryBuilder.parentQueryBuilder.parentQueryBuilder.getTableAlias()}.${sql.identifier( 98 | leftKeyAttribute.name 99 | )})`, 100 | type, 101 | typeModifier 102 | ), 103 | fieldName 104 | ); 105 | }, 106 | }; 107 | }); 108 | return { 109 | description: attr.description, 110 | type: nullableIf( 111 | !attr.isNotNull && 112 | !attr.type.domainIsNotNull && 113 | !attr.tags.notNull, 114 | ReturnType 115 | ), 116 | resolve: (data) => { 117 | return pg2gql(data[fieldName], attr.type); 118 | }, 119 | }; 120 | }, 121 | { 122 | isPgManyToManyRelationEdgeColumnField: true, 123 | pgFieldIntrospection: attr, 124 | } 125 | ), 126 | }, 127 | `Adding field for ${describePgEntity(attr)}.` 128 | ); 129 | return memo; 130 | }, {}), 131 | `Adding columns to '${describePgEntity(junctionTable)}'` 132 | ); 133 | }, 134 | ["PgManyToManyRelationEdgeColumns"] 135 | ); 136 | }; 137 | -------------------------------------------------------------------------------- /src/PgManyToManyRelationEdgeTablePlugin.js: -------------------------------------------------------------------------------- 1 | module.exports = function PgManyToManyEdgeTablePlugin( 2 | builder, 3 | { pgSimpleCollections } 4 | ) { 5 | builder.hook("GraphQLObjectType:fields", (fields, build, context) => { 6 | const { 7 | extend, 8 | getTypeByName, 9 | pgGetGqlTypeByTypeIdAndModifier, 10 | graphql: { GraphQLNonNull, GraphQLList }, 11 | inflection, 12 | getSafeAliasFromResolveInfo, 13 | getSafeAliasFromAlias, 14 | pgQueryFromResolveData: queryFromResolveData, 15 | pgAddStartEndCursor: addStartEndCursor, 16 | pgSql: sql, 17 | describePgEntity, 18 | } = build; 19 | const { 20 | scope: { isPgManyToManyEdgeType, pgManyToManyRelationship }, 21 | fieldWithHooks, 22 | Self, 23 | } = context; 24 | if (!isPgManyToManyEdgeType || !pgManyToManyRelationship) { 25 | return fields; 26 | } 27 | 28 | const { 29 | leftKeyAttributes, 30 | junctionLeftKeyAttributes, 31 | rightTable, 32 | rightKeyAttributes, 33 | junctionRightKeyAttributes, 34 | junctionTable, 35 | junctionRightConstraint, 36 | allowsMultipleEdgesToNode, 37 | } = pgManyToManyRelationship; 38 | 39 | if (!allowsMultipleEdgesToNode) { 40 | return fields; 41 | } 42 | 43 | const JunctionTableType = pgGetGqlTypeByTypeIdAndModifier( 44 | junctionTable.type.id, 45 | null 46 | ); 47 | if (!JunctionTableType) { 48 | throw new Error( 49 | `Could not determine type for table with id ${junctionTable.type.id}` 50 | ); 51 | } 52 | const JunctionTableConnectionType = getTypeByName( 53 | inflection.connection(JunctionTableType.name) 54 | ); 55 | 56 | function makeFields(isConnection) { 57 | const fieldName = isConnection 58 | ? inflection.manyRelationByKeys( 59 | junctionRightKeyAttributes, 60 | junctionTable, 61 | rightTable, 62 | junctionRightConstraint 63 | ) 64 | : inflection.manyRelationByKeysSimple( 65 | junctionRightKeyAttributes, 66 | junctionTable, 67 | rightTable, 68 | junctionRightConstraint 69 | ); 70 | const Type = isConnection 71 | ? JunctionTableConnectionType 72 | : JunctionTableType; 73 | if (!Type) { 74 | return; 75 | } 76 | 77 | fields = extend( 78 | fields, 79 | { 80 | [fieldName]: fieldWithHooks( 81 | fieldName, 82 | ({ getDataFromParsedResolveInfoFragment, addDataGenerator }) => { 83 | const sqlFrom = sql.identifier( 84 | junctionTable.namespace.name, 85 | junctionTable.name 86 | ); 87 | const queryOptions = { 88 | useAsterisk: junctionTable.canUseAsterisk, 89 | withPagination: isConnection, 90 | withPaginationAsFields: false, 91 | asJsonAggregate: !isConnection, 92 | }; 93 | addDataGenerator((parsedResolveInfoFragment) => { 94 | return { 95 | pgQuery: (queryBuilder) => { 96 | queryBuilder.select(() => { 97 | const resolveData = getDataFromParsedResolveInfoFragment( 98 | parsedResolveInfoFragment, 99 | Type 100 | ); 101 | const junctionTableAlias = sql.identifier(Symbol()); 102 | const rightTableAlias = queryBuilder.getTableAlias(); 103 | const leftTableAlias = 104 | queryBuilder.parentQueryBuilder.parentQueryBuilder.getTableAlias(); 105 | const query = queryFromResolveData( 106 | sqlFrom, 107 | junctionTableAlias, 108 | resolveData, 109 | queryOptions, 110 | (innerQueryBuilder) => { 111 | innerQueryBuilder.parentQueryBuilder = queryBuilder; 112 | const junctionPrimaryKeyConstraint = 113 | junctionTable.primaryKeyConstraint; 114 | const junctionPrimaryKeyAttributes = 115 | junctionPrimaryKeyConstraint && 116 | junctionPrimaryKeyConstraint.keyAttributes; 117 | if (junctionPrimaryKeyAttributes) { 118 | innerQueryBuilder.beforeLock("orderBy", () => { 119 | // append order by primary key to the list of orders 120 | if (!innerQueryBuilder.isOrderUnique(false)) { 121 | innerQueryBuilder.data.cursorPrefix = [ 122 | "primary_key_asc", 123 | ]; 124 | junctionPrimaryKeyAttributes.forEach((attr) => { 125 | innerQueryBuilder.orderBy( 126 | sql.fragment`${innerQueryBuilder.getTableAlias()}.${sql.identifier( 127 | attr.name 128 | )}`, 129 | true 130 | ); 131 | }); 132 | innerQueryBuilder.setOrderIsUnique(); 133 | } 134 | }); 135 | } 136 | 137 | junctionRightKeyAttributes.forEach((attr, i) => { 138 | innerQueryBuilder.where( 139 | sql.fragment`${junctionTableAlias}.${sql.identifier( 140 | attr.name 141 | )} = ${rightTableAlias}.${sql.identifier( 142 | rightKeyAttributes[i].name 143 | )}` 144 | ); 145 | }); 146 | 147 | junctionLeftKeyAttributes.forEach((attr, i) => { 148 | innerQueryBuilder.where( 149 | sql.fragment`${junctionTableAlias}.${sql.identifier( 150 | attr.name 151 | )} = ${leftTableAlias}.${sql.identifier( 152 | leftKeyAttributes[i].name 153 | )}` 154 | ); 155 | }); 156 | }, 157 | queryBuilder.context, 158 | queryBuilder.rootValue 159 | ); 160 | return sql.fragment`(${query})`; 161 | }, getSafeAliasFromAlias(parsedResolveInfoFragment.alias)); 162 | }, 163 | }; 164 | }); 165 | 166 | return { 167 | description: `Reads and enables pagination through a set of \`${JunctionTableType.name}\`.`, 168 | type: isConnection 169 | ? new GraphQLNonNull(JunctionTableConnectionType) 170 | : new GraphQLNonNull( 171 | new GraphQLList(new GraphQLNonNull(JunctionTableType)) 172 | ), 173 | args: {}, 174 | resolve: (data, _args, _context, resolveInfo) => { 175 | const safeAlias = getSafeAliasFromResolveInfo(resolveInfo); 176 | if (isConnection) { 177 | return addStartEndCursor(data[safeAlias]); 178 | } else { 179 | return data[safeAlias]; 180 | } 181 | }, 182 | }; 183 | }, 184 | { 185 | isPgFieldConnection: isConnection, 186 | isPgFieldSimpleCollection: !isConnection, 187 | isPgManyToManyRelationEdgeTableField: true, 188 | pgFieldIntrospection: junctionTable, 189 | } 190 | ), 191 | }, 192 | 193 | `Many-to-many relation edge table (${ 194 | isConnection ? "connection" : "simple collection" 195 | }) on ${Self.name} type for ${describePgEntity( 196 | junctionRightConstraint 197 | )}.` 198 | ); 199 | } 200 | const simpleCollections = 201 | junctionRightConstraint.tags.simpleCollections || 202 | junctionTable.tags.simpleCollections || 203 | pgSimpleCollections; 204 | const hasConnections = simpleCollections !== "only"; 205 | const hasSimpleCollections = 206 | simpleCollections === "only" || simpleCollections === "both"; 207 | if (hasConnections) { 208 | makeFields(true); 209 | } 210 | if (hasSimpleCollections) { 211 | makeFields(false); 212 | } 213 | return fields; 214 | }); 215 | }; 216 | -------------------------------------------------------------------------------- /src/PgManyToManyRelationInflectionPlugin.js: -------------------------------------------------------------------------------- 1 | module.exports = function PgManyToManyRelationInflectionPlugin(builder) { 2 | builder.hook("inflection", (inflection) => { 3 | return Object.assign(inflection, { 4 | manyToManyRelationByKeys( 5 | _leftKeyAttributes, 6 | junctionLeftKeyAttributes, 7 | junctionRightKeyAttributes, 8 | _rightKeyAttributes, 9 | junctionTable, 10 | rightTable, 11 | _junctionLeftConstraint, 12 | junctionRightConstraint 13 | ) { 14 | if (junctionRightConstraint.tags.manyToManyFieldName) { 15 | return junctionRightConstraint.tags.manyToManyFieldName; 16 | } 17 | return this.camelCase( 18 | `${this.pluralize( 19 | this._singularizedTableName(rightTable) 20 | )}-by-${this._singularizedTableName(junctionTable)}-${[ 21 | ...junctionLeftKeyAttributes, 22 | ...junctionRightKeyAttributes, 23 | ] 24 | .map((attr) => this.column(attr)) 25 | .join("-and-")}` 26 | ); 27 | }, 28 | manyToManyRelationByKeysSimple( 29 | _leftKeyAttributes, 30 | junctionLeftKeyAttributes, 31 | junctionRightKeyAttributes, 32 | _rightKeyAttributes, 33 | junctionTable, 34 | rightTable, 35 | _junctionLeftConstraint, 36 | junctionRightConstraint 37 | ) { 38 | if (junctionRightConstraint.tags.manyToManySimpleFieldName) { 39 | return junctionRightConstraint.tags.manyToManySimpleFieldName; 40 | } 41 | return this.camelCase( 42 | `${this.pluralize( 43 | this._singularizedTableName(rightTable) 44 | )}-by-${this._singularizedTableName(junctionTable)}-${[ 45 | ...junctionLeftKeyAttributes, 46 | ...junctionRightKeyAttributes, 47 | ] 48 | .map((attr) => this.column(attr)) 49 | .join("-and-")}-list` 50 | ); 51 | }, 52 | manyToManyRelationEdge( 53 | leftKeyAttributes, 54 | junctionLeftKeyAttributes, 55 | junctionRightKeyAttributes, 56 | rightKeyAttributes, 57 | junctionTable, 58 | rightTable, 59 | junctionLeftConstraint, 60 | junctionRightConstraint, 61 | leftTableTypeName 62 | ) { 63 | const relationName = inflection.manyToManyRelationByKeys( 64 | leftKeyAttributes, 65 | junctionLeftKeyAttributes, 66 | junctionRightKeyAttributes, 67 | rightKeyAttributes, 68 | junctionTable, 69 | rightTable, 70 | junctionLeftConstraint, 71 | junctionRightConstraint 72 | ); 73 | return this.upperCamelCase( 74 | `${leftTableTypeName}-${relationName}-many-to-many-edge` 75 | ); 76 | }, 77 | manyToManyRelationConnection( 78 | leftKeyAttributes, 79 | junctionLeftKeyAttributes, 80 | junctionRightKeyAttributes, 81 | rightKeyAttributes, 82 | junctionTable, 83 | rightTable, 84 | junctionLeftConstraint, 85 | junctionRightConstraint, 86 | leftTableTypeName 87 | ) { 88 | const relationName = inflection.manyToManyRelationByKeys( 89 | leftKeyAttributes, 90 | junctionLeftKeyAttributes, 91 | junctionRightKeyAttributes, 92 | rightKeyAttributes, 93 | junctionTable, 94 | rightTable, 95 | junctionLeftConstraint, 96 | junctionRightConstraint, 97 | leftTableTypeName 98 | ); 99 | return this.upperCamelCase( 100 | `${leftTableTypeName}-${relationName}-many-to-many-connection` 101 | ); 102 | }, 103 | /* eslint-disable no-unused-vars */ 104 | manyToManyRelationSubqueryName( 105 | leftKeyAttributes, 106 | junctionLeftKeyAttributes, 107 | junctionRightKeyAttributes, 108 | rightKeyAttributes, 109 | junctionTable, 110 | rightTable, 111 | junctionLeftConstraint, 112 | junctionRightConstraint, 113 | leftTableTypeName 114 | ) { 115 | /* eslint-enable no-unused-vars */ 116 | return `many-to-many-subquery-by-${this._singularizedTableName( 117 | junctionTable 118 | )}`; 119 | }, 120 | }); 121 | }); 122 | }; 123 | -------------------------------------------------------------------------------- /src/PgManyToManyRelationPlugin.js: -------------------------------------------------------------------------------- 1 | const createManyToManyConnectionType = require("./createManyToManyConnectionType"); 2 | const manyToManyRelationships = require("./manyToManyRelationships"); 3 | 4 | module.exports = function PgManyToManyRelationPlugin(builder, options) { 5 | const { pgSimpleCollections } = options; 6 | builder.hook("GraphQLObjectType:fields", (fields, build, context) => { 7 | const { 8 | extend, 9 | pgGetGqlTypeByTypeIdAndModifier, 10 | pgSql: sql, 11 | getSafeAliasFromResolveInfo, 12 | getSafeAliasFromAlias, 13 | graphql: { GraphQLNonNull, GraphQLList }, 14 | inflection, 15 | pgQueryFromResolveData: queryFromResolveData, 16 | pgAddStartEndCursor: addStartEndCursor, 17 | describePgEntity, 18 | } = build; 19 | const { 20 | scope: { isPgRowType, pgIntrospection: leftTable }, 21 | fieldWithHooks, 22 | Self, 23 | } = context; 24 | if (!isPgRowType || !leftTable || leftTable.kind !== "class") { 25 | return fields; 26 | } 27 | 28 | const relationships = manyToManyRelationships(leftTable, build); 29 | return extend( 30 | fields, 31 | relationships.reduce((memo, relationship) => { 32 | const { 33 | leftKeyAttributes, 34 | junctionLeftKeyAttributes, 35 | junctionRightKeyAttributes, 36 | rightKeyAttributes, 37 | junctionTable, 38 | rightTable, 39 | junctionLeftConstraint, 40 | junctionRightConstraint, 41 | } = relationship; 42 | const RightTableType = pgGetGqlTypeByTypeIdAndModifier( 43 | rightTable.type.id, 44 | null 45 | ); 46 | if (!RightTableType) { 47 | throw new Error( 48 | `Could not determine type for table with id ${rightTable.type.id}` 49 | ); 50 | } 51 | const RightTableConnectionType = createManyToManyConnectionType( 52 | relationship, 53 | build, 54 | options, 55 | leftTable 56 | ); 57 | 58 | // Since we're ignoring multi-column keys, we can simplify here 59 | const leftKeyAttribute = leftKeyAttributes[0]; 60 | const junctionLeftKeyAttribute = junctionLeftKeyAttributes[0]; 61 | const junctionRightKeyAttribute = junctionRightKeyAttributes[0]; 62 | const rightKeyAttribute = rightKeyAttributes[0]; 63 | 64 | function makeFields(isConnection) { 65 | const manyRelationFieldName = isConnection 66 | ? inflection.manyToManyRelationByKeys( 67 | leftKeyAttributes, 68 | junctionLeftKeyAttributes, 69 | junctionRightKeyAttributes, 70 | rightKeyAttributes, 71 | junctionTable, 72 | rightTable, 73 | junctionLeftConstraint, 74 | junctionRightConstraint 75 | ) 76 | : inflection.manyToManyRelationByKeysSimple( 77 | leftKeyAttributes, 78 | junctionLeftKeyAttributes, 79 | junctionRightKeyAttributes, 80 | rightKeyAttributes, 81 | junctionTable, 82 | rightTable, 83 | junctionLeftConstraint, 84 | junctionRightConstraint 85 | ); 86 | 87 | memo = extend( 88 | memo, 89 | { 90 | [manyRelationFieldName]: fieldWithHooks( 91 | manyRelationFieldName, 92 | ({ 93 | getDataFromParsedResolveInfoFragment, 94 | addDataGenerator, 95 | }) => { 96 | const sqlFrom = sql.identifier( 97 | rightTable.namespace.name, 98 | rightTable.name 99 | ); 100 | const queryOptions = { 101 | useAsterisk: rightTable.canUseAsterisk, 102 | withPagination: isConnection, 103 | withPaginationAsFields: false, 104 | asJsonAggregate: !isConnection, 105 | }; 106 | addDataGenerator((parsedResolveInfoFragment) => { 107 | return { 108 | pgQuery: (queryBuilder) => { 109 | queryBuilder.select(() => { 110 | const resolveData = 111 | getDataFromParsedResolveInfoFragment( 112 | parsedResolveInfoFragment, 113 | isConnection 114 | ? RightTableConnectionType 115 | : RightTableType 116 | ); 117 | const rightTableAlias = sql.identifier(Symbol()); 118 | const leftTableAlias = queryBuilder.getTableAlias(); 119 | const query = queryFromResolveData( 120 | sqlFrom, 121 | rightTableAlias, 122 | resolveData, 123 | queryOptions, 124 | (innerQueryBuilder) => { 125 | innerQueryBuilder.parentQueryBuilder = 126 | queryBuilder; 127 | const rightPrimaryKeyConstraint = 128 | rightTable.primaryKeyConstraint; 129 | const rightPrimaryKeyAttributes = 130 | rightPrimaryKeyConstraint && 131 | rightPrimaryKeyConstraint.keyAttributes; 132 | if (rightPrimaryKeyAttributes) { 133 | innerQueryBuilder.beforeLock("orderBy", () => { 134 | // append order by primary key to the list of orders 135 | if (!innerQueryBuilder.isOrderUnique(false)) { 136 | innerQueryBuilder.data.cursorPrefix = [ 137 | "primary_key_asc", 138 | ]; 139 | rightPrimaryKeyAttributes.forEach( 140 | (attr) => { 141 | innerQueryBuilder.orderBy( 142 | sql.fragment`${innerQueryBuilder.getTableAlias()}.${sql.identifier( 143 | attr.name 144 | )}`, 145 | true 146 | ); 147 | } 148 | ); 149 | innerQueryBuilder.setOrderIsUnique(); 150 | } 151 | }); 152 | } 153 | 154 | const subqueryName = 155 | inflection.manyToManyRelationSubqueryName( 156 | leftKeyAttributes, 157 | junctionLeftKeyAttributes, 158 | junctionRightKeyAttributes, 159 | rightKeyAttributes, 160 | junctionTable, 161 | rightTable, 162 | junctionLeftConstraint, 163 | junctionRightConstraint 164 | ); 165 | const subqueryBuilder = 166 | innerQueryBuilder.buildNamedChildSelecting( 167 | subqueryName, 168 | sql.identifier( 169 | junctionTable.namespace.name, 170 | junctionTable.name 171 | ), 172 | sql.identifier(junctionRightKeyAttribute.name) 173 | ); 174 | subqueryBuilder.where( 175 | sql.fragment`${sql.identifier( 176 | junctionLeftKeyAttribute.name 177 | )} = ${leftTableAlias}.${sql.identifier( 178 | leftKeyAttribute.name 179 | )}` 180 | ); 181 | 182 | innerQueryBuilder.where( 183 | () => 184 | sql.fragment`${rightTableAlias}.${sql.identifier( 185 | rightKeyAttribute.name 186 | )} in (${subqueryBuilder.build()})` 187 | ); 188 | }, 189 | queryBuilder.context, 190 | queryBuilder.rootValue 191 | ); 192 | return sql.fragment`(${query})`; 193 | }, getSafeAliasFromAlias(parsedResolveInfoFragment.alias)); 194 | }, 195 | }; 196 | }); 197 | 198 | return { 199 | description: `Reads and enables pagination through a set of \`${RightTableType.name}\`.`, 200 | type: isConnection 201 | ? new GraphQLNonNull(RightTableConnectionType) 202 | : new GraphQLNonNull( 203 | new GraphQLList(new GraphQLNonNull(RightTableType)) 204 | ), 205 | args: {}, 206 | resolve: (data, _args, _context, resolveInfo) => { 207 | const safeAlias = 208 | getSafeAliasFromResolveInfo(resolveInfo); 209 | if (isConnection) { 210 | return addStartEndCursor(data[safeAlias]); 211 | } else { 212 | return data[safeAlias]; 213 | } 214 | }, 215 | }; 216 | }, 217 | { 218 | isPgFieldConnection: isConnection, 219 | isPgFieldSimpleCollection: !isConnection, 220 | isPgManyToManyRelationField: true, 221 | pgFieldIntrospection: rightTable, 222 | } 223 | ), 224 | }, 225 | 226 | `Many-to-many relation field (${ 227 | isConnection ? "connection" : "simple collection" 228 | }) on ${Self.name} type for ${describePgEntity( 229 | junctionLeftConstraint 230 | )} and ${describePgEntity(junctionRightConstraint)}.` 231 | ); 232 | } 233 | 234 | const simpleCollections = 235 | junctionRightConstraint.tags.simpleCollections || 236 | rightTable.tags.simpleCollections || 237 | pgSimpleCollections; 238 | const hasConnections = simpleCollections !== "only"; 239 | const hasSimpleCollections = 240 | simpleCollections === "only" || simpleCollections === "both"; 241 | if (hasConnections) { 242 | makeFields(true); 243 | } 244 | if (hasSimpleCollections) { 245 | makeFields(false); 246 | } 247 | return memo; 248 | }, {}), 249 | `Adding many-to-many relations for ${Self.name}` 250 | ); 251 | }); 252 | }; 253 | -------------------------------------------------------------------------------- /src/createManyToManyConnectionType.js: -------------------------------------------------------------------------------- 1 | const hasNonNullKey = (row) => { 2 | if ( 3 | Array.isArray(row.__identifiers) && 4 | row.__identifiers.every((i) => i != null) 5 | ) { 6 | return true; 7 | } 8 | for (const k in row) { 9 | if (Object.prototype.hasOwnProperty.call(row, k)) { 10 | if ((k[0] !== "_" || k[1] !== "_") && row[k] !== null) { 11 | return true; 12 | } 13 | } 14 | } 15 | return false; 16 | }; 17 | 18 | module.exports = function createManyToManyConnectionType( 19 | relationship, 20 | build, 21 | options, 22 | leftTable 23 | ) { 24 | const { 25 | leftKeyAttributes, 26 | junctionLeftKeyAttributes, 27 | junctionRightKeyAttributes, 28 | rightKeyAttributes, 29 | junctionTable, 30 | rightTable, 31 | junctionLeftConstraint, 32 | junctionRightConstraint, 33 | } = relationship; 34 | const { 35 | newWithHooks, 36 | inflection, 37 | graphql: { GraphQLObjectType, GraphQLNonNull, GraphQLList }, 38 | getTypeByName, 39 | pgGetGqlTypeByTypeIdAndModifier, 40 | pgField, 41 | getSafeAliasFromResolveInfo, 42 | describePgEntity, 43 | } = build; 44 | const { pgForbidSetofFunctionsToReturnNull = false } = options; 45 | const nullableIf = (condition, Type) => 46 | condition ? Type : new GraphQLNonNull(Type); 47 | const Cursor = getTypeByName("Cursor"); 48 | const handleNullRow = pgForbidSetofFunctionsToReturnNull 49 | ? (row) => row 50 | : (row, identifiers) => { 51 | if ((identifiers && hasNonNullKey(identifiers)) || hasNonNullKey(row)) { 52 | return row; 53 | } else { 54 | return null; 55 | } 56 | }; 57 | 58 | const LeftTableType = pgGetGqlTypeByTypeIdAndModifier( 59 | leftTable.type.id, 60 | null 61 | ); 62 | if (!LeftTableType) { 63 | throw new Error( 64 | `Could not determine type for table with id ${leftTable.type.id}` 65 | ); 66 | } 67 | 68 | const TableType = pgGetGqlTypeByTypeIdAndModifier(rightTable.type.id, null); 69 | if (!TableType) { 70 | throw new Error( 71 | `Could not determine type for table with id ${rightTable.type.id}` 72 | ); 73 | } 74 | 75 | const rightPrimaryKeyConstraint = rightTable.primaryKeyConstraint; 76 | const rightPrimaryKeyAttributes = 77 | rightPrimaryKeyConstraint && rightPrimaryKeyConstraint.keyAttributes; 78 | 79 | const junctionTypeName = inflection.tableType(junctionTable); 80 | const base64 = (str) => Buffer.from(String(str)).toString("base64"); 81 | 82 | const EdgeType = newWithHooks( 83 | GraphQLObjectType, 84 | { 85 | description: `A \`${TableType.name}\` edge in the connection, with data from \`${junctionTypeName}\`.`, 86 | name: inflection.manyToManyRelationEdge( 87 | leftKeyAttributes, 88 | junctionLeftKeyAttributes, 89 | junctionRightKeyAttributes, 90 | rightKeyAttributes, 91 | junctionTable, 92 | rightTable, 93 | junctionLeftConstraint, 94 | junctionRightConstraint, 95 | LeftTableType.name 96 | ), 97 | fields: ({ fieldWithHooks }) => { 98 | return { 99 | cursor: fieldWithHooks( 100 | "cursor", 101 | ({ addDataGenerator }) => { 102 | addDataGenerator(() => ({ 103 | usesCursor: [true], 104 | pgQuery: (queryBuilder) => { 105 | if (rightPrimaryKeyAttributes) { 106 | queryBuilder.selectIdentifiers(rightTable); 107 | } 108 | }, 109 | })); 110 | return { 111 | description: "A cursor for use in pagination.", 112 | type: Cursor, 113 | resolve(data) { 114 | return data.__cursor && base64(JSON.stringify(data.__cursor)); 115 | }, 116 | }; 117 | }, 118 | { 119 | isCursorField: true, 120 | } 121 | ), 122 | node: pgField( 123 | build, 124 | fieldWithHooks, 125 | "node", 126 | { 127 | description: `The \`${TableType.name}\` at the end of the edge.`, 128 | type: nullableIf(!pgForbidSetofFunctionsToReturnNull, TableType), 129 | resolve(data, _args, _context, resolveInfo) { 130 | const safeAlias = getSafeAliasFromResolveInfo(resolveInfo); 131 | const record = handleNullRow( 132 | data[safeAlias], 133 | data.__identifiers 134 | ); 135 | return record; 136 | }, 137 | }, 138 | {}, 139 | false, 140 | {} 141 | ), 142 | }; 143 | }, 144 | }, 145 | { 146 | __origin: `Adding many-to-many edge type from ${describePgEntity( 147 | leftTable 148 | )} to ${describePgEntity(rightTable)} via ${describePgEntity( 149 | junctionTable 150 | )}.`, 151 | isEdgeType: true, 152 | isPgRowEdgeType: true, 153 | isPgManyToManyEdgeType: true, 154 | nodeType: TableType, 155 | pgManyToManyRelationship: relationship, 156 | } 157 | ); 158 | const PageInfo = getTypeByName(inflection.builtin("PageInfo")); 159 | 160 | return newWithHooks( 161 | GraphQLObjectType, 162 | { 163 | description: `A connection to a list of \`${TableType.name}\` values, with data from \`${junctionTypeName}\`.`, 164 | name: inflection.manyToManyRelationConnection( 165 | leftKeyAttributes, 166 | junctionLeftKeyAttributes, 167 | junctionRightKeyAttributes, 168 | rightKeyAttributes, 169 | junctionTable, 170 | rightTable, 171 | junctionLeftConstraint, 172 | junctionRightConstraint, 173 | LeftTableType.name 174 | ), 175 | fields: ({ recurseDataGeneratorsForField, fieldWithHooks }) => { 176 | recurseDataGeneratorsForField("pageInfo", true); 177 | return { 178 | nodes: pgField( 179 | build, 180 | fieldWithHooks, 181 | "nodes", 182 | { 183 | description: `A list of \`${TableType.name}\` objects.`, 184 | type: new GraphQLNonNull( 185 | new GraphQLList( 186 | nullableIf(!pgForbidSetofFunctionsToReturnNull, TableType) 187 | ) 188 | ), 189 | resolve(data, _args, _context, resolveInfo) { 190 | const safeAlias = getSafeAliasFromResolveInfo(resolveInfo); 191 | return data.data.map((entry) => { 192 | const record = handleNullRow( 193 | entry[safeAlias], 194 | entry[safeAlias].__identifiers 195 | ); 196 | return record; 197 | }); 198 | }, 199 | }, 200 | {}, 201 | false, 202 | {} 203 | ), 204 | edges: pgField( 205 | build, 206 | fieldWithHooks, 207 | "edges", 208 | { 209 | description: `A list of edges which contains the \`${TableType.name}\`, info from the \`${junctionTypeName}\`, and the cursor to aid in pagination.`, 210 | type: new GraphQLNonNull( 211 | new GraphQLList(new GraphQLNonNull(EdgeType)) 212 | ), 213 | resolve(data, _args, _context, resolveInfo) { 214 | const safeAlias = getSafeAliasFromResolveInfo(resolveInfo); 215 | return data.data.map((entry) => ({ 216 | ...entry, 217 | ...entry[safeAlias], 218 | })); 219 | }, 220 | }, 221 | {}, 222 | false, 223 | { 224 | hoistCursor: true, 225 | } 226 | ), 227 | pageInfo: PageInfo && { 228 | description: "Information to aid in pagination.", 229 | type: new GraphQLNonNull(PageInfo), 230 | resolve(data) { 231 | return data; 232 | }, 233 | }, 234 | }; 235 | }, 236 | }, 237 | { 238 | __origin: `Adding many-to-many connection type from ${describePgEntity( 239 | leftTable 240 | )} to ${describePgEntity(rightTable)} via ${describePgEntity( 241 | junctionTable 242 | )}.`, 243 | isConnectionType: true, 244 | isPgRowConnectionType: true, 245 | edgeType: EdgeType, 246 | nodeType: TableType, 247 | pgIntrospection: rightTable, 248 | } 249 | ); 250 | }; 251 | -------------------------------------------------------------------------------- /src/manyToManyRelationships.js: -------------------------------------------------------------------------------- 1 | function arraysAreEqual(array1, array2) { 2 | return ( 3 | array1.length === array2.length && array1.every((el, i) => array2[i] === el) 4 | ); 5 | } 6 | 7 | // Given a `leftTable`, trace through the foreign key relations 8 | // and identify a `junctionTable` and `rightTable`. 9 | // Returns a list of data objects for these many-to-many relationships. 10 | module.exports = function manyToManyRelationships(leftTable, build) { 11 | const { 12 | pgIntrospectionResultsByKind: introspectionResultsByKind, 13 | pgOmit: omit, 14 | } = build; 15 | 16 | return leftTable.foreignConstraints 17 | .filter((con) => con.type === "f") 18 | .reduce((memoLeft, junctionLeftConstraint) => { 19 | if ( 20 | omit(junctionLeftConstraint, "read") || 21 | omit(junctionLeftConstraint, "manyToMany") 22 | ) { 23 | return memoLeft; 24 | } 25 | const junctionTable = 26 | introspectionResultsByKind.classById[junctionLeftConstraint.classId]; 27 | if (!junctionTable) { 28 | throw new Error( 29 | `Could not find the table that referenced us (constraint: ${junctionLeftConstraint.name})` 30 | ); 31 | } 32 | if (omit(junctionTable, "read") || omit(junctionTable, "manyToMany")) { 33 | return memoLeft; 34 | } 35 | const memoRight = junctionTable.constraints 36 | .filter( 37 | (con) => 38 | con.id !== junctionLeftConstraint.id && // Don't follow the same constraint back to the left table 39 | con.type === "f" && 40 | !omit(con, "read") && 41 | !omit(con, "manyToMany") 42 | ) 43 | .reduce((memoRight, junctionRightConstraint) => { 44 | const rightTable = junctionRightConstraint.foreignClass; 45 | if (omit(rightTable, "read") || omit(rightTable, "manyToMany")) { 46 | return memoRight; 47 | } 48 | 49 | const leftKeyAttributes = junctionLeftConstraint.foreignKeyAttributes; 50 | const junctionLeftKeyAttributes = 51 | junctionLeftConstraint.keyAttributes; 52 | const junctionRightKeyAttributes = 53 | junctionRightConstraint.keyAttributes; 54 | const rightKeyAttributes = 55 | junctionRightConstraint.foreignKeyAttributes; 56 | 57 | // Ensure keys were found 58 | if ( 59 | !leftKeyAttributes.every((_) => _) || 60 | !junctionLeftKeyAttributes.every((_) => _) || 61 | !junctionRightKeyAttributes.every((_) => _) || 62 | !rightKeyAttributes.every((_) => _) 63 | ) { 64 | throw new Error("Could not find key columns!"); 65 | } 66 | 67 | // Ensure keys can be read 68 | if ( 69 | leftKeyAttributes.some((attr) => omit(attr, "read")) || 70 | junctionLeftKeyAttributes.some((attr) => omit(attr, "read")) || 71 | junctionRightKeyAttributes.some((attr) => omit(attr, "read")) || 72 | rightKeyAttributes.some((attr) => omit(attr, "read")) 73 | ) { 74 | return memoRight; 75 | } 76 | 77 | // Ensure both constraints are single-column 78 | // TODO: handle multi-column 79 | if (leftKeyAttributes.length > 1 || rightKeyAttributes.length > 1) { 80 | return memoRight; 81 | } 82 | 83 | // Ensure junction constraint keys are not unique (which would result in a one-to-one relation) 84 | const junctionLeftConstraintIsUnique = 85 | !!junctionTable.constraints.find( 86 | (c) => 87 | ["p", "u"].includes(c.type) && 88 | arraysAreEqual( 89 | c.keyAttributeNums, 90 | junctionLeftKeyAttributes.map((attr) => attr.num) 91 | ) 92 | ); 93 | const junctionRightConstraintIsUnique = 94 | !!junctionTable.constraints.find( 95 | (c) => 96 | ["p", "u"].includes(c.type) && 97 | arraysAreEqual( 98 | c.keyAttributeNums, 99 | junctionRightKeyAttributes.map((attr) => attr.num) 100 | ) 101 | ); 102 | if ( 103 | junctionLeftConstraintIsUnique || 104 | junctionRightConstraintIsUnique 105 | ) { 106 | return memoRight; 107 | } 108 | 109 | const allowsMultipleEdgesToNode = !junctionTable.constraints.find( 110 | (c) => 111 | ["p", "u"].includes(c.type) && 112 | arraysAreEqual( 113 | c.keyAttributeNums.concat().sort(), 114 | [ 115 | ...junctionLeftKeyAttributes.map((obj) => obj.num), 116 | ...junctionRightKeyAttributes.map((obj) => obj.num), 117 | ].sort() 118 | ) 119 | ); 120 | 121 | return [ 122 | ...memoRight, 123 | { 124 | leftKeyAttributes, 125 | junctionLeftKeyAttributes, 126 | junctionRightKeyAttributes, 127 | rightKeyAttributes, 128 | junctionTable, 129 | rightTable, 130 | junctionLeftConstraint, 131 | junctionRightConstraint, 132 | allowsMultipleEdgesToNode, 133 | }, 134 | ]; 135 | }, []); 136 | return [...memoLeft, ...memoRight]; 137 | }, []); 138 | }; 139 | --------------------------------------------------------------------------------