From 4e011297e10c6ac654c4b85536a76c5bfa4811b0 Mon Sep 17 00:00:00 2001
From: cgeek <cem.moreau@gmail.com>
Date: Mon, 13 May 2019 21:10:02 +0200
Subject: [PATCH] [enh] GraphQL definition now stored in TypeScript classes

---
 .../schema.graphqls => tests/schema-test.ts}  | 328 +++++++++---------
 back/tsconfig.back.json                       |   4 +-
 back/webmin/graphql/di/application-context.ts |  34 ++
 back/webmin/graphql/resolvers/BigResolver.ts  |  67 ++++
 .../graphql/subscribers/BigSubscriber.ts      |  23 ++
 .../graphql/types/BlockTransactionType.ts     |  19 +
 back/webmin/graphql/types/BlockType.ts        |  38 ++
 .../graphql/types/BlockchainEventType.ts      |   9 +
 back/webmin/graphql/types/DocumentsType.ts    |  24 ++
 .../graphql/types/P2PDataCandidateType.ts     |  17 +
 .../webmin/graphql/types/P2PDataDetailType.ts |  10 +
 back/webmin/graphql/types/P2PDataType.ts      |   9 +
 back/webmin/graphql/types/PeerType.ts         |  12 +
 .../graphql/types/PendingCertificationType.ts |  19 +
 .../graphql/types/PendingIdentityType.ts      |  23 ++
 .../graphql/types/PendingMembershipType.ts    |  21 ++
 .../graphql/types/PendingTransactionType.ts   |  26 ++
 back/webmin/graphql/types/SoftVersionType.ts  |   9 +
 back/webmin/graphql/types/SyncProgressType.ts |  15 +
 .../graphql/types/WS2PConnectionInfoType.ts   |  10 +
 .../graphql/types/WS2PConnectionType.ts       |   9 +
 .../graphql/types/WS2PDisconnectionType.ts    |   6 +
 back/webmin/graphql/types/WS2PHeadType.ts     |  12 +
 back/webmin/graphql/types/WS2PInfosType.ts    |  12 +
 .../transform/p2p-candidate.transform.ts      |  18 +
 .../transform/sync-progress.transform.ts      |  37 ++
 back/webmin/network.ts                        |  15 +-
 back/webmin/queries/gql-node-state.ts         |   1 -
 back/webmin/queries/gql-ws2p.ts               |  10 +-
 .../subscriptions/gql-events-blockchain.ts    |   8 +-
 .../subscriptions/gql-events-documents.ts     |   6 +-
 .../subscriptions/gql-events-sync-progress.ts |  13 +-
 back/webmin/webmin.ts                         |  48 +--
 common/dto.ts                                 |  17 +-
 package.json                                  |   7 +
 src/views/Sync.vue                            |  23 +-
 tsconfig.json                                 |   1 +
 yarn.lock                                     | 235 +++++++++++--
 38 files changed, 918 insertions(+), 277 deletions(-)
 rename back/{webmin/schema.graphqls => tests/schema-test.ts} (60%)
 create mode 100644 back/webmin/graphql/di/application-context.ts
 create mode 100644 back/webmin/graphql/resolvers/BigResolver.ts
 create mode 100644 back/webmin/graphql/subscribers/BigSubscriber.ts
 create mode 100644 back/webmin/graphql/types/BlockTransactionType.ts
 create mode 100644 back/webmin/graphql/types/BlockType.ts
 create mode 100644 back/webmin/graphql/types/BlockchainEventType.ts
 create mode 100644 back/webmin/graphql/types/DocumentsType.ts
 create mode 100644 back/webmin/graphql/types/P2PDataCandidateType.ts
 create mode 100644 back/webmin/graphql/types/P2PDataDetailType.ts
 create mode 100644 back/webmin/graphql/types/P2PDataType.ts
 create mode 100644 back/webmin/graphql/types/PeerType.ts
 create mode 100644 back/webmin/graphql/types/PendingCertificationType.ts
 create mode 100644 back/webmin/graphql/types/PendingIdentityType.ts
 create mode 100644 back/webmin/graphql/types/PendingMembershipType.ts
 create mode 100644 back/webmin/graphql/types/PendingTransactionType.ts
 create mode 100644 back/webmin/graphql/types/SoftVersionType.ts
 create mode 100644 back/webmin/graphql/types/SyncProgressType.ts
 create mode 100644 back/webmin/graphql/types/WS2PConnectionInfoType.ts
 create mode 100644 back/webmin/graphql/types/WS2PConnectionType.ts
 create mode 100644 back/webmin/graphql/types/WS2PDisconnectionType.ts
 create mode 100644 back/webmin/graphql/types/WS2PHeadType.ts
 create mode 100644 back/webmin/graphql/types/WS2PInfosType.ts
 create mode 100644 back/webmin/graphql/types/transform/p2p-candidate.transform.ts
 create mode 100644 back/webmin/graphql/types/transform/sync-progress.transform.ts

