WIP add transaction to batch operation together and deal with failure

Michael Muré created

Change summary

bug/op_add_comment.go              |   14 
cache/bug_cache.go                 |   19 
graphql/graph/gen_graph.go         |  766 +++++++++++++++++
graphql/models/gen_models.go       |   48 +
graphql/resolvers/mutation.go      |   98 ++
graphql/resolvers/root.go          |    3 
graphql/schema/root.graphql        |   22 
graphql/schema/transaction.graphql |   49 +
webui/package-lock.json            | 1323 ++++++++++++++++++-------------
webui/src/pages/list/ListQuery.tsx |    8 
10 files changed, 1,715 insertions(+), 635 deletions(-)

Detailed changes

bug/op_add_comment.go 🔗

@@ -122,11 +122,23 @@ func AddComment(b Interface, author identity.Interface, unixTime int64, message
 	return AddCommentWithFiles(b, author, unixTime, message, nil)
 }
 
+func OpAddComment(author identity.Interface, unixTime int64, message string) (*AddCommentOperation, error) {
+	return OpAddCommentWithFiles(author, unixTime, message, nil)
+}
+
 func AddCommentWithFiles(b Interface, author identity.Interface, unixTime int64, message string, files []git.Hash) (*AddCommentOperation, error) {
+	op, err := OpAddCommentWithFiles(author, unixTime, message, files)
+	if err != nil {
+		return nil, err
+	}
+	b.Append(op)
+	return op, nil
+}
+
+func OpAddCommentWithFiles(author identity.Interface, unixTime int64, message string, files []git.Hash) (*AddCommentOperation, error) {
 	addCommentOp := NewAddCommentOp(author, unixTime, message, files)
 	if err := addCommentOp.Validate(); err != nil {
 		return nil, err
 	}
-	b.Append(addCommentOp)
 	return addCommentOp, nil
 }

cache/bug_cache.go 🔗

@@ -68,6 +68,10 @@ func (c *BugCache) AddComment(message string) (*bug.AddCommentOperation, error)
 	return c.AddCommentWithFiles(message, nil)
 }
 
