├── .editorconfig ├── .gitignore ├── .graphqlconfig.yml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── config.dev.json ├── config.prod.json ├── config.stage.json ├── docker-compose.yaml ├── examples.graphql ├── package.json ├── scripts ├── copydeps.ts ├── schema.generate.ts ├── schema.import.ts ├── schema.patch.js └── schema.serve.ts ├── src ├── Config.ts ├── Context.ts ├── embeddedPostgraphile │ ├── config.ts │ └── index.ts ├── entity │ ├── Photo.ts │ └── User.ts ├── generated │ ├── postgraphile.graphql │ ├── postgraphile.ts │ └── types.ts ├── index.ts ├── migration │ └── 1534731480120-Migration.ts ├── resolvers │ ├── Mutation │ │ ├── auth.ts │ │ └── post.ts │ ├── Node.ts │ ├── Query.ts │ ├── Subscription.ts │ └── index.ts ├── schema.graphql └── utils.ts ├── tsconfig.json ├── tsconfig.prod.json ├── tsconfig.test.json ├── tslint.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | end_of_line = lf 11 | 12 | [{*.ts,*.tsx,*.js,*.jsx,*.css,*scss,*.graphql}] 13 | indent_size = 4 14 | 15 | [*.md] 16 | max_line_length = off 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | package-lock.json 3 | node_modules 4 | .idea 5 | *.log 6 | .env* 7 | -------------------------------------------------------------------------------- /.graphqlconfig.yml: -------------------------------------------------------------------------------- 1 | projects: 2 | app: 3 | schemaPath: src/schema.graphql 4 | extensions: 5 | endpoints: 6 | default: http://localhost:4000 7 | database: 8 | schemaPath: src/generated/postgraphile.graphql 9 | extensions: 10 | endpoints: 11 | default: http://localhost:5000/graphql 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "tslint.exclude": "**/src/migration/*.ts" 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andrey Konstantinov & Contributors 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 | ## Overview 2 | 3 | Starter project for backend development of `Graphql` APIs hooked into `Typeorm` and *embedded* `Postgraphile` infrastructure. 4 | 5 | Alternatives: [Graphql API with typescript via Prisma](https://github.com/graphql-boilerplates/typescript-graphql-server/tree/master/advanced) OR [GraphQL API with typescript via TypeGraphQL](https://19majkel94.github.io/type-graphql/) 6 | 7 | ### Features: 8 | 9 | * Provides great development experience similar to `Prisma`, including code generation of query bindings, `Graphql` API schema, etc.. 10 | * Does not require separate running proxy process, like Prisma or Postrgaphile server. All `GraphQL` queries are wired directly to the connection with the database. 11 | * Server is powered by `Apollo` Server 2.0 12 | * Flexible and powerful schema definitions by `Typeorm` decorators 13 | * Fine control for database migrations using `Typeorm` features, including: 14 | * Automated generation of migration queries based on introspection of source code models and state of the database 15 | * Run of the migrations on application's launch or manually via command line 16 | * Revert of the migrations via command line 17 | * Separate configurations and scripts for development, stage and production environment 18 | * `Typescript` and `TSLint` enabled 19 | * Build script creates distributable folder without dependencies to local `node_modules` folder 20 | * GraphQL playground server for experimenting with application's GraphQL API and with Postgraphile's GraphQL API 21 | * Implement resolvers using `Typeorm` queries or wire to embedded `Postgraphile` binding queries. 22 | 23 | ### Getting started 24 | 25 | 1. Run `yarn install` 26 | 2. Edit `config.dev.json` database connection settings (uses `Typeorm` connection configuration options) 27 | 3. Launch everything in development `watch` mode: `yarn start` - it will run the initial database setup (the first database schema migrations script) and will start development environment. Check terminal output for served endpoints and ports. 28 | 4. Play with example queries saved in [examples.graphql](./examples.graphql) file 29 | 5. Build everthing into self contained distributable folder: `yarn build` 30 | 31 | ### Schema migration workflow 32 | 33 | 1. Edit `src/entity/**` files to match your data model. It uses `Typeorm` to define the database model. 34 | 2. Run `yarn migrations:generate`, review and modify if necessary the generated migration script. In most cases you do not need to modify it, `Typeorm` does nice job generating migration scripts. 35 | 3. Run `yarn migrations:run` or just start the application's server to deploy the migrations 36 | 4. Run `yarn postgraphile:generate` to generate postgraphile bindings and internal GraphQL schema for querying the database with the latest schema (code generation steps are explained [here](https://github.com/degroote22/postgraphile-apollo)) 37 | 5. Wire Application's GraphQL API calls to Postgraphile API calls as it is required by your business logic: see `src/resolvers/**` scripts for example. 38 | 39 | ### Commands 40 | 41 | * `yarn start` starts GraphQL server in `watch` mode and Playground server 42 | * `yarn watch` starts GraphQL server in `watch` mode 43 | * `yarn build` builds self contained distributable folder with production build 44 | * `yarn playground` opens the GraphQL Playground for the `projects` from [`.graphqlconfig.yml`](./.graphqlconfig.yml) 45 | * `yarn postgraphile:serve` runs `Postgraphile` server targeting development database. It is only for development and usage in playground. `Postgraphile` server is not required in production. 46 | * `yarn migrations:generate` generates database schema migration queries 47 | * `yarn migrations:run`, `yarn migrations:run:stage` and `yarn migrations:run:prod` deployes all pending schema migrations targeting development, stage and production databases accordingly 48 | * `yarn migrations:revert` deployes all pending schema migrations 49 | 50 | Check other commands in the `package.json` file. They are all self explanatory. 51 | 52 | ### Project structure 53 | 54 | | File name               | Description         