diff --git a/back/webmin/schema.graphqls b/back/tests/schema-test.ts
similarity index 60%
rename from back/webmin/schema.graphqls
rename to back/tests/schema-test.ts
index 6b30b11..9380234 100644
--- a/back/webmin/schema.graphqls
+++ b/back/tests/schema-test.ts
@@ -1,40 +1,118 @@
-schema {
-  query: Query
-  subscription: Subscription
+import "reflect-metadata";
+import {buildSchema} from 'type-graphql'
+import {BigResolver} from '../webmin/graphql/resolvers/BigResolver'
+import {BigSubscriber} from '../webmin/graphql/subscribers/BigSubscriber'
+import {pubsub} from '../webmin/webmin'
+import {printSchema} from 'graphql'
+import {strictEqual} from 'assert'
+
+describe('GraphQL schema', () => {
+
+  it('should equal', async () => {
+    const schemaObj = await buildSchema({
+      resolvers: [BigResolver, BigSubscriber],
+      pubSub: pubsub
+    })
+
+    strictEqual(printSchema(schemaObj), `type BlockchainEventType {
+  bcEvent: String!
+  block: BlockType!
 }
 
-type Documents {
-  blocks: [Block!]!
-  identities: [PendingIdentity!]!
-  certifications: [PendingCertification!]!
-  memberships: [PendingMembership!]!
-  transactions: [PendingTransaction!]!
-  peers: [Peer!]!
-  ws2pHeads: [WS2PHead!]!
-  ws2pConnections: [WS2PConnection!]!
-  ws2pDisconnections: [WS2PDisconnection!]!
+type BlockTransactionType {
+  version: Int!
+  currency: String!
+  locktime: Int!
+  hash: String!
+  blockstamp: String!
+  blockstampTime: Int!
+  issuers: [String!]!
+  inputs: [String!]!
+  outputs: [String!]!
+  unlocks: [String!]!
+  signatures: [String!]!
+  comment: String!
 }
 
-type PendingIdentity {
-  revoked: Boolean!
-  buid: String!
-  member: Boolean!
-  kick: Boolean!
-  leaving: Boolean
-  wasMember: Boolean!
-  pubkey: String!
-  uid: String!
-  sig: String!
-  revocation_sig: String
+type BlockType {
+  version: Int!
+  number: Int!
+  currency: String!
   hash: String!
-  written: Boolean!
-  revoked_on: Int
-  expires_on: Int!
-  certs: [PendingCertification!]!
-  memberships: [PendingMembership!]!
+  inner_hash: String!
+  signature: String!
+  previousHash: String
+  issuer: String!
+  previousIssuer: String
+  time: Int!
+  powMin: Int!
+  unitbase: Int!
+  membersCount: Int!
+  issuersCount: Int!
+  issuersFrame: Int!
+  issuersFrameVar: Int!
+  identities: [String!]!
+  joiners: [String!]!
+  actives: [String!]!
+  leavers: [String!]!
+  revoked: [String!]!
+  excluded: [String!]!
+  certifications: [String!]!
+  transactions: [BlockTransactionType!]!
+  medianTime: Int!
+  nonce: String!
+  parameters: String
+  monetaryMass: Int!
+  dividend: Int
+  UDTime: Int
+  writtenOn: Int!
+  written_on: String
+}
+
+type DocumentsType {
+  blocks: [BlockType!]!
+  identities: [PendingIdentityType!]!
+  certifications: [PendingCertificationType!]!
+  memberships: [PendingMembershipType!]!
+  transactions: [PendingTransactionType!]!
+  peers: [PeerType!]!
+  ws2pHeads: [WS2PHeadType!]!
+  ws2pConnections: [WS2PConnectionType!]!
+  ws2pDisconnections: [WS2PDisconnectionType!]!
 }
 
-type PendingCertification {
+type P2PDataCandidateType {
+  reserved: Boolean
+  nbSuccess: Int
+  isExcluded: Boolean
+  failures: Int
+  responseTimes: [Float!]!
+  p: PeerType
+  hostName: String
+  hasAvailableApi: Boolean
+  apiName: String
+  avgResponseTime: Float
+}
+
+type P2PDataDetailType {
+  chunkIndex: Int
+  nodes: [P2PDataCandidateType!]
+  node: P2PDataCandidateType
+}
+
+type P2PDataType {
+  name: String
+  data: P2PDataDetailType
+}
+
+type PeerType {
+  hash: String!
+  pubkey: String!
+  block: String!
+  endpoints: [String!]!
+}
+
+type PendingCertificationType {
   linked: Boolean!
   written: Boolean!
   written_block: Int
@@ -50,7 +128,26 @@ type PendingCertification {
   expires_on: Int!
 }
 
-type PendingMembership {
+type PendingIdentityType {
+  revoked: Boolean!
+  buid: String!
+  member: Boolean!
+  kick: Boolean!
+  leaving: Boolean
+  wasMember: Boolean!
+  pubkey: String!
+  uid: String!
+  sig: String!
+  revocation_sig: String
+  hash: String!
+  written: Boolean!
+  revoked_on: Int
+  expires_on: Int!
+  certs: [PendingCertificationType!]!
+  memberships: [PendingMembershipType!]!
+}
+
+type PendingMembershipType {
   membership: String!
   issuer: String!
   number: Int!
@@ -69,7 +166,7 @@ type PendingMembership {
   block_number: Int
 }
 
-type PendingTransaction {
+type PendingTransactionType {
   hash: String!
   block_number: Int
   locktime: Int!
@@ -93,157 +190,72 @@ type PendingTransaction {
   writtenOn: Int
 }
 
-type Block {
-  version: Int!
-  number: Int!
-  currency: String!
-  hash: String!
-  inner_hash: String!
-  signature: String!
-  previousHash: String
-  issuer: String!
-  previousIssuer: String
-  time: Int!
-  powMin: Int!
-  unitbase: Int!
-  membersCount: Int!
-  issuersCount: Int!
-  issuersFrame: Int!
-  issuersFrameVar: Int!
-  identities: [String!]!
-  joiners: [String!]!
-  actives: [String!]!
-  leavers: [String!]!
-  revoked: [String!]!
-  excluded: [String!]!
-  certifications: [String!]!
-  transactions: [BlockTransaction!]!
-  medianTime: Int!
-  nonce: String!
-  parameters: String!
-  monetaryMass: Int!
-  dividend: Int
-  UDTime: Int
-  writtenOn: Int!
-  written_on: String!
-}
-
-type BlockTransaction {
-  version: Int!
-  currency: String!
-  locktime: Int!
-  hash: String!
-  blockstamp: String!
-  blockstampTime: Int!
-  issuers: [String!]!
-  inputs: [String!]!
-  outputs: [String!]!
-  unlocks: [String!]!
-  signatures: [String!]!
-  comment: String!
-}
-
-type Peer {
-  hash: String!
-  pubkey: String!
-  block: String!
-  endpoints: [String!]!
-}
-
-type WS2PHead {
-  message: String!
-  sig: String!
-  messageV2: String
-  sigV2: String
-  step: Int
+type Query {
+  hello: String
+  nodeState: String
+  current: BlockType
+  heads: [WS2PHeadType!]!
+  ws2pinfos: WS2PInfosType!
+  isSyncStarted: Boolean!
+  stopAndResetData: Boolean!
+  startNode: Boolean!
+  synchronize(url: String!): Boolean
+  uid(pub: String!): String
 }
 
-type WS2PConnection {
-  host: String!
-  port: String!
-  pub: String!
+type SoftVersionType {
+  software: String!
+  version: String!
+  pubkeys: [String!]!
 }
 
-type WS2PDisconnection {
-  pub: String
+type Subscription {
+  syncProgress: SyncProgressType
+  newDocuments: DocumentsType
+  bcEvents: BlockchainEventType
 }
 
-type SyncProgress {
+type SyncProgressType {
   download: Float
   saved: Float
   applied: Float
   sandbox: Float
   peersSync: Float
   sync: Boolean
-  p2pData: P2PData
+  p2pData: P2PDataType
   error: String
 }
 
-type P2PData {
-  name: String
-  data: P2PDataDetail
-}
-
-type P2PDataDetail {
-  chunkIndex: Int
-  nodes: [P2PDataCandidate!]
-  node: P2PDataCandidate
-}
-
-type P2PDataCandidate {
-  reserved: Boolean
-  nbSuccess: Int
-  isExcluded: Boolean
-  failures: Int
-  responseTimes: [Int!]
-  p: Peer
-  hostName: String
-  hasAvailableApi: Boolean
-  apiName: String
-  avgResponseTime: Float
-}
-
-type BlockchainEvent {
-  bcEvent: String!
-  block: Block!
-}
-
-type WS2PConnectionInfo {
+type WS2PConnectionInfoType {
   pubkey: String
   ws2pid: String
   uid: String
   handle: String
 }
 
-type SoftVersion {
-  software: String!
-  version: String!
-  pubkeys: [String!]!
+type WS2PConnectionType {
+  host: String!
+  port: String!
+  pub: String!
 }
 
-type WS2PInfos {
-  softVersions: [SoftVersion!]!
-  level1: [WS2PConnectionInfo!]!
-  level2: [WS2PConnectionInfo!]!
+type WS2PDisconnectionType {
+  pub: String
 }
 
-type Subscription {
-  syncProgress: SyncProgress
-  newDocuments: Documents
-  bcEvents: BlockchainEvent
+type WS2PHeadType {
+  message: String!
+  sig: String!
+  messageV2: String
+  sigV2: String
+  step: Int
 }
 
-type Query {
-  hello: String
-  nodeState: String
-  current: Block
-  heads: [WS2PHead!]!
-  ws2pinfos: WS2PInfos!
-
-  isSyncStarted: Boolean!
-  stopAndResetData: Boolean!
-  startNode: Boolean!
-  synchronize(url: String!): Boolean
-  uid(pub: String!): String
+type WS2PInfosType {
+  softVersions: [SoftVersionType!]!
+  level1: [WS2PConnectionInfoType!]!
+  level2: [WS2PConnectionInfoType!]!
 }
-
+`)
+  })
+})
\ No newline at end of file
diff --git a/back/tsconfig.back.json b/back/tsconfig.back.json
index f020cff..ed2de86 100644
--- a/back/tsconfig.back.json
+++ b/back/tsconfig.back.json
@@ -10,11 +10,13 @@
     "noImplicitAny": true,
     "noImplicitReturns": true,
     "skipLibCheck": true,
+    "emitDecoratorMetadata": true,
     "experimentalDecorators": true
   },
   "include": [
     "*.ts",
-    "webmin/*.ts",
+    "webmin/**/*.ts",
+    "tests/**/*.ts",
     "../common/*.ts"
   ],
   "compileOnSave": true
