diff --git a/.markdownlint.json b/.markdownlint.json index e011494f..e7ca3545 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -2,9 +2,7 @@ "MD009": { "br_spaces": 2 }, - "MD013": { - "code_blocks": false - }, + "MD013": false, "MD026": { "punctuation": ".,;:" }, diff --git a/README.md b/README.md index eda90bfe..1d4909c7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Warthog - Auto-generated GraphQL APIs +# Warthog - GraphQL API Framework [![npm version](https://img.shields.io/npm/v/warthog.svg)](https://www.npmjs.org/package/warthog) [![CircleCI](https://circleci.com/gh/goldcaddy77/warthog/tree/master.svg?style=shield)](https://circleci.com/gh/goldcaddy77/warthog/tree/master) @@ -6,8 +6,7 @@ [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![Join the chat at https://gitter.im/warthog-graphql/community](https://badges.gitter.im/warthog-graphql/community.svg)](https://gitter.im/warthog-graphql/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Opinionated framework for building GraphQL APIs with strong conventions. With -Warthog, set up your data models and the following are automatically generated: +Opinionated framework for building GraphQL APIs with strong conventions. With Warthog, set up your data models and the following are automatically generated: - Database schema - generated by [TypeORM](https://github.com/typeorm/typeorm) - Your entire GraphQL Schema including: @@ -20,8 +19,7 @@ Warthog, set up your data models and the following are automatically generated: ## Warning -The API for this library is very much a moving target. It will likely shift -until version 2, at which time it will become stable. +The API for this library is still subject to change. It will likely shift until version 2, at which time it will become stable. I'd love early adopters, but please know that there might be some breaking changes for a few more weeks. ## Install @@ -31,11 +29,11 @@ yarn add warthog ## Usage -Check out the [examples folder](https://github.com/goldcaddy77/warthog/tree/v1/examples) -to see how to use Warthog in a project or check out the -[warthog example](https://github.com/goldcaddy77/warthog-example) repo. +Check out the [warthog-example](https://github.com/goldcaddy77/warthog-example) repo to see how to use Warthog in a project. There are also a bunch of examples in the [examples](./examples/README.md) folder. Note that these use relative import paths to call into Warthog instead of pulling from NPM. -Create an entity: +### 1. Create a Model + +The model will auto-generate your database table and graphql types. Warthog will find all models that match the following glob - `'/**/*.model.ts'`. So for this file, you would name it `user.model.ts` ```typescript import { BaseModel, Model, StringField } from 'warthog'; @@ -47,7 +45,50 @@ export class User extends BaseModel { } ``` -Now, when you start your server, the following will be generated: +### 2. Create a Resolver + +The resolver auto-generates queries and mutations in your GraphQL schema. Warthog will find all resolvers that match the following glob - `'/**/*.resolver.ts'`. So for this file, you would name it `user.resolver.ts` + +```typescript +import { User } from './user.model'; + +@Resolver(User) +export class UserResolver extends BaseResolver { + constructor(@InjectRepository(User) private readonly userRepository: Repository) { + super(User, userRepository); + } + + @Query(returns => [User]) + async users( + @Args() { where, orderBy, limit, offset }: UserWhereArgs + ): Promise { + return this.find(where, orderBy, limit, offset); + } + + @Mutation(returns => User) + async createUser(@Arg('data') data: UserCreateInput, @Ctx() ctx: BaseContext): Promise { + return this.create(data, ctx.user.id); + } +} +``` + +### 3. Run your server + +```typescript + +import 'reflect-metadata'; +import { Container } from 'typedi'; +import { App } from 'warthog'; + +async function bootstrap() { + const app = new App({ container: Container }); + return app.start(); +} + +bootstrap() +``` + +When you start your server, there will be a new `generated` folder that has your GraphQL schema in `schema.graphql`. This contains: ```graphql type User implements BaseGraphQLObject { @@ -115,10 +156,22 @@ input UserWhereUniqueInput { } ``` -## Limitations +Notice how we've only added a single field on the model and you get pagination, filtering and tracking of who created, updated and deleted records automatically. + +## Config -Since Warthog relies heavily on conventions, it only supports postgres currently -for DBs. +| value | ENV var | option name | default | +| --- | --- | --- | --- | +| host | APP_HOST | appOptions.host | no default | +| app port | APP_PORT | appOptions.port | 4000 | +| generated folder | _none_ | appOptions.generatedFolder | _current-dir_ + `generated` | + +## Intentionally Opinionated + +Warthog is intentionally opinionated + +- Database - currently only supports Postgres. This could be easily changed, but I don't have the need currently +- Soft deletes - no records are ever deleted, only "soft deleted". The base service used in resolvers filters out the deleted records by default. ## Thanks @@ -128,7 +181,7 @@ Special thanks to the authors of: - [TypeGraphQL](https://github.com/19majkel94/type-graphql) - [Prisma](https://github.com/prisma/prisma) -Ultimately, Warthog is a really opinionated, yet flexible composition of these libraries +Warthog is essentially a really opinionated composition of TypeORM and TypeGraphQL that uses similar GraphQL conventions to the Prisma project. ## Contribute @@ -137,5 +190,3 @@ PRs accepted, fire away! Or add issues if you have use cases Warthog doesn't co ## License MIT © Dan Caddigan - - diff --git a/examples/1-simple-model/src/index.ts b/examples/1-simple-model/src/index.ts index 3c8cf12f..e3c8113f 100644 --- a/examples/1-simple-model/src/index.ts +++ b/examples/1-simple-model/src/index.ts @@ -1,5 +1,5 @@ -import 'reflect-metadata'; import * as dotenv from 'dotenv'; +import 'reflect-metadata'; import { Container } from 'typedi'; dotenv.config(); @@ -12,7 +12,7 @@ async function bootstrap() { warthogImportPath: '../../../src' // Path written in generated classes }); - await app.start(); + return app.start(); } bootstrap().catch((error: Error) => { diff --git a/examples/1-simple-model/src/user.entity.ts b/examples/1-simple-model/src/user.model.ts similarity index 100% rename from examples/1-simple-model/src/user.entity.ts rename to examples/1-simple-model/src/user.model.ts diff --git a/examples/1-simple-model/src/user.resolver.ts b/examples/1-simple-model/src/user.resolver.ts index 95785935..38925655 100644 --- a/examples/1-simple-model/src/user.resolver.ts +++ b/examples/1-simple-model/src/user.resolver.ts @@ -3,10 +3,10 @@ import { Arg, Args, Ctx, Mutation, Query, Resolver } from 'type-graphql'; import { Repository } from 'typeorm'; import { InjectRepository } from 'typeorm-typedi-extensions'; -import { BaseResolver, Context, StandardDeleteResponse } from '../../../src'; +import { BaseContext, BaseResolver, StandardDeleteResponse } from '../../../src'; import { UserCreateInput, UserUpdateArgs, UserWhereArgs, UserWhereInput, UserWhereUniqueInput } from '../generated'; -import { User } from './user.entity'; +import { User } from './user.model'; // Note: we have to specify `User` here instead of (of => User) because for some reason this // changes the object reference when it's trying to add the FieldResolver and things break @@ -19,7 +19,7 @@ export class UserResolver extends BaseResolver { @Query(returns => [User]) async users( @Args() { where, orderBy, limit, offset }: UserWhereArgs, - @Ctx() ctx: Context, + @Ctx() ctx: BaseContext, info: GraphQLResolveInfo ): Promise { return this.find(where, orderBy, limit, offset); @@ -31,17 +31,20 @@ export class UserResolver extends BaseResolver { } @Mutation(returns => User) - async createUser(@Arg('data') data: UserCreateInput, @Ctx() ctx: Context): Promise { + async createUser(@Arg('data') data: UserCreateInput, @Ctx() ctx: BaseContext): Promise { return this.create(data, ctx.user.id); } @Mutation(returns => User) - async updateUser(@Args() { data, where }: UserUpdateArgs, @Ctx() ctx: Context): Promise { + async updateUser(@Args() { data, where }: UserUpdateArgs, @Ctx() ctx: BaseContext): Promise { return this.update(data, where, ctx.user.id); } @Mutation(returns => StandardDeleteResponse) - async deleteUser(@Arg('where') where: UserWhereUniqueInput, @Ctx() ctx: Context): Promise { + async deleteUser( + @Arg('where') where: UserWhereUniqueInput, + @Ctx() ctx: BaseContext + ): Promise { return this.delete(where, ctx.user.id); } } diff --git a/examples/1-simple-model/tsconfig.json b/examples/1-simple-model/tsconfig.json index b871bcec..1ca802c9 100644 --- a/examples/1-simple-model/tsconfig.json +++ b/examples/1-simple-model/tsconfig.json @@ -20,5 +20,5 @@ "types": ["jest", "isomorphic-fetch", "node"] }, "include": ["src/**/*"], - "exclude": ["node_modules/**/*"] + "exclude": ["node_modules/**/*", "generated/**/*"] } diff --git a/examples/1-simple-model/tslint.json b/examples/1-simple-model/tslint.json new file mode 100644 index 00000000..b97234e7 --- /dev/null +++ b/examples/1-simple-model/tslint.json @@ -0,0 +1,20 @@ +{ + "extends": ["typestrict", "tslint:latest", "tslint-config-prettier"], + "rules": { + "no-string-throw": false, + "class-name": false, + "interface-over-type-literal": false, + "interface-name": [false], + "max-classes-per-file": false, + "member-access": [false], + "no-submodule-imports": false, + "no-unused-variable": false, + "no-implicit-dependencies": false, + "no-var-keyword": false, + "no-floating-promises": true, + "no-console": false + }, + "linterOptions": { + "exclude": ["node_modules/**/*", "generated/*", "src/migration/*"] + } +} diff --git a/examples/2-complex-example/package.json b/examples/2-complex-example/package.json index ed25362a..15356fa1 100644 --- a/examples/2-complex-example/package.json +++ b/examples/2-complex-example/package.json @@ -7,6 +7,7 @@ "db:create": "createdbjs $(dotenv -p TYPEORM_DATABASE) 2>&1 || :", "db:drop": "dropdbjs $(dotenv -p TYPEORM_DATABASE) 2>&1 || :", "db:seed:dev": "dotenv -- ts-node tools/seed.ts", + "lint": "tslint --fix -c ./tslint.json -p ./tsconfig.json", "playground:open": "open http://localhost:$(dotenv -p APP_PORT)/playground", "start": "npm-run-all --parallel start:ts playground:open", "start:debug": "yarn start:ts --inspect", @@ -17,6 +18,7 @@ "watch:ts": "nodemon -e ts,graphql -x ts-node --type-check src/index.ts" }, "dependencies": { + "debug": "^4.1.1", "pgtools": "^0.3.0", "reflect-metadata": "^0.1.12", "typescript": "^3.2.2" @@ -33,7 +35,8 @@ "nodemon": "^1.18.9", "npm-run-all": "^4.1.5", "ts-jest": "^23.10.5", - "ts-node": "^7.0.1" + "ts-node": "^7.0.1", + "tslint": "^5.12.1" }, "jest": { "transform": { diff --git a/examples/2-complex-example/src/app.ts b/examples/2-complex-example/src/app.ts index fb511a9c..b3d66a22 100644 --- a/examples/2-complex-example/src/app.ts +++ b/examples/2-complex-example/src/app.ts @@ -1,17 +1,32 @@ import 'reflect-metadata'; import { Container } from 'typedi'; -import { App, AppOptions } from '../../../src/'; -// import { User } from './modules/user/user.entity'; +import { App, BaseContext } from '../../../src/'; -export function getApp(appOptions: Partial = {}, dbOptions: any = {}) { - return new App( - { - container: Container, - warthogImportPath: '../../../src', // Path written in generated classes - ...appOptions +// import { User } from './modules/user/user.model'; + +interface Context extends BaseContext { + user: { + email: string; + id: string; + permissions: string; + }; +} + +export function getApp() { + return new App({ + container: Container, + // Inject a fake user. In a real app you'd parse a JWT to add the user + context: request => { + return { + user: { + email: 'admin@test.com', + id: 'abc12345', + permissions: ['user:read', 'user:update', 'user:create', 'user:delete', 'photo:delete'] + } + }; }, - dbOptions - ); + warthogImportPath: '../../../src' // Path written in generated classes + }); } diff --git a/examples/2-complex-example/src/index.test.ts b/examples/2-complex-example/src/index.test.ts index cd5ec3d9..23d22691 100644 --- a/examples/2-complex-example/src/index.test.ts +++ b/examples/2-complex-example/src/index.test.ts @@ -1,12 +1,12 @@ -import 'reflect-metadata'; import { GraphQLError } from 'graphql'; +import 'reflect-metadata'; import { Binding } from '../generated/binding'; import { getApp } from './app'; -import { User } from './modules/user/user.entity'; +import { User } from './modules/user/user.model'; -let app = getApp({}, { logging: false }); +const app = getApp({}, { logging: false }); let binding: Binding; let testUser: User; @@ -32,9 +32,9 @@ beforeAll(async done => { done(); }); -afterAll(done => { +afterAll(async done => { (console.error as any).mockRestore(); - app.stop(); + await app.stop(); done(); }); @@ -51,7 +51,7 @@ describe('Users', () => { }); test('createdAt sort', async done => { - let users = await binding.query.users({ limit: 1, orderBy: 'createdAt_DESC' }, `{ id firstName}`); + const users = await binding.query.users({ limit: 1, orderBy: 'createdAt_DESC' }, `{ id firstName}`); expect(console.error).not.toHaveBeenCalled(); expect(users).toBeDefined(); diff --git a/examples/2-complex-example/src/index.ts b/examples/2-complex-example/src/index.ts index 74f8fbeb..04ac7a6d 100644 --- a/examples/2-complex-example/src/index.ts +++ b/examples/2-complex-example/src/index.ts @@ -1,9 +1,11 @@ import 'reflect-metadata'; + import * as dotenv from 'dotenv'; -dotenv.config(); import { getApp } from './app'; +dotenv.config(); + async function bootstrap() { const app = getApp(); await app.start(); @@ -12,7 +14,7 @@ async function bootstrap() { bootstrap().catch((error: Error) => { console.error(error); if (error.stack) { - console.error(error.stack!.split('\n')); + console.error(error.stack.split('\n')); } process.exit(1); }); diff --git a/examples/2-complex-example/src/modules/user/user.entity.ts b/examples/2-complex-example/src/modules/user/user.model.ts similarity index 100% rename from examples/2-complex-example/src/modules/user/user.entity.ts rename to examples/2-complex-example/src/modules/user/user.model.ts diff --git a/examples/2-complex-example/src/modules/user/user.resolver.ts b/examples/2-complex-example/src/modules/user/user.resolver.ts index fa419e96..a3a50a28 100644 --- a/examples/2-complex-example/src/modules/user/user.resolver.ts +++ b/examples/2-complex-example/src/modules/user/user.resolver.ts @@ -3,7 +3,7 @@ import { Arg, Args, Authorized, Ctx, Mutation, Query, Resolver } from 'type-grap import { Repository } from 'typeorm'; import { InjectRepository } from 'typeorm-typedi-extensions'; -import { BaseResolver, Context, StandardDeleteResponse } from '../../../../../src'; +import { BaseContext, BaseResolver, StandardDeleteResponse } from '../../../../../src'; import { UserCreateInput, UserUpdateArgs, @@ -12,23 +12,18 @@ import { UserWhereUniqueInput } from '../../../generated'; -import { User } from './user.entity'; +import { User } from './user.model'; -// Note: we have to specify `User` here instead of (of => User) because for some reason this -// changes the object reference when it's trying to add the FieldResolver and things break @Resolver(User) export class UserResolver extends BaseResolver { constructor(@InjectRepository(User) private readonly userRepository: Repository) { super(User, userRepository); - - // or else it complains about userRepository not being used - console.log.call(null, typeof this.userRepository); } @Query(returns => [User]) async users( @Args() { where, orderBy, limit, offset }: UserWhereArgs, - @Ctx() ctx: Context, + @Ctx() ctx: BaseContext, info: GraphQLResolveInfo ): Promise { return this.find(where, orderBy, limit, offset); @@ -42,19 +37,22 @@ export class UserResolver extends BaseResolver { @Authorized('user:create') @Mutation(returns => User) - async createUser(@Arg('data') data: UserCreateInput, @Ctx() ctx: Context): Promise { + async createUser(@Arg('data') data: UserCreateInput, @Ctx() ctx: BaseContext): Promise { return this.create(data, ctx.user.id); } @Authorized('user:update') @Mutation(returns => User) - async updateUser(@Args() { data, where }: UserUpdateArgs, @Ctx() ctx: Context): Promise { + async updateUser(@Args() { data, where }: UserUpdateArgs, @Ctx() ctx: BaseContext): Promise { return this.update(data, where, ctx.user.id); } - // @Authorized('user:delete') + @Authorized('user:delete') @Mutation(returns => StandardDeleteResponse) - async deleteUser(@Arg('where') where: UserWhereUniqueInput, @Ctx() ctx: Context): Promise { + async deleteUser( + @Arg('where') where: UserWhereUniqueInput, + @Ctx() ctx: BaseContext + ): Promise { return this.delete(where, ctx.user.id); } } diff --git a/examples/2-complex-example/tools/seed.ts b/examples/2-complex-example/tools/seed.ts index 3cb1e946..54fc96fb 100644 --- a/examples/2-complex-example/tools/seed.ts +++ b/examples/2-complex-example/tools/seed.ts @@ -1,3 +1,4 @@ +import * as Debug from 'debug'; import * as Faker from 'faker'; import { getApp } from '../src/app'; @@ -6,11 +7,12 @@ if (process.env.NODE_ENV !== 'development') { throw 'Seeding only available in development environment'; process.exit(1); } +const logger = Debug('warthog:seed'); const NUM_USERS = 100; async function seedDatabase() { - let app = getApp(); + const app = getApp(); await app.start(); const binding = await app.getBinding(); @@ -35,8 +37,7 @@ async function seedDatabase() { }, `{ id email createdAt createdById }` ); - - console.log(user.email); + logger(user.email); } catch (error) { console.error(email, error); } @@ -47,10 +48,10 @@ async function seedDatabase() { seedDatabase() .then(result => { - console.log(result); + logger(result); return process.exit(0); }) .catch(err => { - console.error(err); + logger(err); return process.exit(1); }); diff --git a/examples/2-complex-example/tsconfig.json b/examples/2-complex-example/tsconfig.json index b871bcec..1ca802c9 100644 --- a/examples/2-complex-example/tsconfig.json +++ b/examples/2-complex-example/tsconfig.json @@ -20,5 +20,5 @@ "types": ["jest", "isomorphic-fetch", "node"] }, "include": ["src/**/*"], - "exclude": ["node_modules/**/*"] + "exclude": ["node_modules/**/*", "generated/**/*"] } diff --git a/examples/2-complex-example/tslint.json b/examples/2-complex-example/tslint.json new file mode 100644 index 00000000..b97234e7 --- /dev/null +++ b/examples/2-complex-example/tslint.json @@ -0,0 +1,20 @@ +{ + "extends": ["typestrict", "tslint:latest", "tslint-config-prettier"], + "rules": { + "no-string-throw": false, + "class-name": false, + "interface-over-type-literal": false, + "interface-name": [false], + "max-classes-per-file": false, + "member-access": [false], + "no-submodule-imports": false, + "no-unused-variable": false, + "no-implicit-dependencies": false, + "no-var-keyword": false, + "no-floating-promises": true, + "no-console": false + }, + "linterOptions": { + "exclude": ["node_modules/**/*", "generated/*", "src/migration/*"] + } +} diff --git a/examples/2-complex-example/yarn.lock b/examples/2-complex-example/yarn.lock index b8325e50..3d4cf413 100644 --- a/examples/2-complex-example/yarn.lock +++ b/examples/2-complex-example/yarn.lock @@ -322,7 +322,7 @@ aws4@^1.8.0: resolved "https://indigoag.jfrog.io/indigoag/api/npm/npm/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha1-8OAD2cqef1nHpQiUXXsu+aBKVC8= -babel-code-frame@^6.26.0: +babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://indigoag.jfrog.io/indigoag/api/npm/npm/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= @@ -600,7 +600,7 @@ buffer-writer@1.0.1: resolved "https://indigoag.jfrog.io/indigoag/api/npm/npm/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08" integrity sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg= -builtin-modules@^1.0.0: +builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://indigoag.jfrog.io/indigoag/api/npm/npm/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= @@ -672,6 +672,15 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^2.3.0: + version "2.4.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chokidar@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" @@ -772,6 +781,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +commander@^2.12.1: + version "2.19.0" + resolved "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" + integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + commander@~2.17.1: version "2.17.1" resolved "https://indigoag.jfrog.io/indigoag/api/npm/npm/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" @@ -908,6 +922,13 @@ debug@^3.1.0: dependencies: ms "^2.1.1" +debug@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + decamelize@^1.1.1: version "1.2.0" resolved "https://indigoag.jfrog.io/indigoag/api/npm/npm/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -3589,7 +3610,7 @@ resolve@1.1.7: resolved "https://indigoag.jfrog.io/indigoag/api/npm/npm/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@1.x: +resolve@1.x, resolve@^1.3.2: version "1.9.0" resolved "https://indigoag.jfrog.io/indigoag/api/npm/npm/resolve/-/resolve-1.9.0.tgz#a14c6fdfa8f92a7df1d996cb7105fa744658ea06" integrity sha1-oUxv36j5Kn3x2ZbLcQX6dEZY6gY= @@ -4130,6 +4151,36 @@ ts-node@^7.0.1: source-map-support "^0.5.6" yn "^2.0.0" +tslib@^1.8.0, tslib@^1.8.1: + version "1.9.3" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" + integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== + +tslint@^5.12.1: + version "5.12.1" + resolved "https://registry.npmjs.org/tslint/-/tslint-5.12.1.tgz#8cec9d454cf8a1de9b0a26d7bdbad6de362e52c1" + integrity sha512-sfodBHOucFg6egff8d1BvuofoOQ/nOeYNfbp7LDlKBcLNrL3lmS5zoiDGyOMdT7YsEXAwWpTdAHwOGOc8eRZAw== + dependencies: + babel-code-frame "^6.22.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^3.2.0" + glob "^7.1.1" + js-yaml "^3.7.0" + minimatch "^3.0.4" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.8.0" + tsutils "^2.27.2" + +tsutils@^2.27.2: + version "2.29.0" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://indigoag.jfrog.io/indigoag/api/npm/npm/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" diff --git a/examples/3-one-to-many-relationship/src/post.entity.ts b/examples/3-one-to-many-relationship/src/post.model.ts similarity index 86% rename from examples/3-one-to-many-relationship/src/post.entity.ts rename to examples/3-one-to-many-relationship/src/post.model.ts index 641ecaa9..1c954aa4 100644 --- a/examples/3-one-to-many-relationship/src/post.entity.ts +++ b/examples/3-one-to-many-relationship/src/post.model.ts @@ -1,6 +1,6 @@ import { BaseModel, ID, ManyToOne, Model, StringField } from '../../../src'; -import { User } from './user.entity'; +import { User } from './user.model'; @Model() export class Post extends BaseModel { diff --git a/examples/3-one-to-many-relationship/src/post.resolver.ts b/examples/3-one-to-many-relationship/src/post.resolver.ts index c1dc0db9..db9bd5f0 100644 --- a/examples/3-one-to-many-relationship/src/post.resolver.ts +++ b/examples/3-one-to-many-relationship/src/post.resolver.ts @@ -3,11 +3,11 @@ import { Arg, Args, Ctx, FieldResolver, Mutation, Query, Resolver, Root } from ' import { Repository } from 'typeorm'; import { InjectRepository } from 'typeorm-typedi-extensions'; -import { BaseResolver, Context, StandardDeleteResponse } from '../../../src'; +import { BaseContext, BaseResolver, StandardDeleteResponse } from '../../../src'; import { PostCreateInput, PostUpdateArgs, PostWhereArgs, PostWhereInput, PostWhereUniqueInput } from '../generated'; -import { Post } from './post.entity'; -import { User } from './user.entity'; +import { Post } from './post.model'; +import { User } from './user.model'; // Note: we have to specify `Post` here instead of (of => Post) because for some reason this // changes the object reference when it's trying to add the FieldResolver and things break @@ -18,14 +18,14 @@ export class PostResolver extends BaseResolver { } @FieldResolver(returns => User) - user(@Root() post: Post, @Ctx() ctx: Context): Promise { + user(@Root() post: Post, @Ctx() ctx: BaseContext): Promise { return ctx.dataLoader.loaders.Post.user.load(post); } @Query(returns => [Post]) async posts( @Args() { where, orderBy, limit, offset }: PostWhereArgs, - @Ctx() ctx: Context, + @Ctx() ctx: BaseContext, info: GraphQLResolveInfo ): Promise { return this.find(where, orderBy, limit, offset); @@ -37,17 +37,20 @@ export class PostResolver extends BaseResolver { } @Mutation(returns => Post) - async createPost(@Arg('data') data: PostCreateInput, @Ctx() ctx: Context): Promise { + async createPost(@Arg('data') data: PostCreateInput, @Ctx() ctx: BaseContext): Promise { return this.create(data, ctx.user.id); } @Mutation(returns => Post) - async updatePost(@Args() { data, where }: PostUpdateArgs, @Ctx() ctx: Context): Promise { + async updatePost(@Args() { data, where }: PostUpdateArgs, @Ctx() ctx: BaseContext): Promise { return this.update(data, where, ctx.user.id); } @Mutation(returns => StandardDeleteResponse) - async deletePost(@Arg('where') where: PostWhereUniqueInput, @Ctx() ctx: Context): Promise { + async deletePost( + @Arg('where') where: PostWhereUniqueInput, + @Ctx() ctx: BaseContext + ): Promise { return this.delete(where, ctx.user.id); } } diff --git a/examples/3-one-to-many-relationship/src/user.entity.ts b/examples/3-one-to-many-relationship/src/user.model.ts similarity index 85% rename from examples/3-one-to-many-relationship/src/user.entity.ts rename to examples/3-one-to-many-relationship/src/user.model.ts index 0b381cd6..fdef4369 100644 --- a/examples/3-one-to-many-relationship/src/user.entity.ts +++ b/examples/3-one-to-many-relationship/src/user.model.ts @@ -1,6 +1,6 @@ import { BaseModel, Model, OneToMany, StringField } from '../../../src'; -import { Post } from './post.entity'; +import { Post } from './post.model'; @Model() export class User extends BaseModel { diff --git a/examples/3-one-to-many-relationship/src/user.resolver.ts b/examples/3-one-to-many-relationship/src/user.resolver.ts index 9fd23303..77c1aa18 100644 --- a/examples/3-one-to-many-relationship/src/user.resolver.ts +++ b/examples/3-one-to-many-relationship/src/user.resolver.ts @@ -1,13 +1,13 @@ import { GraphQLResolveInfo } from 'graphql'; -import { Arg, Args, Ctx, FieldResolver, Mutation, Query, Root, Resolver } from 'type-graphql'; +import { Arg, Args, Ctx, FieldResolver, Mutation, Query, Resolver, Root } from 'type-graphql'; import { Repository } from 'typeorm'; import { InjectRepository } from 'typeorm-typedi-extensions'; -import { BaseResolver, Context, StandardDeleteResponse } from '../../../src'; +import { BaseContext, BaseResolver, StandardDeleteResponse } from '../../../src'; import { UserCreateInput, UserUpdateArgs, UserWhereArgs, UserWhereInput, UserWhereUniqueInput } from '../generated'; -import { User } from './user.entity'; -import { Post } from './post.entity'; +import { Post } from './post.model'; +import { User } from './user.model'; // Note: we have to specify `User` here instead of (of => User) because for some reason this // changes the object reference when it's trying to add the FieldResolver and things break @@ -18,14 +18,14 @@ export class UserResolver extends BaseResolver { } @FieldResolver() - posts(@Root() user: User, @Ctx() ctx: Context): Promise { + posts(@Root() user: User, @Ctx() ctx: BaseContext): Promise { return ctx.dataLoader.loaders.User.posts.load(user); } @Query(returns => [User]) async users( @Args() { where, orderBy, limit, offset }: UserWhereArgs, - @Ctx() ctx: Context, + @Ctx() ctx: BaseContext, info: GraphQLResolveInfo ): Promise { return this.find(where, orderBy, limit, offset); @@ -37,17 +37,20 @@ export class UserResolver extends BaseResolver { } @Mutation(returns => User) - async createUser(@Arg('data') data: UserCreateInput, @Ctx() ctx: Context): Promise { + async createUser(@Arg('data') data: UserCreateInput, @Ctx() ctx: BaseContext): Promise { return this.create(data, ctx.user.id); } @Mutation(returns => User) - async updateUser(@Args() { data, where }: UserUpdateArgs, @Ctx() ctx: Context): Promise { + async updateUser(@Args() { data, where }: UserUpdateArgs, @Ctx() ctx: BaseContext): Promise { return this.update(data, where, ctx.user.id); } @Mutation(returns => StandardDeleteResponse) - async deleteUser(@Arg('where') where: UserWhereUniqueInput, @Ctx() ctx: Context): Promise { + async deleteUser( + @Arg('where') where: UserWhereUniqueInput, + @Ctx() ctx: BaseContext + ): Promise { return this.delete(where, ctx.user.id); } } diff --git a/examples/3-one-to-many-relationship/tslint.json b/examples/3-one-to-many-relationship/tslint.json new file mode 100644 index 00000000..b97234e7 --- /dev/null +++ b/examples/3-one-to-many-relationship/tslint.json @@ -0,0 +1,20 @@ +{ + "extends": ["typestrict", "tslint:latest", "tslint-config-prettier"], + "rules": { + "no-string-throw": false, + "class-name": false, + "interface-over-type-literal": false, + "interface-name": [false], + "max-classes-per-file": false, + "member-access": [false], + "no-submodule-imports": false, + "no-unused-variable": false, + "no-implicit-dependencies": false, + "no-var-keyword": false, + "no-floating-promises": true, + "no-console": false + }, + "linterOptions": { + "exclude": ["node_modules/**/*", "generated/*", "src/migration/*"] + } +} diff --git a/examples/4-many-to-many-relationship/src/join-with-metadata/role.entity.ts b/examples/4-many-to-many-relationship/src/join-with-metadata/role.model.ts similarity index 83% rename from examples/4-many-to-many-relationship/src/join-with-metadata/role.entity.ts rename to examples/4-many-to-many-relationship/src/join-with-metadata/role.model.ts index 2d35c5be..435766a5 100644 --- a/examples/4-many-to-many-relationship/src/join-with-metadata/role.entity.ts +++ b/examples/4-many-to-many-relationship/src/join-with-metadata/role.model.ts @@ -1,6 +1,6 @@ import { BaseModel, Model, OneToMany, StringField } from '../../../../src'; -import { UserRole } from './user-role.entity'; +import { UserRole } from './user-role.model'; @Model() export class Role extends BaseModel { diff --git a/examples/4-many-to-many-relationship/src/join-with-metadata/role.resolver.ts b/examples/4-many-to-many-relationship/src/join-with-metadata/role.resolver.ts index e87ba57b..94634218 100644 --- a/examples/4-many-to-many-relationship/src/join-with-metadata/role.resolver.ts +++ b/examples/4-many-to-many-relationship/src/join-with-metadata/role.resolver.ts @@ -3,9 +3,9 @@ import { Arg, Args, Ctx, Mutation, Query, Resolver } from 'type-graphql'; import { Repository } from 'typeorm'; import { InjectRepository } from 'typeorm-typedi-extensions'; -import { BaseResolver, Context } from '../../../../src'; +import { BaseContext, BaseResolver } from '../../../../src'; import { RoleCreateInput, RoleWhereArgs, RoleWhereInput } from '../../generated'; -import { Role } from './role.entity'; +import { Role } from './role.model'; @Resolver(Role) export class RoleResolver extends BaseResolver { @@ -16,14 +16,14 @@ export class RoleResolver extends BaseResolver { @Query(returns => [Role]) async roles( @Args() { where, orderBy, limit, offset }: RoleWhereArgs, - @Ctx() ctx: Context, + @Ctx() ctx: BaseContext, info: GraphQLResolveInfo ): Promise { return this.find(where, orderBy, limit, offset); } @Mutation(returns => Role) - async createRole(@Arg('data') data: RoleCreateInput, @Ctx() ctx: Context): Promise { + async createRole(@Arg('data') data: RoleCreateInput, @Ctx() ctx: BaseContext): Promise { return this.create(data, ctx.user.id); } } diff --git a/examples/4-many-to-many-relationship/src/join-with-metadata/user-role.entity.ts b/examples/4-many-to-many-relationship/src/join-with-metadata/user-role.model.ts similarity index 86% rename from examples/4-many-to-many-relationship/src/join-with-metadata/user-role.entity.ts rename to examples/4-many-to-many-relationship/src/join-with-metadata/user-role.model.ts index 79124700..17280596 100644 --- a/examples/4-many-to-many-relationship/src/join-with-metadata/user-role.entity.ts +++ b/examples/4-many-to-many-relationship/src/join-with-metadata/user-role.model.ts @@ -1,7 +1,7 @@ import { BaseModel, ManyToOne, Model, StringField } from '../../../../src'; -import { Role } from './role.entity'; -import { User } from './user.entity'; +import { Role } from './role.model'; +import { User } from './user.model'; // This is a modified many-to-many table that also allows // for additional metadata as a typical many-to-many is just diff --git a/examples/4-many-to-many-relationship/src/join-with-metadata/user-role.resolver.ts b/examples/4-many-to-many-relationship/src/join-with-metadata/user-role.resolver.ts index 39503db9..755604ca 100644 --- a/examples/4-many-to-many-relationship/src/join-with-metadata/user-role.resolver.ts +++ b/examples/4-many-to-many-relationship/src/join-with-metadata/user-role.resolver.ts @@ -1,13 +1,13 @@ import { GraphQLResolveInfo } from 'graphql'; -import { Arg, Args, ArgsType, Field, Ctx, FieldResolver, Mutation, Query, Resolver, Root } from 'type-graphql'; +import { Arg, Args, ArgsType, Ctx, Field, FieldResolver, Mutation, Query, Resolver, Root } from 'type-graphql'; import { Repository } from 'typeorm'; import { InjectRepository } from 'typeorm-typedi-extensions'; -import { BaseResolver, Context } from '../../../../src'; +import { BaseContext, BaseResolver } from '../../../../src'; import { UserRoleCreateInput, UserRoleCreateManyArgs, UserRoleWhereArgs, UserRoleWhereInput } from '../../generated'; -import { UserRole } from './user-role.entity'; -import { User } from './user.entity'; -import { Role } from './role.entity'; +import { Role } from './role.model'; +import { UserRole } from './user-role.model'; +import { User } from './user.model'; @Resolver(UserRole) export class UserRoleResolver extends BaseResolver { @@ -16,33 +16,31 @@ export class UserRoleResolver extends BaseResolver { } @FieldResolver(returns => User) - user(@Root() userRole: UserRole, @Ctx() ctx: Context): Promise { - console.log('userRole: ', userRole); + user(@Root() userRole: UserRole, @Ctx() ctx: BaseContext): Promise { return ctx.dataLoader.loaders.UserRole.user.load(userRole); } @FieldResolver(returns => Role) - role(@Root() userRole: UserRole, @Ctx() ctx: Context): Promise { - console.log('userRole: ', userRole); + role(@Root() userRole: UserRole, @Ctx() ctx: BaseContext): Promise { return ctx.dataLoader.loaders.UserRole.role.load(userRole); } @Query(returns => [UserRole]) async userRoles( @Args() { where, orderBy, limit, offset }: UserRoleWhereArgs, - @Ctx() ctx: Context, + @Ctx() ctx: BaseContext, info: GraphQLResolveInfo ): Promise { return this.find(where, orderBy, limit, offset); } @Mutation(returns => UserRole) - async createUserRole(@Arg('data') data: UserRoleCreateInput, @Ctx() ctx: Context): Promise { + async createUserRole(@Arg('data') data: UserRoleCreateInput, @Ctx() ctx: BaseContext): Promise { return this.create(data, ctx.user.id); } @Mutation(returns => [UserRole]) - async createManyUserRoles(@Args() { data }: UserRoleCreateManyArgs, @Ctx() ctx: Context): Promise { + async createManyUserRoles(@Args() { data }: UserRoleCreateManyArgs, @Ctx() ctx: BaseContext): Promise { return this.createMany(data, ctx.user.id); } } diff --git a/examples/4-many-to-many-relationship/src/join-with-metadata/user.entity.ts b/examples/4-many-to-many-relationship/src/join-with-metadata/user.model.ts similarity index 84% rename from examples/4-many-to-many-relationship/src/join-with-metadata/user.entity.ts rename to examples/4-many-to-many-relationship/src/join-with-metadata/user.model.ts index 67fed42f..9277a07b 100644 --- a/examples/4-many-to-many-relationship/src/join-with-metadata/user.entity.ts +++ b/examples/4-many-to-many-relationship/src/join-with-metadata/user.model.ts @@ -1,6 +1,6 @@ import { BaseModel, Model, OneToMany, StringField } from '../../../../src'; -import { UserRole } from './user-role.entity'; +import { UserRole } from './user-role.model'; @Model() export class User extends BaseModel { diff --git a/examples/4-many-to-many-relationship/src/join-with-metadata/user.resolver.ts b/examples/4-many-to-many-relationship/src/join-with-metadata/user.resolver.ts index 2c4a3366..ac8ad42f 100644 --- a/examples/4-many-to-many-relationship/src/join-with-metadata/user.resolver.ts +++ b/examples/4-many-to-many-relationship/src/join-with-metadata/user.resolver.ts @@ -3,10 +3,10 @@ import { Arg, Args, Ctx, FieldResolver, Mutation, Query, Resolver, Root } from ' import { Repository } from 'typeorm'; import { InjectRepository } from 'typeorm-typedi-extensions'; -import { BaseResolver, Context } from '../../../../src'; +import { BaseContext, BaseResolver } from '../../../../src'; import { UserCreateInput, UserWhereArgs, UserWhereInput } from '../../generated'; -import { UserRole } from './user-role.entity'; -import { User } from './user.entity'; +import { UserRole } from './user-role.model'; +import { User } from './user.model'; @Resolver(User) export class UserResolver extends BaseResolver { @@ -15,23 +15,21 @@ export class UserResolver extends BaseResolver { } @FieldResolver(returns => [UserRole]) - userRoles(@Root() user: User, @Ctx() ctx: Context): Promise { - console.log('user: ', user); - + userRoles(@Root() user: User, @Ctx() ctx: BaseContext): Promise { return ctx.dataLoader.loaders.User.userRoles.load(user); } @Query(returns => [User]) async users( @Args() { where, orderBy, limit, offset }: UserWhereArgs, - @Ctx() ctx: Context, + @Ctx() ctx: BaseContext, info: GraphQLResolveInfo ): Promise { return this.find(where, orderBy, limit, offset); } @Mutation(returns => User) - async createUser(@Arg('data') data: UserCreateInput, @Ctx() ctx: Context): Promise { + async createUser(@Arg('data') data: UserCreateInput, @Ctx() ctx: BaseContext): Promise { return this.create(data, ctx.user.id); } } diff --git a/examples/4-many-to-many-relationship/src/simple-join-table/author.entity.ts b/examples/4-many-to-many-relationship/src/simple-join-table/author.model.ts similarity index 85% rename from examples/4-many-to-many-relationship/src/simple-join-table/author.entity.ts rename to examples/4-many-to-many-relationship/src/simple-join-table/author.model.ts index 17a19cd1..85a84b0c 100644 --- a/examples/4-many-to-many-relationship/src/simple-join-table/author.entity.ts +++ b/examples/4-many-to-many-relationship/src/simple-join-table/author.model.ts @@ -1,6 +1,6 @@ import { BaseModel, Model, OneToMany, StringField } from '../../../../src'; -import { Post } from './post.entity'; +import { Post } from './post.model'; @Model() export class Author extends BaseModel { diff --git a/examples/4-many-to-many-relationship/src/simple-join-table/author.resolver.ts b/examples/4-many-to-many-relationship/src/simple-join-table/author.resolver.ts index e7aa2cfd..735cddb1 100644 --- a/examples/4-many-to-many-relationship/src/simple-join-table/author.resolver.ts +++ b/examples/4-many-to-many-relationship/src/simple-join-table/author.resolver.ts @@ -3,10 +3,10 @@ import { Arg, Args, Ctx, FieldResolver, Mutation, Query, Resolver, Root } from ' import { Repository } from 'typeorm'; import { InjectRepository } from 'typeorm-typedi-extensions'; -import { BaseResolver, Context } from '../../../../src'; +import { BaseContext, BaseResolver } from '../../../../src'; import { AuthorCreateInput, AuthorWhereArgs, AuthorWhereInput } from '../../generated'; -import { Author } from './author.entity'; -import { Post } from './post.entity'; +import { Author } from './author.model'; +import { Post } from './post.model'; @Resolver(Author) export class AuthorResolver extends BaseResolver { @@ -15,23 +15,21 @@ export class AuthorResolver extends BaseResolver { } @FieldResolver(returns => [Post]) - posts(@Root() author: Author, @Ctx() ctx: Context): Promise { - console.log('author: ', author); - + posts(@Root() author: Author, @Ctx() ctx: BaseContext): Promise { return ctx.dataLoader.loaders.Author.posts.load(author); } @Query(returns => [Author]) async authors( @Args() { where, orderBy, limit, offset }: AuthorWhereArgs, - @Ctx() ctx: Context, + @Ctx() ctx: BaseContext, info: GraphQLResolveInfo ): Promise { return this.find(where, orderBy, limit, offset); } @Mutation(returns => Author) - async createAuthor(@Arg('data') data: AuthorCreateInput, @Ctx() ctx: Context): Promise { + async createAuthor(@Arg('data') data: AuthorCreateInput, @Ctx() ctx: BaseContext): Promise { return this.create(data, ctx.user.id); } } diff --git a/examples/4-many-to-many-relationship/src/simple-join-table/post.entity.ts b/examples/4-many-to-many-relationship/src/simple-join-table/post.model.ts similarity index 84% rename from examples/4-many-to-many-relationship/src/simple-join-table/post.entity.ts rename to examples/4-many-to-many-relationship/src/simple-join-table/post.model.ts index 250301d3..bded6633 100644 --- a/examples/4-many-to-many-relationship/src/simple-join-table/post.entity.ts +++ b/examples/4-many-to-many-relationship/src/simple-join-table/post.model.ts @@ -1,6 +1,6 @@ import { BaseModel, Model, OneToMany, StringField } from '../../../../src'; -import { Author } from './author.entity'; +import { Author } from './author.model'; @Model() export class Post extends BaseModel { diff --git a/examples/4-many-to-many-relationship/src/simple-join-table/post.resolver.ts b/examples/4-many-to-many-relationship/src/simple-join-table/post.resolver.ts index 03e9e64f..3d1994e0 100644 --- a/examples/4-many-to-many-relationship/src/simple-join-table/post.resolver.ts +++ b/examples/4-many-to-many-relationship/src/simple-join-table/post.resolver.ts @@ -3,11 +3,11 @@ import { Arg, Args, Ctx, FieldResolver, Mutation, Query, Resolver, Root } from ' import { Repository } from 'typeorm'; import { InjectRepository } from 'typeorm-typedi-extensions'; -import { BaseResolver, Context } from '../../../../src'; +import { BaseContext, BaseResolver } from '../../../../src'; import { PostCreateInput, PostWhereArgs, PostWhereInput } from '../../generated'; -import { Author } from './author.entity'; -import { Post } from './post.entity'; +import { Author } from './author.model'; +import { Post } from './post.model'; @Resolver(Post) export class PostResolver extends BaseResolver { @@ -16,23 +16,21 @@ export class PostResolver extends BaseResolver { } @FieldResolver(returns => [Post]) - posts(@Root() author: Author, @Ctx() ctx: Context): Promise { - console.log('author: ', author); - + posts(@Root() author: Author, @Ctx() ctx: BaseContext): Promise { return ctx.dataLoader.loaders.Author.posts.load(author); } @Query(returns => [Post]) async roles( @Args() { where, orderBy, limit, offset }: PostWhereArgs, - @Ctx() ctx: Context, + @Ctx() ctx: BaseContext, info: GraphQLResolveInfo ): Promise { return this.find(where, orderBy, limit, offset); } @Mutation(returns => Post) - async createPost(@Arg('data') data: PostCreateInput, @Ctx() ctx: Context): Promise { + async createPost(@Arg('data') data: PostCreateInput, @Ctx() ctx: BaseContext): Promise { return this.create(data, ctx.user.id); } } diff --git a/examples/4-many-to-many-relationship/tslint.json b/examples/4-many-to-many-relationship/tslint.json new file mode 100644 index 00000000..b97234e7 --- /dev/null +++ b/examples/4-many-to-many-relationship/tslint.json @@ -0,0 +1,20 @@ +{ + "extends": ["typestrict", "tslint:latest", "tslint-config-prettier"], + "rules": { + "no-string-throw": false, + "class-name": false, + "interface-over-type-literal": false, + "interface-name": [false], + "max-classes-per-file": false, + "member-access": [false], + "no-submodule-imports": false, + "no-unused-variable": false, + "no-implicit-dependencies": false, + "no-var-keyword": false, + "no-floating-promises": true, + "no-console": false + }, + "linterOptions": { + "exclude": ["node_modules/**/*", "generated/*", "src/migration/*"] + } +} diff --git a/package.json b/package.json index 4ce94e06..bcad019b 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,13 @@ "files": [ "dist" ], - "author": "Dan Caddigan", + "author": { + "name": "Dan Caddigan", + "url": "https://github.com/goldcaddy77" + }, "scripts": { "build": "yarn tsc", + "lint": "tslint --fix -c ./tslint.json -p ./tsconfig.json", "semantic-release": "semantic-release", "test": "jest --verbose --coverage", "test:watch": "jest --verbose --watch", @@ -68,7 +72,9 @@ "graphql": "^14.0.2", "graphql-binding": "^2.4.0", "graphql-iso-date": "^3.6.1", + "graphql-tools": "^4.0.3", "lodash": "^4.17.11", + "mkdirp": "^0.5.1", "node-emoji": "^1.8.1", "pg": "^7.7.1", "reflect-metadata": "^0.1.12", @@ -96,9 +102,15 @@ "prettier": "^1.15.3", "semantic-release": "^15.13.2", "ts-jest": "^23.10.5", - "tslint": "^5.12.0" + "tslint": "^5.12.0", + "tslint-config-prettier": "^1.17.0" }, "lint-staged": { + "*.ts": [ + "tslint --fix", + "prettier --write", + "git add" + ], "*.{js,json}": [ "prettier --write", "git add" diff --git a/src/core/Context.ts b/src/core/Context.ts index 4103cfbd..31f53cac 100644 --- a/src/core/Context.ts +++ b/src/core/Context.ts @@ -2,12 +2,12 @@ import { Request } from 'express'; // tslint:disable-line import { Connection } from 'typeorm'; // TODO-MVP: update with actual context we're getting from Auth0 -export interface Context { +export interface BaseContext { connection: Connection; - request: Request; - user?: any; dataLoader: { initialized: boolean; loaders: { [key: string]: { [key: string]: any } }; }; + request: Request; + user?: any; } diff --git a/src/core/app.ts b/src/core/app.ts index 4b9a7dbd..3cb3af08 100644 --- a/src/core/app.ts +++ b/src/core/app.ts @@ -2,64 +2,87 @@ // import { GraphQLDate, GraphQLDateTime, GraphQLTime } from 'graphql-iso-date'; import { ApolloServer } from 'apollo-server-express'; -import express = require('express'); import { Request } from 'express'; +import express = require('express'); import { writeFileSync } from 'fs'; -import { printSchema, GraphQLSchema } from 'graphql'; +import { GraphQLSchema, printSchema } from 'graphql'; import { Binding } from 'graphql-binding'; import { Server as HttpServer } from 'http'; import { Server as HttpsServer } from 'https'; import * as mkdirp from 'mkdirp'; import * as path from 'path'; -import { buildSchema, useContainer as TypeGraphQLUseContainer } from 'type-graphql'; // formatArgumentValidationError +import { AuthChecker, buildSchema, useContainer as TypeGraphQLUseContainer } from 'type-graphql'; // formatArgumentValidationError +import { Container } from 'typedi'; import { Connection, ConnectionOptions, useContainer as TypeORMUseContainer } from 'typeorm'; -import { getRemoteBinding, Context } from './'; // logger +import { logger, Logger } from '../core/logger'; +import { generateBindingFile, getRemoteBinding } from '../gql'; import { DataLoaderMiddleware, healthCheckMiddleware } from '../middleware'; import { SchemaGenerator } from '../schema/'; import { authChecker } from '../tgql'; import { createDBConnection } from '../torm'; -import { generateBindingFile } from './binding'; -export interface AppOptions { - container?: any; // TODO: fix types - Container from typeDI +import { BaseContext } from './Context'; +import { Maybe } from './types'; + +export interface AppOptions { + authChecker?: AuthChecker; + container?: Container; + context?: (request: Request) => object; host?: string; generatedFolder?: string; middlewares?: any[]; // TODO: fix port?: string | number; warthogImportPath?: string; } -export class App { - // create TypeORM connection - connection!: Connection; - httpServer!: HttpServer | HttpsServer; - graphQLServer!: ApolloServer; + +export class App { appHost: string; appPort: number; + authChecker: AuthChecker; + connection!: Connection; + context: (request: Request) => object; generatedFolder: string; + graphQLServer!: ApolloServer; + httpServer!: HttpServer | HttpsServer; + logger: Logger; schema?: GraphQLSchema; - constructor(private appOptions: AppOptions, private dbOptions: Partial = {}) { + constructor(private appOptions: AppOptions, private dbOptions: Partial = {}) { if (!process.env.NODE_ENV) { throw new Error("NODE_ENV must be set - use 'development' locally"); } + // Ensure that Warthog, TypeORM and TypeGraphQL are all using the same typedi container if (this.appOptions.container) { - // register 3rd party IOC container - TypeGraphQLUseContainer(this.appOptions.container); - TypeORMUseContainer(this.appOptions.container); + TypeGraphQLUseContainer(this.appOptions.container as any); // TODO: fix any + TypeORMUseContainer(this.appOptions.container as any); // TODO: fix any } - const host: string | undefined = this.appOptions.host || process.env.APP_HOST; + const host: Maybe = this.appOptions.host || process.env.APP_HOST; if (!host) { throw new Error('`host` is required'); } - this.appHost = host; + this.appPort = parseInt(String(this.appOptions.port || process.env.APP_PORT), 10) || 4000; + + this.authChecker = this.appOptions.authChecker || authChecker; + + // Use https://github.com/inxilpro/node-app-root-path to find project root this.generatedFolder = this.appOptions.generatedFolder || path.join(process.cwd(), 'generated'); + this.logger = Container.has('LOGGER') ? Container.get('LOGGER') : logger; + + const returnEmpty = () => { + return {}; + }; + this.context = this.appOptions.context || returnEmpty; + + this.createGeneratedFolder(); + } - mkdirp.sync(this.generatedFolder); + createGeneratedFolder() { + return mkdirp.sync(this.generatedFolder); } async establishDBConnection(): Promise { @@ -87,20 +110,10 @@ export class App { return generateBindingFile(schemaFilePath, outputBindingPath); } - async generateTypes() { - await this.establishDBConnection(); - - return SchemaGenerator.generate( - this.connection.entityMetadatas, - this.generatedFolder, - this.appOptions.warthogImportPath - ); - } - async buildGraphQLSchema(): Promise { if (!this.schema) { this.schema = await buildSchema({ - authChecker, + authChecker: this.authChecker, // TODO: ErrorLoggerMiddleware globalMiddlewares: [DataLoaderMiddleware, ...(this.appOptions.middlewares || [])], resolvers: [process.cwd() + '/**/*.resolver.ts'] @@ -111,47 +124,25 @@ export class App { return this.schema; } - async writeSchemaFile() { - // Write schema to dist folder so that it's available in package - return writeFileSync(path.join(this.generatedFolder, 'schema.graphql'), printSchema(this.schema as GraphQLSchema), { - encoding: 'utf8', - flag: 'w' - }); - } - - async writeGeneratedIndexFile() { - // Write schema to dist folder so that it's available in package - const contents = `export * from './classes';`; - return writeFileSync(path.join(this.generatedFolder, 'index.ts'), contents, { - encoding: 'utf8', - flag: 'w' - }); - } - async start() { await this.writeGeneratedIndexFile(); await this.establishDBConnection(); - await this.generateTypes(); - await this.buildGraphQLSchema(); + await this.writeGeneratedTSTypes(); await this.writeSchemaFile(); await this.generateBinding(); this.graphQLServer = new ApolloServer({ - context: (options: { request: Request; context?: any }) => { - const context: Context = { + context: (options: { req: Request }) => { + return { connection: this.connection, dataLoader: { initialized: false, loaders: {} }, - request: options.request, - user: { - email: 'admin@test.com', - id: 'abc12345', - permissions: ['user:read', 'user:update', 'user:create', 'user:delete', 'photo:delete'] - } + request: options.req, + // Allows consumer to add to the context object - ex. context.user + ...this.context(options.req) }; - return { ...context, ...(options.context || {}) }; }, schema: this.schema }); @@ -162,18 +153,47 @@ export class App { this.graphQLServer.applyMiddleware({ app, path: '/graphql' }); this.httpServer = app.listen({ port: this.appPort }, () => - console.log(`🚀 Server ready at http://${this.appHost}:${this.appPort}${this.graphQLServer.graphqlPath}`) + this.logger.info(`🚀 Server ready at http://${this.appHost}:${this.appPort}${this.graphQLServer.graphqlPath}`) ); return this; } async stop() { - console.log('Stopping HTTP Server'); - await this.httpServer.close(); - console.log('Closing DB Connection'); + this.logger.info('Stopping HTTP Server'); + this.httpServer.close(); + this.logger.info('Closing DB Connection'); await this.connection.close(); } -} -export { ConnectionOptions }; + private async writeGeneratedTSTypes() { + const generatedTSTypes = await this.getGeneratedTypes(); + + return this.writeToGeneratedFolder('classes.ts', generatedTSTypes); + } + + private async getGeneratedTypes() { + await this.establishDBConnection(); + + return SchemaGenerator.generate(this.connection.entityMetadatas, this.appOptions.warthogImportPath); + } + + private async writeSchemaFile() { + await this.buildGraphQLSchema(); + + return this.writeToGeneratedFolder('schema.graphql', printSchema(this.schema as GraphQLSchema)); + } + + // Write an index file that loads `classes` so that you can just import `../../generated` + // in your resolvers + private async writeGeneratedIndexFile() { + return this.writeToGeneratedFolder('index.ts', `export * from './classes';`); + } + + private async writeToGeneratedFolder(filename: string, contents: string) { + return writeFileSync(path.join(this.generatedFolder, filename), contents, { + encoding: 'utf8', + flag: 'w' + }); + } +} diff --git a/src/core/index.ts b/src/core/index.ts index ebad6404..6dd5a23e 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,5 +1,4 @@ export * from './app'; export * from './BaseModel'; -export * from './binding'; -export { Context } from './Context'; +export * from './Context'; export * from './types'; diff --git a/src/core/logger.ts b/src/core/logger.ts index 47e132ff..be684632 100644 --- a/src/core/logger.ts +++ b/src/core/logger.ts @@ -1,2 +1,18 @@ +import * as Debug from 'debug'; + // TODO: better logger -export const logger = console; +export const logger = { + error: Debug('warthog:error'), + info: Debug('warthog:info'), + log: Debug('warthog:log'), + warn: Debug('warthog:warn') +}; + +type logFunc = (...args: any[]) => void; + +export interface Logger { + error: logFunc; + info: logFunc; + log: logFunc; + warn: logFunc; +} diff --git a/src/core/types.ts b/src/core/types.ts index 0e3f39ce..6e7a7771 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -20,6 +20,6 @@ export type DeleteReponse = { id: IDType; }; -export interface ClassType { - new (...args: any[]): T; -} +export type ClassType = new (...args: any[]) => T; + +export type Maybe = T | void; diff --git a/src/decorators/EnumField.test.ts b/src/decorators/EnumField.test.ts index 86b85cb4..81b53a68 100644 --- a/src/decorators/EnumField.test.ts +++ b/src/decorators/EnumField.test.ts @@ -1,6 +1,6 @@ import 'reflect-metadata'; -import { IntrospectionSchema, IntrospectionEnumType } from 'graphql'; +import { IntrospectionEnumType, IntrospectionSchema } from 'graphql'; import { ObjectType, Query, Resolver } from 'type-graphql'; import { getSchemaInfo } from '../schema'; diff --git a/src/decorators/EnumField.ts b/src/decorators/EnumField.ts index 7e32b821..e6f00511 100644 --- a/src/decorators/EnumField.ts +++ b/src/decorators/EnumField.ts @@ -1,4 +1,4 @@ -const caller = require('caller'); +const caller = require('caller'); // tslint:disable-line:no-var-requires import { Field, registerEnumType } from 'type-graphql'; import { Column } from 'typeorm'; diff --git a/src/decorators/Model.ts b/src/decorators/Model.ts index 7b51215e..857b6351 100644 --- a/src/decorators/Model.ts +++ b/src/decorators/Model.ts @@ -1,9 +1,9 @@ -const caller = require('caller'); +const caller = require('caller'); // tslint:disable-line:no-var-requires import { ObjectType } from 'type-graphql'; import { Entity } from 'typeorm'; import { getMetadataStorage } from '../metadata'; -import { composeClassDecorators, ClassDecoratorFactory } from '../utils/'; +import { ClassDecoratorFactory, composeClassDecorators } from '../utils/'; interface ModelOptions { auditTableName?: string; diff --git a/src/core/binding.ts b/src/gql/binding.ts similarity index 88% rename from src/core/binding.ts rename to src/gql/binding.ts index c6654d0d..9c1ef7d5 100644 --- a/src/core/binding.ts +++ b/src/gql/binding.ts @@ -1,14 +1,14 @@ import { onError } from 'apollo-link-error'; import { HttpLink } from 'apollo-link-http'; import * as fetch from 'cross-fetch'; -import * as fs from 'fs'; import * as Debug from 'debug'; +import * as fs from 'fs'; import { buildSchema, GraphQLError, printSchema } from 'graphql'; import { Binding, TypescriptGenerator } from 'graphql-binding'; import { introspectSchema, makeRemoteExecutableSchema } from 'graphql-tools'; import * as path from 'path'; -import { StringMapOptional } from '..'; +import { StringMapOptional } from '../core/types'; const debug = Debug('binding'); @@ -19,20 +19,20 @@ interface LinkOptions extends StringMapOptional { export class Link extends HttpLink { constructor(uri: string, options: LinkOptions) { - let headers = { ...options }; + const headers: StringMapOptional = { ...options }; if (headers.token) { - headers['Authorization'] = `Bearer ${headers.token}`; + headers.Authorization = `Bearer ${headers.token}`; delete headers.token; } debug('headers', headers); super({ - uri, - headers, - // cross-fetch library is not a huge fan of TS + // TODO: cross-fetch library does not play nicely with TS // tslint:disable-next-line:no-any - fetch: (fetch as any) as (input: RequestInfo, init?: RequestInit) => Promise + fetch: (fetch as any) as (input: RequestInfo, init?: RequestInit) => Promise, + headers, + uri }); } } @@ -72,10 +72,10 @@ export async function generateBindingFile(inputSchemaPath: string, outputBinding const schema = buildSchema(sdl); const generatorOptions = { - schema, - isDefaultExport: false, inputSchemaPath: path.resolve(inputSchemaPath), - outputBindingPath: path.resolve(outputBindingFile) + isDefaultExport: false, + outputBindingPath: path.resolve(outputBindingFile), + schema }; const generatorInstance = new TypescriptGenerator(generatorOptions); diff --git a/src/gql/index.ts b/src/gql/index.ts new file mode 100644 index 00000000..1ad87729 --- /dev/null +++ b/src/gql/index.ts @@ -0,0 +1 @@ +export * from './binding'; diff --git a/src/metadata/metadata-storage.ts b/src/metadata/metadata-storage.ts index e8a6f8d3..68f95f8e 100644 --- a/src/metadata/metadata-storage.ts +++ b/src/metadata/metadata-storage.ts @@ -12,17 +12,17 @@ export class MetadataStorage { addEnum(tableName: string, columnName: string, enumName: string, enumValues: any, filename: string) { this.enumMap[tableName] = this.enumMap[tableName] || {}; this.enumMap[tableName][columnName] = { - name: enumName, enumeration: enumValues, - filename + filename, + name: enumName }; } addModel(name: string, klass: any, filename: string) { this.classMap[name] = { - name, + filename, klass, - filename + name }; } diff --git a/src/middleware/DataLoaderMiddleware.ts b/src/middleware/DataLoaderMiddleware.ts index 1679df14..c87c486c 100644 --- a/src/middleware/DataLoaderMiddleware.ts +++ b/src/middleware/DataLoaderMiddleware.ts @@ -2,18 +2,18 @@ import DataLoader = require('dataloader'); import { MiddlewareInterface, NextFn, ResolverData } from 'type-graphql'; import { Service } from 'typedi'; -import { Context } from '../core'; +import { BaseContext } from '../core'; @Service() -export class DataLoaderMiddleware implements MiddlewareInterface { - async use({ root, args, context, info }: ResolverData, next: NextFn) { +export class DataLoaderMiddleware implements MiddlewareInterface { + async use({ root, args, context, info }: ResolverData, next: NextFn) { if (!context.dataLoader.initialized) { context.dataLoader = { initialized: true, loaders: {} }; - const loaders = context.dataLoader.loaders!; + const loaders = context.dataLoader.loaders; context.connection.entityMetadatas.forEach(entityMetadata => { const resolverName = entityMetadata.targetName; diff --git a/src/middleware/ErrorMiddleware.ts b/src/middleware/ErrorMiddleware.ts index 41373e3d..7028ff44 100644 --- a/src/middleware/ErrorMiddleware.ts +++ b/src/middleware/ErrorMiddleware.ts @@ -1,16 +1,16 @@ import { ArgumentValidationError, MiddlewareInterface, NextFn, ResolverData } from 'type-graphql'; import { Service } from 'typedi'; -import { Context } from '../core'; +import { BaseContext } from '../core'; @Service() -export class ErrorLoggerMiddleware implements MiddlewareInterface { +export class ErrorLoggerMiddleware implements MiddlewareInterface { // constructor(private readonly logger: Logger) {} constructor() { // } - async use({ context, info }: ResolverData, next: NextFn) { + async use({ context, info }: ResolverData, next: NextFn) { try { return await next(); } catch (err) { diff --git a/src/schema/SchemaGenerator.ts b/src/schema/SchemaGenerator.ts index f82ef5dd..5745bd3e 100644 --- a/src/schema/SchemaGenerator.ts +++ b/src/schema/SchemaGenerator.ts @@ -1,24 +1,25 @@ -import { writeFileSync } from 'fs'; -import * as path from 'path'; import * as prettier from 'prettier'; +import { Container } from 'typedi'; import { EntityMetadata } from 'typeorm'; +import { logger, Logger } from '../core/logger'; import { entityListToImports, + entityToCreateInput, + entityToCreateManyArgs, entityToOrderByEnum, + entityToUpdateInput, + entityToUpdateInputArgs, entityToWhereArgs, - entityToCreateManyArgs, entityToWhereInput, - entityToWhereUniqueInput, - entityToCreateInput, - entityToUpdateInput, - entityToUpdateInputArgs + entityToWhereUniqueInput } from './TypeORMConverter'; export class SchemaGenerator { + static logger: Logger = Container.has('LOGGER') ? Container.get('LOGGER') : logger; + static generate( entities: EntityMetadata[], - destinationFolder: string, // This will reference 'warthog in the deployed module, but we need to do a relative import in the examples library warthogImportPath: string = 'warthog' ): string { @@ -45,24 +46,21 @@ export class SchemaGenerator { `; }); - writeFileSync(path.join(destinationFolder, 'classes.ts'), format(template), { - encoding: 'utf8', - flag: 'w' - }); - - return template; + return this.format(template); } -} -function format(code: string, options: prettier.Options = {}) { - try { - // TODO: grab our prettier options (single quote, etc...) - return prettier.format(code, { - ...options, - parser: 'typescript' - }); - } catch (e) { - console.log(`There is a syntax error in generated code, unformatted code printed, error: ${JSON.stringify(e)}`); - return code; + static format(code: string, options: prettier.Options = {}) { + try { + // TODO: grab our prettier options (single quote, etc...) + return prettier.format(code, { + ...options, + parser: 'typescript' + }); + } catch (e) { + this.logger.log( + `There is a syntax error in generated code, unformatted code printed, error: ${JSON.stringify(e)}` + ); + return code; + } } } diff --git a/src/schema/TypeORMConverter.ts b/src/schema/TypeORMConverter.ts index 95ae8600..6e181589 100644 --- a/src/schema/TypeORMConverter.ts +++ b/src/schema/TypeORMConverter.ts @@ -1,16 +1,16 @@ import { - GraphQLInt, - GraphQLScalarType, - GraphQLString, GraphQLBoolean, - GraphQLFloat, GraphQLEnumType, - GraphQLID + GraphQLFloat, + GraphQLID, + GraphQLInt, + GraphQLScalarType, + GraphQLString } from 'graphql'; +import { GraphQLISODateTime } from 'type-graphql'; import { EntityMetadata } from 'typeorm'; import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'; import { UniqueMetadata } from 'typeorm/metadata/UniqueMetadata'; -import { GraphQLISODateTime } from 'type-graphql'; import { getMetadataStorage } from '../metadata'; const SYSTEM_FIELDS = ['createdAt', 'createdById', 'updatedAt', 'updatedById', 'deletedAt', 'deletedById']; @@ -25,8 +25,8 @@ function uniquesForEntity(entity: EntityMetadata): string[] { } export function entityListToImports(entities: EntityMetadata[]): string[] { - let imports: string[] = []; - let enumMap = getMetadataStorage().enumMap; + const imports: string[] = []; + const enumMap = getMetadataStorage().enumMap; Object.keys(enumMap).forEach((tableName: string) => { Object.keys(enumMap[tableName]).forEach((columnName: string) => { @@ -37,7 +37,7 @@ export function entityListToImports(entities: EntityMetadata[]): string[] { }); }); - let classMap = getMetadataStorage().classMap; + const classMap = getMetadataStorage().classMap; Object.keys(classMap).forEach((tableName: string) => { const classObj = classMap[tableName]; const filename = classObj.filename.replace(/\.(j|t)s$/, ''); @@ -334,8 +334,8 @@ export function columnToTypeScriptType(column: ColumnMetadata): string { const graphqlType = columnTypeToGraphQLDataType(column); const typeMap: any = { DateTime: 'string', - String: 'string', - ID: 'string' // TODO: should this be ID_TYPE? + ID: 'string', // TODO: should this be ID_TYPE? + String: 'string' }; return typeMap[graphqlType] || 'string'; diff --git a/src/schema/getSchemaInfo.ts b/src/schema/getSchemaInfo.ts index 5cbe1061..7cd4ad29 100644 --- a/src/schema/getSchemaInfo.ts +++ b/src/schema/getSchemaInfo.ts @@ -1,5 +1,5 @@ // Borrowed from https://github.com/19majkel94/type-graphql/blob/9778f9fab9e7f50363f2023b7ea366668e3d0ec9/tests/helpers/getSchemaInfo.ts -import { graphql, getIntrospectionQuery, IntrospectionObjectType, IntrospectionSchema } from 'graphql'; +import { getIntrospectionQuery, graphql, IntrospectionObjectType, IntrospectionSchema } from 'graphql'; import { buildSchema, BuildSchemaOptions } from 'type-graphql'; export async function getSchemaInfo(options: BuildSchemaOptions) { @@ -34,10 +34,10 @@ export async function getSchemaInfo(options: BuildSchemaOptions) { } return { + mutationType, + queryType, schema, schemaIntrospection, - queryType, - mutationType, subscriptionType }; } diff --git a/src/tgql/BaseResolver.ts b/src/tgql/BaseResolver.ts index c3047ca0..99c991d0 100644 --- a/src/tgql/BaseResolver.ts +++ b/src/tgql/BaseResolver.ts @@ -1,11 +1,11 @@ import { validate } from 'class-validator'; import { ArgumentValidationError } from 'type-graphql'; import { - getRepository, DeepPartial, Equal, FindManyOptions, FindOperator, + getRepository, In, IsNull, LessThan, @@ -99,7 +99,7 @@ export class BaseResolver { } // TODO: remove any when this is fixed: https://github.com/Microsoft/TypeScript/issues/21592 - return this.repository.save(obj as any, { reload: true }); + return this.repository.save(obj, { reload: true }); } async createMany(data: Array>, userId: string): Promise { @@ -145,7 +145,7 @@ export class BaseResolver { } // TODO: remove `any` - getting issue here - const result = await this.repository.save(merged as any); + const result = await this.repository.save(merged); return this.repository.findOneOrFail({ where: { id: result.id } }); } @@ -159,8 +159,7 @@ export class BaseResolver { const idData = ({ id: found.id } as any) as DeepPartial; const merged = this.repository.merge(new this.entityClass(), data as any, idData); - // TODO: remove `any` - getting issue here - await this.repository.save(merged as any); + await this.repository.save(merged); return { id: where.id }; } diff --git a/src/tgql/authChecker.ts b/src/tgql/authChecker.ts index 1df1f7c3..680f6dde 100644 --- a/src/tgql/authChecker.ts +++ b/src/tgql/authChecker.ts @@ -1,9 +1,9 @@ import { AuthChecker } from 'type-graphql'; -import { Context } from '../core/Context'; +import { BaseContext } from '../core/Context'; // This authChecker is used by type-graphql's @Authorized decorator -export const authChecker: AuthChecker = ({ context: { user } }, permissions) => { +export const authChecker: AuthChecker = ({ context: { user } }, permissions) => { if (!user) { return false; } diff --git a/src/torm/EverythingSubscriber.ts b/src/torm/EverythingSubscriber.ts index 15b1e421..35bf5b3d 100644 --- a/src/torm/EverythingSubscriber.ts +++ b/src/torm/EverythingSubscriber.ts @@ -1,5 +1,7 @@ // This subscriber will log all CUD operations (left off "read" as it would be too noisy) -import { EventSubscriber, EntitySubscriberInterface, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm'; +import { EntitySubscriberInterface, EventSubscriber, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm'; + +import { logger } from './../core/logger'; @EventSubscriber() export class EverythingSubscriber implements EntitySubscriberInterface { @@ -7,41 +9,41 @@ export class EverythingSubscriber implements EntitySubscriberInterface { * Called before entity insertion. */ beforeInsert(event: InsertEvent) { - console.log(`Before Insert: `, event.entity); + logger.info(`Before Insert: `, event.entity); } /** * Called before entity update. */ beforeUpdate(event: UpdateEvent) { - console.log(`BEFORE ENTITY UPDATED: `, event.entity); + logger.info(`BEFORE ENTITY UPDATED: `, event.entity); } /** * Called before entity deletion. */ beforeRemove(event: RemoveEvent) { - console.log(`BEFORE ENTITY WITH ID ${event.entityId} REMOVED: `, event.entity); + logger.info(`BEFORE ENTITY WITH ID ${event.entityId} REMOVED: `, event.entity); } /** * Called after entity insertion. */ afterInsert(event: InsertEvent) { - console.log(`AFTER ENTITY INSERTED: `, event.entity); + logger.info(`AFTER ENTITY INSERTED: `, event.entity); } /** * Called after entity update. */ afterUpdate(event: UpdateEvent) { - console.log(`AFTER ENTITY UPDATED: `, event.entity); + logger.info(`AFTER ENTITY UPDATED: `, event.entity); } /** * Called after entity deletion. */ afterRemove(event: RemoveEvent) { - console.log('Deleted ' + event.entity + ' with ID ' + event.entityId); + logger.info('Deleted', event.entity, 'with ID', event.entityId); } } diff --git a/src/torm/createConnection.ts b/src/torm/createConnection.ts index 314c5b41..1017992a 100644 --- a/src/torm/createConnection.ts +++ b/src/torm/createConnection.ts @@ -1,4 +1,4 @@ -import { createConnection, ConnectionOptions } from 'typeorm'; +import { ConnectionOptions, createConnection } from 'typeorm'; import { SnakeNamingStrategy } from './SnakeNamingStrategy'; @@ -6,26 +6,26 @@ export const createDBConnection = (dbOptions: Partial = {}) = // TODO: Fix any const baseConfig: any = { cli: { - entitiesDir: 'src/entity', + entitiesDir: 'src/models', migrationsDir: 'src/migration', subscribersDir: 'src/subscriber' }, database: process.env.TYPEORM_DATABASE, - entities: process.env.TYPEORM_ENTITIES || ['src/**/*.entity.ts'], + entities: process.env.TYPEORM_ENTITIES || ['src/**/*.model.ts'], host: process.env.TYPEORM_HOST || 'localhost', logger: 'advanced-console', logging: process.env.TYPEORM_LOGGING || 'all', migrations: ['src/migration/**/*.ts'], namingStrategy: new SnakeNamingStrategy(), + password: process.env.TYPEORM_PASSWORD, port: parseInt(process.env.TYPEORM_PORT || '', 10) || 5432, - subscribers: ['src/**/*.entity.ts'], + subscribers: ['src/**/*.model.ts'], synchronize: typeof process.env.TYPEORM_SYNCHRONIZE !== 'undefined' ? process.env.TYPEORM_SYNCHRONIZE : process.env.NODE_ENV === 'development', type: 'postgres', - username: process.env.TYPEORM_USERNAME, - password: process.env.TYPEORM_PASSWORD + username: process.env.TYPEORM_USERNAME }; const config = { diff --git a/src/torm/ormconfig.ts b/src/torm/ormconfig.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/utils/EntityCreator.ts b/src/utils/EntityCreator.ts deleted file mode 100644 index 773b0528..00000000 --- a/src/utils/EntityCreator.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { plainToClass } from 'class-transformer'; -import { ClassType } from '../core'; - -export function createEntity(entityType: ClassType, data: Partial): T { - return plainToClass>(entityType, data); -} diff --git a/src/utils/index.ts b/src/utils/index.ts index af1ad1c3..13476f6c 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,2 @@ export * from './decoratorComposer'; -export * from './EntityCreator'; +export * from './object-to-model'; diff --git a/src/utils/object-to-model.ts b/src/utils/object-to-model.ts new file mode 100644 index 00000000..3dd8768f --- /dev/null +++ b/src/utils/object-to-model.ts @@ -0,0 +1,6 @@ +import { plainToClass } from 'class-transformer'; +import { ClassType } from '../core'; + +export function objectToModel(modelType: ClassType, data: Partial): T { + return plainToClass>(modelType, data); +} diff --git a/tslint.json b/tslint.json index 3581595e..c62b189c 100644 --- a/tslint.json +++ b/tslint.json @@ -10,15 +10,13 @@ "class-name": false, "interface-over-type-literal": false, "interface-name": [false], - "jsx-no-multiline-js": false, "max-classes-per-file": false, - "max-line-length": [true, 120], "member-access": [false], "no-submodule-imports": false, "no-unused-variable": false, "no-implicit-dependencies": [true, "dev"], "no-var-keyword": false, - "no-floating-promises": false + "no-floating-promises": true }, "linterOptions": { "exclude": ["node_modules/**/*", "*/**/*.js", "*/**/generated/*.ts", "src/migration/*"] diff --git a/yarn.lock b/yarn.lock index 0a45a9b9..a965c3a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3200,7 +3200,7 @@ graphql-tag@^2.9.2: resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.0.tgz#87da024be863e357551b2b8700e496ee2d4353ae" integrity sha512-9FD6cw976TLLf9WYIUPCaaTpniawIjHWZSwIRZSjrfufJamcXbVVYfN2TWvJYbw0Xf2JjYbl1/f2+wDnBVw3/w== -graphql-tools@4.0.3, graphql-tools@^4.0.0: +graphql-tools@4.0.3, graphql-tools@^4.0.0, graphql-tools@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.3.tgz#23b5cb52c519212b1b2e4630a361464396ad264b" integrity sha512-NNZM0WSnVLX1zIMUxu7SjzLZ4prCp15N5L2T2ro02OVyydZ0fuCnZYRnx/yK9xjGWbZA0Q58yEO//Bv/psJWrg== @@ -7868,6 +7868,11 @@ tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +tslint-config-prettier@^1.17.0: + version "1.17.0" + resolved "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.17.0.tgz#946ed6117f98f3659a65848279156d87628c33dc" + integrity sha512-NKWNkThwqE4Snn4Cm6SZB7lV5RMDDFsBwz6fWUkTxOKGjMx8ycOHnjIbhn7dZd5XmssW3CwqUjlANR6EhP9YQw== + tslint-microsoft-contrib@^5.0.3: version "5.2.1" resolved "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.2.1.tgz#a6286839f800e2591d041ea2800c77487844ad81"