| 55 | | :-- | :-- | 56 | | `config.*.json` | Datebase connection settings for dev, stage and production environments | 57 | | `scripts/**` | Build and code generation scripts invoked by `npm/yarn` commands | 58 | | `src/entity/**` | Definition of the database model | 59 | | `src/migrations/**` | Generated or manually prepared database schema migrations scripts | 60 | | `src/generated/**` | Code automatically generated for quering the database via `Postgraphile` rendered queries | 61 | | `src/resolvers/**` | Implementation of your application's GraphQL API resolvers | 62 | | `└──Mutation/auth.ts` | Example of implementing GraphQL resolvers using `Typeorm` queries | 63 | | `└──Mutation/post.ts` | Example of implementing GraphQL resolvers using transparent proxing to `Postgraphile` queries resolver | 64 | | `docker-compose.yaml` | Config for launching Postgres database locally via `docker-compose up -d` | 65 | 66 | ### Subscription to database changes 67 | 68 | This feature is intentionally not added to the starter project for few reasons: 69 | 1. Subscriptions, based on `Postgraphile` or `Typeorm` subscriptions, do not scale beyong a single process. If you still prefer this there are many examples and blogs how `Typeorm` subscriptions can be wired to GraphQL subscription endpoint. `Postgraphile` also has got subscriptions support via additional plugin. As `Postgraphile` or `Typeorm` notifications are not caused by the database itself, you will need to wire both libraries for notifications or pick only one for invoking database mutations. 70 | 2. Better subscriptions are based on Postgres triggers raising notifications on row updates. This will scale better but require selective choice of what triggers to place. If you prefer this approach: create new database migration script which will add Postgres notification TRIGGER on create/update/delete AND subscribe to these notification using plaing `pg` driver. 71 | 3. Even better use some message queue, like `Kafka` for resilient, scalable, distributed notifications. But this goes beyond of this project boilerplate. 72 | 73 | Enjoy! 74 | 75 | ### License 76 | 77 | MIT License 78 | 79 | Copyright (c) 2018 Andrey Konstantinov & Contributors 80 | 81 | -------------------------------------------------------------------------------- /config.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "postgres", 3 | "host": "localhost", 4 | "port": 5432, 5 | "username": "pguser", 6 | "password": "pgpass", 7 | "database": "dbname", 8 | "schema": "public", 9 | "logging": true, 10 | "migrationsRun": true, 11 | "entities": [ 12 | "src/entity/**/*" 13 | ], 14 | "migrations": [ 15 | "src/migration/**/*" 16 | ], 17 | "subscribers": [ 18 | "src/subscriber/**/*" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /config.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "postgres", 3 | "host": "localhost", 4 | "port": 5432, 5 | "username": "pguser", 6 | "password": "pgpass", 7 | "database": "dbname", 8 | "schema": "public", 9 | "logging": false, 10 | "migrationsRun": true, 11 | "entities": [ 12 | "src/entity/**/*" 13 | ], 14 | "migrations": [ 15 | "src/migration/**/*" 16 | ], 17 | "subscribers": [ 18 | "src/subscriber/**/*" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /config.stage.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "postgres", 3 | "host": "localhost", 4 | "port": 5432, 5 | "username": "pguser", 6 | "password": "pgpass", 7 | "database": "dbname", 8 | "schema": "public", 9 | "logging": true, 10 | "migrationsRun": true, 11 | "entities": [ 12 | "src/entity/**/*" 13 | ], 14 | "migrations": [ 15 | "src/migration/**/*" 16 | ], 17 | "subscribers": [ 18 | "src/subscriber/**/*" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | postgres: 4 | container_name: postgres 5 | image: postgres 6 | restart: always 7 | ports: 8 | - "5432:5432" 9 | environment: 10 | POSTGRES_USER: pguser 11 | POSTGRES_PASSWORD: pgpass 12 | volumes: 13 | - postgres:/var/lib/postgresql/data 14 | volumes: 15 | postgres: 16 | -------------------------------------------------------------------------------- /examples.graphql: -------------------------------------------------------------------------------- 1 | # create user 2 | mutation { 3 | signup(email: "ass@b.c", name: "user", password: "pass") { 4 | id 5 | } 6 | } 7 | 8 | # login and save token 9 | mutation { 10 | login(email: "ass@b.c", password: "pass") { 11 | token 12 | } 13 | } 14 | 15 | # set token to Authorization header as `Bearer TOKEN_VALUE`, and now you can run auth protected queries: 16 | 17 | # create photo 18 | mutation { 19 | createPhoto(input: { 20 | photo: { 21 | name: "photo name", 22 | description: "some descr", 23 | views: 1, 24 | isPublished: false 25 | filename: "filename.tst" 26 | } 27 | }) { 28 | photo { 29 | id 30 | name 31 | nodeId 32 | filename 33 | description 34 | id 35 | } 36 | } 37 | } 38 | 39 | # list photos 40 | query { 41 | allPhotos { 42 | nodes { 43 | name, 44 | filename, 45 | description, 46 | nodeId 47 | } 48 | } 49 | } 50 | 51 | # delete photo 52 | mutation { 53 | deletePhoto(input: { 54 | nodeId: "WyJwaG90b3MiLDFd" 55 | }) { 56 | photo { 57 | id 58 | name 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "www-backend", 3 | "scripts": { 4 | "build": "npm-run-all server:build tslint:build && ts-node scripts/copydeps.ts", 5 | "start": "npm-run-all -p server:watch tslint:watch postgraphile:serve playground", 6 | "watch": "npm-run-all -p server:watch tslint:watch", 7 | "server:build": "rimraf build && tsc && cp src/schema.graphql build/schema.graphql && cp src/generated/postgraphile.graphql build/generated/postgraphile.graphql", 8 | "server:watch": "npm-watch server:runts", 9 | "server:debug": "ts-node --inspect src/index.ts config.dev.json", 10 | "server:runts": "ts-node src/index.ts config.dev.json", 11 | "server:runts:stage": "ts-node src/index.ts config.stage.json", 12 | "server:runts:prod": "ts-node src/index.ts config.prod.json", 13 | "server:runjs": "node build/index.js config.dev.json", 14 | "server:runjs:stage": "node build/index.js config.stage.json", 15 | "server:runjs:prod": "node build/index.js config.prod.json", 16 | "tslint:build": "tslint --exclude src/migration/** --project .", 17 | "tslint:watch": "npm-watch tslint:build", 18 | "migration:run": "ts-node ./node_modules/typeorm/cli.js migration:run -f config.dev", 19 | "migration:run:stage": "ts-node ./node_modules/typeorm/cli.js migration:run -f config.stage", 20 | "migration:run:prod": "ts-node ./node_modules/typeorm/cli.js migration:run -f config.prod", 21 | "migration:revert": "ts-node ./node_modules/typeorm/cli.js migration:revert -f config.dev", 22 | "migration:revert:stage": "ts-node ./node_modules/typeorm/cli.js migration:revert -f config.stage", 23 | "migration:revert:prod": "ts-node ./node_modules/typeorm/cli.js migration:revert -f config.prod", 24 | "migration:generate": "ts-node ./node_modules/typeorm/cli.js migration:generate -n Migration -d src/migration -f config.dev", 25 | "postgraphile:generate": "npm-run-all postgraphile:generate-step1 postgraphile:generate-step2 postgraphile:generate-step3", 26 | "postgraphile:generate-step1": "ts-node scripts/schema.generate.ts config.dev.json", 27 | "postgraphile:generate-step2": "graphql-binding --language typescript --input src/embeddedPostgraphile/index.ts --outputBinding src/generated/postgraphile.ts && node scripts/schema.patch.js", 28 | "postgraphile:generate-step3": "cross-env CODEGEN_ENUMS_AS_TYPES=TRUE gql-gen --require ts-node/register --template typescript --schema scripts/schema.import.ts --out src/generated/", 29 | "postgraphile:serve": "ts-node scripts/schema.serve.ts config.dev.json", 30 | "playground": "graphql playground" 31 | }, 32 | "watch": { 33 | "server:runts": { 34 | "patterns": [ 35 | "src/**" 36 | ], 37 | "extensions": "ts,js,graphql", 38 | "quiet": false, 39 | "legacyWatch": false, 40 | "delay": 500 41 | }, 42 | "tslint:build": { 43 | "patterns": [ 44 | "src/**" 45 | ], 46 | "extensions": "ts", 47 | "quiet": false, 48 | "legacyWatch": false, 49 | "delay": 500 50 | } 51 | }, 52 | "resolutions": { 53 | "graphql": "0.13.2" 54 | }, 55 | "dependencies": { 56 | "apollo-server": "2.0.0", 57 | "bcryptjs": "2.4.3", 58 | "graphql-import": "0.6.0", 59 | "jsonwebtoken": "8.3.0", 60 | "node-fetch": "2.2.0", 61 | "pg": "7.4.3", 62 | "postgraphile": "4.0.0-rc.2", 63 | "reflect-metadata": "0.1.12", 64 | "typeorm": "0.2.7" 65 | }, 66 | "devDependencies": { 67 | "@types/bcryptjs": "2.4.1", 68 | "@types/graphql": "^0.13.4", 69 | "@types/jsonwebtoken": "7.2.8", 70 | "@types/pg": "7.4.10", 71 | "@types/ws": "6.0.0", 72 | "cross-env": "^5.2.0", 73 | "dotenv-cli": "1.4.0", 74 | "graphql-binding": "^2.2.3", 75 | "graphql-cli": "2.16.5", 76 | "graphql-code-generator": "^0.10.3", 77 | "graphql-codegen-typescript-template": "^0.10.3", 78 | "graphql-tools": "^3.0.5", 79 | "nodemon": "1.18.3", 80 | "npm-run-all": "4.1.3", 81 | "npm-watch": "0.3.0", 82 | "rimraf": "2.6.2", 83 | "ts-node": "6.2.0", 84 | "tslint": "5.11.0", 85 | "tslint-config-airbnb": "5.9.2", 86 | "tslint-react": "3.6.0", 87 | "typescript": "2.9.2" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /scripts/copydeps.ts: -------------------------------------------------------------------------------- 1 | // the script analyses production dependencies recursively 2 | // and publishes required node modules to build directory 3 | 4 | import * as cp from 'child_process'; 5 | import * as fs from 'fs'; 6 | const deps = cp.execSync('yarn list --prod').toString(); 7 | const packs = deps.split('\n').map(l => { 8 | const words = l.split(' '); 9 | return words[words.length - 1].split('@')[0]; 10 | }).filter(i => i.length !== 0); 11 | 12 | if (fs.existsSync('build/node_modules')) { 13 | cp.execSync(`rm -Rf build/node_modules`); 14 | } 15 | fs.mkdirSync('build/node_modules'); 16 | const copied: string[] = []; 17 | packs.forEach(p => { 18 | if (!copied.includes(p) && fs.existsSync(`node_modules/${p}`)) { 19 | console.log(`coping: node_modules/${p} => build/node_modules/${p}`); 20 | cp.execSync(`cp -r node_modules/${p} build/node_modules`).toString(); 21 | copied.push(p); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /scripts/schema.generate.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '../src/Config'; 2 | 3 | import { Connection } from '../src/embeddedPostgraphile/config'; 4 | 5 | const { postgraphile } = require('postgraphile'); 6 | 7 | // simple arguments parser 8 | const configPath = process.argv[process.argv.length - 1]; 9 | if (configPath === __filename) { 10 | console.log(`Config argument required. Run as: > ${__filename} path/to/config.json`); 11 | process.exit(1); 12 | } 13 | const config = Config.fromFile(configPath); 14 | 15 | Connection.init(config); 16 | 17 | postgraphile(Connection.instance().url, Connection.instance().schema, { 18 | ...Connection.instance().postgraphileSchemaOptions, 19 | exportGqlSchemaPath: './src/generated/postgraphile.graphql' 20 | }); 21 | -------------------------------------------------------------------------------- /scripts/schema.import.ts: -------------------------------------------------------------------------------- 1 | import { importSchema } from 'graphql-import'; 2 | const schema = importSchema('./src/schema.graphql'); 3 | export default schema; 4 | -------------------------------------------------------------------------------- /scripts/schema.patch.js: -------------------------------------------------------------------------------- 1 | // temporary workaround for 2 | 3 | 'use strict' 4 | 5 | var lineToSearch = `import schema from '..\\embeddedPostgraphile\\index'`; 6 | var lineToWrite = `import schema from '../embeddedPostgraphile/index'`; 7 | 8 | var lineToSearch2 = `...args)`; 9 | var lineToWrite2 = `...args: any[])`; 10 | 11 | var lineToInsert = '/* tslint:disable */' 12 | 13 | var fileToPatch = 'src/generated/postgraphile.ts'; 14 | 15 | var fs = require('fs') 16 | fs.readFile(fileToPatch, 'utf8', function (err, data) { 17 | if (err) { 18 | console.log(err); 19 | process.exit(1); 20 | } 21 | 22 | if (data.includes(lineToWrite) && data.includes(lineToWrite2) && data.startsWith(lineToInsert)) { 23 | // already patched, nothing to do 24 | process.exit(0); 25 | } 26 | 27 | var result = data.replace(lineToSearch, lineToWrite); 28 | if (result === data) { 29 | console.log(`[bindingFix.js] can not find the line '${lineToSearch}' for patching, skipping...`); 30 | } 31 | 32 | data = result; 33 | result = data.replace(lineToSearch2, lineToWrite2); 34 | if (result === data) { 35 | console.log(`[bindingFix.js] can not find the line '${lineToSearch2}' for patching, skipping...`); 36 | } 37 | 38 | data = result; 39 | if (data.startsWith(lineToInsert)) { 40 | console.log(`[bindingFix.js] tslint is already disabled, skipping...`); 41 | } else { 42 | result = `${lineToInsert}\n${data}`; 43 | } 44 | 45 | fs.writeFile(fileToPatch, result, 'utf8', function (err) { 46 | if (err) { 47 | console.log(err); 48 | process.exit(1); 49 | } 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /scripts/schema.serve.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '../src/Config'; 2 | import { Connection } from '../src/embeddedPostgraphile/config'; 3 | 4 | // simple arguments parser 5 | const configPath = process.argv[process.argv.length - 1]; 6 | if (configPath === __filename) { 7 | console.log(`Config argument required. Run as: > ${__filename} path/to/config.json`); 8 | process.exit(1); 9 | } 10 | const config = Config.fromFile(configPath); 11 | Connection.init(config); 12 | 13 | import { exec } from 'child_process'; 14 | const child = exec( 15 | `postgraphile --cors -c ${Connection.instance().url} --schema ${Connection.instance().schema}`); 16 | 17 | child.stdout.on('data', (data) => { 18 | console.log(data); 19 | }); 20 | child.stderr.on('data', (data) => { 21 | console.log(data); 22 | }); 23 | -------------------------------------------------------------------------------- /src/Config.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | export class Config { 4 | static fromFile(path: string): any { 5 | // it does not validate the config 6 | const config = JSON.parse(fs.readFileSync(path).toString()); 7 | 8 | // patch paths for production run of the compiled code 9 | if (__filename.endsWith('.js')) { 10 | config.entities = [ 11 | __dirname + '/entity/**/*' // accepts both ts and js 12 | ]; 13 | config.migrations = [ 14 | __dirname + '/migration/**/*' // accepts both ts and js 15 | ]; 16 | config.subscribers = [ 17 | __dirname + '/subscriber/**/*' // accepts both ts and js 18 | ]; 19 | } 20 | 21 | console.log(`Running with configuration: ${path}: ${JSON.stringify(config, undefined, 4)}`); 22 | return config; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Context.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from 'typeorm'; 2 | import { Binding } from './generated/postgraphile'; 3 | import { IncomingMessage, ServerResponse } from 'http'; 4 | 5 | export interface Context { 6 | orm: Connection; 7 | gql: Binding; 8 | req: IncomingMessage; 9 | res: ServerResponse; 10 | } 11 | -------------------------------------------------------------------------------- /src/embeddedPostgraphile/config.ts: -------------------------------------------------------------------------------- 1 | const { defaultPlugins } = require('graphile-build-pg'); 2 | const { 3 | defaultPlugins: defaultGraphileBuildPlugins, 4 | MutationPayloadQueryPlugin 5 | } = require('graphile-build'); 6 | 7 | const defaultPluginsToChange = defaultGraphileBuildPlugins.filter( 8 | (p: any) => p !== MutationPayloadQueryPlugin 9 | ); 10 | 11 | export class Connection { 12 | private static _instance: Connection | undefined = undefined; 13 | 14 | url: string; 15 | schema: string; 16 | postgraphileSchemaOptions: any; 17 | 18 | static init(config: any) { 19 | this._instance = { 20 | // tslint:disable-next-line:max-line-length 21 | url: `postgres://${config.username}:${config.password}@${config.host}:${config.port}/${config.database}`, 22 | schema: config.schema.toString(), 23 | postgraphileSchemaOptions: { 24 | ignoreRBAC: false, 25 | replaceAllPlugins: [...defaultPluginsToChange, ...defaultPlugins], 26 | showErrorStack: false, 27 | extendedErrors: 'hint', 28 | 29 | // set it if you rely on postgresql access control 30 | // pgDefaultRole: "example_role2", 31 | // jwtSecret: "secret", 32 | // jwtPgTypeIdentifier: "example.jwt_token", 33 | 34 | // production may need to catch it and log. 35 | // handleErrors: (err) => {} intercept errors in production 36 | 37 | // A file path string. Reads cached values from local cache file 38 | // to improve startup time (you may want to do this in production) 39 | // readCache: 'path to file' 40 | 41 | // A file path string. Writes computed values to local cache file 42 | // so startup can be faster (do this during the build phase) 43 | // writeCache: 'path to file' 44 | } 45 | }; 46 | 47 | if (config.logging) { 48 | this._instance.postgraphileSchemaOptions.showErrorStack = true; 49 | this._instance.postgraphileSchemaOptions.extendedErrors = 'detail'; 50 | } 51 | } 52 | 53 | static instance(): Connection { 54 | if (this._instance) { 55 | return this._instance; 56 | } 57 | throw Error(`${__filename} Connection.init() should be invoked before it can be used`); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/embeddedPostgraphile/index.ts: -------------------------------------------------------------------------------- 1 | const { 2 | withPostGraphileContext, 3 | createPostGraphileSchema 4 | } = require('postgraphile'); 5 | 6 | import { makeRemoteExecutableSchema } from 'graphql-tools'; 7 | import * as fs from 'fs'; 8 | import { FetcherOperation } from 'graphql-tools/dist/stitching/makeRemoteExecutableSchema'; 9 | import { execute } from 'graphql'; 10 | import { Pool } from 'pg'; 11 | import { Connection } from './config'; 12 | 13 | let _schema: any; 14 | const getSchema = async () => { 15 | if (!_schema) { 16 | _schema = await createPostGraphileSchema( 17 | Connection.instance().url, 18 | Connection.instance().schema, 19 | Connection.instance().postgraphileSchemaOptions 20 | ); 21 | return _schema; 22 | } else { 23 | return _schema; 24 | } 25 | }; 26 | 27 | let pgPool: Pool | undefined = undefined; 28 | function initPgPool() { 29 | pgPool = new Pool({ connectionString: Connection.instance().url }); 30 | return pgPool; 31 | } 32 | 33 | const fetcher = async (operation: FetcherOperation) => { 34 | const graphqlContext = operation.context 35 | ? operation.context.graphqlContext 36 | : {}; 37 | 38 | const postGraphileContextOptions = { 39 | ...Connection.instance().postgraphileSchemaOptions, 40 | ...graphqlContext, 41 | pgPool: pgPool || initPgPool() 42 | }; 43 | const postgraphileSchema = await getSchema(); 44 | const result = withPostGraphileContext(postGraphileContextOptions, (context: any) => 45 | execute( 46 | postgraphileSchema, 47 | operation.query, 48 | null, 49 | context, 50 | operation.variables, 51 | operation.operationName 52 | ) 53 | ); 54 | return result; 55 | }; 56 | 57 | const typeDefs = fs.readFileSync('./src/generated/postgraphile.graphql', 'utf-8'); 58 | const schema = makeRemoteExecutableSchema({ fetcher, schema: typeDefs }); 59 | 60 | export default schema; 61 | -------------------------------------------------------------------------------- /src/entity/Photo.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class Photo { 5 | 6 | @PrimaryGeneratedColumn() 7 | id: number; 8 | 9 | @Column({ 10 | length: 100 11 | }) 12 | name: string; 13 | 14 | @Column('text') 15 | description: string; 16 | 17 | @Column() 18 | filename: string; 19 | 20 | @Column('double precision') 21 | views: number; 22 | 23 | @Column() 24 | isPublished: boolean; 25 | } 26 | -------------------------------------------------------------------------------- /src/entity/User.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class User { 5 | @PrimaryGeneratedColumn() 6 | id: number; 7 | 8 | @Column({ 9 | length: 100 10 | }) 11 | name: string; 12 | 13 | @Column('text', { unique: true }) 14 | email: string; 15 | 16 | @Column('text') 17 | password: string; 18 | } 19 | -------------------------------------------------------------------------------- /src/generated/postgraphile.graphql: -------------------------------------------------------------------------------- 1 | """ 2 | A signed eight-byte integer. The upper big integer values are greater then the 3 | max value for a JavaScript number. Therefore all big integers will be output as 4 | strings and not numbers. 5 | """ 6 | scalar BigInt 7 | 8 | """All input for the create `Migration` mutation.""" 9 | input CreateMigrationInput { 10 | """ 11 | An arbitrary string value with no semantic meaning. Will be included in the 12 | payload verbatim. May be used to track mutations by the client. 13 | """ 14 | clientMutationId: String 15 | 16 | """The `Migration` to be created by this mutation.""" 17 | migration: MigrationInput! 18 | } 19 | 20 | """The output of our create `Migration` mutation.""" 21 | type CreateMigrationPayload { 22 | """ 23 | The exact same `clientMutationId` that was provided in the mutation input, 24 | unchanged and unused. May be used by a client to track mutations. 25 | """ 26 | clientMutationId: String 27 | 28 | """The `Migration` that was created by this mutation.""" 29 | migration: Migration 30 | 31 | """An edge for our `Migration`. May be used by Relay 1.""" 32 | migrationEdge( 33 | """The method to use when ordering `Migration`.""" 34 | orderBy: [MigrationsOrderBy!] = PRIMARY_KEY_ASC 35 | ): MigrationsEdge 36 | } 37 | 38 | """All input for the create `Photo` mutation.""" 39 | input CreatePhotoInput { 40 | """ 41 | An arbitrary string value with no semantic meaning. Will be included in the 42 | payload verbatim. May be used to track mutations by the client. 43 | """ 44 | clientMutationId: String 45 | 46 | """The `Photo` to be created by this mutation.""" 47 | photo: PhotoInput! 48 | } 49 | 50 | """The output of our create `Photo` mutation.""" 51 | type CreatePhotoPayload { 52 | """ 53 | The exact same `clientMutationId` that was provided in the mutation input, 54 | unchanged and unused. May be used by a client to track mutations. 55 | """ 56 | clientMutationId: String 57 | 58 | """The `Photo` that was created by this mutation.""" 59 | photo: Photo 60 | 61 | """An edge for our `Photo`. May be used by Relay 1.""" 62 | photoEdge( 63 | """The method to use when ordering `Photo`.""" 64 | orderBy: [PhotosOrderBy!] = PRIMARY_KEY_ASC 65 | ): PhotosEdge 66 | } 67 | 68 | """All input for the create `User` mutation.""" 69 | input CreateUserInput { 70 | """ 71 | An arbitrary string value with no semantic meaning. Will be included in the 72 | payload verbatim. May be used to track mutations by the client. 73 | """ 74 | clientMutationId: String 75 | 76 | """The `User` to be created by this mutation.""" 77 | user: UserInput! 78 | } 79 | 80 | """The output of our create `User` mutation.""" 81 | type CreateUserPayload { 82 | """ 83 | The exact same `clientMutationId` that was provided in the mutation input, 84 | unchanged and unused. May be used by a client to track mutations. 85 | """ 86 | clientMutationId: String 87 | 88 | """The `User` that was created by this mutation.""" 89 | user: User 90 | 91 | """An edge for our `User`. May be used by Relay 1.""" 92 | userEdge( 93 | """The method to use when ordering `User`.""" 94 | orderBy: [UsersOrderBy!] = PRIMARY_KEY_ASC 95 | ): UsersEdge 96 | } 97 | 98 | """A location in a connection that can be used for resuming pagination.""" 99 | scalar Cursor 100 | 101 | """All input for the `deleteMigrationById` mutation.""" 102 | input DeleteMigrationByIdInput { 103 | """ 104 | An arbitrary string value with no semantic meaning. Will be included in the 105 | payload verbatim. May be used to track mutations by the client. 106 | """ 107 | clientMutationId: String 108 | id: Int! 109 | } 110 | 111 | """All input for the `deleteMigration` mutation.""" 112 | input DeleteMigrationInput { 113 | """ 114 | An arbitrary string value with no semantic meaning. Will be included in the 115 | payload verbatim. May be used to track mutations by the client. 116 | """ 117 | clientMutationId: String 118 | 119 | """ 120 | The globally unique `ID` which will identify a single `Migration` to be deleted. 121 | """ 122 | nodeId: ID! 123 | } 124 | 125 | """The output of our delete `Migration` mutation.""" 126 | type DeleteMigrationPayload { 127 | """ 128 | The exact same `clientMutationId` that was provided in the mutation input, 129 | unchanged and unused. May be used by a client to track mutations. 130 | """ 131 | clientMutationId: String 132 | 133 | """The `Migration` that was deleted by this mutation.""" 134 | migration: Migration 135 | deletedMigrationId: ID 136 | 137 | """An edge for our `Migration`. May be used by Relay 1.""" 138 | migrationEdge( 139 | """The method to use when ordering `Migration`.""" 140 | orderBy: [MigrationsOrderBy!] = PRIMARY_KEY_ASC 141 | ): MigrationsEdge 142 | } 143 | 144 | """All input for the `deletePhotoById` mutation.""" 145 | input DeletePhotoByIdInput { 146 | """ 147 | An arbitrary string value with no semantic meaning. Will be included in the 148 | payload verbatim. May be used to track mutations by the client. 149 | """ 150 | clientMutationId: String 151 | id: Int! 152 | } 153 | 154 | """All input for the `deletePhoto` mutation.""" 155 | input DeletePhotoInput { 156 | """ 157 | An arbitrary string value with no semantic meaning. Will be included in the 158 | payload verbatim. May be used to track mutations by the client. 159 | """ 160 | clientMutationId: String 161 | 162 | """ 163 | The globally unique `ID` which will identify a single `Photo` to be deleted. 164 | """ 165 | nodeId: ID! 166 | } 167 | 168 | """The output of our delete `Photo` mutation.""" 169 | type DeletePhotoPayload { 170 | """ 171 | The exact same `clientMutationId` that was provided in the mutation input, 172 | unchanged and unused. May be used by a client to track mutations. 173 | """ 174 | clientMutationId: String 175 | 176 | """The `Photo` that was deleted by this mutation.""" 177 | photo: Photo 178 | deletedPhotoId: ID 179 | 180 | """An edge for our `Photo`. May be used by Relay 1.""" 181 | photoEdge( 182 | """The method to use when ordering `Photo`.""" 183 | orderBy: [PhotosOrderBy!] = PRIMARY_KEY_ASC 184 | ): PhotosEdge 185 | } 186 | 187 | """All input for the `deleteUserById` mutation.""" 188 | input DeleteUserByIdInput { 189 | """ 190 | An arbitrary string value with no semantic meaning. Will be included in the 191 | payload verbatim. May be used to track mutations by the client. 192 | """ 193 | clientMutationId: String 194 | id: Int! 195 | } 196 | 197 | """All input for the `deleteUser` mutation.""" 198 | input DeleteUserInput { 199 | """ 200 | An arbitrary string value with no semantic meaning. Will be included in the 201 | payload verbatim. May be used to track mutations by the client. 202 | """ 203 | clientMutationId: String 204 | 205 | """ 206 | The globally unique `ID` which will identify a single `User` to be deleted. 207 | """ 208 | nodeId: ID! 209 | } 210 | 211 | """The output of our delete `User` mutation.""" 212 | type DeleteUserPayload { 213 | """ 214 | The exact same `clientMutationId` that was provided in the mutation input, 215 | unchanged and unused. May be used by a client to track mutations. 216 | """ 217 | clientMutationId: String 218 | 219 | """The `User` that was deleted by this mutation.""" 220 | user: User 221 | deletedUserId: ID 222 | 223 | """An edge for our `User`. May be used by Relay 1.""" 224 | userEdge( 225 | """The method to use when ordering `User`.""" 226 | orderBy: [UsersOrderBy!] = PRIMARY_KEY_ASC 227 | ): UsersEdge 228 | } 229 | 230 | type Migration implements Node { 231 | """ 232 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 233 | """ 234 | nodeId: ID! 235 | id: Int! 236 | timestamp: BigInt! 237 | name: String! 238 | } 239 | 240 | """ 241 | A condition to be used against `Migration` object types. All fields are tested 242 | for equality and combined with a logical ‘and.’ 243 | """ 244 | input MigrationCondition { 245 | """Checks for equality with the object’s `id` field.""" 246 | id: Int 247 | 248 | """Checks for equality with the object’s `timestamp` field.""" 249 | timestamp: BigInt 250 | 251 | """Checks for equality with the object’s `name` field.""" 252 | name: String 253 | } 254 | 255 | """An input for mutations affecting `Migration`""" 256 | input MigrationInput { 257 | id: Int 258 | timestamp: BigInt! 259 | name: String! 260 | } 261 | 262 | """ 263 | Represents an update to a `Migration`. Fields that are set will be updated. 264 | """ 265 | input MigrationPatch { 266 | id: Int 267 | timestamp: BigInt 268 | name: String 269 | } 270 | 271 | """A connection to a list of `Migration` values.""" 272 | type MigrationsConnection { 273 | """A list of `Migration` objects.""" 274 | nodes: [Migration]! 275 | 276 | """ 277 | A list of edges which contains the `Migration` and cursor to aid in pagination. 278 | """ 279 | edges: [MigrationsEdge!]! 280 | 281 | """Information to aid in pagination.""" 282 | pageInfo: PageInfo! 283 | 284 | """The count of *all* `Migration` you could get from the connection.""" 285 | totalCount: Int 286 | } 287 | 288 | """A `Migration` edge in the connection.""" 289 | type MigrationsEdge { 290 | """A cursor for use in pagination.""" 291 | cursor: Cursor 292 | 293 | """The `Migration` at the end of the edge.""" 294 | node: Migration 295 | } 296 | 297 | """Methods to use when ordering `Migration`.""" 298 | enum MigrationsOrderBy { 299 | NATURAL 300 | ID_ASC 301 | ID_DESC 302 | TIMESTAMP_ASC 303 | TIMESTAMP_DESC 304 | NAME_ASC 305 | NAME_DESC 306 | PRIMARY_KEY_ASC 307 | PRIMARY_KEY_DESC 308 | } 309 | 310 | """ 311 | The root mutation type which contains root level fields which mutate data. 312 | """ 313 | type Mutation { 314 | """Creates a single `Migration`.""" 315 | createMigration( 316 | """ 317 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 318 | """ 319 | input: CreateMigrationInput! 320 | ): CreateMigrationPayload 321 | 322 | """Creates a single `Photo`.""" 323 | createPhoto( 324 | """ 325 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 326 | """ 327 | input: CreatePhotoInput! 328 | ): CreatePhotoPayload 329 | 330 | """Creates a single `User`.""" 331 | createUser( 332 | """ 333 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 334 | """ 335 | input: CreateUserInput! 336 | ): CreateUserPayload 337 | 338 | """ 339 | Updates a single `Migration` using its globally unique id and a patch. 340 | """ 341 | updateMigration( 342 | """ 343 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 344 | """ 345 | input: UpdateMigrationInput! 346 | ): UpdateMigrationPayload 347 | 348 | """Updates a single `Migration` using a unique key and a patch.""" 349 | updateMigrationById( 350 | """ 351 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 352 | """ 353 | input: UpdateMigrationByIdInput! 354 | ): UpdateMigrationPayload 355 | 356 | """Updates a single `Photo` using its globally unique id and a patch.""" 357 | updatePhoto( 358 | """ 359 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 360 | """ 361 | input: UpdatePhotoInput! 362 | ): UpdatePhotoPayload 363 | 364 | """Updates a single `Photo` using a unique key and a patch.""" 365 | updatePhotoById( 366 | """ 367 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 368 | """ 369 | input: UpdatePhotoByIdInput! 370 | ): UpdatePhotoPayload 371 | 372 | """Updates a single `User` using its globally unique id and a patch.""" 373 | updateUser( 374 | """ 375 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 376 | """ 377 | input: UpdateUserInput! 378 | ): UpdateUserPayload 379 | 380 | """Updates a single `User` using a unique key and a patch.""" 381 | updateUserById( 382 | """ 383 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 384 | """ 385 | input: UpdateUserByIdInput! 386 | ): UpdateUserPayload 387 | 388 | """Deletes a single `Migration` using its globally unique id.""" 389 | deleteMigration( 390 | """ 391 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 392 | """ 393 | input: DeleteMigrationInput! 394 | ): DeleteMigrationPayload 395 | 396 | """Deletes a single `Migration` using a unique key.""" 397 | deleteMigrationById( 398 | """ 399 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 400 | """ 401 | input: DeleteMigrationByIdInput! 402 | ): DeleteMigrationPayload 403 | 404 | """Deletes a single `Photo` using its globally unique id.""" 405 | deletePhoto( 406 | """ 407 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 408 | """ 409 | input: DeletePhotoInput! 410 | ): DeletePhotoPayload 411 | 412 | """Deletes a single `Photo` using a unique key.""" 413 | deletePhotoById( 414 | """ 415 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 416 | """ 417 | input: DeletePhotoByIdInput! 418 | ): DeletePhotoPayload 419 | 420 | """Deletes a single `User` using its globally unique id.""" 421 | deleteUser( 422 | """ 423 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 424 | """ 425 | input: DeleteUserInput! 426 | ): DeleteUserPayload 427 | 428 | """Deletes a single `User` using a unique key.""" 429 | deleteUserById( 430 | """ 431 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 432 | """ 433 | input: DeleteUserByIdInput! 434 | ): DeleteUserPayload 435 | } 436 | 437 | """An object with a globally unique `ID`.""" 438 | interface Node { 439 | """ 440 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 441 | """ 442 | nodeId: ID! 443 | } 444 | 445 | """Information about pagination in a connection.""" 446 | type PageInfo { 447 | """When paginating forwards, are there more items?""" 448 | hasNextPage: Boolean! 449 | 450 | """When paginating backwards, are there more items?""" 451 | hasPreviousPage: Boolean! 452 | 453 | """When paginating backwards, the cursor to continue.""" 454 | startCursor: Cursor 455 | 456 | """When paginating forwards, the cursor to continue.""" 457 | endCursor: Cursor 458 | } 459 | 460 | type Photo implements Node { 461 | """ 462 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 463 | """ 464 | nodeId: ID! 465 | id: Int! 466 | name: String! 467 | description: String! 468 | filename: String! 469 | views: Float! 470 | isPublished: Boolean! 471 | } 472 | 473 | """ 474 | A condition to be used against `Photo` object types. All fields are tested for equality and combined with a logical ‘and.’ 475 | """ 476 | input PhotoCondition { 477 | """Checks for equality with the object’s `id` field.""" 478 | id: Int 479 | 480 | """Checks for equality with the object’s `name` field.""" 481 | name: String 482 | 483 | """Checks for equality with the object’s `description` field.""" 484 | description: String 485 | 486 | """Checks for equality with the object’s `filename` field.""" 487 | filename: String 488 | 489 | """Checks for equality with the object’s `views` field.""" 490 | views: Float 491 | 492 | """Checks for equality with the object’s `isPublished` field.""" 493 | isPublished: Boolean 494 | } 495 | 496 | """An input for mutations affecting `Photo`""" 497 | input PhotoInput { 498 | id: Int 499 | name: String! 500 | description: String! 501 | filename: String! 502 | views: Float! 503 | isPublished: Boolean! 504 | } 505 | 506 | """ 507 | Represents an update to a `Photo`. Fields that are set will be updated. 508 | """ 509 | input PhotoPatch { 510 | id: Int 511 | name: String 512 | description: String 513 | filename: String 514 | views: Float 515 | isPublished: Boolean 516 | } 517 | 518 | """A connection to a list of `Photo` values.""" 519 | type PhotosConnection { 520 | """A list of `Photo` objects.""" 521 | nodes: [Photo]! 522 | 523 | """ 524 | A list of edges which contains the `Photo` and cursor to aid in pagination. 525 | """ 526 | edges: [PhotosEdge!]! 527 | 528 | """Information to aid in pagination.""" 529 | pageInfo: PageInfo! 530 | 531 | """The count of *all* `Photo` you could get from the connection.""" 532 | totalCount: Int 533 | } 534 | 535 | """A `Photo` edge in the connection.""" 536 | type PhotosEdge { 537 | """A cursor for use in pagination.""" 538 | cursor: Cursor 539 | 540 | """The `Photo` at the end of the edge.""" 541 | node: Photo 542 | } 543 | 544 | """Methods to use when ordering `Photo`.""" 545 | enum PhotosOrderBy { 546 | NATURAL 547 | ID_ASC 548 | ID_DESC 549 | NAME_ASC 550 | NAME_DESC 551 | DESCRIPTION_ASC 552 | DESCRIPTION_DESC 553 | FILENAME_ASC 554 | FILENAME_DESC 555 | VIEWS_ASC 556 | VIEWS_DESC 557 | IS_PUBLISHED_ASC 558 | IS_PUBLISHED_DESC 559 | PRIMARY_KEY_ASC 560 | PRIMARY_KEY_DESC 561 | } 562 | 563 | """The root query type which gives access points into the data universe.""" 564 | type Query implements Node { 565 | """ 566 | Exposes the root query type nested one level down. This is helpful for Relay 1 567 | which can only query top level fields if they are in a particular form. 568 | """ 569 | query: Query! 570 | 571 | """ 572 | The root query type must be a `Node` to work well with Relay 1 mutations. This just resolves to `query`. 573 | """ 574 | nodeId: ID! 575 | 576 | """Fetches an object given its globally unique `ID`.""" 577 | node( 578 | """The globally unique `ID`.""" 579 | nodeId: ID! 580 | ): Node 581 | 582 | """Reads and enables pagination through a set of `Migration`.""" 583 | allMigrations( 584 | """Only read the first `n` values of the set.""" 585 | first: Int 586 | 587 | """Only read the last `n` values of the set.""" 588 | last: Int 589 | 590 | """ 591 | Skip the first `n` values from our `after` cursor, an alternative to cursor 592 | based pagination. May not be used with `last`. 593 | """ 594 | offset: Int 595 | 596 | """Read all values in the set before (above) this cursor.""" 597 | before: Cursor 598 | 599 | """Read all values in the set after (below) this cursor.""" 600 | after: Cursor 601 | 602 | """The method to use when ordering `Migration`.""" 603 | orderBy: [MigrationsOrderBy!] = [PRIMARY_KEY_ASC] 604 | 605 | """ 606 | A condition to be used in determining which values should be returned by the collection. 607 | """ 608 | condition: MigrationCondition 609 | ): MigrationsConnection 610 | 611 | """Reads and enables pagination through a set of `Photo`.""" 612 | allPhotos( 613 | """Only read the first `n` values of the set.""" 614 | first: Int 615 | 616 | """Only read the last `n` values of the set.""" 617 | last: Int 618 | 619 | """ 620 | Skip the first `n` values from our `after` cursor, an alternative to cursor 621 | based pagination. May not be used with `last`. 622 | """ 623 | offset: Int 624 | 625 | """Read all values in the set before (above) this cursor.""" 626 | before: Cursor 627 | 628 | """Read all values in the set after (below) this cursor.""" 629 | after: Cursor 630 | 631 | """The method to use when ordering `Photo`.""" 632 | orderBy: [PhotosOrderBy!] = [PRIMARY_KEY_ASC] 633 | 634 | """ 635 | A condition to be used in determining which values should be returned by the collection. 636 | """ 637 | condition: PhotoCondition 638 | ): PhotosConnection 639 | 640 | """Reads and enables pagination through a set of `User`.""" 641 | allUsers( 642 | """Only read the first `n` values of the set.""" 643 | first: Int 644 | 645 | """Only read the last `n` values of the set.""" 646 | last: Int 647 | 648 | """ 649 | Skip the first `n` values from our `after` cursor, an alternative to cursor 650 | based pagination. May not be used with `last`. 651 | """ 652 | offset: Int 653 | 654 | """Read all values in the set before (above) this cursor.""" 655 | before: Cursor 656 | 657 | """Read all values in the set after (below) this cursor.""" 658 | after: Cursor 659 | 660 | """The method to use when ordering `User`.""" 661 | orderBy: [UsersOrderBy!] = [PRIMARY_KEY_ASC] 662 | 663 | """ 664 | A condition to be used in determining which values should be returned by the collection. 665 | """ 666 | condition: UserCondition 667 | ): UsersConnection 668 | migrationById(id: Int!): Migration 669 | photoById(id: Int!): Photo 670 | userById(id: Int!): User 671 | 672 | """Reads a single `Migration` using its globally unique `ID`.""" 673 | migration( 674 | """ 675 | The globally unique `ID` to be used in selecting a single `Migration`. 676 | """ 677 | nodeId: ID! 678 | ): Migration 679 | 680 | """Reads a single `Photo` using its globally unique `ID`.""" 681 | photo( 682 | """The globally unique `ID` to be used in selecting a single `Photo`.""" 683 | nodeId: ID! 684 | ): Photo 685 | 686 | """Reads a single `User` using its globally unique `ID`.""" 687 | user( 688 | """The globally unique `ID` to be used in selecting a single `User`.""" 689 | nodeId: ID! 690 | ): User 691 | } 692 | 693 | """All input for the `updateMigrationById` mutation.""" 694 | input UpdateMigrationByIdInput { 695 | """ 696 | An arbitrary string value with no semantic meaning. Will be included in the 697 | payload verbatim. May be used to track mutations by the client. 698 | """ 699 | clientMutationId: String 700 | 701 | """ 702 | An object where the defined keys will be set on the `Migration` being updated. 703 | """ 704 | migrationPatch: MigrationPatch! 705 | id: Int! 706 | } 707 | 708 | """All input for the `updateMigration` mutation.""" 709 | input UpdateMigrationInput { 710 | """ 711 | An arbitrary string value with no semantic meaning. Will be included in the 712 | payload verbatim. May be used to track mutations by the client. 713 | """ 714 | clientMutationId: String 715 | 716 | """ 717 | The globally unique `ID` which will identify a single `Migration` to be updated. 718 | """ 719 | nodeId: ID! 720 | 721 | """ 722 | An object where the defined keys will be set on the `Migration` being updated. 723 | """ 724 | migrationPatch: MigrationPatch! 725 | } 726 | 727 | """The output of our update `Migration` mutation.""" 728 | type UpdateMigrationPayload { 729 | """ 730 | The exact same `clientMutationId` that was provided in the mutation input, 731 | unchanged and unused. May be used by a client to track mutations. 732 | """ 733 | clientMutationId: String 734 | 735 | """The `Migration` that was updated by this mutation.""" 736 | migration: Migration 737 | 738 | """An edge for our `Migration`. May be used by Relay 1.""" 739 | migrationEdge( 740 | """The method to use when ordering `Migration`.""" 741 | orderBy: [MigrationsOrderBy!] = PRIMARY_KEY_ASC 742 | ): MigrationsEdge 743 | } 744 | 745 | """All input for the `updatePhotoById` mutation.""" 746 | input UpdatePhotoByIdInput { 747 | """ 748 | An arbitrary string value with no semantic meaning. Will be included in the 749 | payload verbatim. May be used to track mutations by the client. 750 | """ 751 | clientMutationId: String 752 | 753 | """ 754 | An object where the defined keys will be set on the `Photo` being updated. 755 | """ 756 | photoPatch: PhotoPatch! 757 | id: Int! 758 | } 759 | 760 | """All input for the `updatePhoto` mutation.""" 761 | input UpdatePhotoInput { 762 | """ 763 | An arbitrary string value with no semantic meaning. Will be included in the 764 | payload verbatim. May be used to track mutations by the client. 765 | """ 766 | clientMutationId: String 767 | 768 | """ 769 | The globally unique `ID` which will identify a single `Photo` to be updated. 770 | """ 771 | nodeId: ID! 772 | 773 | """ 774 | An object where the defined keys will be set on the `Photo` being updated. 775 | """ 776 | photoPatch: PhotoPatch! 777 | } 778 | 779 | """The output of our update `Photo` mutation.""" 780 | type UpdatePhotoPayload { 781 | """ 782 | The exact same `clientMutationId` that was provided in the mutation input, 783 | unchanged and unused. May be used by a client to track mutations. 784 | """ 785 | clientMutationId: String 786 | 787 | """The `Photo` that was updated by this mutation.""" 788 | photo: Photo 789 | 790 | """An edge for our `Photo`. May be used by Relay 1.""" 791 | photoEdge( 792 | """The method to use when ordering `Photo`.""" 793 | orderBy: [PhotosOrderBy!] = PRIMARY_KEY_ASC 794 | ): PhotosEdge 795 | } 796 | 797 | """All input for the `updateUserById` mutation.""" 798 | input UpdateUserByIdInput { 799 | """ 800 | An arbitrary string value with no semantic meaning. Will be included in the 801 | payload verbatim. May be used to track mutations by the client. 802 | """ 803 | clientMutationId: String 804 | 805 | """ 806 | An object where the defined keys will be set on the `User` being updated. 807 | """ 808 | userPatch: UserPatch! 809 | id: Int! 810 | } 811 | 812 | """All input for the `updateUser` mutation.""" 813 | input UpdateUserInput { 814 | """ 815 | An arbitrary string value with no semantic meaning. Will be included in the 816 | payload verbatim. May be used to track mutations by the client. 817 | """ 818 | clientMutationId: String 819 | 820 | """ 821 | The globally unique `ID` which will identify a single `User` to be updated. 822 | """ 823 | nodeId: ID! 824 | 825 | """ 826 | An object where the defined keys will be set on the `User` being updated. 827 | """ 828 | userPatch: UserPatch! 829 | } 830 | 831 | """The output of our update `User` mutation.""" 832 | type UpdateUserPayload { 833 | """ 834 | The exact same `clientMutationId` that was provided in the mutation input, 835 | unchanged and unused. May be used by a client to track mutations. 836 | """ 837 | clientMutationId: String 838 | 839 | """The `User` that was updated by this mutation.""" 840 | user: User 841 | 842 | """An edge for our `User`. May be used by Relay 1.""" 843 | userEdge( 844 | """The method to use when ordering `User`.""" 845 | orderBy: [UsersOrderBy!] = PRIMARY_KEY_ASC 846 | ): UsersEdge 847 | } 848 | 849 | type User implements Node { 850 | """ 851 | A globally unique identifier. Can be used in various places throughout the system to identify this single value. 852 | """ 853 | nodeId: ID! 854 | id: Int! 855 | name: String! 856 | email: String! 857 | password: String! 858 | } 859 | 860 | """ 861 | A condition to be used against `User` object types. All fields are tested for equality and combined with a logical ‘and.’ 862 | """ 863 | input UserCondition { 864 | """Checks for equality with the object’s `id` field.""" 865 | id: Int 866 | 867 | """Checks for equality with the object’s `name` field.""" 868 | name: String 869 | 870 | """Checks for equality with the object’s `email` field.""" 871 | email: String 872 | 873 | """Checks for equality with the object’s `password` field.""" 874 | password: String 875 | } 876 | 877 | """An input for mutations affecting `User`""" 878 | input UserInput { 879 | id: Int 880 | name: String! 881 | email: String! 882 | password: String! 883 | } 884 | 885 | """ 886 | Represents an update to a `User`. Fields that are set will be updated. 887 | """ 888 | input UserPatch { 889 | id: Int 890 | name: String 891 | email: String 892 | password: String 893 | } 894 | 895 | """A connection to a list of `User` values.""" 896 | type UsersConnection { 897 | """A list of `User` objects.""" 898 | nodes: [User]! 899 | 900 | """ 901 | A list of edges which contains the `User` and cursor to aid in pagination. 902 | """ 903 | edges: [UsersEdge!]! 904 | 905 | """Information to aid in pagination.""" 906 | pageInfo: PageInfo! 907 | 908 | """The count of *all* `User` you could get from the connection.""" 909 | totalCount: Int 910 | } 911 | 912 | """A `User` edge in the connection.""" 913 | type UsersEdge { 914 | """A cursor for use in pagination.""" 915 | cursor: Cursor 916 | 917 | """The `User` at the end of the edge.""" 918 | node: User 919 | } 920 | 921 | """Methods to use when ordering `User`.""" 922 | enum UsersOrderBy { 923 | NATURAL 924 | ID_ASC 925 | ID_DESC 926 | NAME_ASC 927 | NAME_DESC 928 | EMAIL_ASC 929 | EMAIL_DESC 930 | PASSWORD_ASC 931 | PASSWORD_DESC 932 | PRIMARY_KEY_ASC 933 | PRIMARY_KEY_DESC 934 | } 935 | -------------------------------------------------------------------------------- /src/generated/postgraphile.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { makeBindingClass, Options } from 'graphql-binding' 3 | import { GraphQLResolveInfo, GraphQLSchema } from 'graphql' 4 | import { IResolvers } from 'graphql-tools/dist/Interfaces' 5 | import schema from '../embeddedPostgraphile/index' 6 | 7 | export interface Query { 8 | query: (args?: {}, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 9 | nodeId: (args?: {}, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 10 | node: (args: { nodeId: ID_Output }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 11 | allMigrations: (args: { first?: Int, last?: Int, offset?: Int, before?: Cursor, after?: Cursor, orderBy?: MigrationsOrderBy[], condition?: MigrationCondition }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 12 | allPhotos: (args: { first?: Int, last?: Int, offset?: Int, before?: Cursor, after?: Cursor, orderBy?: PhotosOrderBy[], condition?: PhotoCondition }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 13 | allUsers: (args: { first?: Int, last?: Int, offset?: Int, before?: Cursor, after?: Cursor, orderBy?: UsersOrderBy[], condition?: UserCondition }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 14 | migrationById: (args: { id: Int }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 15 | photoById: (args: { id: Int }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 16 | userById: (args: { id: Int }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 17 | migration: (args: { nodeId: ID_Output }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 18 | photo: (args: { nodeId: ID_Output }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 19 | user: (args: { nodeId: ID_Output }, info?: GraphQLResolveInfo | string, options?: Options) => Promise 20 | } 21 | 22 | export interface Mutation { 23 | createMigration: (args: { input: CreateMigrationInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 24 | createPhoto: (args: { input: CreatePhotoInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 25 | createUser: (args: { input: CreateUserInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 26 | updateMigration: (args: { input: UpdateMigrationInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 27 | updateMigrationById: (args: { input: UpdateMigrationByIdInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 28 | updatePhoto: (args: { input: UpdatePhotoInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 29 | updatePhotoById: (args: { input: UpdatePhotoByIdInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 30 | updateUser: (args: { input: UpdateUserInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 31 | updateUserById: (args: { input: UpdateUserByIdInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 32 | deleteMigration: (args: { input: DeleteMigrationInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 33 | deleteMigrationById: (args: { input: DeleteMigrationByIdInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 34 | deletePhoto: (args: { input: DeletePhotoInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 35 | deletePhotoById: (args: { input: DeletePhotoByIdInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 36 | deleteUser: (args: { input: DeleteUserInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise , 37 | deleteUserById: (args: { input: DeleteUserByIdInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise 38 | } 39 | 40 | export interface Subscription {} 41 | 42 | export interface Binding { 43 | query: Query 44 | mutation: Mutation 45 | subscription: Subscription 46 | request: (query: string, variables?: {[key: string]: any}) => Promise 47 | delegate(operation: 'query' | 'mutation', fieldName: string, args: { 48 | [key: string]: any; 49 | }, infoOrQuery?: GraphQLResolveInfo | string, options?: Options): Promise; 50 | delegateSubscription(fieldName: string, args?: { 51 | [key: string]: any; 52 | }, infoOrQuery?: GraphQLResolveInfo | string, options?: Options): Promise>; 53 | getAbstractResolvers(filterSchema?: GraphQLSchema | string): IResolvers; 54 | } 55 | 56 | export interface BindingConstructor { 57 | new(...args: any[]): T 58 | } 59 | 60 | export const Binding = makeBindingClass>({ schema }) 61 | 62 | /** 63 | * Types 64 | */ 65 | 66 | /* 67 | * Methods to use when ordering `Migration`. 68 | 69 | */ 70 | export type MigrationsOrderBy = 'NATURAL' | 71 | 'ID_ASC' | 72 | 'ID_DESC' | 73 | 'TIMESTAMP_ASC' | 74 | 'TIMESTAMP_DESC' | 75 | 'NAME_ASC' | 76 | 'NAME_DESC' | 77 | 'PRIMARY_KEY_ASC' | 78 | 'PRIMARY_KEY_DESC' 79 | 80 | /* 81 | * Methods to use when ordering `Photo`. 82 | 83 | */ 84 | export type PhotosOrderBy = 'NATURAL' | 85 | 'ID_ASC' | 86 | 'ID_DESC' | 87 | 'NAME_ASC' | 88 | 'NAME_DESC' | 89 | 'DESCRIPTION_ASC' | 90 | 'DESCRIPTION_DESC' | 91 | 'FILENAME_ASC' | 92 | 'FILENAME_DESC' | 93 | 'VIEWS_ASC' | 94 | 'VIEWS_DESC' | 95 | 'IS_PUBLISHED_ASC' | 96 | 'IS_PUBLISHED_DESC' | 97 | 'PRIMARY_KEY_ASC' | 98 | 'PRIMARY_KEY_DESC' 99 | 100 | /* 101 | * Methods to use when ordering `User`. 102 | 103 | */ 104 | export type UsersOrderBy = 'NATURAL' | 105 | 'ID_ASC' | 106 | 'ID_DESC' | 107 | 'NAME_ASC' | 108 | 'NAME_DESC' | 109 | 'EMAIL_ASC' | 110 | 'EMAIL_DESC' | 111 | 'PASSWORD_ASC' | 112 | 'PASSWORD_DESC' | 113 | 'PRIMARY_KEY_ASC' | 114 | 'PRIMARY_KEY_DESC' 115 | 116 | /* 117 | * All input for the create `Photo` mutation. 118 | 119 | */ 120 | export interface CreatePhotoInput { 121 | clientMutationId?: String 122 | photo: PhotoInput 123 | } 124 | 125 | /* 126 | * All input for the `deleteUserById` mutation. 127 | 128 | */ 129 | export interface DeleteUserByIdInput { 130 | clientMutationId?: String 131 | id: Int 132 | } 133 | 134 | /* 135 | * Represents an update to a `Photo`. Fields that are set will be updated. 136 | 137 | */ 138 | export interface PhotoPatch { 139 | id?: Int 140 | name?: String 141 | description?: String 142 | filename?: String 143 | views?: Float 144 | isPublished?: Boolean 145 | } 146 | 147 | /* 148 | * An input for mutations affecting `Photo` 149 | 150 | */ 151 | export interface PhotoInput { 152 | id?: Int 153 | name: String 154 | description: String 155 | filename: String 156 | views: Float 157 | isPublished: Boolean 158 | } 159 | 160 | /* 161 | * All input for the `updatePhoto` mutation. 162 | 163 | */ 164 | export interface UpdatePhotoInput { 165 | clientMutationId?: String 166 | nodeId: ID_Input 167 | photoPatch: PhotoPatch 168 | } 169 | 170 | /* 171 | * A condition to be used against `Migration` object types. All fields are tested 172 | , * for equality and combined with a logical ‘and.’ 173 | 174 | */ 175 | export interface MigrationCondition { 176 | id?: Int 177 | timestamp?: BigInt 178 | name?: String 179 | } 180 | 181 | /* 182 | * All input for the `updateMigrationById` mutation. 183 | 184 | */ 185 | export interface UpdateMigrationByIdInput { 186 | clientMutationId?: String 187 | migrationPatch: MigrationPatch 188 | id: Int 189 | } 190 | 191 | /* 192 | * All input for the `deletePhotoById` mutation. 193 | 194 | */ 195 | export interface DeletePhotoByIdInput { 196 | clientMutationId?: String 197 | id: Int 198 | } 199 | 200 | /* 201 | * A condition to be used against `User` object types. All fields are tested for equality and combined with a logical ‘and.’ 202 | 203 | */ 204 | export interface UserCondition { 205 | id?: Int 206 | name?: String 207 | email?: String 208 | password?: String 209 | } 210 | 211 | /* 212 | * All input for the `deleteMigrationById` mutation. 213 | 214 | */ 215 | export interface DeleteMigrationByIdInput { 216 | clientMutationId?: String 217 | id: Int 218 | } 219 | 220 | /* 221 | * Represents an update to a `Migration`. Fields that are set will be updated. 222 | 223 | */ 224 | export interface MigrationPatch { 225 | id?: Int 226 | timestamp?: BigInt 227 | name?: String 228 | } 229 | 230 | /* 231 | * All input for the `updateUserById` mutation. 232 | 233 | */ 234 | export interface UpdateUserByIdInput { 235 | clientMutationId?: String 236 | userPatch: UserPatch 237 | id: Int 238 | } 239 | 240 | /* 241 | * All input for the `updateMigration` mutation. 242 | 243 | */ 244 | export interface UpdateMigrationInput { 245 | clientMutationId?: String 246 | nodeId: ID_Input 247 | migrationPatch: MigrationPatch 248 | } 249 | 250 | /* 251 | * A condition to be used against `Photo` object types. All fields are tested for equality and combined with a logical ‘and.’ 252 | 253 | */ 254 | export interface PhotoCondition { 255 | id?: Int 256 | name?: String 257 | description?: String 258 | filename?: String 259 | views?: Float 260 | isPublished?: Boolean 261 | } 262 | 263 | /* 264 | * All input for the `updatePhotoById` mutation. 265 | 266 | */ 267 | export interface UpdatePhotoByIdInput { 268 | clientMutationId?: String 269 | photoPatch: PhotoPatch 270 | id: Int 271 | } 272 | 273 | /* 274 | * All input for the create `User` mutation. 275 | 276 | */ 277 | export interface CreateUserInput { 278 | clientMutationId?: String 279 | user: UserInput 280 | } 281 | 282 | /* 283 | * An input for mutations affecting `Migration` 284 | 285 | */ 286 | export interface MigrationInput { 287 | id?: Int 288 | timestamp: BigInt 289 | name: String 290 | } 291 | 292 | /* 293 | * All input for the create `Migration` mutation. 294 | 295 | */ 296 | export interface CreateMigrationInput { 297 | clientMutationId?: String 298 | migration: MigrationInput 299 | } 300 | 301 | /* 302 | * An input for mutations affecting `User` 303 | 304 | */ 305 | export interface UserInput { 306 | id?: Int 307 | name: String 308 | email: String 309 | password: String 310 | } 311 | 312 | /* 313 | * All input for the `deleteUser` mutation. 314 | 315 | */ 316 | export interface DeleteUserInput { 317 | clientMutationId?: String 318 | nodeId: ID_Input 319 | } 320 | 321 | /* 322 | * All input for the `updateUser` mutation. 323 | 324 | */ 325 | export interface UpdateUserInput { 326 | clientMutationId?: String 327 | nodeId: ID_Input 328 | userPatch: UserPatch 329 | } 330 | 331 | /* 332 | * Represents an update to a `User`. Fields that are set will be updated. 333 | 334 | */ 335 | export interface UserPatch { 336 | id?: Int 337 | name?: String 338 | email?: String 339 | password?: String 340 | } 341 | 342 | /* 343 | * All input for the `deleteMigration` mutation. 344 | 345 | */ 346 | export interface DeleteMigrationInput { 347 | clientMutationId?: String 348 | nodeId: ID_Input 349 | } 350 | 351 | /* 352 | * All input for the `deletePhoto` mutation. 353 | 354 | */ 355 | export interface DeletePhotoInput { 356 | clientMutationId?: String 357 | nodeId: ID_Input 358 | } 359 | 360 | /* 361 | * An object with a globally unique `ID`. 362 | 363 | */ 364 | export interface Node { 365 | nodeId: ID_Output 366 | } 367 | 368 | export interface Photo extends Node { 369 | nodeId: ID_Output 370 | id: Int 371 | name: String 372 | description: String 373 | filename: String 374 | views: Float 375 | isPublished: Boolean 376 | } 377 | 378 | /* 379 | * A `Migration` edge in the connection. 380 | 381 | */ 382 | export interface MigrationsEdge { 383 | cursor?: Cursor 384 | node?: Migration 385 | } 386 | 387 | /* 388 | * The output of our update `Photo` mutation. 389 | 390 | */ 391 | export interface UpdatePhotoPayload { 392 | clientMutationId?: String 393 | photo?: Photo 394 | photoEdge?: PhotosEdge 395 | } 396 | 397 | /* 398 | * The output of our create `Photo` mutation. 399 | 400 | */ 401 | export interface CreatePhotoPayload { 402 | clientMutationId?: String 403 | photo?: Photo 404 | photoEdge?: PhotosEdge 405 | } 406 | 407 | /* 408 | * A connection to a list of `Photo` values. 409 | 410 | */ 411 | export interface PhotosConnection { 412 | nodes: Photo[] 413 | edges: PhotosEdge[] 414 | pageInfo: PageInfo 415 | totalCount?: Int 416 | } 417 | 418 | /* 419 | * A `User` edge in the connection. 420 | 421 | */ 422 | export interface UsersEdge { 423 | cursor?: Cursor 424 | node?: User 425 | } 426 | 427 | /* 428 | * A connection to a list of `Migration` values. 429 | 430 | */ 431 | export interface MigrationsConnection { 432 | nodes: Migration[] 433 | edges: MigrationsEdge[] 434 | pageInfo: PageInfo 435 | totalCount?: Int 436 | } 437 | 438 | export interface User extends Node { 439 | nodeId: ID_Output 440 | id: Int 441 | name: String 442 | email: String 443 | password: String 444 | } 445 | 446 | /* 447 | * The output of our delete `Photo` mutation. 448 | 449 | */ 450 | export interface DeletePhotoPayload { 451 | clientMutationId?: String 452 | photo?: Photo 453 | deletedPhotoId?: ID_Output 454 | photoEdge?: PhotosEdge 455 | } 456 | 457 | /* 458 | * The output of our update `Migration` mutation. 459 | 460 | */ 461 | export interface UpdateMigrationPayload { 462 | clientMutationId?: String 463 | migration?: Migration 464 | migrationEdge?: MigrationsEdge 465 | } 466 | 467 | /* 468 | * A `Photo` edge in the connection. 469 | 470 | */ 471 | export interface PhotosEdge { 472 | cursor?: Cursor 473 | node?: Photo 474 | } 475 | 476 | /* 477 | * The output of our delete `Migration` mutation. 478 | 479 | */ 480 | export interface DeleteMigrationPayload { 481 | clientMutationId?: String 482 | migration?: Migration 483 | deletedMigrationId?: ID_Output 484 | migrationEdge?: MigrationsEdge 485 | } 486 | 487 | /* 488 | * Information about pagination in a connection. 489 | 490 | */ 491 | export interface PageInfo { 492 | hasNextPage: Boolean 493 | hasPreviousPage: Boolean 494 | startCursor?: Cursor 495 | endCursor?: Cursor 496 | } 497 | 498 | export interface Migration extends Node { 499 | nodeId: ID_Output 500 | id: Int 501 | timestamp: BigInt 502 | name: String 503 | } 504 | 505 | /* 506 | * The output of our update `User` mutation. 507 | 508 | */ 509 | export interface UpdateUserPayload { 510 | clientMutationId?: String 511 | user?: User 512 | userEdge?: UsersEdge 513 | } 514 | 515 | /* 516 | * The output of our create `Migration` mutation. 517 | 518 | */ 519 | export interface CreateMigrationPayload { 520 | clientMutationId?: String 521 | migration?: Migration 522 | migrationEdge?: MigrationsEdge 523 | } 524 | 525 | /* 526 | * The output of our delete `User` mutation. 527 | 528 | */ 529 | export interface DeleteUserPayload { 530 | clientMutationId?: String 531 | user?: User 532 | deletedUserId?: ID_Output 533 | userEdge?: UsersEdge 534 | } 535 | 536 | /* 537 | * A connection to a list of `User` values. 538 | 539 | */ 540 | export interface UsersConnection { 541 | nodes: User[] 542 | edges: UsersEdge[] 543 | pageInfo: PageInfo 544 | totalCount?: Int 545 | } 546 | 547 | /* 548 | * The output of our create `User` mutation. 549 | 550 | */ 551 | export interface CreateUserPayload { 552 | clientMutationId?: String 553 | user?: User 554 | userEdge?: UsersEdge 555 | } 556 | 557 | /* 558 | The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). 559 | */ 560 | export type Float = number 561 | 562 | /* 563 | The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID. 564 | */ 565 | export type ID_Input = string | number 566 | export type ID_Output = string 567 | 568 | /* 569 | The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. 570 | */ 571 | export type Int = number 572 | 573 | /* 574 | The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text. 575 | */ 576 | export type String = string 577 | 578 | /* 579 | A signed eight-byte integer. The upper big integer values are greater then the 580 | max value for a JavaScript number. Therefore all big integers will be output as 581 | strings and not numbers. 582 | */ 583 | export type BigInt = string 584 | 585 | /* 586 | A location in a connection that can be used for resuming pagination. 587 | */ 588 | export type Cursor = string 589 | 590 | /* 591 | The `Boolean` scalar type represents `true` or `false`. 592 | */ 593 | export type Boolean = boolean -------------------------------------------------------------------------------- /src/generated/types.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { GraphQLResolveInfo } from "graphql"; 3 | 4 | export type Resolver = ( 5 | parent?: Parent, 6 | args?: Args, 7 | context?: Context, 8 | info?: GraphQLResolveInfo 9 | ) => Promise | Result; 10 | 11 | export type SubscriptionResolver< 12 | Result, 13 | Parent = any, 14 | Context = any, 15 | Args = any 16 | > = { 17 | subscribe( 18 | parent?: P, 19 | args?: Args, 20 | context?: Context, 21 | info?: GraphQLResolveInfo 22 | ): AsyncIterator; 23 | resolve?( 24 | parent?: P, 25 | args?: Args, 26 | context?: Context, 27 | info?: GraphQLResolveInfo 28 | ): R | Result | Promise; 29 | }; 30 | 31 | /** A location in a connection that can be used for resuming pagination. */ 32 | export type Cursor = any; 33 | 34 | /** A signed eight-byte integer. The upper big integer values are greater then themax value for a JavaScript number. Therefore all big integers will be output asstrings and not numbers. */ 35 | export type BigInt = any; 36 | /** An object with a globally unique `ID`. */ 37 | export interface Node { 38 | nodeId: string /** A globally unique identifier. Can be used in various places throughout the system to identify this single value. */; 39 | } 40 | 41 | export interface Query { 42 | me?: User | null; 43 | allPhotos?: PhotosConnection | null /** Reads and enables pagination through a set of `Photo`. */; 44 | } 45 | 46 | export interface User { 47 | id: string; 48 | email: string; 49 | name: string; 50 | } 51 | /** A connection to a list of `Photo` values. */ 52 | export interface PhotosConnection { 53 | nodes: (Photo | null)[] /** A list of `Photo` objects. */; 54 | edges: PhotosEdge[] /** A list of edges which contains the `Photo` and cursor to aid in pagination. */; 55 | pageInfo: PageInfo /** Information to aid in pagination. */; 56 | totalCount?: 57 | | number 58 | | null /** The count of *all* `Photo` you could get from the connection. */; 59 | } 60 | 61 | export interface Photo extends Node { 62 | nodeId: string /** A globally unique identifier. Can be used in various places throughout the system to identify this single value. */; 63 | id: number; 64 | name: string; 65 | description: string; 66 | filename: string; 67 | views: number; 68 | isPublished: boolean; 69 | } 70 | /** A `Photo` edge in the connection. */ 71 | export interface PhotosEdge { 72 | cursor?: Cursor | null /** A cursor for use in pagination. */; 73 | node?: Photo | null /** The `Photo` at the end of the edge. */; 74 | } 75 | /** Information about pagination in a connection. */ 76 | export interface PageInfo { 77 | hasNextPage: boolean /** When paginating forwards, are there more items? */; 78 | hasPreviousPage: boolean /** When paginating backwards, are there more items? */; 79 | startCursor?: Cursor | null /** When paginating backwards, the cursor to continue. */; 80 | endCursor?: Cursor | null /** When paginating forwards, the cursor to continue. */; 81 | } 82 | 83 | export interface Mutation { 84 | signup: AuthPayload; 85 | login: AuthPayload; 86 | createPhoto?: CreatePhotoPayload | null /** Creates a single `Photo`. */; 87 | deletePhoto?: DeletePhotoPayload | null /** Deletes a single `Photo` using its globally unique id. */; 88 | } 89 | 90 | export interface AuthPayload { 91 | token: string; 92 | user: User; 93 | } 94 | /** The output of our create `Photo` mutation. */ 95 | export interface CreatePhotoPayload { 96 | clientMutationId?: 97 | | string 98 | | null /** The exact same `clientMutationId` that was provided in the mutation input,unchanged and unused. May be used by a client to track mutations. */; 99 | photo?: Photo | null /** The `Photo` that was created by this mutation. */; 100 | photoEdge?: PhotosEdge | null /** An edge for our `Photo`. May be used by Relay 1. */; 101 | } 102 | /** The output of our delete `Photo` mutation. */ 103 | export interface DeletePhotoPayload { 104 | clientMutationId?: 105 | | string 106 | | null /** The exact same `clientMutationId` that was provided in the mutation input,unchanged and unused. May be used by a client to track mutations. */; 107 | photo?: Photo | null /** The `Photo` that was deleted by this mutation. */; 108 | deletedPhotoId?: string | null; 109 | photoEdge?: PhotosEdge | null /** An edge for our `Photo`. May be used by Relay 1. */; 110 | } 111 | 112 | export interface Migration extends Node { 113 | nodeId: string /** A globally unique identifier. Can be used in various places throughout the system to identify this single value. */; 114 | id: number; 115 | timestamp: BigInt; 116 | name: string; 117 | } 118 | /** A condition to be used against `Photo` object types. All fields are tested for equality and combined with a logical ‘and.’ */ 119 | export interface PhotoCondition { 120 | id?: number | null /** Checks for equality with the object’s `id` field. */; 121 | name?: 122 | | string 123 | | null /** Checks for equality with the object’s `name` field. */; 124 | description?: 125 | | string 126 | | null /** Checks for equality with the object’s `description` field. */; 127 | filename?: 128 | | string 129 | | null /** Checks for equality with the object’s `filename` field. */; 130 | views?: 131 | | number 132 | | null /** Checks for equality with the object’s `views` field. */; 133 | isPublished?: 134 | | boolean 135 | | null /** Checks for equality with the object’s `isPublished` field. */; 136 | } 137 | /** All input for the create `Photo` mutation. */ 138 | export interface CreatePhotoInput { 139 | clientMutationId?: 140 | | string 141 | | null /** An arbitrary string value with no semantic meaning. Will be included in thepayload verbatim. May be used to track mutations by the client. */; 142 | photo: PhotoInput /** The `Photo` to be created by this mutation. */; 143 | } 144 | /** An input for mutations affecting `Photo` */ 145 | export interface PhotoInput { 146 | id?: number | null; 147 | name: string; 148 | description: string; 149 | filename: string; 150 | views: number; 151 | isPublished: boolean; 152 | } 153 | /** All input for the `deletePhoto` mutation. */ 154 | export interface DeletePhotoInput { 155 | clientMutationId?: 156 | | string 157 | | null /** An arbitrary string value with no semantic meaning. Will be included in thepayload verbatim. May be used to track mutations by the client. */; 158 | nodeId: string /** The globally unique `ID` which will identify a single `Photo` to be deleted. */; 159 | } 160 | export interface AllPhotosQueryArgs { 161 | first?: number | null /** Only read the first `n` values of the set. */; 162 | last?: number | null /** Only read the last `n` values of the set. */; 163 | offset?: 164 | | number 165 | | null /** Skip the first `n` values from our `after` cursor, an alternative to cursorbased pagination. May not be used with `last`. */; 166 | before?: Cursor | null /** Read all values in the set before (above) this cursor. */; 167 | after?: Cursor | null /** Read all values in the set after (below) this cursor. */; 168 | orderBy?: 169 | | PhotosOrderBy[] 170 | | null /** The method to use when ordering `Photo`. */; 171 | condition?: PhotoCondition | null /** A condition to be used in determining which values should be returned by the collection. */; 172 | } 173 | export interface SignupMutationArgs { 174 | email: string; 175 | password: string; 176 | name: string; 177 | } 178 | export interface LoginMutationArgs { 179 | email: string; 180 | password: string; 181 | } 182 | export interface CreatePhotoMutationArgs { 183 | input: CreatePhotoInput /** The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. */; 184 | } 185 | export interface DeletePhotoMutationArgs { 186 | input: DeletePhotoInput /** The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. */; 187 | } 188 | export interface PhotoEdgeCreatePhotoPayloadArgs { 189 | orderBy?: 190 | | PhotosOrderBy[] 191 | | null /** The method to use when ordering `Photo`. */; 192 | } 193 | export interface PhotoEdgeDeletePhotoPayloadArgs { 194 | orderBy?: 195 | | PhotosOrderBy[] 196 | | null /** The method to use when ordering `Photo`. */; 197 | } 198 | /** Methods to use when ordering `Photo`. */ 199 | export type PhotosOrderBy = 200 | | "NATURAL" 201 | | "ID_ASC" 202 | | "ID_DESC" 203 | | "NAME_ASC" 204 | | "NAME_DESC" 205 | | "DESCRIPTION_ASC" 206 | | "DESCRIPTION_DESC" 207 | | "FILENAME_ASC" 208 | | "FILENAME_DESC" 209 | | "VIEWS_ASC" 210 | | "VIEWS_DESC" 211 | | "IS_PUBLISHED_ASC" 212 | | "IS_PUBLISHED_DESC" 213 | | "PRIMARY_KEY_ASC" 214 | | "PRIMARY_KEY_DESC"; 215 | 216 | export namespace QueryResolvers { 217 | export interface Resolvers { 218 | me?: MeResolver; 219 | allPhotos?: AllPhotosResolver< 220 | PhotosConnection | null, 221 | any, 222 | Context 223 | > /** Reads and enables pagination through a set of `Photo`. */; 224 | } 225 | 226 | export type MeResolver< 227 | R = User | null, 228 | Parent = any, 229 | Context = any 230 | > = Resolver; 231 | export type AllPhotosResolver< 232 | R = PhotosConnection | null, 233 | Parent = any, 234 | Context = any 235 | > = Resolver; 236 | export interface AllPhotosArgs { 237 | first?: number | null /** Only read the first `n` values of the set. */; 238 | last?: number | null /** Only read the last `n` values of the set. */; 239 | offset?: 240 | | number 241 | | null /** Skip the first `n` values from our `after` cursor, an alternative to cursorbased pagination. May not be used with `last`. */; 242 | before?: Cursor | null /** Read all values in the set before (above) this cursor. */; 243 | after?: Cursor | null /** Read all values in the set after (below) this cursor. */; 244 | orderBy?: 245 | | PhotosOrderBy[] 246 | | null /** The method to use when ordering `Photo`. */; 247 | condition?: PhotoCondition | null /** A condition to be used in determining which values should be returned by the collection. */; 248 | } 249 | } 250 | 251 | export namespace UserResolvers { 252 | export interface Resolvers { 253 | id?: IdResolver; 254 | email?: EmailResolver; 255 | name?: NameResolver; 256 | } 257 | 258 | export type IdResolver = Resolver< 259 | R, 260 | Parent, 261 | Context 262 | >; 263 | export type EmailResolver = Resolver< 264 | R, 265 | Parent, 266 | Context 267 | >; 268 | export type NameResolver = Resolver< 269 | R, 270 | Parent, 271 | Context 272 | >; 273 | } 274 | /** A connection to a list of `Photo` values. */ 275 | export namespace PhotosConnectionResolvers { 276 | export interface Resolvers { 277 | nodes?: NodesResolver< 278 | (Photo | null)[], 279 | any, 280 | Context 281 | > /** A list of `Photo` objects. */; 282 | edges?: EdgesResolver< 283 | PhotosEdge[], 284 | any, 285 | Context 286 | > /** A list of edges which contains the `Photo` and cursor to aid in pagination. */; 287 | pageInfo?: PageInfoResolver< 288 | PageInfo, 289 | any, 290 | Context 291 | > /** Information to aid in pagination. */; 292 | totalCount?: TotalCountResolver< 293 | number | null, 294 | any, 295 | Context 296 | > /** The count of *all* `Photo` you could get from the connection. */; 297 | } 298 | 299 | export type NodesResolver< 300 | R = (Photo | null)[], 301 | Parent = any, 302 | Context = any 303 | > = Resolver; 304 | export type EdgesResolver< 305 | R = PhotosEdge[], 306 | Parent = any, 307 | Context = any 308 | > = Resolver; 309 | export type PageInfoResolver< 310 | R = PageInfo, 311 | Parent = any, 312 | Context = any 313 | > = Resolver; 314 | export type TotalCountResolver< 315 | R = number | null, 316 | Parent = any, 317 | Context = any 318 | > = Resolver; 319 | } 320 | 321 | export namespace PhotoResolvers { 322 | export interface Resolvers { 323 | nodeId?: NodeIdResolver< 324 | string, 325 | any, 326 | Context 327 | > /** A globally unique identifier. Can be used in various places throughout the system to identify this single value. */; 328 | id?: IdResolver; 329 | name?: NameResolver; 330 | description?: DescriptionResolver; 331 | filename?: FilenameResolver; 332 | views?: ViewsResolver; 333 | isPublished?: IsPublishedResolver; 334 | } 335 | 336 | export type NodeIdResolver< 337 | R = string, 338 | Parent = any, 339 | Context = any 340 | > = Resolver; 341 | export type IdResolver = Resolver< 342 | R, 343 | Parent, 344 | Context 345 | >; 346 | export type NameResolver = Resolver< 347 | R, 348 | Parent, 349 | Context 350 | >; 351 | export type DescriptionResolver< 352 | R = string, 353 | Parent = any, 354 | Context = any 355 | > = Resolver; 356 | export type FilenameResolver< 357 | R = string, 358 | Parent = any, 359 | Context = any 360 | > = Resolver; 361 | export type ViewsResolver = Resolver< 362 | R, 363 | Parent, 364 | Context 365 | >; 366 | export type IsPublishedResolver< 367 | R = boolean, 368 | Parent = any, 369 | Context = any 370 | > = Resolver; 371 | } 372 | /** A `Photo` edge in the connection. */ 373 | export namespace PhotosEdgeResolvers { 374 | export interface Resolvers { 375 | cursor?: CursorResolver< 376 | Cursor | null, 377 | any, 378 | Context 379 | > /** A cursor for use in pagination. */; 380 | node?: NodeResolver< 381 | Photo | null, 382 | any, 383 | Context 384 | > /** The `Photo` at the end of the edge. */; 385 | } 386 | 387 | export type CursorResolver< 388 | R = Cursor | null, 389 | Parent = any, 390 | Context = any 391 | > = Resolver; 392 | export type NodeResolver< 393 | R = Photo | null, 394 | Parent = any, 395 | Context = any 396 | > = Resolver; 397 | } 398 | /** Information about pagination in a connection. */ 399 | export namespace PageInfoResolvers { 400 | export interface Resolvers { 401 | hasNextPage?: HasNextPageResolver< 402 | boolean, 403 | any, 404 | Context 405 | > /** When paginating forwards, are there more items? */; 406 | hasPreviousPage?: HasPreviousPageResolver< 407 | boolean, 408 | any, 409 | Context 410 | > /** When paginating backwards, are there more items? */; 411 | startCursor?: StartCursorResolver< 412 | Cursor | null, 413 | any, 414 | Context 415 | > /** When paginating backwards, the cursor to continue. */; 416 | endCursor?: EndCursorResolver< 417 | Cursor | null, 418 | any, 419 | Context 420 | > /** When paginating forwards, the cursor to continue. */; 421 | } 422 | 423 | export type HasNextPageResolver< 424 | R = boolean, 425 | Parent = any, 426 | Context = any 427 | > = Resolver; 428 | export type HasPreviousPageResolver< 429 | R = boolean, 430 | Parent = any, 431 | Context = any 432 | > = Resolver; 433 | export type StartCursorResolver< 434 | R = Cursor | null, 435 | Parent = any, 436 | Context = any 437 | > = Resolver; 438 | export type EndCursorResolver< 439 | R = Cursor | null, 440 | Parent = any, 441 | Context = any 442 | > = Resolver; 443 | } 444 | 445 | export namespace MutationResolvers { 446 | export interface Resolvers { 447 | signup?: SignupResolver; 448 | login?: LoginResolver; 449 | createPhoto?: CreatePhotoResolver< 450 | CreatePhotoPayload | null, 451 | any, 452 | Context 453 | > /** Creates a single `Photo`. */; 454 | deletePhoto?: DeletePhotoResolver< 455 | DeletePhotoPayload | null, 456 | any, 457 | Context 458 | > /** Deletes a single `Photo` using its globally unique id. */; 459 | } 460 | 461 | export type SignupResolver< 462 | R = AuthPayload, 463 | Parent = any, 464 | Context = any 465 | > = Resolver; 466 | export interface SignupArgs { 467 | email: string; 468 | password: string; 469 | name: string; 470 | } 471 | 472 | export type LoginResolver< 473 | R = AuthPayload, 474 | Parent = any, 475 | Context = any 476 | > = Resolver; 477 | export interface LoginArgs { 478 | email: string; 479 | password: string; 480 | } 481 | 482 | export type CreatePhotoResolver< 483 | R = CreatePhotoPayload | null, 484 | Parent = any, 485 | Context = any 486 | > = Resolver; 487 | export interface CreatePhotoArgs { 488 | input: CreatePhotoInput /** The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. */; 489 | } 490 | 491 | export type DeletePhotoResolver< 492 | R = DeletePhotoPayload | null, 493 | Parent = any, 494 | Context = any 495 | > = Resolver; 496 | export interface DeletePhotoArgs { 497 | input: DeletePhotoInput /** The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. */; 498 | } 499 | } 500 | 501 | export namespace AuthPayloadResolvers { 502 | export interface Resolvers { 503 | token?: TokenResolver; 504 | user?: UserResolver; 505 | } 506 | 507 | export type TokenResolver = Resolver< 508 | R, 509 | Parent, 510 | Context 511 | >; 512 | export type UserResolver = Resolver< 513 | R, 514 | Parent, 515 | Context 516 | >; 517 | } 518 | /** The output of our create `Photo` mutation. */ 519 | export namespace CreatePhotoPayloadResolvers { 520 | export interface Resolvers { 521 | clientMutationId?: ClientMutationIdResolver< 522 | string | null, 523 | any, 524 | Context 525 | > /** The exact same `clientMutationId` that was provided in the mutation input,unchanged and unused. May be used by a client to track mutations. */; 526 | photo?: PhotoResolver< 527 | Photo | null, 528 | any, 529 | Context 530 | > /** The `Photo` that was created by this mutation. */; 531 | photoEdge?: PhotoEdgeResolver< 532 | PhotosEdge | null, 533 | any, 534 | Context 535 | > /** An edge for our `Photo`. May be used by Relay 1. */; 536 | } 537 | 538 | export type ClientMutationIdResolver< 539 | R = string | null, 540 | Parent = any, 541 | Context = any 542 | > = Resolver; 543 | export type PhotoResolver< 544 | R = Photo | null, 545 | Parent = any, 546 | Context = any 547 | > = Resolver; 548 | export type PhotoEdgeResolver< 549 | R = PhotosEdge | null, 550 | Parent = any, 551 | Context = any 552 | > = Resolver; 553 | export interface PhotoEdgeArgs { 554 | orderBy?: 555 | | PhotosOrderBy[] 556 | | null /** The method to use when ordering `Photo`. */; 557 | } 558 | } 559 | /** The output of our delete `Photo` mutation. */ 560 | export namespace DeletePhotoPayloadResolvers { 561 | export interface Resolvers { 562 | clientMutationId?: ClientMutationIdResolver< 563 | string | null, 564 | any, 565 | Context 566 | > /** The exact same `clientMutationId` that was provided in the mutation input,unchanged and unused. May be used by a client to track mutations. */; 567 | photo?: PhotoResolver< 568 | Photo | null, 569 | any, 570 | Context 571 | > /** The `Photo` that was deleted by this mutation. */; 572 | deletedPhotoId?: DeletedPhotoIdResolver; 573 | photoEdge?: PhotoEdgeResolver< 574 | PhotosEdge | null, 575 | any, 576 | Context 577 | > /** An edge for our `Photo`. May be used by Relay 1. */; 578 | } 579 | 580 | export type ClientMutationIdResolver< 581 | R = string | null, 582 | Parent = any, 583 | Context = any 584 | > = Resolver; 585 | export type PhotoResolver< 586 | R = Photo | null, 587 | Parent = any, 588 | Context = any 589 | > = Resolver; 590 | export type DeletedPhotoIdResolver< 591 | R = string | null, 592 | Parent = any, 593 | Context = any 594 | > = Resolver; 595 | export type PhotoEdgeResolver< 596 | R = PhotosEdge | null, 597 | Parent = any, 598 | Context = any 599 | > = Resolver; 600 | export interface PhotoEdgeArgs { 601 | orderBy?: 602 | | PhotosOrderBy[] 603 | | null /** The method to use when ordering `Photo`. */; 604 | } 605 | } 606 | 607 | export namespace MigrationResolvers { 608 | export interface Resolvers { 609 | nodeId?: NodeIdResolver< 610 | string, 611 | any, 612 | Context 613 | > /** A globally unique identifier. Can be used in various places throughout the system to identify this single value. */; 614 | id?: IdResolver; 615 | timestamp?: TimestampResolver; 616 | name?: NameResolver; 617 | } 618 | 619 | export type NodeIdResolver< 620 | R = string, 621 | Parent = any, 622 | Context = any 623 | > = Resolver; 624 | export type IdResolver = Resolver< 625 | R, 626 | Parent, 627 | Context 628 | >; 629 | export type TimestampResolver< 630 | R = BigInt, 631 | Parent = any, 632 | Context = any 633 | > = Resolver; 634 | export type NameResolver = Resolver< 635 | R, 636 | Parent, 637 | Context 638 | >; 639 | } 640 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // patch for missed fetch in production 2 | (global as any).fetch = require('node-fetch'); 3 | 4 | import { importSchema } from 'graphql-import'; 5 | import { ApolloServer, gql } from 'apollo-server'; 6 | 7 | import 'reflect-metadata'; 8 | import { createConnection } from 'typeorm'; 9 | 10 | import { Config } from './Config'; 11 | import { Connection } from './embeddedPostgraphile/config'; 12 | import { Binding } from './generated/postgraphile'; 13 | import resolvers from './resolvers'; 14 | 15 | // simple arguments parser 16 | const configPath = process.argv[process.argv.length - 1]; 17 | if (configPath === __filename) { 18 | console.log(`Config argument required. Run as: > ${__filename} path/to/config.json`); 19 | process.exit(1); 20 | } 21 | const config = Config.fromFile(configPath); 22 | 23 | createConnection(config).then(async connection => { 24 | // load end user facing graphql schema 25 | const typeDefs = gql(importSchema(__dirname + '/schema.graphql')); 26 | 27 | // connect postgraphile bindings with the postgres DB using typeorm configuration 28 | Connection.init(config); 29 | const postgraphileConnection = new Binding(); 30 | 31 | const server = new ApolloServer({ 32 | typeDefs, 33 | resolvers, 34 | mocks: true, 35 | // onHealthCheck: () => fetch('https://fourtonfish.com/hellosalut/?mode=auto'), 36 | context: (c: any) => ({ 37 | ...c, 38 | orm: connection, 39 | gql: postgraphileConnection 40 | }) 41 | }); 42 | 43 | const url = await server.listen(); 44 | console.log(`Graphql API server is running on ${url.url}`); 45 | }).catch(error => console.log(error)); 46 | -------------------------------------------------------------------------------- /src/migration/1534731480120-Migration.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner} from "typeorm"; 2 | import { PostgresConnectionOptions } from "typeorm/driver/postgres/PostgresConnectionOptions"; 3 | 4 | export class Migration1534731480120 implements MigrationInterface { 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | const schema = (queryRunner.connection.options as PostgresConnectionOptions).schema; 8 | await queryRunner.query(`CREATE TABLE "${schema}"."photo" ("id" SERIAL NOT NULL, "name" character varying(100) NOT NULL, "description" text NOT NULL, "filename" character varying NOT NULL, "views" double precision NOT NULL, "isPublished" boolean NOT NULL, CONSTRAINT "PK_3de63c33511727e82cefc06c2ae" PRIMARY KEY ("id"))`); 9 | await queryRunner.query(`CREATE TABLE "${schema}"."user" ("id" SERIAL NOT NULL, "name" character varying(100) NOT NULL, "email" text NOT NULL, "password" text NOT NULL, CONSTRAINT "UQ_89249f6d281c7676eb93d2276c4" UNIQUE ("email"), CONSTRAINT "PK_ba3483e2dc9d4b8478d7cc931ab" PRIMARY KEY ("id"))`); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | const schema = (queryRunner.connection.options as PostgresConnectionOptions).schema; 14 | await queryRunner.query(`DROP TABLE "${schema}"."user"`); 15 | await queryRunner.query(`DROP TABLE "${schema}"."photo"`); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/resolvers/Mutation/auth.ts: -------------------------------------------------------------------------------- 1 | import * as bcrypt from 'bcryptjs'; 2 | import * as jwt from 'jsonwebtoken'; 3 | import { Context } from '../../Context'; 4 | import { ApolloError, AuthenticationError } from 'apollo-server-errors'; 5 | import { User } from '../../entity/User'; 6 | 7 | export const auth = { 8 | async signup(parent: any, args: any, ctx: Context, info: any) { 9 | const user = new User(); 10 | user.name = args.name; 11 | user.email = args.email; 12 | user.password = await bcrypt.hash(args.password, 10); 13 | const result = await ctx.orm.manager.save(user); 14 | return { 15 | result 16 | }; 17 | }, 18 | 19 | async login(parent: any, { email, password }: any, ctx: Context, info: any) { 20 | const user = await ctx.orm.manager.findOne(User, { email: email }); 21 | 22 | if (!user) { 23 | throw new ApolloError(`No such user found for email: ${email}`, 'NOT_FOUND'); 24 | } 25 | 26 | const valid = await bcrypt.compare(password, user.password); 27 | if (!valid) { 28 | throw new AuthenticationError('Invalid password'); 29 | } 30 | 31 | return { 32 | user, 33 | token: jwt.sign({ userId: user.id }, process.env.APP_SECRET || 'secret'), 34 | }; 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/resolvers/Mutation/post.ts: -------------------------------------------------------------------------------- 1 | import { Context } from '../../Context'; 2 | import { getUserId } from '../../utils'; 3 | 4 | export const post = { 5 | async createPhoto(parent: any, args: any, ctx: Context, info: any) { 6 | const unused = getUserId(ctx); // check token 7 | return ctx.gql.mutation.createPhoto( 8 | args, 9 | info 10 | ); 11 | }, 12 | 13 | async deletePhoto(parent: any, args: any, ctx: Context, info: any) { 14 | const unused = getUserId(ctx); // check token 15 | return ctx.gql.mutation.deletePhoto( 16 | args, 17 | info 18 | ); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/resolvers/Node.ts: -------------------------------------------------------------------------------- 1 | // Run-time warning: 2 | // Type "Node" is missing a "resolveType" resolver. 3 | // Pass false into "resolverValidationOptions.requireResolversForResolveType" to disable this warning. 4 | // can be fixed using this implementation of a node resolution: 5 | 6 | // TODO see https://github.com/graphql-cli/graphql-cli/issues/338 7 | // export const Node = { 8 | // // tslint:disable-next-line:function-name 9 | // __resolveType(obj: any, context: Context, info: any) { 10 | // if ((obj as Post).author) { 11 | // return 'Post'; 12 | // } 13 | // if ((obj as User).email) { 14 | // return 'User'; 15 | // } 16 | // return null; 17 | // }, 18 | // }; 19 | -------------------------------------------------------------------------------- /src/resolvers/Query.ts: -------------------------------------------------------------------------------- 1 | import { getUserId } from '../utils'; 2 | import { Context } from '../Context'; 3 | 4 | export const Query = { 5 | async allPhotos(parent: any, args: any, ctx: Context, info: any) { 6 | const unused = getUserId(ctx); // check token 7 | return ctx.gql.query.allPhotos( 8 | args, 9 | info 10 | ); 11 | }, 12 | 13 | async me(parent: any, args: any, ctx: Context, info: any) { 14 | const id = getUserId(ctx); 15 | return ctx.gql.query.userById({ id: id }, info); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/resolvers/Subscription.ts: -------------------------------------------------------------------------------- 1 | // import { Context } from '../utils'; 2 | 3 | // export const Subscription = { 4 | // feedSubscription: { 5 | // subscribe: (parent: any, args: any, ctx: Context, info: any) => { 6 | // return ctx.db.subscription.post( 7 | // { 8 | // where: { 9 | // node: { 10 | // isPublished: true, 11 | // }, 12 | // }, 13 | // }, 14 | // info, 15 | // ); 16 | // }, 17 | // }, 18 | // }; 19 | -------------------------------------------------------------------------------- /src/resolvers/index.ts: -------------------------------------------------------------------------------- 1 | import { Query } from './Query'; 2 | import { auth } from './Mutation/auth'; 3 | import { post } from './Mutation/post'; 4 | // import { Node } from './Node'; 5 | 6 | export default { 7 | Query, 8 | // Node, 9 | Mutation: { 10 | ...auth, 11 | ...post 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/schema.graphql: -------------------------------------------------------------------------------- 1 | # import UsersConnection, Photo, CreatePhotoPayload, CreatePhotoInput, DeletePhotoInput, DeletePhotoPayload, PhotosConnection, PhotosOrderBy, Cursor from "./generated/postgraphile.graphql" 2 | 3 | type Query { 4 | # """Retruns the current user from JWT token, which was previously obtained via `login` mutation""" 5 | me: User 6 | 7 | """Reads and enables pagination through a set of `Photo`.""" 8 | allPhotos( 9 | """Only read the first `n` values of the set.""" 10 | first: Int 11 | 12 | """Only read the last `n` values of the set.""" 13 | last: Int 14 | 15 | """ 16 | Skip the first `n` values from our `after` cursor, an alternative to cursor 17 | based pagination. May not be used with `last`. 18 | """ 19 | offset: Int 20 | 21 | """Read all values in the set before (above) this cursor.""" 22 | before: Cursor 23 | 24 | """Read all values in the set after (below) this cursor.""" 25 | after: Cursor 26 | 27 | """The method to use when ordering `Photo`.""" 28 | orderBy: [PhotosOrderBy!] = [PRIMARY_KEY_ASC] 29 | 30 | """ 31 | A condition to be used in determining which values should be returned by the collection. 32 | """ 33 | condition: PhotoCondition 34 | ): PhotosConnection 35 | } 36 | 37 | type Mutation { 38 | signup(email: String!, password: String!, name: String!): User! 39 | login(email: String!, password: String!): AuthPayload! 40 | 41 | """Creates a single `Photo`.""" 42 | createPhoto( 43 | """ 44 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 45 | """ 46 | input: CreatePhotoInput! 47 | ): CreatePhotoPayload 48 | 49 | """Deletes a single `Photo` using its globally unique id.""" 50 | deletePhoto( 51 | """ 52 | The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. 53 | """ 54 | input: DeletePhotoInput! 55 | ): DeletePhotoPayload 56 | } 57 | 58 | type AuthPayload { 59 | token: String! 60 | user: User! 61 | } 62 | 63 | # do not import generated user, but define it here to hide the encrypted password field 64 | type User { 65 | id: Int! 66 | email: String! 67 | name: String! 68 | } 69 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as jwt from 'jsonwebtoken'; 2 | import { Context } from './Context'; 3 | 4 | export class AuthError extends Error { 5 | constructor() { 6 | super('Not authorized'); 7 | } 8 | } 9 | 10 | export function getUserId(ctx: Context) { 11 | const Authorization = ctx.req.headers.authorization; 12 | if (Authorization) { 13 | const token = Authorization.replace('Bearer ', ''); 14 | const { userId } = jwt.verify(token, process.env.APP_SECRET || 'secret') as { userId: number }; 15 | return userId; 16 | } 17 | throw new AuthError(); 18 | } 19 | 20 | export function ensure(value: T | null | undefined): T { 21 | if (value !== undefined && value !== null) { 22 | return value; 23 | } 24 | throw Error(`unexpected '${value}' instance`); 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "moduleResolution": "node", 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "rootDir": "src", 8 | "outDir": "build", 9 | "lib": [ 10 | "esnext", 11 | "dom" 12 | ], 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": false, 20 | "emitDecoratorMetadata": true, 21 | "experimentalDecorators": true, 22 | "baseUrl": ".", 23 | "newLine": "LF" 24 | }, 25 | "exclude": [ 26 | "scripts" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-config-airbnb", "tslint-react"], 3 | "rules": { 4 | "align": [ 5 | true, 6 | "members", 7 | "statements" 8 | ], 9 | "ban": false, 10 | "class-name": true, 11 | "comment-format": false, 12 | "curly": true, 13 | "eofline": false, 14 | "forin": true, 15 | "indent": [ true, "spaces" ], 16 | "interface-name": [true, "never-prefix"], 17 | "jsdoc-format": true, 18 | "jsx-no-lambda": false, 19 | "jsx-no-multiline-js": false, 20 | "label-position": true, 21 | "max-line-length": [ true, 120 ], 22 | "member-ordering": [ 23 | true, 24 | "public-before-private", 25 | "static-before-instance", 26 | "variables-before-functions" 27 | ], 28 | "no-any": false, 29 | "no-arg": true, 30 | "no-bitwise": true, 31 | "no-console": [ 32 | false, 33 | "log", 34 | "error", 35 | "debug", 36 | "info", 37 | "time", 38 | "timeEnd", 39 | "trace" 40 | ], 41 | "no-consecutive-blank-lines": true, 42 | "no-construct": true, 43 | "no-debugger": true, 44 | "no-duplicate-variable": true, 45 | "no-empty": true, 46 | "no-eval": true, 47 | "no-shadowed-variable": true, 48 | "no-string-literal": true, 49 | "no-switch-case-fall-through": true, 50 | "no-trailing-whitespace": false, 51 | "no-unused-expression": true, 52 | "no-use-before-declare": true, 53 | "one-line": [ 54 | true, 55 | "check-catch", 56 | "check-else", 57 | "check-open-brace", 58 | "check-whitespace" 59 | ], 60 | "quotemark": [true, "single", "jsx-double"], 61 | "radix": true, 62 | "semicolon": [true, "always"], 63 | "switch-default": true, 64 | 65 | "trailing-comma": [false], 66 | 67 | "triple-equals": [ true, "allow-null-check" ], 68 | "typedef": [ 69 | true, 70 | "parameter", 71 | "property-declaration" 72 | ], 73 | "typedef-whitespace": [ 74 | true, 75 | { 76 | "call-signature": "nospace", 77 | "index-signature": "nospace", 78 | "parameter": "nospace", 79 | "property-declaration": "nospace", 80 | "variable-declaration": "nospace" 81 | } 82 | ], 83 | "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"], 84 | "whitespace": [ 85 | true, 86 | "check-branch", 87 | "check-decl", 88 | "check-module", 89 | "check-operator", 90 | "check-separator", 91 | "check-type", 92 | "check-typecast" 93 | ], 94 | "import-name": false, 95 | "ter-indent": [ 96 | true, 97 | 4, 98 | { 99 | "SwitchCase": 1 100 | } 101 | ], 102 | "no-this-assignment": false, 103 | "object-literal-shorthand": false, 104 | "no-parameter-reassignment": false, 105 | "ter-arrow-parens": false, 106 | "no-else-after-return": false 107 | } 108 | } 109 | --------------------------------------------------------------------------------