diff --git a/back/webmin/graphql/di/application-context.ts b/back/webmin/graphql/di/application-context.ts
new file mode 100644
index 0000000..5f5dfc9
--- /dev/null
+++ b/back/webmin/graphql/di/application-context.ts
@@ -0,0 +1,34 @@
+import {Server} from 'duniter/server'
+
+class LocalApplicationContext {
+
+  private _server: Server
+  private _startServices: () => Promise<void>
+  private _stopServices: () => Promise<void>
+
+  set server(value: Server) {
+    this._server = value
+  }
+
+  get server(): Server {
+    return this._server
+  }
+
+
+  get startServices(): () => Promise<void> {
+    return this._startServices
+  }
+
+  set startServices(value: () => Promise<void>) {
+    this._startServices = value
+  }
+  get stopServices(): () => Promise<void> {
+    return this._stopServices
+  }
+
+  set stopServices(value: () => Promise<void>) {
+    this._stopServices = value
+  }
+}
+
+export const ApplicationContext = new LocalApplicationContext()
\ No newline at end of file
diff --git a/back/webmin/graphql/resolvers/BigResolver.ts b/back/webmin/graphql/resolvers/BigResolver.ts
new file mode 100644
index 0000000..fee2914
--- /dev/null
+++ b/back/webmin/graphql/resolvers/BigResolver.ts
@@ -0,0 +1,67 @@
+import {BlockType} from '../types/BlockType'
+import {WS2PHeadType} from '../types/WS2PHeadType'
+import {WS2PInfosType} from '../types/WS2PInfosType'
+import {Resolver} from 'type-graphql/dist/decorators/Resolver'
+import {Query} from 'type-graphql'
+import {gqlNodeStart, gqlNodeState, gqlStopAndResetData} from '../../queries/gql-node-state'
+import {ApplicationContext} from '../di/application-context'
+import {gqlCurrent} from '../../queries/gql-current'
+import {gqlHeads} from '../../queries/gql-heads'
+import {gqlWs2pInfos} from '../../queries/gql-ws2p'
+import {gqlIsSyncStarted, gqlSynchronize} from '../../queries/gql-synchronize'
+import {gqlUid} from '../../queries/gql-uid'
+import {Arg} from 'type-graphql/dist/decorators/Arg'
+
+@Resolver()
+export class BigResolver {
+
+  @Query({ nullable: true })
+  hello(): string {
+    return 'Hello from Duniter Web Admin API'
+  }
+
+  @Query(type => String, { nullable: true })
+  async nodeState() {
+    return await gqlNodeState(ApplicationContext.server)()
+  }
+
+  @Query(type => BlockType, { nullable: true })
+  async current() {
+    return await gqlCurrent(ApplicationContext.server)()
+  }
+
+  @Query(type => [WS2PHeadType])
+  async heads(): Promise<WS2PHeadType[]> {
+    return gqlHeads(ApplicationContext.server)()
+  }
+
+  @Query(type => WS2PInfosType)
+  ws2pinfos(): Promise<WS2PInfosType> {
+    return gqlWs2pInfos(ApplicationContext.server)()
+  }
+
+  @Query(type => Boolean)
+  isSyncStarted() {
+    return gqlIsSyncStarted()()
+  }
+
+  @Query(type => Boolean)
+  stopAndResetData(): Promise<boolean> {
+    return gqlStopAndResetData(ApplicationContext.server, ApplicationContext.startServices)()
+  }
+
+  @Query(type => Boolean)
+  startNode(): Promise<boolean> {
+    return gqlNodeStart(ApplicationContext.startServices)()
+  }
+
+  @Query(type => Boolean, { nullable: true })
+  synchronize(@Arg("url") url: string) {
+    return gqlSynchronize(ApplicationContext.server)(null, { url })
+  }
+
+  @Query(type => String, { nullable: true })
+  uid(@Arg("pub") pub: string): Promise<string|null> {
+    return gqlUid(ApplicationContext.server)(null, { pub })
+  }
+}
diff --git a/back/webmin/graphql/subscribers/BigSubscriber.ts b/back/webmin/graphql/subscribers/BigSubscriber.ts
new file mode 100644
index 0000000..0fb525a
--- /dev/null
+++ b/back/webmin/graphql/subscribers/BigSubscriber.ts
@@ -0,0 +1,23 @@
+import {Root, Subscription} from 'type-graphql'
+import {GraphQLSubscriptions} from '../../constants'
+import {SyncProgressType} from '../types/SyncProgressType'
+import {DocumentsType} from '../types/DocumentsType'
+import {BlockchainEventType} from '../types/BlockchainEventType'
+
+export class BigSubscriber {
+
+  @Subscription(type => SyncProgressType, { nullable: true, topics: GraphQLSubscriptions.SYNC_PROGRESS })
+  syncProgress(@Root() progress: SyncProgressType): SyncProgressType {
+    return progress
+  }
+
+  @Subscription(type => DocumentsType, { nullable: true, topics: GraphQLSubscriptions.NEW_DOCUMENTS })
+  newDocuments(@Root() documents: DocumentsType): DocumentsType {
+    return documents
+  }
+
+  @Subscription(type => BlockchainEventType, { nullable: true, topics: GraphQLSubscriptions.BC_EVENTS })
+  bcEvents(@Root() bcEvents: BlockchainEventType): BlockchainEventType {
+    return bcEvents
+  }
+}
diff --git a/back/webmin/graphql/types/BlockTransactionType.ts b/back/webmin/graphql/types/BlockTransactionType.ts
new file mode 100644
index 0000000..18d515a
--- /dev/null
+++ b/back/webmin/graphql/types/BlockTransactionType.ts
@@ -0,0 +1,19 @@
+import {Int, ObjectType} from 'type-graphql'
+import {Field} from 'type-graphql/dist/decorators/Field'
+
+@ObjectType()
+export class BlockTransactionType {
+
+  @Field(type => Int) version: number
+  @Field() currency?: string
+  @Field(type => Int) locktime: number
+  @Field() hash?: string
+  @Field() blockstamp?: string
+  @Field(type => Int) blockstampTime: number
+  @Field(type => [String]) issuers: string[]
+  @Field(type => [String]) inputs: string[]
+  @Field(type => [String]) outputs: string[]
+  @Field(type => [String]) unlocks: string[]
+  @Field(type => [String]) signatures: string[]
+  @Field() comment?: string
+}
diff --git a/back/webmin/graphql/types/BlockType.ts b/back/webmin/graphql/types/BlockType.ts
new file mode 100644
index 0000000..a78b74b
--- /dev/null
+++ b/back/webmin/graphql/types/BlockType.ts
@@ -0,0 +1,38 @@
+import {Field, Int, ObjectType} from 'type-graphql'
+import {BlockTransactionType} from './BlockTransactionType'
+
+@ObjectType()
+export class BlockType {
+  @Field(type => Int) version: number
+  @Field(type => Int) number: number
+  @Field() currency: string
+  @Field() hash: string
+  @Field() inner_hash: string
+  @Field() signature: string
+  @Field({ nullable: true }) previousHash?: string
+  @Field() issuer: string
+  @Field({ nullable: true }) previousIssuer?: string
+  @Field(type => Int) time: number
+  @Field(type => Int) powMin: number
+  @Field(type => Int) unitbase: number
+  @Field(type => Int) membersCount: number
+  @Field(type => Int) issuersCount: number
+  @Field(type => Int) issuersFrame: number
+  @Field(type => Int) issuersFrameVar: number
+  @Field(type => [String]) identities: string[]
+  @Field(type => [String]) joiners: string[]
+  @Field(type => [String]) actives: string[]
+  @Field(type => [String]) leavers: string[]
+  @Field(type => [String]) revoked: string[]
+  @Field(type => [String]) excluded: string[]
+  @Field(type => [String]) certifications: string[]
+  @Field(type => [BlockTransactionType]) transactions: BlockTransactionType[]
+  @Field(type => Int) medianTime: number
+  @Field() nonce?: string
+  @Field({ nullable: true }) parameters?: string
+  @Field(type => Int) monetaryMass: number
+  @Field(type => Int, { nullable: true }) dividend?: number
+  @Field(type => Int, { nullable: true }) UDTime?: number
+  @Field(type => Int) writtenOn: number
+  @Field({ nullable: true }) written_on?: string
+}
diff --git a/back/webmin/graphql/types/BlockchainEventType.ts b/back/webmin/graphql/types/BlockchainEventType.ts
new file mode 100644
index 0000000..8b1c007
--- /dev/null
+++ b/back/webmin/graphql/types/BlockchainEventType.ts
@@ -0,0 +1,9 @@
+import {Field, ObjectType} from 'type-graphql'
+import {BlockType} from './BlockType'
+
+@ObjectType()
+export class BlockchainEventType {
+
+  @Field() bcEvent?: string
+  @Field() block: BlockType
+}
diff --git a/back/webmin/graphql/types/DocumentsType.ts b/back/webmin/graphql/types/DocumentsType.ts
new file mode 100644
index 0000000..1e7f1f8
--- /dev/null
+++ b/back/webmin/graphql/types/DocumentsType.ts
@@ -0,0 +1,24 @@
+import {Field, ObjectType} from 'type-graphql'
+import {WS2PDisconnectionType} from './WS2PDisconnectionType'
+import {WS2PConnectionType} from './WS2PConnectionType'
+import {WS2PHeadType} from './WS2PHeadType'
+import {PeerType} from './PeerType'
+import {BlockType} from './BlockType'
+import {PendingIdentityType} from './PendingIdentityType'
+import {PendingCertificationType} from './PendingCertificationType'
+import {PendingMembershipType} from './PendingMembershipType'
+import {PendingTransactionType} from './PendingTransactionType'
+
+@ObjectType()
+export class DocumentsType {
+
+  @Field(type => [BlockType]) blocks: BlockType[]
+  @Field(type => [PendingIdentityType]) identities: PendingIdentityType[]
+  @Field(type => [PendingCertificationType]) certifications: PendingCertificationType[]
+  @Field(type => [PendingMembershipType]) memberships: PendingMembershipType[]
+  @Field(type => [PendingTransactionType]) transactions: PendingTransactionType[]
+  @Field(type => [PeerType]) peers: PeerType[]
+  @Field(type => [WS2PHeadType]) ws2pHeads: WS2PHeadType[]
+  @Field(type => [WS2PConnectionType]) ws2pConnections: WS2PConnectionType[]
+  @Field(type => [WS2PDisconnectionType]) ws2pDisconnections: WS2PDisconnectionType[]
+}
\ No newline at end of file
diff --git a/back/webmin/graphql/types/P2PDataCandidateType.ts b/back/webmin/graphql/types/P2PDataCandidateType.ts
new file mode 100644
index 0000000..7374679
--- /dev/null
+++ b/back/webmin/graphql/types/P2PDataCandidateType.ts
@@ -0,0 +1,17 @@
+import {Field, Float, Int, ObjectType} from 'type-graphql'
+import {PeerType} from './PeerType'
+
+@ObjectType()
+export class P2PDataCandidateType {
+
+  @Field({ nullable: true }) reserved?: boolean
+  @Field(type => Int, { nullable: true }) nbSuccess?: number
+  @Field({ nullable: true }) isExcluded?: boolean
+  @Field(type => Int, { nullable: true }) failures?: number
+  @Field(type => [Float]) responseTimes?: number[]
+  @Field({ nullable: true }) p?: PeerType
+  @Field({ nullable: true }) hostName?: string
+  @Field({ nullable: true }) hasAvailableApi?: boolean
+  @Field({ nullable: true }) apiName?: string
+  @Field({ nullable: true }) avgResponseTime?: number
+}
\ No newline at end of file
diff --git a/back/webmin/graphql/types/P2PDataDetailType.ts b/back/webmin/graphql/types/P2PDataDetailType.ts
new file mode 100644
index 0000000..74df95b
--- /dev/null
+++ b/back/webmin/graphql/types/P2PDataDetailType.ts
@@ -0,0 +1,10 @@
+import {Field, Int, ObjectType} from 'type-graphql'
+import {P2PDataCandidateType} from './P2PDataCandidateType'
+
+@ObjectType()
+export class P2PDataDetailType {
+
+  @Field(type => Int, { nullable: true }) chunkIndex?: number
+  @Field(type => [P2PDataCandidateType], { nullable: true }) nodes?: P2PDataCandidateType[]
+  @Field({ nullable: true }) node?: P2PDataCandidateType
+}
\ No newline at end of file
diff --git a/back/webmin/graphql/types/P2PDataType.ts b/back/webmin/graphql/types/P2PDataType.ts
new file mode 100644
index 0000000..dedee4a
--- /dev/null
+++ b/back/webmin/graphql/types/P2PDataType.ts
@@ -0,0 +1,9 @@
+import {Field, ObjectType} from 'type-graphql'
+import {P2PDataDetailType} from './P2PDataDetailType'
+
+@ObjectType()
+export class P2PDataType {
+
+  @Field({ nullable: true }) name?: string
+  @Field({ nullable: true }) data?: P2PDataDetailType
+}
\ No newline at end of file
diff --git a/back/webmin/graphql/types/PeerType.ts b/back/webmin/graphql/types/PeerType.ts
new file mode 100644
index 0000000..db6c1ae
--- /dev/null
+++ b/back/webmin/graphql/types/PeerType.ts
@@ -0,0 +1,12 @@
+import {Field, ObjectType} from 'type-graphql'
+
+@ObjectType()
+export class PeerType {
+
+  @Field() hash?: string
+  @Field() pubkey?: string
+  @Field() block?: string
+
+  @Field(type => [String])
+  endpoints?: string[]
+}
diff --git a/back/webmin/graphql/types/PendingCertificationType.ts b/back/webmin/graphql/types/PendingCertificationType.ts
new file mode 100644
index 0000000..a77f161
--- /dev/null
+++ b/back/webmin/graphql/types/PendingCertificationType.ts
@@ -0,0 +1,19 @@
+import {Field, Int, ObjectType} from 'type-graphql'
+
+@ObjectType()
+export class PendingCertificationType {
+
+  @Field() linked?: boolean
+  @Field() written?: boolean
+  @Field(type => Int, { nullable: true }) written_block?: number
+  @Field({ nullable: true }) written_hash?: string
+  @Field() sig?: string
+  @Field(type => Int) block_number: number
+  @Field() block_hash?: string
+  @Field({ nullable: true }) target?: string
+  @Field() to?: string
+  @Field() from?: string
+  @Field(type => Int) block: number
+  @Field({ nullable: true }) expired?: boolean
+  @Field(type => Int) expires_on: number
+}
diff --git a/back/webmin/graphql/types/PendingIdentityType.ts b/back/webmin/graphql/types/PendingIdentityType.ts
new file mode 100644
index 0000000..504172d
--- /dev/null
+++ b/back/webmin/graphql/types/PendingIdentityType.ts
@@ -0,0 +1,23 @@
+import {Field, Int, ObjectType} from 'type-graphql'
+import {PendingCertificationType} from './PendingCertificationType'
+import {PendingMembershipType} from './PendingMembershipType'
+
+@ObjectType()
+export class PendingIdentityType {
+  @Field() revoked?: boolean
+  @Field() buid?: string
+  @Field() member?: boolean
+  @Field() kick?: boolean
+  @Field({ nullable: true }) leaving?: boolean
+  @Field() wasMember?: boolean
+  @Field() pubkey?: string
+  @Field() uid?: string
+  @Field() sig?: string
+  @Field({ nullable: true }) revocation_sig?: string
+  @Field() hash?: string
+  @Field() written?: boolean
+  @Field(type => Int, { nullable: true }) revoked_on?: number
+  @Field(type => Int) expires_on: number
+  @Field(type => [PendingCertificationType]) certs: PendingCertificationType[]
+  @Field(type => [PendingMembershipType]) memberships: PendingMembershipType[]
+}
diff --git a/back/webmin/graphql/types/PendingMembershipType.ts b/back/webmin/graphql/types/PendingMembershipType.ts
new file mode 100644
index 0000000..7d81016
--- /dev/null
+++ b/back/webmin/graphql/types/PendingMembershipType.ts
@@ -0,0 +1,21 @@
+import {Field, Int, ObjectType} from 'type-graphql'
+
+@ObjectType()
+export class PendingMembershipType {
+  @Field() membership?: string
+  @Field() issuer?: string
+  @Field(type => Int) number: number
+  @Field(type => Int) blockNumber: number
+  @Field() blockHash?: string
+  @Field() userid?: string
+  @Field() certts?: string
+  @Field({ nullable: true }) block?: string
+  @Field() fpr?: string
+  @Field() idtyHash?: string
+  @Field() written?: boolean
+  @Field(type => Int, { nullable: true }) written_number?: number
+  @Field(type => Int) expires_on: number
+  @Field() signature: string
+  @Field({ nullable: true }) expired?: boolean
+  @Field(type => Int, { nullable: true }) block_number?: number
+}
diff --git a/back/webmin/graphql/types/PendingTransactionType.ts b/back/webmin/graphql/types/PendingTransactionType.ts
new file mode 100644
index 0000000..7c7efa0
--- /dev/null
+++ b/back/webmin/graphql/types/PendingTransactionType.ts
@@ -0,0 +1,26 @@
+import {Field, Int, ObjectType} from 'type-graphql'
+
+@ObjectType()
+export class PendingTransactionType {
+  @Field() hash?: string
+  @Field(type => Int, { nullable: true }) block_number?: number
+  @Field(type => Int) locktime: number
+  @Field(type => Int) version: number
+  @Field() currency?: string
+  @Field() comment?: string
+  @Field() blockstamp?: string
+  @Field(type => Int, { nullable: true }) blockstampTime?: number
+  @Field(type => Int, { nullable: true }) time?: number
+  @Field(type => [String]) inputs: string[]
+  @Field(type => [String]) unlocks: string[]
+  @Field(type => [String]) outputs: string[]
+  @Field(type => [String]) issuers: string[]
+  @Field(type => [String]) signatures: string[]
+  @Field({ nullable: true }) written?: boolean
+  @Field({ nullable: true }) removed?: boolean
+  @Field(type => Int, { nullable: true }) received?: number
+  @Field(type => Int) output_base: number
+  @Field(type => Int) output_amount: number
+  @Field({ nullable: true }) written_on?: string
+  @Field(type => Int, { nullable: true }) writtenOn?: number
+}
diff --git a/back/webmin/graphql/types/SoftVersionType.ts b/back/webmin/graphql/types/SoftVersionType.ts
new file mode 100644
index 0000000..007de98
--- /dev/null
+++ b/back/webmin/graphql/types/SoftVersionType.ts
@@ -0,0 +1,9 @@
+import {Field, ObjectType} from 'type-graphql'
+
+@ObjectType()
+export class SoftVersionType {
+
+  @Field() software?: string
+  @Field() version?: string
+  @Field(type => [String]) pubkeys: string[]
+}
\ No newline at end of file
diff --git a/back/webmin/graphql/types/SyncProgressType.ts b/back/webmin/graphql/types/SyncProgressType.ts
new file mode 100644
index 0000000..32ac3c5
--- /dev/null
+++ b/back/webmin/graphql/types/SyncProgressType.ts
@@ -0,0 +1,15 @@
+import {Field, Float, ObjectType} from 'type-graphql'
+import {P2PDataType} from './P2PDataType'
+
+@ObjectType()
+export class SyncProgressType {
+
+  @Field(type => Float, { nullable: true }) download?: number
+  @Field(type => Float, { nullable: true }) saved?: number
+  @Field(type => Float, { nullable: true }) applied?: number
+  @Field(type => Float, { nullable: true }) sandbox?: number
+  @Field(type => Float, { nullable: true }) peersSync?: number
+  @Field({ nullable: true }) sync?: boolean
+  @Field({ nullable: true }) p2pData?: P2PDataType
+  @Field({ nullable: true }) error?: string
+}
diff --git a/back/webmin/graphql/types/WS2PConnectionInfoType.ts b/back/webmin/graphql/types/WS2PConnectionInfoType.ts
new file mode 100644
index 0000000..1ecd4ea
--- /dev/null
+++ b/back/webmin/graphql/types/WS2PConnectionInfoType.ts
@@ -0,0 +1,10 @@
+import {Field, ObjectType} from 'type-graphql'
+
+@ObjectType()
+export class WS2PConnectionInfoType {
+
+  @Field({ nullable: true }) pubkey?: string
+  @Field({ nullable: true }) ws2pid?: string
+  @Field({ nullable: true }) uid?: string
+  @Field({ nullable: true }) handle?: string
+}
\ No newline at end of file
diff --git a/back/webmin/graphql/types/WS2PConnectionType.ts b/back/webmin/graphql/types/WS2PConnectionType.ts
new file mode 100644
index 0000000..4bc3a8d
--- /dev/null
+++ b/back/webmin/graphql/types/WS2PConnectionType.ts
@@ -0,0 +1,9 @@
+import {Field, ObjectType} from 'type-graphql'
+
+@ObjectType()
+export class WS2PConnectionType {
+
+  @Field() host?: string
+  @Field() port?: string
+  @Field() pub?: string
+}
\ No newline at end of file
diff --git a/back/webmin/graphql/types/WS2PDisconnectionType.ts b/back/webmin/graphql/types/WS2PDisconnectionType.ts
new file mode 100644
index 0000000..9ff3566
--- /dev/null
+++ b/back/webmin/graphql/types/WS2PDisconnectionType.ts
@@ -0,0 +1,6 @@
+import {Field, ObjectType} from 'type-graphql'
+
+@ObjectType()
+export class WS2PDisconnectionType {
+  @Field({ nullable: true }) pub?: string
+}
diff --git a/back/webmin/graphql/types/WS2PHeadType.ts b/back/webmin/graphql/types/WS2PHeadType.ts
new file mode 100644
index 0000000..7aa064e
--- /dev/null
+++ b/back/webmin/graphql/types/WS2PHeadType.ts
@@ -0,0 +1,12 @@
+import {ObjectType} from 'type-graphql/dist/decorators/ObjectType'
+import {Field, Int} from 'type-graphql'
+
+@ObjectType()
+export class WS2PHeadType {
+
+  @Field() message?: string
+  @Field() sig?: string
+  @Field({ nullable: true }) messageV2?: string
+  @Field({ nullable: true }) sigV2?: string
+  @Field(type => Int, { nullable: true }) step?: number
+}
diff --git a/back/webmin/graphql/types/WS2PInfosType.ts b/back/webmin/graphql/types/WS2PInfosType.ts
new file mode 100644
index 0000000..99b5bac
--- /dev/null
+++ b/back/webmin/graphql/types/WS2PInfosType.ts
@@ -0,0 +1,12 @@
+import {ObjectType} from 'type-graphql'
+import {Field} from 'type-graphql/dist/decorators/Field'
+import {WS2PConnectionInfoType} from './WS2PConnectionInfoType'
+import {SoftVersionType} from './SoftVersionType'
+
+@ObjectType()
+export class WS2PInfosType {
+
+  @Field(type => [SoftVersionType]) softVersions: SoftVersionType[]
+  @Field(type => [WS2PConnectionInfoType]) level1: WS2PConnectionInfoType[]
+  @Field(type => [WS2PConnectionInfoType]) level2: WS2PConnectionInfoType[]
+}
diff --git a/back/webmin/graphql/types/transform/p2p-candidate.transform.ts b/back/webmin/graphql/types/transform/p2p-candidate.transform.ts
new file mode 100644
index 0000000..eac0dbf
--- /dev/null
+++ b/back/webmin/graphql/types/transform/p2p-candidate.transform.ts
@@ -0,0 +1,18 @@
+import {P2pCandidate} from 'duniter/app/modules/crawler/lib/sync/p2p/p2p-candidate'
+import {P2PDataCandidateType} from '../P2PDataCandidateType'
+
+
+export function transformP2pCandidate(node: P2pCandidate): P2PDataCandidateType {
+  return {
+    reserved: (node as any).reserved,
+    nbSuccess: (node as any).nbSuccess,
+    isExcluded: (node as any).isExcluded,
+    failures: (node as any).failures,
+    responseTimes: (node as any).responseTimes,
+    p: node.p,
+    hostName: node.hostName,
+    hasAvailableApi: node.hasAvailableApi(),
+    apiName: node.apiName || undefined,
+    avgResponseTime: node.avgResponseTime(),
+  };
+}
diff --git a/back/webmin/graphql/types/transform/sync-progress.transform.ts b/back/webmin/graphql/types/transform/sync-progress.transform.ts
new file mode 100644
index 0000000..b130469
--- /dev/null
+++ b/back/webmin/graphql/types/transform/sync-progress.transform.ts
@@ -0,0 +1,37 @@
+import {SyncProgress} from '../../../../../common/dto'
+import {P2PDataDetailType} from '../P2PDataDetailType'
+import {SyncProgressType} from '../SyncProgressType'
+import {transformP2pCandidate} from './p2p-candidate.transform'
+
+
+/**
+ * Transforms the `progress` object from Duniter to our GraphQL type
+ * @param progress
+ */
+export function transformSyncProgress(progress: SyncProgress): SyncProgressType {
+  const p: SyncProgressType = {
+    download: progress.download,
+    saved: progress.saved,
+    applied: progress.applied,
+    sandbox: progress.sandbox,
+    peersSync: progress.peersSync,
+    sync: progress.sync,
+    error: progress.error,
+  }
+  if (progress.p2pData && progress.p2pData.data) {
+    const data: P2PDataDetailType = {
+      chunkIndex: progress.p2pData.data.chunkIndex
+    }
+    if (progress.p2pData.data.node) {
+      data.node = transformP2pCandidate(progress.p2pData.data.node)
+    }
+    if (progress.p2pData.data.nodes) {
+      data.nodes = progress.p2pData.data.nodes.map(transformP2pCandidate)
+    }
+    p.p2pData = {
+      name: progress.p2pData.name,
+      data
+    }
+  }
+  return p
+}
\ No newline at end of file
diff --git a/back/webmin/network.ts b/back/webmin/network.ts
index dcdeb09..823061f 100644
--- a/back/webmin/network.ts
+++ b/back/webmin/network.ts
@@ -5,22 +5,22 @@ import {Server} from 'duniter/server';
 import {graphiqlExpress, graphqlExpress,} from 'graphql-server-express';
 import {plugModule} from './webmin';
 import {execute, subscribe} from 'graphql';