+func (c *BugCache) OpAddComment(message string) (*bug.AddCommentOperation, error) {
+	return c.Op
+}
+
 func (c *BugCache) AddCommentWithFiles(message string, files []git.Hash) (*bug.AddCommentOperation, error) {
 	author, err := c.repoCache.GetUserIdentity()
 	if err != nil {
@@ -77,6 +81,10 @@ func (c *BugCache) AddCommentWithFiles(message string, files []git.Hash) (*bug.A
 	return c.AddCommentRaw(author, time.Now().Unix(), message, files, nil)
 }
 
+func (c *BugCache) OpAddCommentWithFiles(message string, files []git.Hash) (*bug.AddCommentOperation, error) {
+	return c.Op
+}
+
 func (c *BugCache) AddCommentRaw(author *IdentityCache, unixTime int64, message string, files []git.Hash, metadata map[string]string) (*bug.AddCommentOperation, error) {
 	op, err := bug.AddCommentWithFiles(c.bug, author.Identity, unixTime, message, files)
 	if err != nil {
@@ -90,6 +98,17 @@ func (c *BugCache) AddCommentRaw(author *IdentityCache, unixTime int64, message
 	return op, c.notifyUpdated()
 }
 
+func (c *BugCache) OpAddCommentRaw(author *IdentityCache, unixTime int64, message string, files []git.Hash, metadata map[string]string) (*bug.AddCommentOperation, error) {
+	op, err := bug.OpAddCommentWithFiles(author.Identity, unixTime, message, files)
+	if err != nil {
+		return nil, err
+	}
+
+	for key, value := range metadata {
+		op.SetMetadata(key, value)
+	}
+}
+
 func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
 	author, err := c.repoCache.GetUserIdentity()
 	if err != nil {

graphql/graph/gen_graph.go 🔗

@@ -163,6 +163,12 @@ type ComplexityRoot struct {
 		Message func(childComplexity int) int
 	}
 
+	CommitPayload struct {
+		Bug              func(childComplexity int) int
+		ClientMutationID func(childComplexity int) int
+		ID               func(childComplexity int) int
+	}
+
 	CreateOperation struct {
 		Author  func(childComplexity int) int
 		Date    func(childComplexity int) int
@@ -254,12 +260,15 @@ type ComplexityRoot struct {
 	}
 
 	Mutation struct {
-		AddComment   func(childComplexity int, input models.AddCommentInput) int
-		ChangeLabels func(childComplexity int, input *models.ChangeLabelInput) int
-		CloseBug     func(childComplexity int, input models.CloseBugInput) int
-		NewBug       func(childComplexity int, input models.NewBugInput) int
-		OpenBug      func(childComplexity int, input models.OpenBugInput) int
-		SetTitle     func(childComplexity int, input models.SetTitleInput) int
+		AddComment       func(childComplexity int, input models.AddCommentInput, txID *string) int
+		ChangeLabels     func(childComplexity int, input models.ChangeLabelInput, txID *string) int
+		CloseBug         func(childComplexity int, input models.CloseBugInput, txID *string) int
+		Commit           func(childComplexity int, input models.CommitInput) int
+		NewBug           func(childComplexity int, input models.NewBugInput, txID *string) int
+		OpenBug          func(childComplexity int, input models.OpenBugInput, txID *string) int
+		Rollback         func(childComplexity int, input models.RollbackInput) int
+		SetTitle         func(childComplexity int, input models.SetTitleInput, txID *string) int
+		StartTransaction func(childComplexity int, input models.StartTransactionInput) int
 	}
 
 	NewBugPayload struct {
@@ -307,6 +316,12 @@ type ComplexityRoot struct {
 		ValidLabels   func(childComplexity int, after *string, before *string, first *int, last *int) int
 	}
 
+	RollbackPayload struct {
+		Bug              func(childComplexity int) int
+		ClientMutationID func(childComplexity int) int
+		ID               func(childComplexity int) int
+	}
+
 	SetStatusOperation struct {
 		Author func(childComplexity int) int
 		Date   func(childComplexity int) int
@@ -343,6 +358,11 @@ type ComplexityRoot struct {
 		Was    func(childComplexity int) int
 	}
 
+	StartTransactionPayload struct {
+		ClientMutationID func(childComplexity int) int
+		ID               func(childComplexity int) int
+	}
+
 	TimelineItemConnection struct {
 		Edges      func(childComplexity int) int
 		Nodes      func(childComplexity int) int
@@ -430,12 +450,15 @@ type LabelChangeTimelineItemResolver interface {
 	Date(ctx context.Context, obj *bug.LabelChangeTimelineItem) (*time.Time, error)
 }
 type MutationResolver interface {
-	NewBug(ctx context.Context, input models.NewBugInput) (*models.NewBugPayload, error)
-	AddComment(ctx context.Context, input models.AddCommentInput) (*models.AddCommentPayload, error)
-	ChangeLabels(ctx context.Context, input *models.ChangeLabelInput) (*models.ChangeLabelPayload, error)
-	OpenBug(ctx context.Context, input models.OpenBugInput) (*models.OpenBugPayload, error)
-	CloseBug(ctx context.Context, input models.CloseBugInput) (*models.CloseBugPayload, error)
-	SetTitle(ctx context.Context, input models.SetTitleInput) (*models.SetTitlePayload, error)
+	NewBug(ctx context.Context, input models.NewBugInput, txID *string) (*models.NewBugPayload, error)
+	AddComment(ctx context.Context, input models.AddCommentInput, txID *string) (*models.AddCommentPayload, error)
+	ChangeLabels(ctx context.Context, input models.ChangeLabelInput, txID *string) (*models.ChangeLabelPayload, error)
+	OpenBug(ctx context.Context, input models.OpenBugInput, txID *string) (*models.OpenBugPayload, error)
+	CloseBug(ctx context.Context, input models.CloseBugInput, txID *string) (*models.CloseBugPayload, error)
+	SetTitle(ctx context.Context, input models.SetTitleInput, txID *string) (*models.SetTitlePayload, error)
+	StartTransaction(ctx context.Context, input models.StartTransactionInput) (*models.StartTransactionPayload, error)
+	Commit(ctx context.Context, input models.CommitInput) (*models.CommitPayload, error)
+	Rollback(ctx context.Context, input models.RollbackInput) (*models.RollbackPayload, error)
 }
 type QueryResolver interface {
 	Repository(ctx context.Context, ref *string) (*models.Repository, error)
@@ -911,6 +934,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.CommentHistoryStep.Message(childComplexity), true
 
+	case "CommitPayload.bug":
+		if e.complexity.CommitPayload.Bug == nil {
+			break
+		}
+
+		return e.complexity.CommitPayload.Bug(childComplexity), true
+
+	case "CommitPayload.clientMutationId":
+		if e.complexity.CommitPayload.ClientMutationID == nil {
+			break
+		}
+
+		return e.complexity.CommitPayload.ClientMutationID(childComplexity), true
+
+	case "CommitPayload.id":
+		if e.complexity.CommitPayload.ID == nil {
+			break
+		}
+
+		return e.complexity.CommitPayload.ID(childComplexity), true
+
 	case "CreateOperation.author":
 		if e.complexity.CreateOperation.Author == nil {
 			break
@@ -1299,7 +1343,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 			return 0, false
 		}
 
-		return e.complexity.Mutation.AddComment(childComplexity, args["input"].(models.AddCommentInput)), true
+		return e.complexity.Mutation.AddComment(childComplexity, args["input"].(models.AddCommentInput), args["txId"].(*string)), true
 
 	case "Mutation.changeLabels":
 		if e.complexity.Mutation.ChangeLabels == nil {
@@ -1311,7 +1355,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 			return 0, false
 		}
 
-		return e.complexity.Mutation.ChangeLabels(childComplexity, args["input"].(*models.ChangeLabelInput)), true
+		return e.complexity.Mutation.ChangeLabels(childComplexity, args["input"].(models.ChangeLabelInput), args["txId"].(*string)), true
 
 	case "Mutation.closeBug":
 		if e.complexity.Mutation.CloseBug == nil {
@@ -1323,7 +1367,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 			return 0, false
 		}
 
-		return e.complexity.Mutation.CloseBug(childComplexity, args["input"].(models.CloseBugInput)), true
+		return e.complexity.Mutation.CloseBug(childComplexity, args["input"].(models.CloseBugInput), args["txId"].(*string)), true
+
+	case "Mutation.commit":
+		if e.complexity.Mutation.Commit == nil {
+			break
+		}
+
+		args, err := ec.field_Mutation_commit_args(context.TODO(), rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Mutation.Commit(childComplexity, args["input"].(models.CommitInput)), true
 
 	case "Mutation.newBug":
 		if e.complexity.Mutation.NewBug == nil {
@@ -1335,7 +1391,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 			return 0, false
 		}
 
-		return e.complexity.Mutation.NewBug(childComplexity, args["input"].(models.NewBugInput)), true
+		return e.complexity.Mutation.NewBug(childComplexity, args["input"].(models.NewBugInput), args["txId"].(*string)), true
 
 	case "Mutation.openBug":
 		if e.complexity.Mutation.OpenBug == nil {
@@ -1347,7 +1403,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 			return 0, false
 		}
 
-		return e.complexity.Mutation.OpenBug(childComplexity, args["input"].(models.OpenBugInput)), true
+		return e.complexity.Mutation.OpenBug(childComplexity, args["input"].(models.OpenBugInput), args["txId"].(*string)), true
+
+	case "Mutation.rollback":
+		if e.complexity.Mutation.Rollback == nil {
+			break
+		}
+
+		args, err := ec.field_Mutation_rollback_args(context.TODO(), rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Mutation.Rollback(childComplexity, args["input"].(models.RollbackInput)), true
 
 	case "Mutation.setTitle":
 		if e.complexity.Mutation.SetTitle == nil {
@@ -1359,7 +1427,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 			return 0, false
 		}
 
-		return e.complexity.Mutation.SetTitle(childComplexity, args["input"].(models.SetTitleInput)), true
+		return e.complexity.Mutation.SetTitle(childComplexity, args["input"].(models.SetTitleInput), args["txId"].(*string)), true
+
+	case "Mutation.startTransaction":
+		if e.complexity.Mutation.StartTransaction == nil {
+			break
+		}
+
+		args, err := ec.field_Mutation_startTransaction_args(context.TODO(), rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Mutation.StartTransaction(childComplexity, args["input"].(models.StartTransactionInput)), true
 
 	case "NewBugPayload.bug":
 		if e.complexity.NewBugPayload.Bug == nil {
@@ -1559,6 +1639,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.Repository.ValidLabels(childComplexity, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int)), true
 
+	case "RollbackPayload.bug":
+		if e.complexity.RollbackPayload.Bug == nil {
+			break
+		}
+
+		return e.complexity.RollbackPayload.Bug(childComplexity), true
+
+	case "RollbackPayload.clientMutationId":
+		if e.complexity.RollbackPayload.ClientMutationID == nil {
+			break
+		}
+
+		return e.complexity.RollbackPayload.ClientMutationID(childComplexity), true
+
+	case "RollbackPayload.id":
+		if e.complexity.RollbackPayload.ID == nil {
+			break
+		}
+
+		return e.complexity.RollbackPayload.ID(childComplexity), true
+
 	case "SetStatusOperation.author":
 		if e.complexity.SetStatusOperation.Author == nil {
 			break
@@ -1706,6 +1807,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.SetTitleTimelineItem.Was(childComplexity), true
 
+	case "StartTransactionPayload.clientMutationId":
+		if e.complexity.StartTransactionPayload.ClientMutationID == nil {
+			break
+		}
+
+		return e.complexity.StartTransactionPayload.ClientMutationID(childComplexity), true
+
+	case "StartTransactionPayload.id":
+		if e.complexity.StartTransactionPayload.ID == nil {
+			break
+		}
+
+		return e.complexity.StartTransactionPayload.ID(childComplexity), true
+
 	case "TimelineItemConnection.edges":
 		if e.complexity.TimelineItemConnection.Edges == nil {
 			break
@@ -2277,17 +2392,27 @@ type Repository {
 
 type Mutation {
     """Create a new bug"""
-    newBug(input: NewBugInput!): NewBugPayload!
+    newBug(input: NewBugInput!, txId: TxId): NewBugPayload!
     """Add a new comment to a bug"""
-    addComment(input: AddCommentInput!): AddCommentPayload!
+    addComment(input: AddCommentInput!, txId: TxId): AddCommentPayload!
     """Add or remove a set of label on a bug"""
-    changeLabels(input: ChangeLabelInput): ChangeLabelPayload!
+    changeLabels(input: ChangeLabelInput!, txId: TxId): ChangeLabelPayload!
     """Change a bug's status to open"""
-    openBug(input: OpenBugInput!): OpenBugPayload!
+    openBug(input: OpenBugInput!, txId: TxId): OpenBugPayload!
     """Change a bug's status to closed"""
-    closeBug(input: CloseBugInput!): CloseBugPayload!
+    closeBug(input: CloseBugInput!, txId: TxId): CloseBugPayload!
     """Change a bug's title"""
-    setTitle(input: SetTitleInput!): SetTitlePayload!
+    setTitle(input: SetTitleInput!, txId: TxId): SetTitlePayload!
+
+    """Start a transaction.
+    If a transaction ID if passed to the mutation, operation are accumulated
+    in memory until a commit happen.
+    """
+    startTransaction(input: StartTransactionInput!): StartTransactionPayload!
+    """Commit a transaction. All mutation linked to this transaction are written on disk."""
+    commit(input: CommitInput!): CommitPayload!
+    """Rollback a transaction. Alll mutationlinked to this transaction are discarded."""
+    rollback(input: RollbackInput!): RollbackPayload!
 }
 `, BuiltIn: false},
 	&ast.Source{Name: "schema/timeline.graphql", Input: `"""An item in the timeline of events"""
@@ -2377,6 +2502,55 @@ type SetTitleTimelineItem implements TimelineItem & Authored {
     was: String!
 }
 `, BuiltIn: false},
+	&ast.Source{Name: "schema/transaction.graphql", Input: `scalar TxId
+
+input StartTransactionInput {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """"The name of the repository. If not set, the default repository is used."""
+    repoRef: String
+    """The bug ID's prefix."""
+    prefix: String!
+}
+
+type StartTransactionPayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The identifier of the transaction"""
+    id: TxId!
+}
+
+input CommitInput {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The identifier of the transaction"""
+    id: TxId!
+}
+
+type CommitPayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The identifier of the transaction"""
+    id: TxId!
+    """The affected bug."""
+    bug: Bug!
+}
+
+input RollbackInput {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The identifier of the transaction"""
+    id: TxId!
+}
+
+type RollbackPayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The identifier of the transaction"""
+    id: TxId!
+    """The affected bug."""
+    bug: Bug!
+}`, BuiltIn: false},
 	&ast.Source{Name: "schema/types.graphql", Input: `scalar Time
 scalar Hash
 
@@ -2616,20 +2790,36 @@ func (ec *executionContext) field_Mutation_addComment_args(ctx context.Context,
 		}
 	}
 	args["input"] = arg0
+	var arg1 *string
+	if tmp, ok := rawArgs["txId"]; ok {
+		arg1, err = ec.unmarshalOTxId2ᚖstring(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["txId"] = arg1
 	return args, nil
 }
 
 func (ec *executionContext) field_Mutation_changeLabels_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
 	var err error
 	args := map[string]interface{}{}
-	var arg0 *models.ChangeLabelInput
+	var arg0 models.ChangeLabelInput
 	if tmp, ok := rawArgs["input"]; ok {
-		arg0, err = ec.unmarshalOChangeLabelInput2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐChangeLabelInput(ctx, tmp)
+		arg0, err = ec.unmarshalNChangeLabelInput2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐChangeLabelInput(ctx, tmp)
 		if err != nil {
 			return nil, err
 		}
 	}
 	args["input"] = arg0
+	var arg1 *string
+	if tmp, ok := rawArgs["txId"]; ok {
+		arg1, err = ec.unmarshalOTxId2ᚖstring(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["txId"] = arg1
 	return args, nil
 }
 
@@ -2644,6 +2834,28 @@ func (ec *executionContext) field_Mutation_closeBug_args(ctx context.Context, ra
 		}
 	}
 	args["input"] = arg0
+	var arg1 *string
+	if tmp, ok := rawArgs["txId"]; ok {
+		arg1, err = ec.unmarshalOTxId2ᚖstring(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["txId"] = arg1
+	return args, nil
+}
+
+func (ec *executionContext) field_Mutation_commit_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
+	var err error
+	args := map[string]interface{}{}
+	var arg0 models.CommitInput
+	if tmp, ok := rawArgs["input"]; ok {
+		arg0, err = ec.unmarshalNCommitInput2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐCommitInput(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["input"] = arg0
 	return args, nil
 }
 
@@ -2658,6 +2870,14 @@ func (ec *executionContext) field_Mutation_newBug_args(ctx context.Context, rawA
 		}
 	}
 	args["input"] = arg0
+	var arg1 *string
+	if tmp, ok := rawArgs["txId"]; ok {
+		arg1, err = ec.unmarshalOTxId2ᚖstring(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["txId"] = arg1
 	return args, nil
 }
 
@@ -2672,6 +2892,28 @@ func (ec *executionContext) field_Mutation_openBug_args(ctx context.Context, raw
 		}
 	}
 	args["input"] = arg0
+	var arg1 *string
+	if tmp, ok := rawArgs["txId"]; ok {
+		arg1, err = ec.unmarshalOTxId2ᚖstring(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["txId"] = arg1
+	return args, nil
+}
+
+func (ec *executionContext) field_Mutation_rollback_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
+	var err error
+	args := map[string]interface{}{}
+	var arg0 models.RollbackInput
+	if tmp, ok := rawArgs["input"]; ok {
+		arg0, err = ec.unmarshalNRollbackInput2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐRollbackInput(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["input"] = arg0
 	return args, nil
 }
 
@@ -2686,6 +2928,28 @@ func (ec *executionContext) field_Mutation_setTitle_args(ctx context.Context, ra
 		}
 	}
 	args["input"] = arg0
+	var arg1 *string
+	if tmp, ok := rawArgs["txId"]; ok {
+		arg1, err = ec.unmarshalOTxId2ᚖstring(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["txId"] = arg1
+	return args, nil
+}
+
+func (ec *executionContext) field_Mutation_startTransaction_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
+	var err error
+	args := map[string]interface{}{}
+	var arg0 models.StartTransactionInput
+	if tmp, ok := rawArgs["input"]; ok {
+		arg0, err = ec.unmarshalNStartTransactionInput2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐStartTransactionInput(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["input"] = arg0
 	return args, nil
 }
 
@@ -4867,6 +5131,105 @@ func (ec *executionContext) _CommentHistoryStep_date(ctx context.Context, field
 	return ec.marshalNTime2ᚖtimeᚐTime(ctx, field.Selections, res)
 }
 
+func (ec *executionContext) _CommitPayload_clientMutationId(ctx context.Context, field graphql.CollectedField, obj *models.CommitPayload) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:   "CommitPayload",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.ClientMutationID, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	fc.Result = res
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _CommitPayload_id(ctx context.Context, field graphql.CollectedField, obj *models.CommitPayload) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:   "CommitPayload",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.ID, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !graphql.HasFieldError(ctx, fc) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(string)
+	fc.Result = res
+	return ec.marshalNTxId2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _CommitPayload_bug(ctx context.Context, field graphql.CollectedField, obj *models.CommitPayload) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:   "CommitPayload",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Bug, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !graphql.HasFieldError(ctx, fc) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(models.BugWrapper)
+	fc.Result = res
+	return ec.marshalNBug2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐBugWrapper(ctx, field.Selections, res)
+}
+
 func (ec *executionContext) _CreateOperation_id(ctx context.Context, field graphql.CollectedField, obj *bug.CreateOperation) (ret graphql.Marshaler) {
 	defer func() {
 		if r := recover(); r != nil {
@@ -6718,7 +7081,7 @@ func (ec *executionContext) _Mutation_newBug(ctx context.Context, field graphql.
 	fc.Args = args
 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Mutation().NewBug(rctx, args["input"].(models.NewBugInput))
+		return ec.resolvers.Mutation().NewBug(rctx, args["input"].(models.NewBugInput), args["txId"].(*string))
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -6759,7 +7122,7 @@ func (ec *executionContext) _Mutation_addComment(ctx context.Context, field grap
 	fc.Args = args
 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Mutation().AddComment(rctx, args["input"].(models.AddCommentInput))
+		return ec.resolvers.Mutation().AddComment(rctx, args["input"].(models.AddCommentInput), args["txId"].(*string))
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -6800,7 +7163,7 @@ func (ec *executionContext) _Mutation_changeLabels(ctx context.Context, field gr
 	fc.Args = args
 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Mutation().ChangeLabels(rctx, args["input"].(*models.ChangeLabelInput))
+		return ec.resolvers.Mutation().ChangeLabels(rctx, args["input"].(models.ChangeLabelInput), args["txId"].(*string))
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -6841,7 +7204,7 @@ func (ec *executionContext) _Mutation_openBug(ctx context.Context, field graphql
 	fc.Args = args
 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Mutation().OpenBug(rctx, args["input"].(models.OpenBugInput))
+		return ec.resolvers.Mutation().OpenBug(rctx, args["input"].(models.OpenBugInput), args["txId"].(*string))
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -6882,7 +7245,7 @@ func (ec *executionContext) _Mutation_closeBug(ctx context.Context, field graphq
 	fc.Args = args
 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Mutation().CloseBug(rctx, args["input"].(models.CloseBugInput))
+		return ec.resolvers.Mutation().CloseBug(rctx, args["input"].(models.CloseBugInput), args["txId"].(*string))
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -6923,7 +7286,7 @@ func (ec *executionContext) _Mutation_setTitle(ctx context.Context, field graphq
 	fc.Args = args
 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Mutation().SetTitle(rctx, args["input"].(models.SetTitleInput))
+		return ec.resolvers.Mutation().SetTitle(rctx, args["input"].(models.SetTitleInput), args["txId"].(*string))
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -6940,7 +7303,7 @@ func (ec *executionContext) _Mutation_setTitle(ctx context.Context, field graphq
 	return ec.marshalNSetTitlePayload2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐSetTitlePayload(ctx, field.Selections, res)
 }
 
-func (ec *executionContext) _NewBugPayload_clientMutationId(ctx context.Context, field graphql.CollectedField, obj *models.NewBugPayload) (ret graphql.Marshaler) {
+func (ec *executionContext) _Mutation_startTransaction(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
 	defer func() {
 		if r := recover(); r != nil {
 			ec.Error(ctx, ec.Recover(ctx, r))
@@ -6948,7 +7311,130 @@ func (ec *executionContext) _NewBugPayload_clientMutationId(ctx context.Context,
 		}
 	}()
 	fc := &graphql.FieldContext{
-		Object:   "NewBugPayload",
+		Object:   "Mutation",
+		Field:    field,
+		Args:     nil,
+		IsMethod: true,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	rawArgs := field.ArgumentMap(ec.Variables)
+	args, err := ec.field_Mutation_startTransaction_args(ctx, rawArgs)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	fc.Args = args
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Mutation().StartTransaction(rctx, args["input"].(models.StartTransactionInput))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !graphql.HasFieldError(ctx, fc) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(*models.StartTransactionPayload)
+	fc.Result = res
+	return ec.marshalNStartTransactionPayload2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐStartTransactionPayload(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Mutation_commit(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:   "Mutation",
+		Field:    field,
+		Args:     nil,
+		IsMethod: true,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	rawArgs := field.ArgumentMap(ec.Variables)
+	args, err := ec.field_Mutation_commit_args(ctx, rawArgs)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	fc.Args = args
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Mutation().Commit(rctx, args["input"].(models.CommitInput))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !graphql.HasFieldError(ctx, fc) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(*models.CommitPayload)
+	fc.Result = res
+	return ec.marshalNCommitPayload2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐCommitPayload(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Mutation_rollback(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:   "Mutation",
+		Field:    field,
+		Args:     nil,
+		IsMethod: true,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	rawArgs := field.ArgumentMap(ec.Variables)
+	args, err := ec.field_Mutation_rollback_args(ctx, rawArgs)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	fc.Args = args
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Mutation().Rollback(rctx, args["input"].(models.RollbackInput))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !graphql.HasFieldError(ctx, fc) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(*models.RollbackPayload)
+	fc.Result = res
+	return ec.marshalNRollbackPayload2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐRollbackPayload(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _NewBugPayload_clientMutationId(ctx context.Context, field graphql.CollectedField, obj *models.NewBugPayload) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:   "NewBugPayload",
 		Field:    field,
 		Args:     nil,
 		IsMethod: false,
@@ -7846,6 +8332,105 @@ func (ec *executionContext) _Repository_validLabels(ctx context.Context, field g
 	return ec.marshalNLabelConnection2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐLabelConnection(ctx, field.Selections, res)
 }
 
+func (ec *executionContext) _RollbackPayload_clientMutationId(ctx context.Context, field graphql.CollectedField, obj *models.RollbackPayload) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:   "RollbackPayload",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.ClientMutationID, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	fc.Result = res
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _RollbackPayload_id(ctx context.Context, field graphql.CollectedField, obj *models.RollbackPayload) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:   "RollbackPayload",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.ID, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !graphql.HasFieldError(ctx, fc) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(string)
+	fc.Result = res
+	return ec.marshalNTxId2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _RollbackPayload_bug(ctx context.Context, field graphql.CollectedField, obj *models.RollbackPayload) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:   "RollbackPayload",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Bug, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !graphql.HasFieldError(ctx, fc) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(models.BugWrapper)
+	fc.Result = res
+	return ec.marshalNBug2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐBugWrapper(ctx, field.Selections, res)
+}
+
 func (ec *executionContext) _SetStatusOperation_id(ctx context.Context, field graphql.CollectedField, obj *bug.SetStatusOperation) (ret graphql.Marshaler) {
 	defer func() {
 		if r := recover(); r != nil {
@@ -8557,6 +9142,71 @@ func (ec *executionContext) _SetTitleTimelineItem_was(ctx context.Context, field
 	return ec.marshalNString2string(ctx, field.Selections, res)
 }
 
+func (ec *executionContext) _StartTransactionPayload_clientMutationId(ctx context.Context, field graphql.CollectedField, obj *models.StartTransactionPayload) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:   "StartTransactionPayload",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.ClientMutationID, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	fc.Result = res
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _StartTransactionPayload_id(ctx context.Context, field graphql.CollectedField, obj *models.StartTransactionPayload) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:   "StartTransactionPayload",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.ID, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !graphql.HasFieldError(ctx, fc) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(string)
+	fc.Result = res
+	return ec.marshalNTxId2string(ctx, field.Selections, res)
+}
+
 func (ec *executionContext) _TimelineItemConnection_edges(ctx context.Context, field graphql.CollectedField, obj *models.TimelineItemConnection) (ret graphql.Marshaler) {
 	defer func() {
 		if r := recover(); r != nil {
@@ -9930,6 +10580,30 @@ func (ec *executionContext) unmarshalInputCloseBugInput(ctx context.Context, obj
 	return it, nil
 }
 
+func (ec *executionContext) unmarshalInputCommitInput(ctx context.Context, obj interface{}) (models.CommitInput, error) {
+	var it models.CommitInput
+	var asMap = obj.(map[string]interface{})
+
+	for k, v := range asMap {
+		switch k {
+		case "clientMutationId":
+			var err error
+			it.ClientMutationID, err = ec.unmarshalOString2ᚖstring(ctx, v)
+			if err != nil {
+				return it, err
+			}
+		case "id":
+			var err error
+			it.ID, err = ec.unmarshalNTxId2string(ctx, v)
+			if err != nil {
+				return it, err
+			}
+		}
+	}
+
+	return it, nil
+}
+
 func (ec *executionContext) unmarshalInputNewBugInput(ctx context.Context, obj interface{}) (models.NewBugInput, error) {
 	var it models.NewBugInput
 	var asMap = obj.(map[string]interface{})
@@ -10002,6 +10676,30 @@ func (ec *executionContext) unmarshalInputOpenBugInput(ctx context.Context, obj
 	return it, nil
 }
 
+func (ec *executionContext) unmarshalInputRollbackInput(ctx context.Context, obj interface{}) (models.RollbackInput, error) {
+	var it models.RollbackInput
+	var asMap = obj.(map[string]interface{})
+
+	for k, v := range asMap {
+		switch k {
+		case "clientMutationId":
+			var err error
+			it.ClientMutationID, err = ec.unmarshalOString2ᚖstring(ctx, v)
+			if err != nil {
+				return it, err
+			}
+		case "id":
+			var err error
+			it.ID, err = ec.unmarshalNTxId2string(ctx, v)
+			if err != nil {
+				return it, err
+			}
+		}
+	}
+
+	return it, nil
+}
+
 func (ec *executionContext) unmarshalInputSetTitleInput(ctx context.Context, obj interface{}) (models.SetTitleInput, error) {
 	var it models.SetTitleInput
 	var asMap = obj.(map[string]interface{})

graphql/models/gen_models.go 🔗

@@ -111,6 +111,22 @@ type CommentEdge struct {
 	Node   *bug.Comment `json:"node"`
 }
 
+type CommitInput struct {
+	// A unique identifier for the client performing the mutation.
+	ClientMutationID *string `json:"clientMutationId"`
+	// The identifier of the transaction
+	ID string `json:"id"`
+}
+
+type CommitPayload struct {
+	// A unique identifier for the client performing the mutation.
+	ClientMutationID *string `json:"clientMutationId"`
+	// The identifier of the transaction
+	ID string `json:"id"`
+	// The affected bug.
+	Bug BugWrapper `json:"bug"`
+}
+
 type IdentityConnection struct {
 	Edges      []*IdentityEdge   `json:"edges"`
 	Nodes      []IdentityWrapper `json:"nodes"`
@@ -201,6 +217,22 @@ type PageInfo struct {
 	EndCursor string `json:"endCursor"`
 }
 
+type RollbackInput struct {
+	// A unique identifier for the client performing the mutation.
+	ClientMutationID *string `json:"clientMutationId"`
+	// The identifier of the transaction
+	ID string `json:"id"`
+}
+
+type RollbackPayload struct {
+	// A unique identifier for the client performing the mutation.
+	ClientMutationID *string `json:"clientMutationId"`
+	// The identifier of the transaction
+	ID string `json:"id"`
+	// The affected bug.
+	Bug BugWrapper `json:"bug"`
+}
+
 type SetTitleInput struct {
 	// A unique identifier for the client performing the mutation.
 	ClientMutationID *string `json:"clientMutationId"`
@@ -221,6 +253,22 @@ type SetTitlePayload struct {
 	Operation *bug.SetTitleOperation `json:"operation"`
 }
 
+type StartTransactionInput struct {
+	// A unique identifier for the client performing the mutation.
+	ClientMutationID *string `json:"clientMutationId"`
+	// "The name of the repository. If not set, the default repository is used.
+	RepoRef *string `json:"repoRef"`
+	// The bug ID's prefix.
+	Prefix string `json:"prefix"`
+}
+
+type StartTransactionPayload struct {
+	// A unique identifier for the client performing the mutation.
+	ClientMutationID *string `json:"clientMutationId"`
+	// The identifier of the transaction
+	ID string `json:"id"`
+}
+
 // The connection type for TimelineItem
 type TimelineItemConnection struct {
 	Edges      []*TimelineItemEdge `json:"edges"`

graphql/resolvers/mutation.go 🔗

@@ -2,6 +2,10 @@ package resolvers
 
 import (
 	"context"
+	"crypto/rand"
+	"encoding/base64"
+	"fmt"
+	"sync"
 
 	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/cache"
@@ -9,10 +13,73 @@ import (
 	"github.com/MichaelMure/git-bug/graphql/models"
 )
 
+const transactionIdLength = 16
+
 var _ graph.MutationResolver = &mutationResolver{}
 
 type mutationResolver struct {
 	cache *cache.MultiRepoCache
+
+	mu           sync.Mutex
+	transactions map[string]transaction
+}
+
+type transaction struct {
+	bug *cache.BugCache
+	ops []bug.Operation
+}
+
+func (r mutationResolver) makeId() (string, error) {
+	result := make([]byte, transactionIdLength)
+
+	i := 0
+	for {
+		_, err := rand.Read(result)
+		if err != nil {
+			panic(err)
+		}
+		id := base64.StdEncoding.EncodeToString(result)
+		if _, has := r.transactions[id]; !has {
+			return id, nil
+		}
+		i++
+		if i > 100 {
+			return "", fmt.Errorf("can't generate a transaction ID")
+		}
+	}
+}
+
+func (r mutationResolver) StartTransaction(_ context.Context, input models.StartTransactionInput) (*models.StartTransactionPayload, error) {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	b, err := r.getBug(input.RepoRef, input.Prefix)
+	if err != nil {
+		return nil, err
+	}
+
+	id, err := r.makeId()
+	if err != nil {
+		return nil, err
+	}
+
+	r.transactions[id] = transaction{
+		bug: b,
+		ops: make([]bug.Operation, 0, 8),
+	}
+
+	return &models.StartTransactionPayload{
+		ClientMutationID: input.ClientMutationID,
+		ID:               id,
+	}, nil
+}
+
+func (r mutationResolver) Commit(_ context.Context, input models.CommitInput) (*models.CommitPayload, error) {
+	panic("implement me")
+}
+
+func (r mutationResolver) Rollback(_ context.Context, input models.RollbackInput) (*models.RollbackPayload, error) {
+	panic("implement me")
 }
 
 func (r mutationResolver) getRepo(ref *string) (*cache.RepoCache, error) {
@@ -32,7 +99,7 @@ func (r mutationResolver) getBug(repoRef *string, bugPrefix string) (*cache.BugC
 	return repo.ResolveBugPrefix(bugPrefix)
 }
 
-func (r mutationResolver) NewBug(_ context.Context, input models.NewBugInput) (*models.NewBugPayload, error) {
+func (r mutationResolver) NewBug(_ context.Context, input models.NewBugInput, txID *string) (*models.NewBugPayload, error) {
 	repo, err := r.getRepo(input.RepoRef)
 	if err != nil {
 		return nil, err
@@ -50,20 +117,25 @@ func (r mutationResolver) NewBug(_ context.Context, input models.NewBugInput) (*
 	}, nil
 }
 
-func (r mutationResolver) AddComment(_ context.Context, input models.AddCommentInput) (*models.AddCommentPayload, error) {
+func (r mutationResolver) AddComment(_ context.Context, input models.AddCommentInput, txID *string) (*models.AddCommentPayload, error) {
 	b, err := r.getBug(input.RepoRef, input.Prefix)
 	if err != nil {
 		return nil, err
 	}
 
-	op, err := b.AddCommentWithFiles(input.Message, input.Files)
-	if err != nil {
-		return nil, err
-	}
+	var op *bug.AddCommentOperation
+	if txID == nil {
+		op, err = b.AddCommentWithFiles(input.Message, input.Files)
+		if err != nil {
+			return nil, err
+		}
 
-	err = b.Commit()
-	if err != nil {
-		return nil, err
+		err = b.Commit()
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		op, err = bug.OpAddCommentWithFiles(input.Message)
 	}
 
 	return &models.AddCommentPayload{
@@ -73,7 +145,7 @@ func (r mutationResolver) AddComment(_ context.Context, input models.AddCommentI
 	}, nil
 }
 
-func (r mutationResolver) ChangeLabels(_ context.Context, input *models.ChangeLabelInput) (*models.ChangeLabelPayload, error) {
+func (r mutationResolver) ChangeLabels(_ context.Context, input models.ChangeLabelInput, txID *string) (*models.ChangeLabelPayload, error) {
 	b, err := r.getBug(input.RepoRef, input.Prefix)
 	if err != nil {
 		return nil, err
@@ -102,7 +174,7 @@ func (r mutationResolver) ChangeLabels(_ context.Context, input *models.ChangeLa
 	}, nil
 }
 
-func (r mutationResolver) OpenBug(_ context.Context, input models.OpenBugInput) (*models.OpenBugPayload, error) {
+func (r mutationResolver) OpenBug(_ context.Context, input models.OpenBugInput, txID *string) (*models.OpenBugPayload, error) {
 	b, err := r.getBug(input.RepoRef, input.Prefix)
 	if err != nil {
 		return nil, err
@@ -125,7 +197,7 @@ func (r mutationResolver) OpenBug(_ context.Context, input models.OpenBugInput)
 	}, nil
 }
 
-func (r mutationResolver) CloseBug(_ context.Context, input models.CloseBugInput) (*models.CloseBugPayload, error) {
+func (r mutationResolver) CloseBug(_ context.Context, input models.CloseBugInput, txID *string) (*models.CloseBugPayload, error) {
 	b, err := r.getBug(input.RepoRef, input.Prefix)
 	if err != nil {
 		return nil, err
@@ -148,7 +220,7 @@ func (r mutationResolver) CloseBug(_ context.Context, input models.CloseBugInput
 	}, nil
 }
 
-func (r mutationResolver) SetTitle(_ context.Context, input models.SetTitleInput) (*models.SetTitlePayload, error) {
+func (r mutationResolver) SetTitle(_ context.Context, input models.SetTitleInput, txID *string) (*models.SetTitlePayload, error) {
 	b, err := r.getBug(input.RepoRef, input.Prefix)
 	if err != nil {
 		return nil, err

graphql/resolvers/root.go 🔗

@@ -26,7 +26,8 @@ func (r RootResolver) Query() graph.QueryResolver {
 
 func (r RootResolver) Mutation() graph.MutationResolver {
 	return &mutationResolver{
-		cache: &r.MultiRepoCache,
+		cache:        &r.MultiRepoCache,
+		transactions: make(map[string]transaction),
 	}
 }
 

graphql/schema/root.graphql 🔗

@@ -5,15 +5,25 @@ type Query {
 
 type Mutation {
     """Create a new bug"""
-    newBug(input: NewBugInput!): NewBugPayload!
+    newBug(input: NewBugInput!, txId: TxId): NewBugPayload!
     """Add a new comment to a bug"""
-    addComment(input: AddCommentInput!): AddCommentPayload!
+    addComment(input: AddCommentInput!, txId: TxId): AddCommentPayload!
     """Add or remove a set of label on a bug"""
-    changeLabels(input: ChangeLabelInput): ChangeLabelPayload!
+    changeLabels(input: ChangeLabelInput!, txId: TxId): ChangeLabelPayload!
     """Change a bug's status to open"""
-    openBug(input: OpenBugInput!): OpenBugPayload!
+    openBug(input: OpenBugInput!, txId: TxId): OpenBugPayload!
     """Change a bug's status to closed"""
-    closeBug(input: CloseBugInput!): CloseBugPayload!
+    closeBug(input: CloseBugInput!, txId: TxId): CloseBugPayload!
     """Change a bug's title"""
-    setTitle(input: SetTitleInput!): SetTitlePayload!
+    setTitle(input: SetTitleInput!, txId: TxId): SetTitlePayload!
+
+    """Start a transaction.
+    If a transaction ID if passed to the mutation, operation are accumulated
+    in memory until a commit happen.
+    """
+    startTransaction(input: StartTransactionInput!): StartTransactionPayload!
+    """Commit a transaction. All mutation linked to this transaction are written on disk."""
+    commit(input: CommitInput!): CommitPayload!
+    """Rollback a transaction. Alll mutationlinked to this transaction are discarded."""
+    rollback(input: RollbackInput!): RollbackPayload!
 }

graphql/schema/transaction.graphql 🔗

@@ -0,0 +1,49 @@
+scalar TxId
+
+input StartTransactionInput {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """"The name of the repository. If not set, the default repository is used."""
+    repoRef: String
+    """The bug ID's prefix."""
+    prefix: String!
+}
+
+type StartTransactionPayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The identifier of the transaction"""
+    id: TxId!
+}
+
+input CommitInput {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The identifier of the transaction"""
+    id: TxId!
+}
+
+type CommitPayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The identifier of the transaction"""
+    id: TxId!
+    """The affected bug."""
+    bug: Bug!
+}
+
+input RollbackInput {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The identifier of the transaction"""
+    id: TxId!
+}
+
+type RollbackPayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The identifier of the transaction"""
+    id: TxId!
+    """The affected bug."""
+    bug: Bug!
+}

webui/package-lock.json 🔗

@@ -114,24 +114,6 @@
         "@apollographql/graphql-language-service-types": "^2.0.0"
       }
     },
-    "@ardatan/graphql-tools": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/@ardatan/graphql-tools/-/graphql-tools-4.1.0.tgz",
-      "integrity": "sha512-0b+KH5RZN9vCMpEjxrwFwZ7v3K6QDjs1EH+R6eRrgKMR2X274JWqYraHKLWE1uJ8iwrkRaOYfCV12jLVuvWS+A==",
-      "dev": true,
-      "requires": {
-        "apollo-link": "^1.2.3",
-        "apollo-utilities": "^1.0.1",
-        "deprecated-decorator": "^0.1.6",
-        "iterall": "^1.1.3",
-        "uuid": "^3.1.0"
-      }
-    },
-    "@arrows/composition": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/@arrows/composition/-/composition-1.2.2.tgz",
-      "integrity": "sha512-9fh1yHwrx32lundiB3SlZ/VwuStPB4QakPsSLrGJFH6rCXvdrd060ivAZ7/2vlqPnEjBkPRRXOcG1YOu19p2GQ=="
-    },
     "@babel/code-frame": {
       "version": "7.8.3",
       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
@@ -141,30 +123,36 @@
       }
     },
     "@babel/compat-data": {
-      "version": "7.8.4",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.4.tgz",
-      "integrity": "sha512-lHLhlsvFjJAqNU71b7k6Vv9ewjmTXKvqaMv7n0G1etdCabWLw3nEYE8mmgoVOxMIFE07xOvo7H7XBASirX6Rrg==",
+      "version": "7.8.1",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.1.tgz",
+      "integrity": "sha512-Z+6ZOXvyOWYxJ50BwxzdhRnRsGST8Y3jaZgxYig575lTjVSs3KtJnmESwZegg6e2Dn0td1eDhoWlp1wI4BTCPw==",
       "requires": {
-        "browserslist": "^4.8.5",
+        "browserslist": "^4.8.2",
         "invariant": "^2.2.4",
         "semver": "^5.5.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        }
       }
     },
     "@babel/core": {
-      "version": "7.8.4",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.4.tgz",
-      "integrity": "sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA==",
-      "requires": {
-        "@babel/code-frame": "^7.8.3",
-        "@babel/generator": "^7.8.4",
-        "@babel/helpers": "^7.8.4",
-        "@babel/parser": "^7.8.4",
-        "@babel/template": "^7.8.3",
-        "@babel/traverse": "^7.8.4",
-        "@babel/types": "^7.8.3",
+      "version": "7.7.4",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.4.tgz",
+      "integrity": "sha512-+bYbx56j4nYBmpsWtnPUsKW3NdnYxbqyfrP2w9wILBuHzdfIKz9prieZK0DFPyIzkjYVUe4QkusGL07r5pXznQ==",
+      "requires": {
+        "@babel/code-frame": "^7.5.5",
+        "@babel/generator": "^7.7.4",
+        "@babel/helpers": "^7.7.4",
+        "@babel/parser": "^7.7.4",
+        "@babel/template": "^7.7.4",
+        "@babel/traverse": "^7.7.4",
+        "@babel/types": "^7.7.4",
         "convert-source-map": "^1.7.0",
         "debug": "^4.1.0",
-        "gensync": "^1.0.0-beta.1",
         "json5": "^2.1.0",
         "lodash": "^4.17.13",
         "resolve": "^1.3.2",
@@ -172,50 +160,22 @@
         "source-map": "^0.5.0"
       },
       "dependencies": {
-        "@babel/parser": {
-          "version": "7.8.4",
-          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz",
-          "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw=="
-        },
-        "@babel/traverse": {
-          "version": "7.8.4",
-          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz",
-          "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==",
-          "requires": {
-            "@babel/code-frame": "^7.8.3",
-            "@babel/generator": "^7.8.4",
-            "@babel/helper-function-name": "^7.8.3",
-            "@babel/helper-split-export-declaration": "^7.8.3",
-            "@babel/parser": "^7.8.4",
-            "@babel/types": "^7.8.3",
-            "debug": "^4.1.0",
-            "globals": "^11.1.0",
-            "lodash": "^4.17.13"
-          }
-        },
-        "source-map": {
-          "version": "0.5.7",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
         }
       }
     },
     "@babel/generator": {
-      "version": "7.8.4",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz",
-      "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==",
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.3.tgz",
+      "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==",
       "requires": {
         "@babel/types": "^7.8.3",
         "jsesc": "^2.5.1",
         "lodash": "^4.17.13",
         "source-map": "^0.5.0"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.5.7",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
-        }
       }
     },
     "@babel/helper-annotate-as-pure": {
@@ -255,15 +215,22 @@
       }
     },
     "@babel/helper-compilation-targets": {
-      "version": "7.8.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.4.tgz",
-      "integrity": "sha512-3k3BsKMvPp5bjxgMdrFyq0UaEO48HciVrOVF0+lon8pp95cyJ2ujAh0TrBHNMnJGT2rr0iKOJPFFbSqjDyf/Pg==",
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.3.tgz",
+      "integrity": "sha512-JLylPCsFjhLN+6uBSSh3iYdxKdeO9MNmoY96PE/99d8kyBFaXLORtAVhqN6iHa+wtPeqxKLghDOZry0+Aiw9Tw==",
       "requires": {
-        "@babel/compat-data": "^7.8.4",
-        "browserslist": "^4.8.5",
+        "@babel/compat-data": "^7.8.1",
+        "browserslist": "^4.8.2",
         "invariant": "^2.2.4",
-        "levenary": "^1.1.1",
+        "levenary": "^1.1.0",
         "semver": "^5.5.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        }
       }
     },
     "@babel/helper-create-class-features-plugin": {
@@ -435,36 +402,13 @@
       }
     },
     "@babel/helpers": {
-      "version": "7.8.4",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.4.tgz",
-      "integrity": "sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w==",
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.3.tgz",
+      "integrity": "sha512-LmU3q9Pah/XyZU89QvBgGt+BCsTPoQa+73RxAQh8fb8qkDyIfeQnmgs+hvzhTCKTzqOyk7JTkS3MS1S8Mq5yrQ==",
       "requires": {
         "@babel/template": "^7.8.3",
-        "@babel/traverse": "^7.8.4",
+        "@babel/traverse": "^7.8.3",
         "@babel/types": "^7.8.3"
-      },
-      "dependencies": {
-        "@babel/parser": {
-          "version": "7.8.4",
-          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz",
-          "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw=="
-        },
-        "@babel/traverse": {
-          "version": "7.8.4",
-          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz",
-          "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==",
-          "requires": {
-            "@babel/code-frame": "^7.8.3",
-            "@babel/generator": "^7.8.4",
-            "@babel/helper-function-name": "^7.8.3",
-            "@babel/helper-split-export-declaration": "^7.8.3",
-            "@babel/parser": "^7.8.4",
-            "@babel/types": "^7.8.3",
-            "debug": "^4.1.0",
-            "globals": "^11.1.0",
-            "lodash": "^4.17.13"
-          }
-        }
       }
     },
     "@babel/highlight": {
@@ -475,18 +419,6 @@
         "chalk": "^2.0.0",
         "esutils": "^2.0.2",
         "js-tokens": "^4.0.0"
-      },
-      "dependencies": {
-        "chalk": {
-          "version": "2.4.2",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
-          "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
-          "requires": {
-            "ansi-styles": "^3.2.1",
-            "escape-string-regexp": "^1.0.5",
-            "supports-color": "^5.3.0"
-          }
-        }
       }
     },
     "@babel/parser": {
@@ -505,22 +437,22 @@
       }
     },
     "@babel/plugin-proposal-class-properties": {
-      "version": "7.8.3",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz",
-      "integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==",
+      "version": "7.7.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.7.4.tgz",
+      "integrity": "sha512-EcuXeV4Hv1X3+Q1TsuOmyyxeTRiSqurGJ26+I/FW1WbymmRRapVORm6x1Zl3iDIHyRxEs+VXWp6qnlcfcJSbbw==",
       "requires": {
-        "@babel/helper-create-class-features-plugin": "^7.8.3",
-        "@babel/helper-plugin-utils": "^7.8.3"
+        "@babel/helper-create-class-features-plugin": "^7.7.4",
+        "@babel/helper-plugin-utils": "^7.0.0"
       }
     },
     "@babel/plugin-proposal-decorators": {
-      "version": "7.8.3",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.8.3.tgz",
-      "integrity": "sha512-e3RvdvS4qPJVTe288DlXjwKflpfy1hr0j5dz5WpIYYeP7vQZg2WfAEIp8k5/Lwis/m5REXEteIz6rrcDtXXG7w==",
+      "version": "7.7.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.7.4.tgz",
+      "integrity": "sha512-GftcVDcLCwVdzKmwOBDjATd548+IE+mBo7ttgatqNDR7VG7GqIuZPtRWlMLHbhTXhcnFZiGER8iIYl1n/imtsg==",
       "requires": {
-        "@babel/helper-create-class-features-plugin": "^7.8.3",
-        "@babel/helper-plugin-utils": "^7.8.3",
-        "@babel/plugin-syntax-decorators": "^7.8.3"
+        "@babel/helper-create-class-features-plugin": "^7.7.4",
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/plugin-syntax-decorators": "^7.7.4"
       }
     },
     "@babel/plugin-proposal-dynamic-import": {
@@ -551,12 +483,12 @@
       }
     },
     "@babel/plugin-proposal-numeric-separator": {
-      "version": "7.8.3",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz",
-      "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==",
+      "version": "7.7.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.7.4.tgz",
+      "integrity": "sha512-CG605v7lLpVgVldSY6kxsN9ui1DxFOyepBfuX2AzU2TNriMAYApoU55mrGw9Jr4TlrTzPCG10CL8YXyi+E/iPw==",
       "requires": {
-        "@babel/helper-plugin-utils": "^7.8.3",
-        "@babel/plugin-syntax-numeric-separator": "^7.8.3"
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/plugin-syntax-numeric-separator": "^7.7.4"
       }
     },
     "@babel/plugin-proposal-object-rest-spread": {
@@ -603,15 +535,6 @@
         "@babel/helper-plugin-utils": "^7.8.0"
       }
     },
-    "@babel/plugin-syntax-class-properties": {
-      "version": "7.8.3",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.8.3.tgz",
-      "integrity": "sha512-UcAyQWg2bAN647Q+O811tG9MrJ38Z10jjhQdKNAL8fsyPzE3cCN/uT+f55cFVY4aGO4jqJAvmqsuY3GQDwAoXg==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-plugin-utils": "^7.8.3"
-      }
-    },
     "@babel/plugin-syntax-decorators": {
       "version": "7.8.3",
       "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.8.3.tgz",
@@ -801,18 +724,18 @@
       }
     },
     "@babel/plugin-transform-flow-strip-types": {
-      "version": "7.8.3",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.8.3.tgz",
-      "integrity": "sha512-g/6WTWG/xbdd2exBBzMfygjX/zw4eyNC4X8pRaq7aRHRoDUCzAIu3kGYIXviOv8BjCuWm8vDBwjHcjiRNgXrPA==",
+      "version": "7.7.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.7.4.tgz",
+      "integrity": "sha512-w9dRNlHY5ElNimyMYy0oQowvQpwt/PRHI0QS98ZJCTZU2bvSnKXo5zEiD5u76FBPigTm8TkqzmnUTg16T7qbkA==",
       "requires": {
-        "@babel/helper-plugin-utils": "^7.8.3",
-        "@babel/plugin-syntax-flow": "^7.8.3"
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/plugin-syntax-flow": "^7.7.4"
       }
     },
     "@babel/plugin-transform-for-of": {
-      "version": "7.8.4",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.4.tgz",
-      "integrity": "sha512-iAXNlOWvcYUYoV8YIxwS7TxGRJcxyl8eQCfT+A5j8sKUzRFvJdcyjp97jL2IghWSRDaL2PU2O2tX8Cu9dTBq5A==",
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.3.tgz",
+      "integrity": "sha512-ZjXznLNTxhpf4Q5q3x1NsngzGA38t9naWH8Gt+0qYZEJAcvPI9waSStSh56u19Ofjr7QmD0wUsQ8hw8s/p1VnA==",
       "requires": {
         "@babel/helper-plugin-utils": "^7.8.3"
       }
@@ -909,9 +832,9 @@
       }
     },
     "@babel/plugin-transform-parameters": {
-      "version": "7.8.4",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.4.tgz",
-      "integrity": "sha512-IsS3oTxeTsZlE5KqzTbcC2sV0P9pXdec53SU+Yxv7o/6dvGM5AkTotQKhoSffhNgZ/dftsSiOoxy7evCYJXzVA==",
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.3.tgz",
+      "integrity": "sha512-/pqngtGb54JwMBZ6S/D3XYylQDFtGjWrnoCF4gXZOUpFV/ujbxnoNGNvDGu6doFWRPBveE72qTx/RRU44j5I/Q==",
       "requires": {
         "@babel/helper-call-delegate": "^7.8.3",
         "@babel/helper-get-function-arity": "^7.8.3",
@@ -988,14 +911,21 @@
       }
     },
     "@babel/plugin-transform-runtime": {
-      "version": "7.8.3",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.8.3.tgz",
-      "integrity": "sha512-/vqUt5Yh+cgPZXXjmaG9NT8aVfThKk7G4OqkVhrXqwsC5soMn/qTCxs36rZ2QFhpfTJcjw4SNDIZ4RUb8OL4jQ==",
+      "version": "7.7.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.7.4.tgz",
+      "integrity": "sha512-O8kSkS5fP74Ad/8pfsCMGa8sBRdLxYoSReaARRNSz3FbFQj3z/QUvoUmJ28gn9BO93YfnXc3j+Xyaqe8cKDNBQ==",
       "requires": {
-        "@babel/helper-module-imports": "^7.8.3",
-        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-module-imports": "^7.7.4",
+        "@babel/helper-plugin-utils": "^7.0.0",
         "resolve": "^1.8.1",
         "semver": "^5.5.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        }
       }
     },
     "@babel/plugin-transform-shorthand-properties": {
@@ -1033,9 +963,9 @@
       }
     },
     "@babel/plugin-transform-typeof-symbol": {
-      "version": "7.8.4",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz",
-      "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==",
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.3.tgz",
+      "integrity": "sha512-3TrkKd4LPqm4jHs6nPtSDI/SV9Cm5PRJkHLUgTcqRQQTMAZ44ZaAdDZJtvWFSaRcvT0a1rTmJ5ZA5tDKjleF3g==",
       "requires": {
         "@babel/helper-plugin-utils": "^7.8.3"
       }
@@ -1060,12 +990,12 @@
       }
     },
     "@babel/preset-env": {
-      "version": "7.8.4",
-      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.4.tgz",
-      "integrity": "sha512-HihCgpr45AnSOHRbS5cWNTINs0TwaR8BS8xIIH+QwiW8cKL0llV91njQMpeMReEPVs+1Ao0x3RLEBLtt1hOq4w==",
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.3.tgz",
+      "integrity": "sha512-Rs4RPL2KjSLSE2mWAx5/iCH+GC1ikKdxPrhnRS6PfFVaiZeom22VFKN4X8ZthyN61kAaR05tfXTbCvatl9WIQg==",
       "requires": {
-        "@babel/compat-data": "^7.8.4",
-        "@babel/helper-compilation-targets": "^7.8.4",
+        "@babel/compat-data": "^7.8.0",
+        "@babel/helper-compilation-targets": "^7.8.3",
         "@babel/helper-module-imports": "^7.8.3",
         "@babel/helper-plugin-utils": "^7.8.3",
         "@babel/plugin-proposal-async-generator-functions": "^7.8.3",
@@ -1094,7 +1024,7 @@
         "@babel/plugin-transform-dotall-regex": "^7.8.3",
         "@babel/plugin-transform-duplicate-keys": "^7.8.3",
         "@babel/plugin-transform-exponentiation-operator": "^7.8.3",
-        "@babel/plugin-transform-for-of": "^7.8.4",
+        "@babel/plugin-transform-for-of": "^7.8.3",
         "@babel/plugin-transform-function-name": "^7.8.3",
         "@babel/plugin-transform-literals": "^7.8.3",
         "@babel/plugin-transform-member-expression-literals": "^7.8.3",
@@ -1105,7 +1035,7 @@
         "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3",
         "@babel/plugin-transform-new-target": "^7.8.3",
         "@babel/plugin-transform-object-super": "^7.8.3",
-        "@babel/plugin-transform-parameters": "^7.8.4",
+        "@babel/plugin-transform-parameters": "^7.8.3",
         "@babel/plugin-transform-property-literals": "^7.8.3",
         "@babel/plugin-transform-regenerator": "^7.8.3",
         "@babel/plugin-transform-reserved-words": "^7.8.3",
@@ -1113,14 +1043,21 @@
         "@babel/plugin-transform-spread": "^7.8.3",
         "@babel/plugin-transform-sticky-regex": "^7.8.3",
         "@babel/plugin-transform-template-literals": "^7.8.3",
-        "@babel/plugin-transform-typeof-symbol": "^7.8.4",
+        "@babel/plugin-transform-typeof-symbol": "^7.8.3",
         "@babel/plugin-transform-unicode-regex": "^7.8.3",
         "@babel/types": "^7.8.3",
-        "browserslist": "^4.8.5",
+        "browserslist": "^4.8.2",
         "core-js-compat": "^3.6.2",
         "invariant": "^2.2.2",
-        "levenary": "^1.1.1",
+        "levenary": "^1.1.0",
         "semver": "^5.5.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        }
       }
     },
     "@babel/preset-react": {
@@ -1136,12 +1073,12 @@
       }
     },
     "@babel/preset-typescript": {
-      "version": "7.8.3",
-      "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.8.3.tgz",
-      "integrity": "sha512-qee5LgPGui9zQ0jR1TeU5/fP9L+ovoArklEqY12ek8P/wV5ZeM/VYSQYwICeoT6FfpJTekG9Ilay5PhwsOpMHA==",
+      "version": "7.7.4",
+      "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.7.4.tgz",
+      "integrity": "sha512-rqrjxfdiHPsnuPur0jKrIIGQCIgoTWMTjlbWE69G4QJ6TIOVnnRnIJhUxNTL/VwDmEAVX08Tq3B1nirer5341w==",
       "requires": {
-        "@babel/helper-plugin-utils": "^7.8.3",
-        "@babel/plugin-transform-typescript": "^7.8.3"
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/plugin-transform-typescript": "^7.7.4"
       }
     },
     "@babel/runtime": {
@@ -1153,9 +1090,9 @@
       }
     },
     "@babel/runtime-corejs3": {
-      "version": "7.8.4",
-      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.8.4.tgz",
-      "integrity": "sha512-+wpLqy5+fbQhvbllvlJEVRIpYj+COUWnnsm+I4jZlA8Lo7/MJmBhGTCHyk1/RWfOqBRJ2MbadddG6QltTKTlrg==",
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.8.3.tgz",
+      "integrity": "sha512-lrIU4aVbmlM/wQPzhEvzvNJskKyYptuXb0fGC0lTQTupTOYtR2Vqbu6/jf8vTr4M8Wt1nIzxVrSvPI5qESa/xA==",
       "requires": {
         "core-js-pure": "^3.0.0",
         "regenerator-runtime": "^0.13.2"
@@ -1233,55 +1170,24 @@
         "tslib": "^1"
       }
     },
-    "@graphql-codegen/add": {
-      "version": "1.12.2-alpha-ea7264f9.15",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-1.12.2-alpha-ea7264f9.15.tgz",
-      "integrity": "sha512-XfOZH2lIR3qw/mHqXThb32EA7NR37nPJpzuNtx1McGTy0sEEd5PVTLP4u89cgvMXfx18cMMM7ZWAnz2T7XCCkQ==",
-      "dev": true,
-      "requires": {
-        "@graphql-codegen/plugin-helpers": "1.12.2-alpha-ea7264f9.15+ea7264f9",
-        "tslib": "1.10.0"
-      },
-      "dependencies": {
-        "@graphql-codegen/plugin-helpers": {
-          "version": "1.12.2-alpha-ea7264f9.15",
-          "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.12.2-alpha-ea7264f9.15.tgz",
-          "integrity": "sha512-EgRHQVFswVQUevMEtsrsA45JmTWj3UAUK8laMyDqbQQuOqlTOgpqdceTLYWWCpyfybaEaagw+rWpkwPXyUjWYQ==",
-          "dev": true,
-          "requires": {
-            "@graphql-toolkit/common": "0.9.7",
-            "camel-case": "4.1.1",
-            "common-tags": "1.8.0",
-            "constant-case": "3.0.3",
-            "import-from": "3.0.0",
-            "lower-case": "2.0.1",
-            "param-case": "3.0.3",
-            "pascal-case": "3.1.1",
-            "tslib": "1.10.0",
-            "upper-case": "2.0.1"
-          }
-        }
-      }
-    },
     "@graphql-codegen/cli": {
-      "version": "1.12.1",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-1.12.1.tgz",
-      "integrity": "sha512-ObbuUaBC48i8glFXcBNo9oBMfv9HZ4JQtP52UX6Ppv/KUOISQWCYvC1YmtWIkuVYRz65Ds2kHpNS9YknVf7eEQ==",
+      "version": "1.11.2",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-1.11.2.tgz",
+      "integrity": "sha512-ox0vqUq+BWngD+UBCpzXuLrOBTPQZza7IGPxKSF3Z7lOPim+NX75HL5sO/58xU8I/5lEjaZpHx3hvSZdiGu4kg==",
       "dev": true,
       "requires": {
-        "@babel/parser": "7.8.3",
-        "@graphql-codegen/core": "1.12.1",
-        "@graphql-codegen/plugin-helpers": "1.12.1",
-        "@graphql-toolkit/apollo-engine-loader": "0.9.7",
-        "@graphql-toolkit/code-file-loader": "0.9.7",
-        "@graphql-toolkit/common": "0.9.7",
-        "@graphql-toolkit/core": "0.9.7",
-        "@graphql-toolkit/git-loader": "0.9.7",
-        "@graphql-toolkit/github-loader": "0.9.7",
-        "@graphql-toolkit/graphql-file-loader": "0.9.7",
-        "@graphql-toolkit/json-file-loader": "0.9.7",
-        "@graphql-toolkit/prisma-loader": "0.9.7",
-        "@graphql-toolkit/url-loader": "0.9.7",
+        "@babel/parser": "7.7.7",
+        "@graphql-codegen/core": "1.11.2",
+        "@graphql-codegen/plugin-helpers": "1.11.2",
+        "@graphql-toolkit/apollo-engine-loader": "0.9.0",
+        "@graphql-toolkit/code-file-loader": "0.9.0",
+        "@graphql-toolkit/core": "0.9.0",
+        "@graphql-toolkit/git-loader": "0.9.0",
+        "@graphql-toolkit/github-loader": "0.9.0",
+        "@graphql-toolkit/graphql-file-loader": "0.9.0",
+        "@graphql-toolkit/json-file-loader": "0.9.0",
+        "@graphql-toolkit/prisma-loader": "0.9.0",
+        "@graphql-toolkit/url-loader": "0.9.0",
         "@types/debounce": "1.2.0",
         "@types/is-glob": "4.0.1",
         "@types/mkdirp": "0.5.2",
@@ -1296,20 +1202,21 @@
         "debounce": "1.2.0",
         "detect-indent": "6.0.0",
         "glob": "7.1.6",
-        "graphql-config": "3.0.0-alpha.18",
+        "graphql-config": "3.0.0-alpha.16",
         "graphql-import": "0.7.1",
         "graphql-tag-pluck": "0.8.7",
         "graphql-tools": "4.0.6",
         "indent-string": "4.0.0",
-        "inquirer": "7.0.4",
+        "inquirer": "7.0.3",
         "is-glob": "4.0.1",
+        "is-valid-path": "0.1.1",
         "json-to-pretty-yaml": "1.2.2",
         "listr": "0.14.3",
         "listr-update-renderer": "0.5.0",
         "log-symbols": "3.0.0",
         "log-update": "3.3.0",
         "lower-case": "2.0.1",
-        "mkdirp": "1.0.3",
+        "mkdirp": "0.5.1",
         "pascal-case": "3.1.1",
         "prettier": "1.19.1",
         "request": "2.88.0",
@@ -1317,403 +1224,665 @@
         "tslib": "1.10.0",
         "upper-case": "2.0.1",
         "valid-url": "1.0.9"
-      }
-    },
-    "@graphql-codegen/core": {
-      "version": "1.12.1",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/core/-/core-1.12.1.tgz",
-      "integrity": "sha512-tlGVaqbJ6sWCC28wrNaLGigw7SXPti1lEse0In5+sePZRjbNof8AbNQIICBOgaNxvt9TVfZ80VVM/pCFNCU2vA==",
-      "dev": true,
-      "requires": {
-        "@graphql-codegen/plugin-helpers": "1.12.1",
-        "@graphql-toolkit/common": "0.9.7",
-        "@graphql-toolkit/schema-merging": "0.9.7",
-        "tslib": "1.10.0"
-      }
-    },
-    "@graphql-codegen/fragment-matcher": {
-      "version": "1.12.1",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/fragment-matcher/-/fragment-matcher-1.12.1.tgz",
-      "integrity": "sha512-WDg1aNiz3/+ksCJ8QEOX6dk7Ki6o0jurdp/Itc2ZjVKbeLypH7omOFonDoM7mi9uq0uDsXk9MioW7wsoIVfHCA==",
-      "dev": true,
-      "requires": {
-        "@graphql-codegen/plugin-helpers": "1.12.1"
-      }
-    },
-    "@graphql-codegen/introspection": {
-      "version": "1.12.2",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/introspection/-/introspection-1.12.2.tgz",
-      "integrity": "sha512-YMQIn1R2CxYZLkxkW6fUxqfTBr2WC3Cimp6/0oEcSyLA/W1scVuDw/IUeU5Iu4CEmz8IaqPSjSMEN+8l95wKlA==",
-      "dev": true,
-      "requires": {
-        "@graphql-codegen/plugin-helpers": "1.12.2",
-        "tslib": "1.10.0"
       },
       "dependencies": {
-        "@graphql-codegen/plugin-helpers": {
-          "version": "1.12.2",
-          "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.12.2.tgz",
-          "integrity": "sha512-N294rqdBh+mCi4HWHbhPV9wE0XLCVKx524pYL4yp8qWiSdAs3Iz9+q9C9QNsLBvHypZdqml44M8kBMG41A9I/Q==",
+        "@babel/parser": {
+          "version": "7.7.7",
+          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.7.tgz",
+          "integrity": "sha512-WtTZMZAZLbeymhkd/sEaPD8IQyGAhmuTuvTzLiCFM7iXiVdY0gc0IaI+cW0fh1BnSMbJSzXX6/fHllgHKwHhXw==",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "4.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+          "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
           "dev": true,
           "requires": {
-            "@graphql-toolkit/common": "0.9.7",
-            "camel-case": "4.1.1",
-            "common-tags": "1.8.0",
-            "constant-case": "3.0.3",
-            "import-from": "3.0.0",
-            "lower-case": "2.0.1",
-            "param-case": "3.0.3",
-            "pascal-case": "3.1.1",
-            "tslib": "1.10.0",
-            "upper-case": "2.0.1"
+            "@types/color-name": "^1.1.1",
+            "color-convert": "^2.0.1"
           }
-        }
-      }
-    },
-    "@graphql-codegen/near-operation-file-preset": {
-      "version": "1.12.2-alpha-ea7264f9.15",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/near-operation-file-preset/-/near-operation-file-preset-1.12.2-alpha-ea7264f9.15.tgz",
-      "integrity": "sha512-jbj7+2FlHRLpqN3e44EZ88n2juImhMuXzv6Mlun4CEVkxC8zW6MYkptaeAxb+iCn2r2nO3vXNrNEPs/1czF97w==",
-      "dev": true,
-      "requires": {
-        "@graphql-codegen/add": "1.12.2-alpha-ea7264f9.15+ea7264f9",
-        "@graphql-codegen/plugin-helpers": "1.12.2-alpha-ea7264f9.15+ea7264f9",
-        "@graphql-codegen/visitor-plugin-common": "1.12.2-alpha-ea7264f9.15+ea7264f9",
-        "tslib": "1.10.0"
-      },
-      "dependencies": {
-        "@graphql-codegen/plugin-helpers": {
-          "version": "1.12.2-alpha-ea7264f9.15",
-          "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.12.2-alpha-ea7264f9.15.tgz",
-          "integrity": "sha512-EgRHQVFswVQUevMEtsrsA45JmTWj3UAUK8laMyDqbQQuOqlTOgpqdceTLYWWCpyfybaEaagw+rWpkwPXyUjWYQ==",
+        },
+        "anymatch": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+          "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
           "dev": true,
           "requires": {
-            "@graphql-toolkit/common": "0.9.7",
-            "camel-case": "4.1.1",
-            "common-tags": "1.8.0",
-            "constant-case": "3.0.3",
-            "import-from": "3.0.0",
-            "lower-case": "2.0.1",
-            "param-case": "3.0.3",
-            "pascal-case": "3.1.1",
-            "tslib": "1.10.0",
-            "upper-case": "2.0.1"
-          }
-        },
-        "@graphql-codegen/visitor-plugin-common": {
-          "version": "1.12.2-alpha-ea7264f9.15",
-          "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.12.2-alpha-ea7264f9.15.tgz",
-          "integrity": "sha512-Y+4b5ArGOcXtGZ7gCLKhfOfiElH36uNSYs/8y0+9bxbjV1OuGfunnluysvpDSqIqatyVXviJh+P832VjO5Cviw==",
+            "normalize-path": "^3.0.0",
+            "picomatch": "^2.0.4"
+          }
+        },
+        "binary-extensions": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
+          "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
+          "dev": true
+        },
+        "braces": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+          "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
           "dev": true,
           "requires": {
-            "@graphql-codegen/plugin-helpers": "1.12.2-alpha-ea7264f9.15+ea7264f9",
-            "@graphql-toolkit/relay-operation-optimizer": "0.9.7",
-            "auto-bind": "4.0.0",
-            "dependency-graph": "0.8.1",
-            "graphql-tag": "2.10.1",
-            "pascal-case": "3.1.1",
-            "tslib": "1.10.0"
+            "fill-range": "^7.0.1"
           }
-        }
-      }
-    },
-    "@graphql-codegen/plugin-helpers": {
-      "version": "1.12.1",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.12.1.tgz",
-      "integrity": "sha512-mrc+trorCTuJYeD0gJLXO7xxabAyOmwXvDkPTxecWNfw5oJIloKsIorWkK00ZFbPHjorzJlOgON8vgXljREzKA==",
-      "dev": true,
-      "requires": {
-        "@graphql-toolkit/common": "0.9.7",
-        "camel-case": "4.1.1",
-        "common-tags": "1.8.0",
-        "constant-case": "3.0.3",
-        "import-from": "3.0.0",
-        "lower-case": "2.0.1",
-        "param-case": "3.0.3",
-        "pascal-case": "3.1.1",
-        "tslib": "1.10.0",
-        "upper-case": "2.0.1"
-      }
-    },
-    "@graphql-codegen/typescript": {
-      "version": "1.12.1",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-1.12.1.tgz",
-      "integrity": "sha512-j2iQrxSIMxF6MlzJ5OvnawRyewfEHlC21It+Z6Md2z7yJK5T2bYLs4jd0RkzDGVezodnpd0TzV/Yv+jGxdMjSQ==",
-      "dev": true,
-      "requires": {
-        "@graphql-codegen/plugin-helpers": "1.12.1",
-        "@graphql-codegen/visitor-plugin-common": "1.12.1",
-        "auto-bind": "4.0.0",
-        "tslib": "1.10.0"
-      }
-    },
-    "@graphql-codegen/typescript-operations": {
-      "version": "1.12.1",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-1.12.1.tgz",
-      "integrity": "sha512-lGF5wzi5Rndca4YoQCTczRamBrKkieuLIMW3/WT+t3s9wwTkC802HAuNLmUW9CUO+lcArrXV23qpyi9hElKZAA==",
-      "dev": true,
-      "requires": {
-        "@graphql-codegen/plugin-helpers": "1.12.1",
-        "@graphql-codegen/typescript": "1.12.1",
-        "@graphql-codegen/visitor-plugin-common": "1.12.1",
-        "auto-bind": "4.0.0",
-        "tslib": "1.10.0"
-      }
-    },
-    "@graphql-codegen/typescript-react-apollo": {
-      "version": "1.12.1",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-react-apollo/-/typescript-react-apollo-1.12.1.tgz",
-      "integrity": "sha512-uy6R2M6EvLXu9BWA9untZBpE+64OUnC2zjV0TO2cK6tz1Ktut9pyC7F4BNGoFHfTvmx80dxtKfwbqdmP48mddQ==",
-      "dev": true,
-      "requires": {
-        "@graphql-codegen/plugin-helpers": "1.12.1",
-        "@graphql-codegen/visitor-plugin-common": "1.12.1",
-        "auto-bind": "4.0.0",
-        "camel-case": "4.1.1",
-        "pascal-case": "3.1.1",
-        "tslib": "1.10.0"
-      }
-    },
-    "@graphql-codegen/visitor-plugin-common": {
-      "version": "1.12.1",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.12.1.tgz",
-      "integrity": "sha512-dfcb0d1u0bGo7KgaGoWMUrFec9rKB5JiiFatHfs2CK6h6iT6giqkuyHy8247tH3yvjk+xFoIG2NVIqg9jUyv+w==",
-      "dev": true,
-      "requires": {
-        "@graphql-codegen/plugin-helpers": "1.12.1",
-        "@graphql-toolkit/relay-operation-optimizer": "0.9.7",
-        "auto-bind": "4.0.0",
-        "dependency-graph": "0.8.1",
-        "graphql-tag": "2.10.1",
-        "pascal-case": "3.1.1",
-        "tslib": "1.10.0"
-      }
-    },
-    "@graphql-toolkit/apollo-engine-loader": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/apollo-engine-loader/-/apollo-engine-loader-0.9.7.tgz",
-      "integrity": "sha512-gIrkMUOkkY1yIXxyR3K9e8xwj+sR4WHlCUWzjk5UTuReCcv4CZlm8omEqQjeZrPg9Naj+D2velKYxhgXhxm2ZQ==",
-      "dev": true,
-      "requires": {
-        "@graphql-toolkit/common": "0.9.7",
-        "apollo-language-server": "1.18.0",
-        "tslib": "1.10.0"
-      }
-    },
-    "@graphql-toolkit/code-file-loader": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/code-file-loader/-/code-file-loader-0.9.7.tgz",
-      "integrity": "sha512-Vs2E7ojJ2gmhTz+U0MRLMib8yPz4+U1THCE3QgP4Pqnrqnkp/kEI7qpt3G3XI7JRRBWDHU2gdwOydnHmM/Cjow==",
-      "dev": true,
-      "requires": {
-        "@graphql-toolkit/common": "0.9.7",
-        "@graphql-toolkit/graphql-tag-pluck": "0.9.7",
-        "tslib": "1.10.0"
-      }
-    },
-    "@graphql-toolkit/common": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/common/-/common-0.9.7.tgz",
-      "integrity": "sha512-dpSRBMLeIiRct2gkjj24bp0EV7hbK/7dpJAPqNgvDH2535LhOkprYiCXQJyP4N1LODAEkpN/zzlJfKMVn773MQ==",
-      "dev": true,
-      "requires": {
-        "@ardatan/graphql-tools": "4.1.0",
-        "aggregate-error": "3.0.1",
-        "lodash": "4.17.15"
-      }
-    },
-    "@graphql-toolkit/core": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/core/-/core-0.9.7.tgz",
-      "integrity": "sha512-w1WU0iOq6AEBTICDxcu1xjFruFfGCHg6ERdWTWdIBOTn30qysIC0ek+XWN67vF9yV9QIdAxNu66gXKjUUWm2Tg==",
-      "dev": true,
-      "requires": {
-        "@graphql-toolkit/common": "0.9.7",
-        "@graphql-toolkit/schema-merging": "0.9.7",
-        "aggregate-error": "3.0.1",
-        "globby": "11.0.0",
-        "import-from": "^3.0.0",
-        "is-glob": "4.0.1",
-        "lodash": "4.17.15",
-        "resolve-from": "5.0.0",
-        "tslib": "1.10.0",
-        "unixify": "1.0.0",
-        "valid-url": "1.0.9"
-      }
-    },
-    "@graphql-toolkit/file-loading": {
-      "version": "0.9.1",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/file-loading/-/file-loading-0.9.1.tgz",
-      "integrity": "sha512-jiN2cvWjBR4ubwesU+IlpSCyXBZ1E7Xl5rakq63mFT9zVR5w0kEbO7Bs1nmIGlVbSvjbCzmn4d/kzrpgfwL7UQ==",
-      "dev": true,
-      "requires": {
-        "globby": "11.0.0",
-        "unixify": "1.0.0"
-      }
-    },
-    "@graphql-toolkit/git-loader": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/git-loader/-/git-loader-0.9.7.tgz",
-      "integrity": "sha512-/uJa6kudtPv/M8iPlGpm87mnJIAYQ5hcNG8wPY4frgol5a13Jvq9ro8jvaCueTa+kpybgUtVl4ZuCqsGa2P6CA==",
-      "dev": true,
-      "requires": {
-        "@graphql-toolkit/common": "0.9.7",
-        "@graphql-toolkit/graphql-tag-pluck": "0.9.7",
-        "simple-git": "1.131.0"
-      }
-    },
-    "@graphql-toolkit/github-loader": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/github-loader/-/github-loader-0.9.7.tgz",
-      "integrity": "sha512-30f0IMCn/abufu05YGiLuTdzwZk7N93zY+AEfEx7nSCpxVZYu7FSWuDxYxdMFQ2eWJvFXrh6u0As+TXB0njHZA==",
-      "dev": true,
-      "requires": {
-        "@graphql-toolkit/common": "0.9.7",
-        "@graphql-toolkit/graphql-tag-pluck": "0.9.7",
-        "cross-fetch": "3.0.4"
-      }
-    },
-    "@graphql-toolkit/graphql-file-loader": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/graphql-file-loader/-/graphql-file-loader-0.9.7.tgz",
-      "integrity": "sha512-t7CfYjghuXAtIqzwHhkUoE/u0a918UTOOVtHdLHh8rojjIUfsSeLeqMcFacRv+/z+kyKl9lgi4TE/qiyIpyR5A==",
-      "dev": true,
-      "requires": {
-        "@graphql-toolkit/common": "0.9.7",
-        "tslib": "1.10.0"
-      }
-    },
-    "@graphql-toolkit/graphql-tag-pluck": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/graphql-tag-pluck/-/graphql-tag-pluck-0.9.7.tgz",
-      "integrity": "sha512-hfHs9m/6rK0JRPrZg9LW8fPmQiNy7zvueey+TUH+qLHOkQiklDVDtQ/2yje35B16bwiyk3axxmHZ/H3fb5nWiQ==",
-      "dev": true,
-      "requires": {
-        "@babel/parser": "7.8.3",
-        "@babel/traverse": "7.8.3",
-        "@babel/types": "7.8.3",
-        "@graphql-toolkit/common": "0.9.7",
-        "vue-template-compiler": "^2.6.11"
+        },
+        "camel-case": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz",
+          "integrity": "sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==",
+          "dev": true,
+          "requires": {
+            "pascal-case": "^3.1.1",
+            "tslib": "^1.10.0"
+          }
+        },
+        "chalk": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "chokidar": {
+          "version": "3.3.1",
+          "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz",
+          "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==",
+          "dev": true,
+          "requires": {
+            "anymatch": "~3.1.1",
+            "braces": "~3.0.2",
+            "fsevents": "~2.1.2",
+            "glob-parent": "~5.1.0",
+            "is-binary-path": "~2.1.0",
+            "is-glob": "~4.0.1",
+            "normalize-path": "~3.0.0",
+            "readdirp": "~3.3.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "dev": true
+        },
+        "commander": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.0.tgz",
+          "integrity": "sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==",
+          "dev": true
+        },
+        "cosmiconfig": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
+          "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
+          "dev": true,
+          "requires": {
+            "@types/parse-json": "^4.0.0",
+            "import-fresh": "^3.1.0",
+            "parse-json": "^5.0.0",
+            "path-type": "^4.0.0",
+            "yaml": "^1.7.2"
+          }
+        },
+        "fill-range": {
+          "version": "7.0.1",
+          "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+          "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+          "dev": true,
+          "requires": {
+            "to-regex-range": "^5.0.1"
+          }
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true
+        },
+        "import-fresh": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
+          "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
+          "dev": true,
+          "requires": {
+            "parent-module": "^1.0.0",
+            "resolve-from": "^4.0.0"
+          }
+        },
+        "is-binary-path": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+          "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+          "dev": true,
+          "requires": {
+            "binary-extensions": "^2.0.0"
+          }
+        },
+        "is-number": {
+          "version": "7.0.0",
+          "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+          "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+          "dev": true
+        },
+        "lower-case": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz",
+          "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.10.0"
+          }
+        },
+        "normalize-path": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+          "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+          "dev": true
+        },
+        "parse-json": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
+          "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
+          "dev": true,
+          "requires": {
+            "@babel/code-frame": "^7.0.0",
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1",
+            "lines-and-columns": "^1.1.6"
+          }
+        },
+        "path-type": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+          "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+          "dev": true
+        },
+        "readdirp": {
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz",
+          "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==",
+          "dev": true,
+          "requires": {
+            "picomatch": "^2.0.7"
+          }
+        },
+        "resolve-from": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+          "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "7.1.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+          "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        },
+        "to-regex-range": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+          "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+          "dev": true,
+          "requires": {
+            "is-number": "^7.0.0"
+          }
+        },
+        "upper-case": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.1.tgz",
+          "integrity": "sha512-laAsbea9SY5osxrv7S99vH9xAaJKrw5Qpdh4ENRLcaxipjKsiaBwiAsxfa8X5mObKNTQPsupSq0J/VIxsSJe3A==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.10.0"
+          }
+        }
       }
     },
-    "@graphql-toolkit/json-file-loader": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/json-file-loader/-/json-file-loader-0.9.7.tgz",
-      "integrity": "sha512-MNnCX201p011FPOm/rlDLkBTpx4LvooG9pdMU1ijRD/sqpHSkhZ2U/aKyoiDDKrLUgK7cvHws1KXBvLcg7r6aQ==",
+    "@graphql-codegen/core": {
+      "version": "1.11.2",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/core/-/core-1.11.2.tgz",
+      "integrity": "sha512-ZEHbCtivUQXXPkTd+vrb6sSmCss45Z7YjeyC1mf0kStcEeAKygs6XM2k7F5a9wUQn3mxnyJRAnqfqNIdoagoUg==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.9.7",
+        "@graphql-codegen/plugin-helpers": "1.11.2",
+        "@graphql-toolkit/common": "0.9.0",
+        "@graphql-toolkit/schema-merging": "0.9.0",
         "tslib": "1.10.0"
       }
     },
-    "@graphql-toolkit/prisma-loader": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/prisma-loader/-/prisma-loader-0.9.7.tgz",
-      "integrity": "sha512-SSl7bqGomTzhpXORHSEa4fThF6XC2zOuHJlOqwxGMdsbkouogR77vn7OXlCxRhNxClfBF/f7nSQdSOONVlFBvw==",
+    "@graphql-codegen/fragment-matcher": {
+      "version": "1.11.2",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/fragment-matcher/-/fragment-matcher-1.11.2.tgz",
+      "integrity": "sha512-pLXTwf1dN+CmeZb+5fIjVq41ygPFgQ4rnj6S5N/efHY8AYuGROS58+YXFjU4Nx0eMFj18ymsrrTBWEr2Xug7ZQ==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.9.7",
-        "@graphql-toolkit/url-loader": "0.9.7",
-        "prisma-yml": "1.34.10",
-        "tslib": "1.10.0"
+        "@graphql-codegen/plugin-helpers": "1.11.2"
       }
     },
-    "@graphql-toolkit/relay-operation-optimizer": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/relay-operation-optimizer/-/relay-operation-optimizer-0.9.7.tgz",
-      "integrity": "sha512-IPFAbKMOX3RdjyDuamK9ziuTFD5tsCiTVvHACHA2wgg+32krJZJsV6STKhFLqIwytS40vt5zhZydQCFxIrCD5g==",
+    "@graphql-codegen/plugin-helpers": {
+      "version": "1.11.2",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.11.2.tgz",
+      "integrity": "sha512-jggDX2ykLU8EOdP8bpArkMtPTvJ72XYWa44f0GFIdhfSfZtK8PO/gMaSY8iPbV5DqD4HnYvMc3mXCoJTAPT8VQ==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.9.7",
-        "relay-compiler": "8.0.0"
+        "@graphql-toolkit/common": "0.9.0",
+        "camel-case": "4.1.1",
+        "common-tags": "1.8.0",
+        "constant-case": "3.0.3",
+        "import-from": "3.0.0",
+        "lower-case": "2.0.1",
+        "param-case": "3.0.3",
+        "pascal-case": "3.1.1",
+        "tslib": "1.10.0",
+        "upper-case": "2.0.1"
+      },
+      "dependencies": {
+        "camel-case": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz",
+          "integrity": "sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==",
+          "dev": true,
+          "requires": {
+            "pascal-case": "^3.1.1",
+            "tslib": "^1.10.0"
+          }
+        },
+        "import-from": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz",
+          "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==",
+          "dev": true,
+          "requires": {
+            "resolve-from": "^5.0.0"
+          }
+        },
+        "lower-case": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz",
+          "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.10.0"
+          }
+        },
+        "param-case": {
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz",
+          "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==",
+          "dev": true,
+          "requires": {
+            "dot-case": "^3.0.3",
+            "tslib": "^1.10.0"
+          }
+        },
+        "resolve-from": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+          "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+          "dev": true
+        },
+        "upper-case": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.1.tgz",
+          "integrity": "sha512-laAsbea9SY5osxrv7S99vH9xAaJKrw5Qpdh4ENRLcaxipjKsiaBwiAsxfa8X5mObKNTQPsupSq0J/VIxsSJe3A==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.10.0"
+          }
+        }
       }
     },
-    "@graphql-toolkit/schema-merging": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/schema-merging/-/schema-merging-0.9.7.tgz",
-      "integrity": "sha512-RLhP0+XT4JGoPGCvlcTPdCE8stA7l0D5+gZ8ZP0snqzZOdsDFG4cNxpJtwf48i7uArsXkfu5OjOvTwh0MR0Wrw==",
+    "@graphql-toolkit/apollo-engine-loader": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/apollo-engine-loader/-/apollo-engine-loader-0.9.0.tgz",
+      "integrity": "sha512-CryB0LklKpMFSWTjp9YpFabrL7DZZsSzSt4jzY1f+cHC8Em/2icTJAe102NzUMxljKxVGo/FeAJIxOaV1hKVFQ==",
       "dev": true,
       "requires": {
-        "@ardatan/graphql-tools": "4.1.0",
-        "@graphql-toolkit/common": "0.9.7",
-        "deepmerge": "4.2.2",
+        "@graphql-toolkit/common": "0.9.0",
+        "apollo-language-server": "1.17.2",
         "tslib": "1.10.0"
       }
     },
-    "@graphql-toolkit/url-loader": {
-      "version": "0.9.7",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/url-loader/-/url-loader-0.9.7.tgz",
-      "integrity": "sha512-cOT2XJVZLWOKG4V9ucVtUTqJMW0BJqEqrHvpR8YcIWffrEChmzZQX+ug3BkRNomaUe8ywgExJ80aZuKWeSHvew==",
+    "@graphql-toolkit/code-file-loader": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/code-file-loader/-/code-file-loader-0.9.0.tgz",
+      "integrity": "sha512-Aswx0oFctDhzg53jY1nwyMwRl/NLYB8jP0Du2SQkXIeSWy5UDqMdMa48VQgwERnmXv13OIqeSR4Oh+Ri30bhyw==",
       "dev": true,
       "requires": {
-        "@ardatan/graphql-tools": "4.1.0",
-        "@graphql-toolkit/common": "0.9.7",
-        "cross-fetch": "3.0.4",
-        "tslib": "1.10.0",
-        "valid-url": "1.0.9"
-      }
-    },
-    "@hapi/address": {
-      "version": "2.1.4",
-      "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
-      "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ=="
-    },
-    "@hapi/bourne": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz",
-      "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA=="
-    },
-    "@hapi/hoek": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.0.tgz",
-      "integrity": "sha512-7XYT10CZfPsH7j9F1Jmg1+d0ezOux2oM2GfArAzLwWe4mE2Dr3hVjsAL6+TFY49RRJlCdJDMw3nJsLFroTc8Kw=="
-    },
-    "@hapi/joi": {
-      "version": "15.1.1",
-      "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz",
-      "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==",
-      "requires": {
-        "@hapi/address": "2.x.x",
-        "@hapi/bourne": "1.x.x",
-        "@hapi/hoek": "8.x.x",
-        "@hapi/topo": "3.x.x"
+        "@graphql-toolkit/common": "0.9.0",
+        "@graphql-toolkit/graphql-tag-pluck": "0.9.0",
+        "tslib": "1.10.0"
       }
     },
-    "@hapi/topo": {
-      "version": "3.1.6",
-      "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz",
-      "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==",
+    "@graphql-toolkit/common": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/common/-/common-0.9.0.tgz",
+      "integrity": "sha512-bLuyt4yV/XIHUS+gP4aF5xjnb5M2K+uuB35Hojw0er+tkNhWiOuWQzRMWPovds/4WN2C9PuknQby/+ntgBOm/g==",
+      "dev": true,
       "requires": {
-        "@hapi/hoek": "^8.3.0"
+        "@kamilkisiela/graphql-tools": "4.0.6",
+        "aggregate-error": "3.0.1",
+        "lodash": "4.17.15"
       }
     },
-    "@jest/console": {
-      "version": "24.9.0",
-      "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
-      "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==",
+    "@graphql-toolkit/core": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/core/-/core-0.9.0.tgz",
+      "integrity": "sha512-/0Q9S4uw27D/Q1zJsfswrsMHO2cTsvBU9G9tVxQKSI5WjHQGNJUOn0mA+Fl0bfQAjRDoC0p4BSsH1PcS5yagPA==",
+      "dev": true,
       "requires": {
-        "@jest/source-map": "^24.9.0",
-        "chalk": "^2.0.1",
-        "slash": "^2.0.0"
+        "@graphql-toolkit/common": "0.9.0",
+        "@graphql-toolkit/schema-merging": "0.9.0",
+        "aggregate-error": "3.0.1",
+        "globby": "11.0.0",
+        "is-glob": "4.0.1",
+        "resolve-from": "5.0.0",
+        "tslib": "1.10.0",
+        "unixify": "1.0.0",
+        "valid-url": "1.0.9"
       },
       "dependencies": {
-        "chalk": {
-          "version": "2.4.2",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
-          "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
-          "requires": {
-            "ansi-styles": "^3.2.1",
-            "escape-string-regexp": "^1.0.5",
-            "supports-color": "^5.3.0"
-          }
+        "@nodelib/fs.stat": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz",
+          "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==",
+          "dev": true
+        },
+        "array-union": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+          "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+          "dev": true
+        },
+        "braces": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+          "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+          "dev": true,
+          "requires": {
+            "fill-range": "^7.0.1"
+          }
+        },
+        "dir-glob": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+          "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+          "dev": true,
+          "requires": {
+            "path-type": "^4.0.0"
+          }
+        },
+        "fast-glob": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz",
+          "integrity": "sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g==",
+          "dev": true,
+          "requires": {
+            "@nodelib/fs.stat": "^2.0.2",
+            "@nodelib/fs.walk": "^1.2.3",
+            "glob-parent": "^5.1.0",
+            "merge2": "^1.3.0",
+            "micromatch": "^4.0.2"
+          }
+        },
+        "fill-range": {
+          "version": "7.0.1",
+          "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+          "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+          "dev": true,
+          "requires": {
+            "to-regex-range": "^5.0.1"
+          }
+        },
+        "globby": {
+          "version": "11.0.0",
+          "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz",
+          "integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==",
+          "dev": true,
+          "requires": {
+            "array-union": "^2.1.0",
+            "dir-glob": "^3.0.1",
+            "fast-glob": "^3.1.1",
+            "ignore": "^5.1.4",
+            "merge2": "^1.3.0",
+            "slash": "^3.0.0"
+          }
+        },
+        "ignore": {
+          "version": "5.1.4",
+          "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
+          "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==",
+          "dev": true
+        },
+        "is-number": {
+          "version": "7.0.0",
+          "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+          "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+          "dev": true
+        },
+        "micromatch": {
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
+          "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+          "dev": true,
+          "requires": {
+            "braces": "^3.0.1",
+            "picomatch": "^2.0.5"
+          }
+        },
+        "path-type": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+          "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+          "dev": true
+        },
+        "resolve-from": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+          "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+          "dev": true
         },
         "slash": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
-          "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+          "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+          "dev": true
+        },
+        "to-regex-range": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+          "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+          "dev": true,
+          "requires": {
+            "is-number": "^7.0.0"
+          }
+        }
+      }
+    },
+    "@graphql-toolkit/git-loader": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/git-loader/-/git-loader-0.9.0.tgz",
+      "integrity": "sha512-B3QpM3YQhekjrLVIBev8qi95HK6As/gezjfdwT75sTYZ9+VgJwU2MDo08EKUWjHU1KoV1MDBGJNOtzopiogUlA==",
+      "dev": true,
+      "requires": {
+        "@graphql-toolkit/common": "0.9.0",
+        "simple-git": "1.129.0"
+      }
+    },
+    "@graphql-toolkit/github-loader": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/github-loader/-/github-loader-0.9.0.tgz",
+      "integrity": "sha512-srLPYVXkqZkLzJsqRo+IcLAu+HfRm6QXjLNrn6XOUb0JLmqZnXoFOMGG9k3rlHO8mPm36F+bkq2hE4tmk/mW5A==",
+      "dev": true,
+      "requires": {
+        "@graphql-toolkit/common": "0.9.0",
+        "cross-fetch": "3.0.4"
+      }
+    },
+    "@graphql-toolkit/graphql-file-loader": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/graphql-file-loader/-/graphql-file-loader-0.9.0.tgz",
+      "integrity": "sha512-mUgmjCF1oLhYbbQC0bcX/5DdQJNJ63Pmm8JCyPhgZOy2pdbLwLKQj6KnWznny2VOGF63jjTiGUp1JfxsvYpXmQ==",
+      "dev": true,
+      "requires": {
+        "@graphql-toolkit/common": "0.9.0",
+        "tslib": "1.10.0"
+      }
+    },
+    "@graphql-toolkit/graphql-tag-pluck": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/graphql-tag-pluck/-/graphql-tag-pluck-0.9.0.tgz",
+      "integrity": "sha512-MiMxyBM4DK+7hZNjdWeiNTw/K/8M/gSHkEPtbSGaCn0csy9GYeLM6ojz2JDdg0rxNT4Zb9Z1DzQcauMpnX3tmA==",
+      "dev": true,
+      "requires": {
+        "@babel/parser": "7.7.7",
+        "@babel/traverse": "7.7.4",
+        "@babel/types": "7.7.4",
+        "vue-template-compiler": "^2.6.10"
+      },
+      "dependencies": {
+        "@babel/parser": {
+          "version": "7.7.7",
+          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.7.tgz",
+          "integrity": "sha512-WtTZMZAZLbeymhkd/sEaPD8IQyGAhmuTuvTzLiCFM7iXiVdY0gc0IaI+cW0fh1BnSMbJSzXX6/fHllgHKwHhXw==",
+          "dev": true
+        },
+        "@babel/traverse": {
+          "version": "7.7.4",
+          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz",
+          "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==",
+          "dev": true,
+          "requires": {
+            "@babel/code-frame": "^7.5.5",
+            "@babel/generator": "^7.7.4",
+            "@babel/helper-function-name": "^7.7.4",
+            "@babel/helper-split-export-declaration": "^7.7.4",
+            "@babel/parser": "^7.7.4",
+            "@babel/types": "^7.7.4",
+            "debug": "^4.1.0",
+            "globals": "^11.1.0",
+            "lodash": "^4.17.13"
+          }
+        },
+        "@babel/types": {
+          "version": "7.7.4",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz",
+          "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
         }
       }
     },
+    "@graphql-toolkit/json-file-loader": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/json-file-loader/-/json-file-loader-0.9.0.tgz",
+      "integrity": "sha512-YyS7HP0gVj3SuyjFAu550BUi7nQ7m++OF8ZOsEkV6KHObrKXRF4GYocffRDcdCefb02MjjrkLwcXzvMYNq8dKQ==",
+      "dev": true,
+      "requires": {
+        "@graphql-toolkit/common": "0.9.0",
+        "tslib": "1.10.0"
+      }
+    },
+    "@graphql-toolkit/prisma-loader": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/prisma-loader/-/prisma-loader-0.9.0.tgz",
+      "integrity": "sha512-+vQgKp+7hhcd5DNv/fB9OQcgoThqiHflaI7UzKOPikUf24wYRNknZiAwie+9u3IP8NEymvnFaOy2nUQxqRCc3w==",
+      "dev": true,
+      "requires": {
+        "@graphql-toolkit/common": "0.9.0",
+        "@graphql-toolkit/url-loader": "0.9.0",
+        "prisma-yml": "1.34.10",
+        "tslib": "1.10.0"
+      }
+    },
+    "@graphql-toolkit/schema-merging": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/schema-merging/-/schema-merging-0.9.0.tgz",
+      "integrity": "sha512-vrzkqkFXxZ4dXQrHeNGDDWONbOAVDeJmGPwK0cRu2aVszftvkYVJXBrmkMYzZJHwk+tGVkNywf1r00GR6prpOw==",
+      "dev": true,
+      "requires": {
+        "@graphql-toolkit/common": "0.9.0",
+        "@kamilkisiela/graphql-tools": "4.0.6",
+        "deepmerge": "4.2.2",
+        "tslib": "1.10.0"
+      }
+    },
+    "@graphql-toolkit/url-loader": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/url-loader/-/url-loader-0.9.0.tgz",
+      "integrity": "sha512-umz+V9KbFv4oCWjZWiiIIH2PQrdK7tPxs8vbLdNy+EvdQrFSV5lJSknfsusyAoJfOpH+W294Ugfs0ySH5jZDxw==",
+      "dev": true,
+      "requires": {
+        "@graphql-toolkit/common": "0.9.0",
+        "cross-fetch": "3.0.4",
+        "tslib": "1.10.0",
+        "valid-url": "1.0.9"
+      }
+    },
+    "@hapi/address": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
+      "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ=="
+    },
+    "@hapi/bourne": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz",
+      "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA=="
+    },
+    "@hapi/hoek": {
+      "version": "8.5.0",
+      "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.0.tgz",
+      "integrity": "sha512-7XYT10CZfPsH7j9F1Jmg1+d0ezOux2oM2GfArAzLwWe4mE2Dr3hVjsAL6+TFY49RRJlCdJDMw3nJsLFroTc8Kw=="
+    },
+    "@hapi/joi": {
+      "version": "15.1.1",
+      "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz",
+      "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==",
+      "requires": {
+        "@hapi/address": "2.x.x",
+        "@hapi/bourne": "1.x.x",
+        "@hapi/hoek": "8.x.x",
+        "@hapi/topo": "3.x.x"
+      }
+    },
+    "@hapi/topo": {
+      "version": "3.1.6",
+      "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz",
+      "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==",
+      "requires": {
+        "@hapi/hoek": "^8.3.0"
+      }
+    },
+    "@jest/console": {
+      "version": "24.9.0",
+      "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
+      "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==",
+      "requires": {
+        "@jest/source-map": "^24.9.0",
+        "chalk": "^2.0.1",
+        "slash": "^2.0.0"
+      }
+    },
     "@jest/core": {
       "version": "24.9.0",
       "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz",

webui/src/pages/list/ListQuery.tsx 🔗

@@ -289,9 +289,11 @@ function ListQuery() {
       {content}
       <div className={classes.pagination}>
         {previousPage ? (
-          <IconButton component={Link} to={previousPage}>
-            <KeyboardArrowLeft />
-          </IconButton>
+          <Link to={previousPage}>
+            <IconButton component={Link} to={previousPage}>
+              <KeyboardArrowLeft />
+            </IconButton>
+          </Link>
         ) : (
           <IconButton disabled>
             <KeyboardArrowLeft />