diff --git a/gql commands b/gql commands new file mode 100644 index 0000000..dbc6e56 --- /dev/null +++ b/gql commands @@ -0,0 +1,60 @@ + { + stores{ + id, + name, + city, + number, + postalCode, + street, + products{description,name,price} + } +} + +mutation{ + createStore( + input:{ + name: "asdf", + city: "asdf", + number:2, + postalCode: "hallohallo", + street: "asdf" + } + ) { + id, + name, + city, + number, + postalCode, + street + } +} + +query getStore($storeId: String!, $withProducts: Boolean!){ + store (id:$storeId){ + id, + name, + city, + number, + postalCode, + street, + products @include(if: $withProducts) {id,description,name,price} + } +} +var {"storeId": "5f2919aa-333a-4745-8166-3002ab30de0e","withProducts":true} + +mutation($productId:String!){ + createReservation( + input:{ + reservationProducts: + [ + { + productId:$productId, + quantity:2 + } + ] + } + ) { + id,date,reservationProducts{product{name,description,price},quantity} + } +} +var {"productId":"5bb3fbcc-7ec2-44fe-a04b-a0251cecf1e6"} \ No newline at end of file diff --git a/package.json b/package.json index 5987d8a..4782824 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,26 @@ { "name": "food-graphql-server", + "type": "module", "version": "1.0.0", "description": "The GraphQL server to reserve food.", "main": "index.js", "scripts": { - "start": "nodemon src/index.js" + "start": "tsc-watch --onsuccess \"node ./src/index.js\"" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { - "apollo-server": "^2.3.1", - "graphql": "^14.1.1", + "apollo-server": "^2.19.2", + "class-validator": "^0.13.1", + "graphql": "^15.5.0", + "reflect-metadata": "^0.1.13", + "type-graphql": "^1.1.1", + "typescript": "^4.1.3", "uuid": "^3.3.2", - "yup": "^0.26.10" + "yup": "^0.32.8" }, "devDependencies": { - "nodemon": "^1.18.9" + "tsc-watch": "^4.2.9" } -} +} \ No newline at end of file diff --git a/schema.gql b/schema.gql new file mode 100644 index 0000000..fcf2815 --- /dev/null +++ b/schema.gql @@ -0,0 +1,70 @@ +# ----------------------------------------------- +# !!! THIS FILE WAS GENERATED BY TYPE-GRAPHQL !!! +# !!! DO NOT MODIFY THIS FILE BY YOURSELF !!! +# ----------------------------------------------- + +""" +The javascript `Date` as string. Type represents date and time as the ISO Date string. +""" +scalar DateTime + +type Mutation { + """Create a new reservation""" + createReservation(input: ReservationInput): Reservation + + """Create a new store""" + createStore(input: StoreInput): Store +} + +type Product { + description: String + id: String + name: String + price: Float +} + +type Query { + """Get a specific store""" + store(id: String): Store + + """Get all the stores""" + stores: [Store] +} + +type Reservation { + date: DateTime + id: String + reservationProducts: [ReservationProduct] +} + +input ReservationInput { + reservationProducts: [ReservationProductInput] +} + +type ReservationProduct { + product: Product + quantity: Int +} + +input ReservationProductInput { + productId: String + quantity: Int +} + +type Store { + city: String + id: String + name: String + number: Int + postalCode: String + products: [Product] + street: String +} + +input StoreInput { + city: String + name: String + number: Int + postalCode: String + street: String +} diff --git a/src/data.js b/src/data.js index 221aaea..fa605a4 100644 --- a/src/data.js +++ b/src/data.js @@ -1,4 +1,4 @@ -const uuid = require('uuid'); +import uuid from 'uuid'; const stores = [ { @@ -55,7 +55,8 @@ const reservations = []; let reservationProducts = []; -function createStore({ city, name, number, postalCode, street }) { + +export function createStore({ city, name, number, postalCode, street }) { const newStore = { city, id: uuid(), @@ -76,41 +77,38 @@ function getStores() { return stores; } -function getStoreProducts(storeId) { - return products.filter((p) => p.storeId === storeId) +export function getStore(storeId) { + return stores.find(store => store.id === storeId); } -function getReservationProducts(reservationId) { +export function getStoreProducts(storeId) { + return products.filter(prod => prod.storeId === storeId) +} + +export function getReservationProducts(reservationId) { return reservationProducts - // Join - .filter((rp) => rp.reservationId === reservationId) - .map((rp) => ({ + .filter(rp => rp.reservationId === reservationId) + .map(rp => ({ product: products.find(p => p.id === rp.productId), quantity: rp.quantity - })); + }) + ); } -function createReservation(reservation) { +export function createReservation(reservation) { + //create new reservation and add it to db const reservationId = uuid(); const newReservation = { - date: new Date(), id: reservationId, + date: new Date(), }; reservations.push(newReservation); + //add the reservationID to the reservationproducts, then add them to the db reservationProducts = reservationProducts.concat( - reservation.reservationProducts.map((reservationProduct) => ({ - ...reservationProduct, + reservation.reservationProducts.map(rp => ({ + ...rp, reservationId }) - )); + )); return newReservation; } - -module.exports = { - createStore, - createReservation, - getStore, - getStores, - getStoreProducts, - getReservationProducts -}; \ No newline at end of file diff --git a/src/index.js b/src/index.js index f34c9a4..0541ef1 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,10 @@ -const { ApolloServer, gql } = require('apollo-server'); -const resolvers = require('./resolvers'); -const typeDefs = require('./typeDefs'); + +import "reflect-metadata"; +import { buildSchema } from "type-graphql"; + +import * as path from "path"; +import { ApolloServer, gql } from "apollo-server"; +import { StoreResolver, ReservationResolver } from './resolvers.js'; // ⚽️ Goal // -------- @@ -14,22 +18,26 @@ const typeDefs = require('./typeDefs'); // 🏪 Exercise 4 // -------------- -// Now we will focus on creating a reservation, which looks like this: -// { id, date, reservationProducts: { product: { id, name price }, quantity })). +// 1) First we create two files. One for our type definitions, `typeDefs.js`, +// and one for our resolver functions, `resolvers.js`. +// 2) Create a GraphQL type definition for our store. A store has an id and a name. +// 3) Create a Query `stores` to get a list of stores. +// 4) Create a resolver function that returns the list of stores. +// 5) Try out the GraphQL query in the GraphQL Playground (🚀 http://localhost:4000/) -// 1) Create a Mutation to create a reservation `createReservation` -// that takes an object as input. The input type is named `ReservationInput`. -// ReservationInput contains a list of { productId, quantity } (input type is named `ReservationProductInput`), -// named `reservationProducts` (field of `ReservationInput`). -// 2) Now create a resolver function for the mutation and insert it into our in-memory database. -// 3) Create a query field reservationProducts under Reservation, to get the reservationProducts [{ product, quantity }]. -// Similar as stores under Query. -// 4) Go to the GraphQL Playground and try out the createReservation mutation! + + +// create the schema using TypeGraphQL, pass the resolver +const schema = await buildSchema({ + resolvers: [StoreResolver, ReservationResolver], + nullableByDefault: true, + emitSchemaFile: path.resolve(".", "schema.gql"), +}); // In the most basic sense, the ApolloServer can be started // by passing type definitions (typeDefs) and the resolvers // responsible for fetching the data for those types. -const server = new ApolloServer({ typeDefs, resolvers }); +const server = new ApolloServer({ schema }); // This `listen` method launches a web-server. Existing apps // can utilize middleware options. diff --git a/src/resolvers.js b/src/resolvers.js index e4e0e3f..e2953e7 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -1,82 +1,134 @@ -const { UserInputError } = require('apollo-server'); -const yup = require('yup'); -const dateTimeScalar = require('./scalars/dateTime'); -const data = require('./data'); - -const uuidSchema = yup.string().min(36).max(36); - +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +import "reflect-metadata"; +import { Resolver, Query, Mutation, Arg, FieldResolver, Root } from "type-graphql"; +import { Store, StoreInput, Product, Reservation, ReservationProduct, ReservationInput } from "./typeDefs.js"; +import { createStore, getStores, getStore, getStoreProducts, createReservation, getReservationProducts } from "./data.js"; +import * as yup from 'yup'; +import { UserInputError } from "apollo-server"; +// define input validations const createStoreSchema = yup.object() .shape({ - city: yup.string().max(255), - name: yup.string().min(1).max(255).required(), - number: yup - .number() - .required() - .positive() - .integer(), - postalCode: yup.string().max(10), - street: yup.string().max(255), + city: yup.string().max(255).required(), + name: yup.string().max(255).required(), + number: yup.number().required().positive().integer(), + postalCode: yup.string().required().max(10), + street: yup.string().required().max(255), }); - const getStoreSchema = yup.object() .shape({ - id: uuidSchema, + id: yup.string().length(36).required() }); - const createReservationSchema = yup.object() .shape({ - reservationProducts: yup.array( - yup.object().shape({ - productId: uuidSchema, - quantity: yup.number().required().positive().integer(), - }) - ), + reservationProducts: yup.array(yup.object().shape({ + productId: yup.string().length(36), + quantity: yup.number().required().positive().integer(), + })), }); - -// Resolvers define the technique for fetching the types in the -// schema. -module.exports = { - Query: { - stores: () => data.getStores(), - store: async (parent, input) => { - try { - await getStoreSchema.validate(input); - } catch (e) { - throw new UserInputError('Invalid input.', { validationErrors: e.errors }); - } - const { id } = input; - return data.getStore(id); - }, - }, - Reservation: { - reservationProducts: async ({ id }) => { - return data.getReservationProducts(id); - } - }, - Store: { - products: async ({ id: storeId }) => { - return data.getStoreProducts(storeId); - } - }, - Mutation: { - createStore: async (parent, { input }) => { - try { - await createStoreSchema.validate(input); - } catch (e) { - throw new UserInputError('Invalid input.', { validationErrors: e.errors }); - } - const { city, name, number, postalCode, street } = input; - return data.createStore({ city, name, number, postalCode, street }); - }, - createReservation: async (parent, { input }) => { - try { - await createReservationSchema.validate(input); - } catch (e) { - throw new UserInputError('Invalid input.', { validationErrors: e.errors }); - } - const { reservationProducts } = input; - return data.createReservation({ reservationProducts }); - } - }, - DateTime: dateTimeScalar, -}; \ No newline at end of file +let StoreResolver = class StoreResolver { + async stores() { + return await getStores(); + } + async store(id /* ,@Arg("withProducts", { nullable: true }) withProducts: boolean*/) { + // check validity + return await getStoreSchema + .validate({ id: id }) + .then(validData => { + return getStore(validData.id); + }) + .catch(err => { + return new UserInputError('Invalid input', { validationErrors: err.errors }); + }); + } + //Extend the Store type with a list of its products. + async products(store) { + return await getStoreProducts(store.id); + } + async createStore(input) { + // check validity + return await createStoreSchema + .validate(input) + .then(validData => { + // put in db with data.js and return the value to grapqhl + return createStore(validData); + }) + .catch(err => { + return new UserInputError('Invalid input', { validationErrors: err.errors }); + }); + } +}; +__decorate([ + Query(() => [Store], { description: "Get all the stores" }), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], StoreResolver.prototype, "stores", null); +__decorate([ + Query(() => Store, { description: "Get a specific store" }), + __param(0, Arg("id")), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String /* ,@Arg("withProducts", { nullable: true }) withProducts: boolean*/]), + __metadata("design:returntype", Promise) +], StoreResolver.prototype, "store", null); +__decorate([ + FieldResolver(() => [Product]), + __param(0, Root()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Store]), + __metadata("design:returntype", Promise) +], StoreResolver.prototype, "products", null); +__decorate([ + Mutation(() => Store, { description: "Create a new store" }), + __param(0, Arg("input")), + __metadata("design:type", Function), + __metadata("design:paramtypes", [StoreInput]), + __metadata("design:returntype", Promise) +], StoreResolver.prototype, "createStore", null); +StoreResolver = __decorate([ + Resolver(() => Store) +], StoreResolver); +export { StoreResolver }; +let ReservationResolver = class ReservationResolver { + async reservationProducts(reservation) { + return await getReservationProducts(reservation.id); + } + async createReservation(input) { + return await createReservationSchema + .validate(input) + .then(validData => { + return createReservation(validData); + }) + .catch(err => { + return new UserInputError('Invalid input', { validationErrors: err.errors }); + }); + } +}; +__decorate([ + FieldResolver(() => [ReservationProduct]), + __param(0, Root()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Reservation]), + __metadata("design:returntype", Promise) +], ReservationResolver.prototype, "reservationProducts", null); +__decorate([ + Mutation(() => Reservation, { description: "Create a new reservation" }), + __param(0, Arg("input")), + __metadata("design:type", Function), + __metadata("design:paramtypes", [ReservationInput]), + __metadata("design:returntype", Promise) +], ReservationResolver.prototype, "createReservation", null); +ReservationResolver = __decorate([ + Resolver(() => Reservation) +], ReservationResolver); +export { ReservationResolver }; diff --git a/src/resolvers.ts b/src/resolvers.ts new file mode 100644 index 0000000..31084ef --- /dev/null +++ b/src/resolvers.ts @@ -0,0 +1,95 @@ +import "reflect-metadata"; +import { Resolver, Query, Mutation, Arg, FieldResolver, Root } from "type-graphql"; +import { Store, StoreInput, Product, Reservation, ReservationProduct, ReservationInput } from "./typeDefs.js"; +import { createStore, getStores, getStore, getStoreProducts, createReservation, getReservationProducts } from "./data.js"; +import * as yup from 'yup'; +import { UserInputError } from "apollo-server"; + +// define input validations +const createStoreSchema = yup.object() + .shape({ + city: yup.string().max(255).required(), + name: yup.string().max(255).required(), + number: yup.number().required().positive().integer(), + postalCode: yup.string().required().max(10), + street: yup.string().required().max(255), + }); + +const getStoreSchema = yup.object() + .shape({ + id: yup.string().length(36).required() + }); + +const createReservationSchema = yup.object() + .shape({ + reservationProducts: yup.array( + yup.object().shape({ + productId: yup.string().length(36), + quantity: yup.number().required().positive().integer(), + }) + ), + }); + +@Resolver(() => Store) +export class StoreResolver { + + @Query(() => [Store], { description: "Get all the stores" }) + async stores(): Promise { + return await getStores(); + } + + @Query(() => Store, { description: "Get a specific store" }) + async store(@Arg("id") id: String/* ,@Arg("withProducts", { nullable: true }) withProducts: boolean*/): Promise { + // check validity + return await getStoreSchema + .validate({ id: id }) + .then(validData => { + return getStore(validData.id); + }) + .catch(err => { + return new UserInputError('Invalid input', { validationErrors: err.errors }); + }); + } + + //Extend the Store type with a list of its products. + @FieldResolver(() => [Product]) + async products(@Root() store: Store): Promise<[Product]> { + return await getStoreProducts(store.id); + } + + + + @Mutation(() => Store, { description: "Create a new store" }) + async createStore(@Arg("input") input: StoreInput): Promise { + // check validity + return await createStoreSchema + .validate(input) + .then(validData => { + // put in db with data.js and return the value to grapqhl + return createStore(validData); + }) + .catch(err => { + return new UserInputError('Invalid input', { validationErrors: err.errors }); + }); + } +} +@Resolver(() => Reservation) +export class ReservationResolver { + + @FieldResolver(() => [ReservationProduct]) + async reservationProducts(@Root() reservation: Reservation) { + return await getReservationProducts(reservation.id); + } + + @Mutation(() => Reservation, { description: "Create a new reservation" }) + async createReservation(@Arg("input") input: ReservationInput): Promise { + return await createReservationSchema + .validate(input) + .then(validData => { + return createReservation(validData); + }) + .catch(err => { + return new UserInputError('Invalid input', { validationErrors: err.errors }); + }) + } +} diff --git a/src/typeDefs.js b/src/typeDefs.js index 2247f4b..36163dd 100644 --- a/src/typeDefs.js +++ b/src/typeDefs.js @@ -1,67 +1,151 @@ -const { gql } = require('apollo-server'); - // Type definitions define the "shape" of your data and specify // which ways the data can be fetched from the GraphQL server. -module.exports = gql` - scalar DateTime - - # Comments in GraphQL are defined with the hash (#) symbol. - - type Product { - description: String - id: ID - name: String - price: Float - } - - # This "Store" type can be used in other type declarations. - type Store { - city: String - id: ID - name: String - number: Int - postalCode: String - products: [Product] - street: String - } - - type ReservationProduct { - product: Product - quantity: Int - } - - type Reservation { - date: DateTime - id: ID - reservationProducts: [ReservationProduct] - } - - # The "Query" type is the root of all GraphQL queries. - # (A "Mutation" type will be covered later on.) - type Query { - stores: [Store] - store(id: ID): Store - } - - input StoreInput { - city: String - name: String! - number: Int - postalCode: String - street: String - } - - input ReservationProductInput { - productId: ID - quantity: Int - } - - input ReservationInput { - reservationProducts: [ReservationProductInput] - } - - type Mutation { - createStore(input: StoreInput): Store - createReservation(input: ReservationInput): Reservation - } -`; \ No newline at end of file +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +import "reflect-metadata"; +import { ObjectType, Field, InputType, Int, Float } from "type-graphql"; +let Store = class Store { +}; +__decorate([ + Field(), + __metadata("design:type", String) +], Store.prototype, "id", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], Store.prototype, "name", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], Store.prototype, "city", void 0); +__decorate([ + Field(type => Int), + __metadata("design:type", Number) +], Store.prototype, "number", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], Store.prototype, "postalCode", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], Store.prototype, "street", void 0); +__decorate([ + Field(type => [Product]), + __metadata("design:type", Array) +], Store.prototype, "products", void 0); +Store = __decorate([ + ObjectType() +], Store); +export { Store }; +let StoreInput = class StoreInput { +}; +__decorate([ + Field(), + __metadata("design:type", String) +], StoreInput.prototype, "name", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], StoreInput.prototype, "city", void 0); +__decorate([ + Field(type => Int), + __metadata("design:type", Number) +], StoreInput.prototype, "number", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], StoreInput.prototype, "postalCode", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], StoreInput.prototype, "street", void 0); +StoreInput = __decorate([ + InputType() +], StoreInput); +export { StoreInput }; +let Product = class Product { +}; +__decorate([ + Field(), + __metadata("design:type", String) +], Product.prototype, "id", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], Product.prototype, "name", void 0); +__decorate([ + Field(), + __metadata("design:type", String) +], Product.prototype, "description", void 0); +__decorate([ + Field(type => Float), + __metadata("design:type", Number) +], Product.prototype, "price", void 0); +Product = __decorate([ + ObjectType() +], Product); +export { Product }; +let Reservation = class Reservation { +}; +__decorate([ + Field(), + __metadata("design:type", String) +], Reservation.prototype, "id", void 0); +__decorate([ + Field(type => [ReservationProduct]), + __metadata("design:type", Array) +], Reservation.prototype, "reservationProducts", void 0); +__decorate([ + Field(type => Date), + __metadata("design:type", Date) +], Reservation.prototype, "date", void 0); +Reservation = __decorate([ + ObjectType() +], Reservation); +export { Reservation }; +let ReservationProduct = class ReservationProduct { +}; +__decorate([ + Field(type => Product), + __metadata("design:type", Product) +], ReservationProduct.prototype, "product", void 0); +__decorate([ + Field(type => Int), + __metadata("design:type", Number) +], ReservationProduct.prototype, "quantity", void 0); +ReservationProduct = __decorate([ + ObjectType() +], ReservationProduct); +export { ReservationProduct }; +let ReservationInput = class ReservationInput { +}; +__decorate([ + Field(type => [ReservationProductInput]), + __metadata("design:type", Array) +], ReservationInput.prototype, "reservationProducts", void 0); +ReservationInput = __decorate([ + InputType() +], ReservationInput); +export { ReservationInput }; +let ReservationProductInput = class ReservationProductInput { +}; +__decorate([ + Field(), + __metadata("design:type", String) +], ReservationProductInput.prototype, "productId", void 0); +__decorate([ + Field(type => Int), + __metadata("design:type", Number) +], ReservationProductInput.prototype, "quantity", void 0); +ReservationProductInput = __decorate([ + InputType() +], ReservationProductInput); +export { ReservationProductInput }; diff --git a/src/typeDefs.ts b/src/typeDefs.ts new file mode 100644 index 0000000..cc05f41 --- /dev/null +++ b/src/typeDefs.ts @@ -0,0 +1,82 @@ +// Type definitions define the "shape" of your data and specify +// which ways the data can be fetched from the GraphQL server. + +import "reflect-metadata"; +import { ObjectType, Field, InputType, Int, Float } from "type-graphql"; + + +@ObjectType() +export class Store { + @Field() + id: string; + @Field() + name: string; + @Field() + city: string; + @Field(type => Int) + number: number; + @Field() + postalCode: string + @Field() + street: string + @Field(type => [Product]) + products?: [Product] +} +@InputType() +export class StoreInput { + @Field() + name: string; + @Field() + city: string; + @Field(type => Int) + number: number; + @Field() + postalCode: string + @Field() + street: string +} + +@ObjectType() +export class Product { + @Field() + id: string; + @Field() + name: string; + @Field() + description: string; + @Field(type => Float) + price: number; +} + +@ObjectType() +export class Reservation { + @Field() + id: String; + @Field(type => [ReservationProduct]) + reservationProducts: [ReservationProduct]; + @Field(type => Date) + date: Date; +} + +@ObjectType() +export class ReservationProduct { + @Field(type => Product) + product: Product; + @Field(type => Int) + quantity: number; +} + + +@InputType() +export class ReservationInput { + @Field(type => [ReservationProductInput]) + reservationProducts: [ReservationProductInput]; +} + +@InputType() +export class ReservationProductInput { + @Field() + productId: string; + @Field(type => Int) + quantity: number; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..80c8404 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "esnext", + "lib": [ + "es2018", + "esnext.asynciterable" + ], + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "allowSyntheticDefaultImports": true + } +} \ No newline at end of file