-import bodyParser = require('body-parser')
-import cors = require('cors')
 import * as path from 'path'
 import {SubscriptionServer} from 'subscriptions-transport-ws'
+import bodyParser = require('body-parser')
+import cors = require('cors')
 
-export function webminHttpListen(
+export async function webminHttpListen(
   server: Server,
   port: number,
   host = 'localhost',
   startServices: () => Promise<void>,
   stopServices: () => Promise<void>,
-): http.Server {
+): Promise<http.Server> {
 
-  const schema = plugModule(server, startServices, stopServices)
+  const schema = await plugModule(server, startServices, stopServices)
 
-  const app = express()
+  const app = (express as any)()
 
   app.use('*', cors({ origin: new RegExp(`${host}`) }))
 
@@ -34,9 +34,6 @@ export function webminHttpListen(
   }))
 
   const front = path.join(__dirname, '../..', 'front/dist/front')
-  app.use(/^\/(run|sync)/, (req, res) => {
-    res.redirect('/');
-  })
   app.use(express.static(front))
 
   // Wrap the Express server
diff --git a/back/webmin/queries/gql-node-state.ts b/back/webmin/queries/gql-node-state.ts
index caa024f..2fda2d3 100644
--- a/back/webmin/queries/gql-node-state.ts
+++ b/back/webmin/queries/gql-node-state.ts
@@ -1,5 +1,4 @@
 import {Server} from 'duniter/server'
-import {Querable} from 'duniter/app/lib/common-libs/querable'
 import {getSyncPromise} from './gql-synchronize'
 
 let started = false
diff --git a/back/webmin/queries/gql-ws2p.ts b/back/webmin/queries/gql-ws2p.ts
index 7463d12..df6044a 100644
--- a/back/webmin/queries/gql-ws2p.ts
+++ b/back/webmin/queries/gql-ws2p.ts
@@ -1,9 +1,11 @@
 import {Server} from 'duniter/server'
 import {WS2PConnection} from 'duniter/app/modules/ws2p/lib/WS2PConnection'
 import {softVersions} from '../shared/gql-network'
+import {WS2PInfosType} from '../graphql/types/WS2PInfosType'
+import {WS2PConnectionInfoType} from '../graphql/types/WS2PConnectionInfoType'
 
 export function gqlWs2pInfos(server: Server) {
-  return async () => {
+  return async (): Promise<WS2PInfosType> => {
     if (server.ws2pCluster) {
       let level1 = await server.ws2pCluster.getLevel1Connections()
       let level2 = await server.ws2pCluster.getLevel2Connections()
@@ -23,8 +25,8 @@ export function gqlWs2pInfos(server: Server) {
       })
       return {
         softVersions: theSoftVersions,
-        level1: await level1.map(c => ws2pConnectionToJSON(server, c)),
-        level2: await level2.map(c => ws2pConnectionToJSON(server, c))
+        level1: await Promise.all(level1.map(c => ws2pConnectionToJSON(server, c))),
+        level2: await Promise.all(level2.map(c => ws2pConnectionToJSON(server, c)))
       }
     } else {
       return {
@@ -36,7 +38,7 @@ export function gqlWs2pInfos(server: Server) {
   }
 }
 
-async function ws2pConnectionToJSON(server: Server, connection: WS2PConnection|any) {
+async function ws2pConnectionToJSON(server: Server, connection: WS2PConnection|any): Promise<WS2PConnectionInfoType> {
   const pubkey = connection.pubkey
   const ws2pid = connection.uuid
   const member = await server.dal.getWrittenIdtyByPubkey(pubkey)
diff --git a/back/webmin/subscriptions/gql-events-blockchain.ts b/back/webmin/subscriptions/gql-events-blockchain.ts
index 9c8635f..a6121e0 100644
--- a/back/webmin/subscriptions/gql-events-blockchain.ts
+++ b/back/webmin/subscriptions/gql-events-blockchain.ts
@@ -6,11 +6,7 @@ export function gqlSubscribeBlockchainEvents(server: Server) {
 
   server.on('data', async (data: any) => {
     if (data.bcEvent) {
-      return pubsub.publish(GraphQLSubscriptions.BC_EVENTS, {bcEvents: data})
+      return pubsub.publish(GraphQLSubscriptions.BC_EVENTS, data)
     }
   })
-
-  return {
-    subscribe: () => pubsub.asyncIterator(GraphQLSubscriptions.BC_EVENTS)
-  }
-}
\ No newline at end of file
+}
diff --git a/back/webmin/subscriptions/gql-events-documents.ts b/back/webmin/subscriptions/gql-events-documents.ts
index bd67881..b1fed50 100644
--- a/back/webmin/subscriptions/gql-events-documents.ts
+++ b/back/webmin/subscriptions/gql-events-documents.ts
@@ -64,10 +64,6 @@ export function gqlSubscribeDocuments(server: Server) {
       newDocuments.identities.push(data)
     }
 
-    await pubsub.publish(GraphQLSubscriptions.NEW_DOCUMENTS, { newDocuments })
+    await pubsub.publish(GraphQLSubscriptions.NEW_DOCUMENTS, newDocuments)
   })
-
-  return {
-   subscribe: () => pubsub.asyncIterator(GraphQLSubscriptions.NEW_DOCUMENTS)
-  }
 }
diff --git a/back/webmin/subscriptions/gql-events-sync-progress.ts b/back/webmin/subscriptions/gql-events-sync-progress.ts
index b79c325..a26c73a 100644
--- a/back/webmin/subscriptions/gql-events-sync-progress.ts
+++ b/back/webmin/subscriptions/gql-events-sync-progress.ts
@@ -1,15 +1,8 @@
 import {GraphQLSubscriptions} from '../constants'
-import {SyncProgress} from '../../../common/dto'
 import {pubsub} from '../webmin'
+import {SyncProgress} from '../../../common/dto'
+import {transformSyncProgress} from '../graphql/types/transform/sync-progress.transform'
 
 export function gqlPushSyncProgress(progress: SyncProgress) {
-  return pubsub.publish(GraphQLSubscriptions.SYNC_PROGRESS, {
-    syncProgress: progress
-  })
-}
-
-export function gqlSubscribeSyncProgress() {
-  return {
-    subscribe: () => pubsub.asyncIterator(GraphQLSubscriptions.SYNC_PROGRESS)
-  }
+  return pubsub.publish(GraphQLSubscriptions.SYNC_PROGRESS, transformSyncProgress(progress))
 }
diff --git a/back/webmin/webmin.ts b/back/webmin/webmin.ts
index 4e9a5a7..e02d220 100644
--- a/back/webmin/webmin.ts
+++ b/back/webmin/webmin.ts
@@ -1,48 +1,28 @@
-import * as path from 'path';
-import * as fs from 'fs';
-import {makeExecutableSchema} from 'graphql-tools';
+import "reflect-metadata";
 import {Server} from 'duniter/server';
 
 import {PubSub} from 'graphql-subscriptions';
-import {gqlUid} from './queries/gql-uid'
-import {gqlIsSyncStarted, gqlSynchronize} from './queries/gql-synchronize'
-import {gqlNodeStart, gqlNodeState, gqlStopAndResetData} from './queries/gql-node-state'
-import {gqlWs2pInfos} from './queries/gql-ws2p'
-import {gqlSubscribeSyncProgress} from './subscriptions/gql-events-sync-progress'
+import {buildSchema} from 'type-graphql'
+import {BigResolver} from './graphql/resolvers/BigResolver'
+import {ApplicationContext} from './graphql/di/application-context'
+import {BigSubscriber} from './graphql/subscribers/BigSubscriber'
 import {gqlSubscribeBlockchainEvents} from './subscriptions/gql-events-blockchain'
 import {gqlSubscribeDocuments} from './subscriptions/gql-events-documents'
-import {gqlHeads} from './queries/gql-heads'
-import {gqlCurrent} from './queries/gql-current'
 
 export const pubsub = new PubSub();
 
 export function plugModule(server: Server, startServices: () => Promise<void>, stopServices: () => Promise<void>) {
 
-  return makeExecutableSchema({
+  // Dependency Injection
+  ApplicationContext.server = server
+  ApplicationContext.startServices = startServices
+  ApplicationContext.stopServices = stopServices
 
-    // Read the GraphQL schema definition
-    typeDefs: fs.readFileSync(path.join(__dirname, 'schema.graphqls'), 'utf8'),
+  gqlSubscribeBlockchainEvents(server)
+  gqlSubscribeDocuments(server)
 
-    resolvers: {
-
-      Query: {
-        hello:            () => 'Welcome to Duniter WEBMIN API.',
-        isSyncStarted:    gqlIsSyncStarted(),
-        current:          gqlCurrent(server),
-        heads:            gqlHeads(server),
-        ws2pinfos:        gqlWs2pInfos(server),
-        nodeState:        gqlNodeState(server),
-        stopAndResetData: gqlStopAndResetData(server, stopServices),
-        startNode:        gqlNodeStart(startServices),
-        synchronize:      gqlSynchronize(server),
-        uid:              gqlUid(server)
-      },
-
-      Subscription: {
-        syncProgress:     gqlSubscribeSyncProgress(),
-        newDocuments:     gqlSubscribeDocuments(server),
-        bcEvents:         gqlSubscribeBlockchainEvents(server)
-      },
-    }
+  return buildSchema({
+    resolvers: [BigResolver, BigSubscriber],
+    pubSub: pubsub
   })
 }
diff --git a/common/dto.ts b/common/dto.ts
index c2248a5..87d2d7e 100644
--- a/common/dto.ts
+++ b/common/dto.ts
@@ -1,3 +1,5 @@
+import {P2pCandidate} from 'duniter/app/modules/crawler/lib/sync/p2p/p2p-candidate'
+
 export interface SyncProgress {
   download?: number
   saved?: number
@@ -6,7 +8,20 @@ export interface SyncProgress {
   peersSync?: number
   sync?: boolean
   error?: string
-  p2pData?: any
+  p2pData?: P2PData
+}
+
+export class P2PData {
+
+  name?: string
+  data?: P2PDataDetail
+}
+
+export class P2PDataDetail {
+
+  chunkIndex?: number
+  nodes?: P2pCandidate[]
+  node?: P2pCandidate
 }
 
 export interface SyncEnding {
diff --git a/package.json b/package.json
index d0b6703..e700a73 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,8 @@
   "version": "0.1.0",
   "private": true,
   "scripts": {
+    "test": "tsc --build back/tsconfig.back.json && mocha back/tests/",
+    "test:run": "mocha back/tests/",
     "serve": "vue-cli-service serve",
     "build": "vue-cli-service build",
     "dev": "concurrently --kill-others \"tsc --build back/tsconfig.back.json -w\" \"nodemon back/index.js ui\" \"vue-cli-service serve\"",
@@ -20,8 +22,10 @@
     "graphql-server-express": "^1.4.0",
     "graphql-subscriptions": "^1.0.0",
     "graphql-tools": "^4.0.3",
+    "reflect-metadata": "^0.1.13",
     "register-service-worker": "^1.6.2",
     "subscriptions-transport-ws": "^0.9.15",
+    "type-graphql": "^0.17.3",
     "vue": "^2.6.10",
     "vue-apollo": "^3.0.0-beta.11",
     "vue-class-component": "^7.0.2",
@@ -39,6 +43,7 @@
     "@types/graphql": "^14.0.3",
     "@types/js-yaml": "^3.12.0",
     "@types/minimist": "^1.2.0",
+    "@types/mocha": "^5.2.6",
     "@types/node": "^12.0.0",
     "@types/request-promise": "^4.1.42",
     "@vue/cli-plugin-pwa": "^3.7.0",
@@ -52,6 +57,7 @@
     "express-http-proxy": "^1.5.1",
     "graphql-tag": "^2.9.0",
     "js-yaml": "^3.12.1",
+    "mocha": "^6.1.4",
     "moment": "^2.23.0",
     "nodemon": "^1.18.9",
     "querablep": "^0.1.0",
@@ -59,6 +65,7 @@
     "request-promise": "^4.2.2",
     "sass": "^1.18.0",
     "sass-loader": "^7.1.0",
+    "ts-node": "^8.1.0",
     "typescript": "^3.4.3",
     "vue-cli-plugin-apollo": "^0.20.0",
     "vue-template-compiler": "^2.5.21"
diff --git a/src/views/Sync.vue b/src/views/Sync.vue
index 2c19854..cf73e54 100644
--- a/src/views/Sync.vue
+++ b/src/views/Sync.vue
@@ -72,9 +72,10 @@
 
 <script lang="ts">
   import {Component, Vue} from 'vue-property-decorator';
-  import {SyncProgress} from '../../common/dto';
   import {RouteNames} from '@/lib/constants';
   import SyncProgressBar from '@/components/sync-progress-bar.vue';
+  import {P2PDataCandidateType} from "../../back/webmin/graphql/types/P2PDataCandidateType";
+  import {SyncProgressType} from "../../back/webmin/graphql/types/SyncProgressType";
 
   @Component({
     components: {
@@ -96,7 +97,7 @@
     }
 
     downloadings: SyncChunk[] = []
-    p2pCandidates: { [hash: string]: P2pCandidate } = {}
+    p2pCandidates: { [hash: string]: P2PDataCandidateType } = {}
     syncFailedNoNodeFound = false
     syncFailCannotConnectToRemote = false
 
@@ -115,7 +116,7 @@
     async mounted() {
       // Listen to sync events
       this.$webmin.syncProgress()
-        .subscribe(async (data: { data: { syncProgress: SyncProgress } }) => {
+        .subscribe(async (data: { data: { syncProgress: SyncProgressType } }) => {
           this.applied = data.data.syncProgress.applied || this.applied
           this.download = data.data.syncProgress.download || this.download
           this.saved = data.data.syncProgress.saved || this.saved
@@ -193,23 +194,9 @@
   export interface SyncChunk {
     chunkIndex: number
     order: number
-    downloadingNode?: P2pCandidate
+    downloadingNode?: P2PDataCandidateType
     gotten: boolean
     isDownload: boolean
   }
 
-  export interface SyncCandidate {
-    c: P2pCandidate
-  }
-
-
-  export class P2pCandidate {
-
-    pubkey: string
-    hostName: string
-    hasAvailableApi: boolean
-    apiName: string
-    avgResponseTime: number
-    responseTimes: number[]
-  }
 </script>
diff --git a/tsconfig.json b/tsconfig.json
index 88b61d1..a82b7cf 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,6 +6,7 @@
     "jsx": "preserve",
     "importHelpers": true,
     "moduleResolution": "node",
+    "emitDecoratorMetadata": true,
     "experimentalDecorators": true,
     "esModuleInterop": true,
     "skipLibCheck": true,
diff --git a/yarn.lock b/yarn.lock
index 40d58d0..ba951c9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -301,6 +301,11 @@
   resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6"
   integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
 
+"@types/mocha@^5.2.6":
+  version "5.2.6"
+  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b"
+  integrity sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==
+
 "@types/node@*", "@types/node@^12.0.0":
   version "12.0.0"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.0.tgz#d11813b9c0ff8aaca29f04cbc12817f4c7d656e5"
@@ -311,6 +316,11 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.6.tgz#9cbfcb62c50947217f4d88d4d274cc40c22625a9"
   integrity sha512-Fvm24+u85lGmV4hT5G++aht2C5I4Z4dYlWZIh62FAfFO/TfzXtPpoLI6I7AuBWkIFqZCnhFOoTT7RjjaIL5Fjg==
 
+"@types/node@^11.13.7":
+  version "11.13.10"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.10.tgz#4df59e5966b56f512bac98898bcbee5067411f0f"
+  integrity sha512-leUNzbFTMX94TWaIKz8N15Chu55F9QSH+INKayQr5xpkasBQBRF3qQXfo3/dOnMU/dEIit+Y/SU8HyOjq++GwA==
+
 "@types/normalize-package-data@^2.4.0":
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
@@ -344,6 +354,11 @@
     "@types/node" "*"
     "@types/tough-cookie" "*"
 
+"@types/semver@^6.0.0":
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.0.0.tgz#86ba89f02a414e39c68d02b351872e4ed31bd773"
+  integrity sha512-OO0srjOGH99a4LUN2its3+r6CBYcplhJ466yLqs+zvAWgphCpS8hYZEZ797tRDP/QKcqTdb/YCN6ifASoAWkrQ==
+
 "@types/serve-static@*":
   version "1.13.2"
   resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.2.tgz#f5ac4d7a6420a99a6a45af4719f4dcd8cd907a48"
@@ -790,6 +805,11 @@ ansi-align@^2.0.0:
   dependencies:
     string-width "^2.0.0"
 
+ansi-colors@3.2.3:
+  version "3.2.3"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813"
+  integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==
+
 ansi-colors@^3.0.0:
   version "3.2.4"
   resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
@@ -1704,6 +1724,11 @@ brorand@^1.0.1:
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
   integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
 
+browser-stdout@1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
+  integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
+
 browserify-aes@^1.0.0, browserify-aes@^1.0.4:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48"
@@ -2157,6 +2182,14 @@ class-utils@^0.3.5:
     isobject "^3.0.0"
     static-extend "^0.1.1"
 
+class-validator@>=0.9.1:
+  version "0.9.1"
+  resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.9.1.tgz#d60e58c5d14abca0a41bce38cf792ad4c46d1531"
+  integrity sha512-3wApflrd3ywVZyx4jaasGoFt8pmo4aGLPPAEKCKCsTRWVGPilahD88q3jQjRQwja50rl9a7rsP5LAxJYwGK8/Q==
+  dependencies:
+    google-libphonenumber "^3.1.6"
+    validator "10.4.0"
+
 clean-css@4.2.x:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17"
@@ -2953,7 +2986,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
   dependencies:
     ms "2.0.0"
 
-debug@^3.0.1, debug@^3.1.0, debug@^3.2.5, debug@^3.2.6:
+debug@3.2.6, debug@^3.0.1, debug@^3.1.0, debug@^3.2.5, debug@^3.2.6:
   version "3.2.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
   integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -3131,7 +3164,7 @@ dicer@0.3.0:
   dependencies:
     streamsearch "0.1.2"
 
-diff@^3.1.0, diff@^3.2.0:
+diff@3.5.0, diff@^3.1.0, diff@^3.2.0:
   version "3.5.0"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
   integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
@@ -3495,7 +3528,7 @@ escape-html@~1.0.3:
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
   integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
 
-escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
@@ -3955,6 +3988,13 @@ find-cache-dir@^2.0.0:
     make-dir "^2.0.0"
     pkg-dir "^3.0.0"
 
+find-up@3.0.0, find-up@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
+  integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
+  dependencies:
+    locate-path "^3.0.0"
+
 find-up@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
@@ -3962,12 +4002,12 @@ find-up@^2.1.0:
   dependencies:
     locate-path "^2.0.0"
 
-find-up@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
-  integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
+flat@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2"
+  integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==
   dependencies:
-    locate-path "^3.0.0"
+    is-buffer "~2.0.3"
 
 flush-write-stream@^1.0.0:
   version "1.1.1"
@@ -4268,6 +4308,18 @@ glob-to-regexp@^0.3.0:
   resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
   integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
 
+glob@7.1.3:
+  version "7.1.3"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
+  integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
 glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
   version "7.1.4"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
@@ -4324,6 +4376,11 @@ globby@^9.2.0:
     pify "^4.0.1"
     slash "^2.0.0"
 
+google-libphonenumber@^3.1.6:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/google-libphonenumber/-/google-libphonenumber-3.2.2.tgz#3d9d7ba727e99a50812f21b0ed313723b76c5c54"
+  integrity sha512-ubjGeosYPeusjYbUHy76lCniGTTI0k1rIFc+uKBX+jHQLDmWOSUtlFUxaeoLJ+Y+PAMM6dWp+C1HjHx5BI8kEw==
+
 got@^6.7.1:
   version "6.7.1"
   resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
@@ -4404,6 +4461,13 @@ graphql-extensions@^0.0.x, graphql-extensions@~0.0.9:
     core-js "^2.5.3"
     source-map-support "^0.5.1"
 
+graphql-query-complexity@^0.2.3:
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/graphql-query-complexity/-/graphql-query-complexity-0.2.3.tgz#ca26790fd5d22cb3d4ca8f43d19605929d8cf27f"
+  integrity sha512-XLvEsqGTJmJmgof8u5NjIkBHL75b4Inw1F8JQ3jGRBhr3hVFx6aWOTL7C2aknp1uIh8dRmqwzrb9gas2NLHnfA==
+  dependencies:
+    lodash.get "^4.4.2"
+
 graphql-server-express@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/graphql-server-express/-/graphql-server-express-1.4.0.tgz#f62b49dc70c860b653e76e21defa7f27324b764d"
@@ -4411,7 +4475,7 @@ graphql-server-express@^1.4.0:
   dependencies:
     apollo-server-express "^1.4.0"
 
-graphql-subscriptions@^1.0.0:
+graphql-subscriptions@^1.0.0, graphql-subscriptions@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-1.1.0.tgz#5f2fa4233eda44cf7570526adfcf3c16937aef11"
   integrity sha512-6WzlBFC0lWmXJbIVE8OgFgXIP4RJi3OQgTPa0DVMsDXdpRDjTsM1K9wfl5HSYX7R87QAGlvcv2Y4BIZa/ItonA==
@@ -4451,6 +4515,11 @@ graphql@^14.0.2, graphql@^14.1.1:
   dependencies:
     iterall "^1.2.2"
 
+growl@1.10.5:
+  version "1.10.5"
+  resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
+  integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
+
 gzip-size@^5.0.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.0.tgz#2db0396c71f5c902d5cf6b52add5030b93c99bd2"
@@ -4621,7 +4690,7 @@ hawk@~3.1.3:
     hoek "2.x.x"
     sntp "1.x.x"
 
-he@1.2.x, he@^1.1.0:
+he@1.2.0, he@1.2.x, he@^1.1.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
@@ -5068,6 +5137,11 @@ is-buffer@^1.1.5:
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
   integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
 
+is-buffer@~2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
+  integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
+
 is-callable@^1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
@@ -5430,6 +5504,14 @@ js-tokens@^4.0.0:
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
   integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
 
+js-yaml@3.13.1, js-yaml@^3.12.1, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.9.0:
+  version "3.13.1"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
+  integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
+  dependencies:
+    argparse "^1.0.7"
+    esprima "^4.0.0"
+
 js-yaml@3.8.2:
   version "3.8.2"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.2.tgz#02d3e2c0f6beab20248d412c352203827d786721"
@@ -5438,14 +5520,6 @@ js-yaml@3.8.2:
     argparse "^1.0.7"
     esprima "^3.1.1"
 
-js-yaml@^3.12.1, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.9.0:
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
-  integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
-  dependencies:
-    argparse "^1.0.7"
-    esprima "^4.0.0"
-
 jsbn@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
@@ -5711,6 +5785,11 @@ lodash.defaultsdeep@^4.6.0:
   resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz#bec1024f85b1bd96cbea405b23c14ad6443a6f81"
   integrity sha1-vsECT4WxvZbL6kBbI8FK1kQ6b4E=
 
+lodash.get@^4.4.2:
+  version "4.4.2"
+  resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
+  integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
+
 lodash.mapvalues@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
@@ -5776,7 +5855,7 @@ lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.3
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
   integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
 
-log-symbols@^2.2.0:
+log-symbols@2.2.0, log-symbols@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
   integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
@@ -6067,7 +6146,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
 
-minimatch@^3.0.0, minimatch@^3.0.4:
+minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
   integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@@ -6152,13 +6231,42 @@ mixin-object@^2.0.1:
     for-in "^0.1.3"
     is-extendable "^0.1.1"
 
-mkdirp@0.5, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5, mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
   integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
   dependencies:
     minimist "0.0.8"
 
+mocha@^6.1.4:
+  version "6.1.4"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.1.4.tgz#e35fada242d5434a7e163d555c705f6875951640"
+  integrity sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==
+  dependencies:
+    ansi-colors "3.2.3"
+    browser-stdout "1.3.1"
+    debug "3.2.6"
+    diff "3.5.0"
+    escape-string-regexp "1.0.5"
+    find-up "3.0.0"
+    glob "7.1.3"
+    growl "1.10.5"
+    he "1.2.0"
+    js-yaml "3.13.1"
+    log-symbols "2.2.0"
+    minimatch "3.0.4"
+    mkdirp "0.5.1"
+    ms "2.1.1"
+    node-environment-flags "1.0.5"
+    object.assign "4.1.0"
+    strip-json-comments "2.0.1"
+    supports-color "6.0.0"
+    which "1.3.1"
+    wide-align "1.1.3"
+    yargs "13.2.2"
+    yargs-parser "13.0.0"
+    yargs-unparser "1.5.0"
+
 moment@2.19.3:
   version "2.19.3"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.3.tgz#bdb99d270d6d7fda78cc0fbace855e27fe7da69f"
@@ -6207,7 +6315,7 @@ ms@2.0.0:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
 
-ms@^2.1.1:
+ms@2.1.1, ms@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
   integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
@@ -6350,6 +6458,14 @@ node-abi@^2.7.0:
   dependencies:
     semver "^5.4.1"
 
+node-environment-flags@1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a"
+  integrity sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==
+  dependencies:
+    object.getownpropertydescriptors "^2.0.3"
+    semver "^5.7.0"
+
 node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.3.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.5.0.tgz#8028c49fc1191bba56a07adc6e2a954644a48501"
@@ -6677,7 +6793,7 @@ object-copy@^0.1.0:
     define-property "^0.2.5"
     kind-of "^3.0.3"
 
-object-keys@^1.0.12:
+object-keys@^1.0.11, object-keys@^1.0.12:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
   integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
@@ -6694,6 +6810,16 @@ object-visit@^1.0.0:
   dependencies:
     isobject "^3.0.0"
 
+object.assign@4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
+  integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
+  dependencies:
+    define-properties "^1.1.2"
+    function-bind "^1.1.1"
+    has-symbols "^1.0.0"
+    object-keys "^1.0.11"
+
 object.getownpropertydescriptors@^2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
@@ -7884,6 +8010,11 @@ rechoir@^0.6.2:
   dependencies:
     resolve "^1.1.6"
 
+reflect-metadata@^0.1.13:
+  version "0.1.13"
+  resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
+  integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
+
 regenerate@^1.2.1:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
@@ -8294,7 +8425,7 @@ semver-diff@^2.0.0:
   dependencies:
     semver "^5.0.3"
 
-"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0:
+"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0:
   version "5.7.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
   integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
@@ -8994,7 +9125,7 @@ strip-indent@^2.0.0:
   resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
   integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=
 
-strip-json-comments@~2.0.1:
+strip-json-comments@2.0.1, strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
   integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
@@ -9019,6 +9150,13 @@ subscriptions-transport-ws@^0.9.11, subscriptions-transport-ws@^0.9.15, subscrip
     symbol-observable "^1.0.4"
     ws "^5.2.0"
 
+supports-color@6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a"
+  integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==
+  dependencies:
+    has-flag "^3.0.0"
+
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -9406,7 +9544,7 @@ ts-loader@^5.3.3:
     micromatch "^3.1.4"
     semver "^5.0.1"
 
-ts-node@^8.0.3:
+ts-node@^8.0.3, ts-node@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.1.0.tgz#8c4b37036abd448577db22a061fd7a67d47e658e"
   integrity sha512-34jpuOrxDuf+O6iW1JpgTRDFynUZ1iEqtYruBqh35gICNjN8x+LpVcPAcwzLPi9VU6mdA3ym+x233nZmZp445A==
@@ -9475,6 +9613,21 @@ type-fest@^0.4.1:
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8"
   integrity sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==
 
+type-graphql@^0.17.3:
+  version "0.17.3"
+  resolved "https://registry.yarnpkg.com/type-graphql/-/type-graphql-0.17.3.tgz#91ee6b3dcc57c34d9b9a1297cbf64e4c57570176"
+  integrity sha512-/y4xFjAeluoQQR47RPw7cJarmXKDfn0GQFXCeizjYTtzAdcii6RqdnXOMDpsHHvfzeGvE6BUnneJhzekUfV/+A==
+  dependencies:
+    "@types/glob" "^7.1.1"
+    "@types/node" "^11.13.7"
+    "@types/semver" "^6.0.0"
+    class-validator ">=0.9.1"
+    glob "^7.1.3"
+    graphql-query-complexity "^0.2.3"
+    graphql-subscriptions "^1.1.0"
+    semver "^6.0.0"
+    tslib "^1.9.3"
+
 type-is@^1.6.16, type-is@~1.6.14, type-is@~1.6.16, type-is@~1.6.17:
   version "1.6.18"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
@@ -9814,6 +9967,11 @@ validate-npm-package-license@^3.0.1:
     spdx-correct "^3.0.0"
     spdx-expression-parse "^3.0.0"
 
+validator@10.4.0:
+  version "10.4.0"
+  resolved "https://registry.yarnpkg.com/validator/-/validator-10.4.0.tgz#ee99a44afb3bb5ed350a159f056ca72a204cfc3c"
+  integrity sha512-Q/wBy3LB1uOyssgNlXSRmaf22NxjvDNZM2MtIQ4jaEOAB61xsh1TQxsq1CgzUMBV1lDrVMogIh8GjG1DYW0zLg==
+
 vary@^1, vary@~1.1.0, vary@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@@ -10132,14 +10290,14 @@ which-pm-runs@^1.0.0:
   resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
   integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
 
-which@^1.2.9:
+which@1.3.1, which@^1.2.9:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
   integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
   dependencies:
     isexe "^2.0.0"
 
-wide-align@^1.1.0:
+wide-align@1.1.3, wide-align@^1.1.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
   integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
@@ -10398,6 +10556,14 @@ yallist@^3.0.0, yallist@^3.0.2:
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
   integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
 
+yargs-parser@13.0.0:
+  version "13.0.0"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.0.0.tgz#3fc44f3e76a8bdb1cc3602e860108602e5ccde8b"
+  integrity sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
 yargs-parser@^11.1.1:
   version "11.1.1"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
@@ -10414,7 +10580,16 @@ yargs-parser@^13.0.0:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
-yargs@12.0.5, yargs@^12.0.1:
+yargs-unparser@1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.5.0.tgz#f2bb2a7e83cbc87bb95c8e572828a06c9add6e0d"
+  integrity sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==
+  dependencies:
+    flat "^4.1.0"
+    lodash "^4.17.11"
+    yargs "^12.0.5"
+
+yargs@12.0.5, yargs@^12.0.1, yargs@^12.0.5:
   version "12.0.5"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
   integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
@@ -10432,7 +10607,7 @@ yargs@12.0.5, yargs@^12.0.1:
     y18n "^3.2.1 || ^4.0.0"
     yargs-parser "^11.1.1"
 
-yargs@^13.0.0:
+yargs@13.2.2, yargs@^13.0.0:
   version "13.2.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.2.tgz#0c101f580ae95cea7f39d927e7770e3fdc97f993"
   integrity sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==
-- 
GitLab