Browse Source

Refactorization part 1: Queries (+ some small fixes)

Leszek Wiesner 3 years ago
parent
commit
8970a90044

+ 7 - 4
query-node/mappings/workingGroups.ts

@@ -282,19 +282,22 @@ async function handleRemoveUpcomingOpeningAction(
 async function handleSetWorkingGroupMetadataAction(
   db: DatabaseManager,
   event_: SubstrateEvent,
+  statusChangedEvent: StatusTextChangedEvent,
   action: SetGroupMetadata
 ): Promise<WorkingGroupMetadataSet> {
   const { newMetadata } = action.toObject()
   const group = await getWorkingGroup(db, event_, ['metadata'])
-  const groupMetadata = group.metadata!
+  const groupMetadata = group.metadata
   const eventTime = new Date(event_.blockTimestamp.toNumber())
 
   const newGroupMetadata = new WorkingGroupMetadata({
-    ...groupMetadata,
-    ...newMetadata,
+    ..._.merge(groupMetadata, newMetadata),
+    id: undefined,
     createdAt: eventTime,
     updatedAt: eventTime,
     setAtBlock: await getOrCreateBlock(db, event_),
+    setInEvent: statusChangedEvent,
+    group,
   })
   await db.save<WorkingGroupMetadata>(newGroupMetadata)
 
@@ -322,7 +325,7 @@ async function handleWorkingGroupMetadataAction(
       return handleRemoveUpcomingOpeningAction(db, action.getRemoveUpcomingOpening()!)
     }
     case WorkingGroupMetadataAction.ActionCase.SET_GROUP_METADATA: {
-      return handleSetWorkingGroupMetadataAction(db, event_, action.getSetGroupMetadata()!)
+      return handleSetWorkingGroupMetadataAction(db, event_, statusChangedEvent, action.getSetGroupMetadata()!)
     }
   }
   const result = new InvalidActionMetadata()

+ 6 - 0
query-node/schemas/workingGroups.graphql

@@ -87,6 +87,12 @@ type WorkingGroupMetadata @entity {
 
   "Block the metadata was set at"
   setAtBlock: Block!
+
+  "Event the working group metadata was set in"
+  setInEvent: StatusTextChangedEvent!
+
+  "Related group"
+  group: WorkingGroup!
 }
 
 type WorkingGroup @entity {

+ 21 - 1
tests/integration-tests/codegen.yml

@@ -2,11 +2,31 @@ overwrite: true
 
 schema: '../../query-node/generated/graphql-server/generated/schema.graphql'
 
+documents:
+  - './src/graphql/queries/*.graphql'
+
+config:
+  scalars:
+    Date: Date
+  preResolveTypes: true # avoid using Pick
+  skipTypename: true # skip __typename field in typings unless it's part of the query
+
 generates:
-  src/QueryNodeApiSchema.generated.ts:
+  src/graphql/generated/schema.ts:
     hooks:
       afterOneFileWrite:
         - prettier --write
         - eslint --fix
     plugins:
       - typescript
+  src/graphql/generated/queries.ts:
+    preset: import-types
+    presetConfig:
+      typesPath: ./schema
+    hooks:
+      afterOneFileWrite:
+        - prettier --write
+        - eslint --fix
+    plugins:
+      - typescript-operations
+      - typescript-document-nodes

+ 6 - 3
tests/integration-tests/package.json

@@ -10,7 +10,7 @@
     "lint": "eslint . --quiet --ext .ts",
     "checks": "tsc --noEmit --pretty && prettier ./ --check && yarn lint",
     "format": "prettier ./ --write",
-    "generate:query-node-types": "graphql-codegen"
+    "generate:graphql-types": "graphql-codegen"
   },
   "dependencies": {
     "@apollo/client": "^3.2.5",
@@ -35,8 +35,11 @@
     "prettier": "2.0.2",
     "ts-node": "^8.8.1",
     "typescript": "^3.8.3",
-    "@graphql-codegen/cli": "^1.21.3",
-    "@graphql-codegen/typescript": "^1.21.1"
+    "@graphql-codegen/cli": "^1.21.4",
+    "@graphql-codegen/typescript": "^1.22.0",
+    "@graphql-codegen/import-types-preset": "^1.18.1",
+    "@graphql-codegen/typescript-operations": "^1.17.16",
+    "@graphql-codegen/typescript-document-nodes": "^1.17.11"
   },
   "volta": {
     "extends": "../../package.json"

+ 345 - 789
tests/integration-tests/src/QueryNodeApi.ts

@@ -1,40 +1,118 @@
-import { gql, ApolloClient, ApolloQueryResult, NormalizedCacheObject } from '@apollo/client'
+import { ApolloClient, DocumentNode, NormalizedCacheObject } from '@apollo/client'
 import { MemberId } from '@joystream/types/common'
-import {
-  ApplicationWithdrawnEvent,
-  AppliedOnOpeningEvent,
-  InitialInvitationBalanceUpdatedEvent,
-  InitialInvitationCountUpdatedEvent,
-  MembershipPriceUpdatedEvent,
-  MembershipSystemSnapshot,
-  OpeningAddedEvent,
-  OpeningCanceledEvent,
-  OpeningFilledEvent,
-  Query,
-  ReferralCutUpdatedEvent,
-  StatusTextChangedEvent,
-  UpcomingWorkingGroupOpening,
-  WorkingGroup,
-  WorkingGroupMetadata,
-} from './QueryNodeApiSchema.generated'
 import Debugger from 'debug'
 import { ApplicationId, OpeningId } from '@joystream/types/working-group'
 import { WorkingGroupModuleName } from './types'
-
-const EVENT_GENERIC_FIELDS = `
-  id
-  event {
-    inBlock {
-      number
-      timestamp
-      network
-    }
-    inExtrinsic
-    indexInBlock
-    type
-  }
-`
-
+import {
+  GetMemberByIdQuery,
+  GetMemberByIdQueryVariables,
+  GetMemberById,
+  GetMembershipBoughtEventsByMemberIdQuery,
+  GetMembershipBoughtEventsByMemberIdQueryVariables,
+  GetMembershipBoughtEventsByMemberId,
+  GetMemberProfileUpdatedEventsByMemberIdQuery,
+  GetMemberProfileUpdatedEventsByMemberIdQueryVariables,
+  GetMemberProfileUpdatedEventsByMemberId,
+  GetMemberAccountsUpdatedEventsByMemberIdQuery,
+  GetMemberAccountsUpdatedEventsByMemberIdQueryVariables,
+  GetMemberAccountsUpdatedEventsByMemberId,
+  GetMemberInvitedEventsByNewMemberIdQuery,
+  GetMemberInvitedEventsByNewMemberIdQueryVariables,
+  GetMemberInvitedEventsByNewMemberId,
+  GetInvitesTransferredEventsBySourceMemberIdQuery,
+  GetInvitesTransferredEventsBySourceMemberIdQueryVariables,
+  GetInvitesTransferredEventsBySourceMemberId,
+  GetStakingAccountAddedEventsByMemberIdQuery,
+  GetStakingAccountAddedEventsByMemberIdQueryVariables,
+  GetStakingAccountAddedEventsByMemberId,
+  GetStakingAccountConfirmedEventsByMemberIdQuery,
+  GetStakingAccountConfirmedEventsByMemberIdQueryVariables,
+  GetStakingAccountConfirmedEventsByMemberId,
+  GetStakingAccountRemovedEventsByMemberIdQuery,
+  GetStakingAccountRemovedEventsByMemberIdQueryVariables,
+  GetStakingAccountRemovedEventsByMemberId,
+  GetMembershipSystemSnapshotAtQuery,
+  GetMembershipSystemSnapshotAtQueryVariables,
+  GetMembershipSystemSnapshotAt,
+  GetMembershipSystemSnapshotBeforeQuery,
+  GetMembershipSystemSnapshotBeforeQueryVariables,
+  GetMembershipSystemSnapshotBefore,
+  GetReferralCutUpdatedEventsByEventIdQuery,
+  GetReferralCutUpdatedEventsByEventIdQueryVariables,
+  GetReferralCutUpdatedEventsByEventId,
+  GetMembershipPriceUpdatedEventsByEventIdQuery,
+  GetMembershipPriceUpdatedEventsByEventIdQueryVariables,
+  GetMembershipPriceUpdatedEventsByEventId,
+  GetInitialInvitationBalanceUpdatedEventsByEventIdQuery,
+  GetInitialInvitationBalanceUpdatedEventsByEventIdQueryVariables,
+  GetInitialInvitationBalanceUpdatedEventsByEventId,
+  GetInitialInvitationCountUpdatedEventsByEventIdQuery,
+  GetInitialInvitationCountUpdatedEventsByEventIdQueryVariables,
+  GetInitialInvitationCountUpdatedEventsByEventId,
+  GetOpeningByIdQuery,
+  GetOpeningByIdQueryVariables,
+  GetOpeningById,
+  GetApplicationByIdQuery,
+  GetApplicationByIdQueryVariables,
+  GetApplicationById,
+  GetAppliedOnOpeningEventsByEventIdQuery,
+  GetAppliedOnOpeningEventsByEventIdQueryVariables,
+  GetAppliedOnOpeningEventsByEventId,
+  GetOpeningAddedEventsByEventIdQuery,
+  GetOpeningAddedEventsByEventIdQueryVariables,
+  GetOpeningAddedEventsByEventId,
+  GetOpeningFilledEventsByEventIdQuery,
+  GetOpeningFilledEventsByEventIdQueryVariables,
+  GetOpeningFilledEventsByEventId,
+  GetApplicationWithdrawnEventsByEventIdQuery,
+  GetApplicationWithdrawnEventsByEventIdQueryVariables,
+  GetApplicationWithdrawnEventsByEventId,
+  GetOpeningCancelledEventsByEventIdQuery,
+  GetOpeningCancelledEventsByEventIdQueryVariables,
+  GetOpeningCancelledEventsByEventId,
+  GetStatusTextChangedEventsByEventIdQuery,
+  GetStatusTextChangedEventsByEventIdQueryVariables,
+  GetStatusTextChangedEventsByEventId,
+  GetUpcomingOpeningByCreatedInEventIdQuery,
+  GetUpcomingOpeningByCreatedInEventIdQueryVariables,
+  GetUpcomingOpeningByCreatedInEventId,
+  GetWorkingGroupByNameQuery,
+  GetWorkingGroupByNameQueryVariables,
+  GetWorkingGroupByName,
+  GetWorkingGroupMetadataSnapshotAtQuery,
+  GetWorkingGroupMetadataSnapshotAtQueryVariables,
+  GetWorkingGroupMetadataSnapshotAt,
+  GetWorkingGroupMetadataSnapshotBeforeQuery,
+  GetWorkingGroupMetadataSnapshotBeforeQueryVariables,
+  GetWorkingGroupMetadataSnapshotBefore,
+  MembershipFieldsFragment,
+  MembershipBoughtEventFieldsFragment,
+  MemberProfileUpdatedEventFieldsFragment,
+  MemberAccountsUpdatedEventFieldsFragment,
+  MemberInvitedEventFieldsFragment,
+  InvitesTransferredEventFieldsFragment,
+  StakingAccountAddedEventFieldsFragment,
+  StakingAccountConfirmedEventFieldsFragment,
+  StakingAccountRemovedEventFieldsFragment,
+  MembershipSystemSnapshotFieldsFragment,
+  ReferralCutUpdatedEventFieldsFragment,
+  MembershipPriceUpdatedEventFieldsFragment,
+  InitialInvitationBalanceUpdatedEventFieldsFragment,
+  InitialInvitationCountUpdatedEventFieldsFragment,
+  OpeningFieldsFragment,
+  ApplicationFieldsFragment,
+  AppliedOnOpeningEventFieldsFragment,
+  OpeningAddedEventFieldsFragment,
+  OpeningFilledEventFieldsFragment,
+  ApplicationWithdrawnEventFieldsFragment,
+  OpeningCanceledEventFieldsFragment,
+  StatusTextChangedEventFieldsFragment,
+  UpcomingOpeningFieldsFragment,
+  WorkingGroupFieldsFragment,
+  WorkingGroupMetadataFieldsFragment,
+} from './graphql/generated/queries'
+import { Maybe } from './graphql/generated/schema'
+import { OperationDefinitionNode } from 'graphql'
 export class QueryNodeApi {
   private readonly queryNodeProvider: ApolloClient<NormalizedCacheObject>
   private readonly debug: Debugger.Debugger
@@ -91,848 +169,326 @@ export class QueryNodeApi {
     })
   }
 
-  public async getMemberById(id: MemberId): Promise<ApolloQueryResult<Pick<Query, 'membershipByUniqueInput'>>> {
-    const MEMBER_BY_ID_QUERY = gql`
-      query($id: ID!) {
-        membershipByUniqueInput(where: { id: $id }) {
-          id
-          handle
-          metadata {
-            name
-            about
-          }
-          controllerAccount
-          rootAccount
-          registeredAtBlock {
-            number
-            timestamp
-            network
-          }
-          registeredAtTime
-          entry
-          isVerified
-          inviteCount
-          invitedBy {
-            id
-          }
-          invitees {
-            id
-          }
-          boundAccounts
-        }
-      }
-    `
-
-    this.queryDebug(`Executing getMemberById(${id.toString()}) query`)
-
-    return this.queryNodeProvider.query({ query: MEMBER_BY_ID_QUERY, variables: { id: id.toNumber() } })
+  private debugQuery(query: DocumentNode, args: Record<string, unknown>): void {
+    const queryDef = query.definitions.find((d) => d.kind === 'OperationDefinition') as OperationDefinitionNode
+    this.queryDebug(`${queryDef.name!.value}(${JSON.stringify(args)})`)
   }
 
-  public async getMembershipBoughtEvents(
-    memberId: MemberId
-  ): Promise<ApolloQueryResult<Pick<Query, 'membershipBoughtEvents'>>> {
-    const MEMBERTSHIP_BOUGHT_BY_MEMBER_ID = gql`
-      query($memberId: ID!) {
-        membershipBoughtEvents(where: { newMemberId_eq: $memberId }) {
-          ${EVENT_GENERIC_FIELDS}
-          newMember {
-            id
-          }
-          rootAccount
-          controllerAccount
-          handle
-          metadata {
-            name
-            about
-          }
-          referrer {
-            id
-          }
-        }
-      }
-    `
-
-    this.queryDebug(`Executing getMembershipBoughtEvents(${memberId.toString()})`)
-
-    return this.queryNodeProvider.query({
-      query: MEMBERTSHIP_BOUGHT_BY_MEMBER_ID,
-      variables: { memberId: memberId.toNumber() },
-    })
+  // Query entity by unique input
+  private async uniqueEntityQuery<
+    QueryT extends { [k: string]: Maybe<Record<string, unknown>> | undefined },
+    VariablesT extends Record<string, unknown>
+  >(
+    query: DocumentNode,
+    variables: VariablesT,
+    resultKey: keyof QueryT
+  ): Promise<Required<QueryT>[keyof QueryT] | null> {
+    this.debugQuery(query, variables)
+    return (await this.queryNodeProvider.query<QueryT, VariablesT>({ query, variables })).data[resultKey] || null
   }
 
-  public async getMemberProfileUpdatedEvents(
-    memberId: MemberId
-  ): Promise<ApolloQueryResult<Pick<Query, 'memberProfileUpdatedEvents'>>> {
-    const MEMBER_PROFILE_UPDATED_BY_MEMBER_ID = gql`
-      query($memberId: ID!) {
-        memberProfileUpdatedEvents(where: { memberId_eq: $memberId }) {
-          ${EVENT_GENERIC_FIELDS}
-          member {
-            id
-          }
-          newHandle
-          newMetadata {
-            name
-            about
-          }
-        }
-      }
-    `
-
-    this.queryDebug(`Executing getMemberProfileUpdatedEvents(${memberId.toString()})`)
-
-    return this.queryNodeProvider.query({
-      query: MEMBER_PROFILE_UPDATED_BY_MEMBER_ID,
-      variables: { memberId: memberId.toNumber() },
-    })
+  // Query entities by "non-unique" input and return first result
+  private async firstEntityQuery<QueryT extends { [k: string]: unknown[] }, VariablesT extends Record<string, unknown>>(
+    query: DocumentNode,
+    variables: VariablesT,
+    resultKey: keyof QueryT
+  ): Promise<QueryT[keyof QueryT][number] | null> {
+    this.debugQuery(query, variables)
+    return (await this.queryNodeProvider.query<QueryT, VariablesT>({ query, variables })).data[resultKey][0] || null
   }
 
-  public async getMemberAccountsUpdatedEvents(
-    memberId: MemberId
-  ): Promise<ApolloQueryResult<Pick<Query, 'memberAccountsUpdatedEvents'>>> {
-    const MEMBER_ACCOUNTS_UPDATED_BY_MEMBER_ID = gql`
-      query($memberId: ID!) {
-        memberAccountsUpdatedEvents(where: { memberId_eq: $memberId }) {
-          ${EVENT_GENERIC_FIELDS}
-          member {
-            id
-          }
-          newRootAccount
-          newControllerAccount
-        }
-      }
-    `
-
-    this.queryDebug(`Executing getMemberAccountsUpdatedEvents(${memberId.toString()})`)
-
-    return this.queryNodeProvider.query({
-      query: MEMBER_ACCOUNTS_UPDATED_BY_MEMBER_ID,
-      variables: { memberId: memberId.toNumber() },
-    })
+  // Query multiple entities
+  private async multipleEntitiesQuery<
+    QueryT extends { [k: string]: unknown[] },
+    VariablesT extends Record<string, unknown>
+  >(query: DocumentNode, variables: VariablesT, resultKey: keyof QueryT): Promise<QueryT[keyof QueryT]> {
+    this.debugQuery(query, variables)
+    return (await this.queryNodeProvider.query<QueryT, VariablesT>({ query, variables })).data[resultKey]
   }
 
-  public async getMemberInvitedEvents(
-    memberId: MemberId
-  ): Promise<ApolloQueryResult<Pick<Query, 'memberInvitedEvents'>>> {
-    const MEMBER_INVITED_BY_MEMBER_ID = gql`
-      query($memberId: ID!) {
-        memberInvitedEvents(where: { newMemberId_eq: $memberId }) {
-          ${EVENT_GENERIC_FIELDS}
-          invitingMember {
-            id
-          }
-          newMember {
-            id
-          }
-          rootAccount
-          controllerAccount
-          handle
-          metadata {
-            name
-            about
-          }
-        }
-      }
-    `
-
-    this.queryDebug(`Executing getMemberInvitedEvents(${memberId.toString()})`)
+  public getQueryNodeEventId(blockNumber: number, indexInBlock: number): string {
+    return `${blockNumber}-${indexInBlock}`
+  }
 
-    return this.queryNodeProvider.query({
-      query: MEMBER_INVITED_BY_MEMBER_ID,
-      variables: { memberId: memberId.toNumber() },
-    })
+  public async getMemberById(id: MemberId): Promise<MembershipFieldsFragment | null> {
+    return this.uniqueEntityQuery<GetMemberByIdQuery, GetMemberByIdQueryVariables>(
+      GetMemberById,
+      { id: id.toString() },
+      'membershipByUniqueInput'
+    )
   }
 
-  public async getInvitesTransferredEvents(
-    fromMemberId: MemberId
-  ): Promise<ApolloQueryResult<Pick<Query, 'invitesTransferredEvents'>>> {
-    const INVITES_TRANSFERRED_BY_MEMBER_ID = gql`
-      query($from: ID!) {
-        invitesTransferredEvents(where: { sourceMemberId_eq: $from }) {
-          ${EVENT_GENERIC_FIELDS}
-          sourceMember {
-            id
-          }
-          targetMember {
-            id
-          }
-          numberOfInvites
-        }
-      }
-    `
+  public async getMembershipBoughtEvent(memberId: MemberId): Promise<MembershipBoughtEventFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetMembershipBoughtEventsByMemberIdQuery,
+      GetMembershipBoughtEventsByMemberIdQueryVariables
+    >(GetMembershipBoughtEventsByMemberId, { memberId: memberId.toString() }, 'membershipBoughtEvents')
+  }
 
-    this.queryDebug(`Executing getInvitesTransferredEvents(${fromMemberId.toString()})`)
+  public async getMemberProfileUpdatedEvents(memberId: MemberId): Promise<MemberProfileUpdatedEventFieldsFragment[]> {
+    return this.multipleEntitiesQuery<
+      GetMemberProfileUpdatedEventsByMemberIdQuery,
+      GetMemberProfileUpdatedEventsByMemberIdQueryVariables
+    >(GetMemberProfileUpdatedEventsByMemberId, { memberId: memberId.toString() }, 'memberProfileUpdatedEvents')
+  }
 
-    return this.queryNodeProvider.query({
-      query: INVITES_TRANSFERRED_BY_MEMBER_ID,
-      variables: { from: fromMemberId.toNumber() },
-    })
+  public async getMemberAccountsUpdatedEvents(memberId: MemberId): Promise<MemberAccountsUpdatedEventFieldsFragment[]> {
+    return this.multipleEntitiesQuery<
+      GetMemberAccountsUpdatedEventsByMemberIdQuery,
+      GetMemberAccountsUpdatedEventsByMemberIdQueryVariables
+    >(GetMemberAccountsUpdatedEventsByMemberId, { memberId: memberId.toString() }, 'memberAccountsUpdatedEvents')
   }
 
-  public async getStakingAccountAddedEvents(
-    memberId: MemberId
-  ): Promise<ApolloQueryResult<Pick<Query, 'stakingAccountAddedEvents'>>> {
-    const STAKING_ACCOUNT_ADDED_BY_MEMBER_ID = gql`
-      query($memberId: ID!) {
-        stakingAccountAddedEvents(where: { memberId_eq: $memberId }) {
-          ${EVENT_GENERIC_FIELDS}
-          member {
-            id
-          }
-          account
-        }
-      }
-    `
+  public async getMemberInvitedEvent(memberId: MemberId): Promise<MemberInvitedEventFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetMemberInvitedEventsByNewMemberIdQuery,
+      GetMemberInvitedEventsByNewMemberIdQueryVariables
+    >(GetMemberInvitedEventsByNewMemberId, { newMemberId: memberId.toString() }, 'memberInvitedEvents')
+  }
 
-    this.queryDebug(`Executing getStakingAccountAddedEvents(${memberId.toString()})`)
+  // TODO: Use event id
+  public async getInvitesTransferredEvent(
+    sourceMemberId: MemberId
+  ): Promise<InvitesTransferredEventFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetInvitesTransferredEventsBySourceMemberIdQuery,
+      GetInvitesTransferredEventsBySourceMemberIdQueryVariables
+    >(
+      GetInvitesTransferredEventsBySourceMemberId,
+      { sourceMemberId: sourceMemberId.toString() },
+      'invitesTransferredEvents'
+    )
+  }
 
-    return this.queryNodeProvider.query({
-      query: STAKING_ACCOUNT_ADDED_BY_MEMBER_ID,
-      variables: { memberId: memberId.toNumber() },
-    })
+  public async getStakingAccountAddedEvents(memberId: MemberId): Promise<StakingAccountAddedEventFieldsFragment[]> {
+    return this.multipleEntitiesQuery<
+      GetStakingAccountAddedEventsByMemberIdQuery,
+      GetStakingAccountAddedEventsByMemberIdQueryVariables
+    >(GetStakingAccountAddedEventsByMemberId, { memberId: memberId.toString() }, 'stakingAccountAddedEvents')
   }
 
   public async getStakingAccountConfirmedEvents(
     memberId: MemberId
-  ): Promise<ApolloQueryResult<Pick<Query, 'stakingAccountConfirmedEvents'>>> {
-    const STAKING_ACCOUNT_CONFIRMED_BY_MEMBER_ID = gql`
-      query($memberId: ID!) {
-        stakingAccountConfirmedEvents(where: { memberId_eq: $memberId }) {
-          ${EVENT_GENERIC_FIELDS}
-          member {
-            id
-          }
-          account
-        }
-      }
-    `
-
-    this.queryDebug(`Executing getStakingAccountConfirmedEvents(${memberId.toString()})`)
-
-    return this.queryNodeProvider.query({
-      query: STAKING_ACCOUNT_CONFIRMED_BY_MEMBER_ID,
-      variables: { memberId: memberId.toNumber() },
-    })
+  ): Promise<StakingAccountConfirmedEventFieldsFragment[]> {
+    return this.multipleEntitiesQuery<
+      GetStakingAccountConfirmedEventsByMemberIdQuery,
+      GetStakingAccountConfirmedEventsByMemberIdQueryVariables
+    >(GetStakingAccountConfirmedEventsByMemberId, { memberId: memberId.toString() }, 'stakingAccountConfirmedEvents')
   }
 
-  public async getStakingAccountRemovedEvents(
-    memberId: MemberId
-  ): Promise<ApolloQueryResult<Pick<Query, 'stakingAccountRemovedEvents'>>> {
-    const STAKING_ACCOUNT_REMOVED_BY_MEMBER_ID = gql`
-      query($memberId: ID!) {
-        stakingAccountRemovedEvents(where: { memberId_eq: $memberId }) {
-          ${EVENT_GENERIC_FIELDS}
-          member {
-            id
-          }
-          account
-        }
-      }
-    `
-
-    this.queryDebug(`Executing getStakingAccountRemovedEvents(${memberId.toString()})`)
-
-    return this.queryNodeProvider.query({
-      query: STAKING_ACCOUNT_REMOVED_BY_MEMBER_ID,
-      variables: { memberId: memberId.toNumber() },
-    })
+  public async getStakingAccountRemovedEvents(memberId: MemberId): Promise<StakingAccountRemovedEventFieldsFragment[]> {
+    return this.multipleEntitiesQuery<
+      GetStakingAccountRemovedEventsByMemberIdQuery,
+      GetStakingAccountRemovedEventsByMemberIdQueryVariables
+    >(GetStakingAccountRemovedEventsByMemberId, { memberId: memberId.toString() }, 'stakingAccountRemovedEvents')
   }
 
   // FIXME: Cross-filtering is not enabled yet, so we have to use timestamp workaround
-  public async getMembershipSystemSnapshot(
-    timestamp: number,
-    matchType: 'eq' | 'lt' | 'lte' | 'gt' | 'gte' = 'eq'
-  ): Promise<MembershipSystemSnapshot | undefined> {
-    const MEMBERSHIP_SYSTEM_SNAPSHOT_QUERY = gql`
-      query($time: DateTime!) {
-        membershipSystemSnapshots(where: { snapshotTime_${matchType}: $time }, orderBy: snapshotTime_DESC, limit: 1) {
-          snapshotBlock {
-            timestamp
-            network
-            number
-          }
-          snapshotTime
-          referralCut
-          invitedInitialBalance
-          defaultInviteCount
-          membershipPrice
-        }
-      }
-    `
-
-    this.queryDebug(`Executing getMembershipSystemSnapshot(${matchType} ${timestamp})`)
+  public async getMembershipSystemSnapshotAt(
+    timestamp: number
+  ): Promise<MembershipSystemSnapshotFieldsFragment | null> {
+    return this.firstEntityQuery<GetMembershipSystemSnapshotAtQuery, GetMembershipSystemSnapshotAtQueryVariables>(
+      GetMembershipSystemSnapshotAt,
+      { time: new Date(timestamp) },
+      'membershipSystemSnapshots'
+    )
+  }
 
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'membershipSystemSnapshots'>>({
-        query: MEMBERSHIP_SYSTEM_SNAPSHOT_QUERY,
-        variables: { time: new Date(timestamp) },
-      })
-    ).data.membershipSystemSnapshots[0]
+  public async getMembershipSystemSnapshotBefore(
+    timestamp: number
+  ): Promise<MembershipSystemSnapshotFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetMembershipSystemSnapshotBeforeQuery,
+      GetMembershipSystemSnapshotBeforeQueryVariables
+    >(GetMembershipSystemSnapshotBefore, { time: new Date(timestamp) }, 'membershipSystemSnapshots')
   }
 
   public async getReferralCutUpdatedEvent(
     blockNumber: number,
     indexInBlock: number
-  ): Promise<ReferralCutUpdatedEvent | undefined> {
-    const REFERRAL_CUT_UPDATED_BY_ID = gql`
-      query($eventId: ID!) {
-        referralCutUpdatedEvents(where: { eventId_eq: $eventId }) {
-          ${EVENT_GENERIC_FIELDS}
-          newValue
-        }
-      }
-    `
-
-    const eventId = `${blockNumber}-${indexInBlock}`
-    this.queryDebug(`Executing getReferralCutUpdatedEvent(${eventId})`)
-
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'referralCutUpdatedEvents'>>({
-        query: REFERRAL_CUT_UPDATED_BY_ID,
-        variables: { eventId },
-      })
-    ).data.referralCutUpdatedEvents[0]
+  ): Promise<ReferralCutUpdatedEventFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetReferralCutUpdatedEventsByEventIdQuery,
+      GetReferralCutUpdatedEventsByEventIdQueryVariables
+    >(
+      GetReferralCutUpdatedEventsByEventId,
+      { eventId: this.getQueryNodeEventId(blockNumber, indexInBlock) },
+      'referralCutUpdatedEvents'
+    )
   }
 
   public async getMembershipPriceUpdatedEvent(
     blockNumber: number,
     indexInBlock: number
-  ): Promise<MembershipPriceUpdatedEvent | undefined> {
-    const MEMBERSHIP_PRICE_UPDATED_BY_ID = gql`
-      query($eventId: ID!) {
-        membershipPriceUpdatedEvents(where: { eventId_eq: $eventId }) {
-          ${EVENT_GENERIC_FIELDS}
-          newPrice
-        }
-      }
-    `
-
-    const eventId = `${blockNumber}-${indexInBlock}`
-    this.queryDebug(`Executing getMembershipPriceUpdatedEvent(${eventId})`)
-
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'membershipPriceUpdatedEvents'>>({
-        query: MEMBERSHIP_PRICE_UPDATED_BY_ID,
-        variables: { eventId },
-      })
-    ).data.membershipPriceUpdatedEvents[0]
+  ): Promise<MembershipPriceUpdatedEventFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetMembershipPriceUpdatedEventsByEventIdQuery,
+      GetMembershipPriceUpdatedEventsByEventIdQueryVariables
+    >(
+      GetMembershipPriceUpdatedEventsByEventId,
+      { eventId: this.getQueryNodeEventId(blockNumber, indexInBlock) },
+      'membershipPriceUpdatedEvents'
+    )
   }
 
   public async getInitialInvitationBalanceUpdatedEvent(
     blockNumber: number,
     indexInBlock: number
-  ): Promise<InitialInvitationBalanceUpdatedEvent | undefined> {
-    const INITIAL_INVITATION_BALANCE_UPDATED_BY_ID = gql`
-      query($eventId: ID!) {
-        initialInvitationBalanceUpdatedEvents(where: { eventId_eq: $eventId }) {
-          ${EVENT_GENERIC_FIELDS}
-          newInitialBalance
-        }
-      }
-    `
-
-    const eventId = `${blockNumber}-${indexInBlock}`
-    this.queryDebug(`Executing getInitialInvitationBalanceUpdatedEvent(${eventId})`)
-
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'initialInvitationBalanceUpdatedEvents'>>({
-        query: INITIAL_INVITATION_BALANCE_UPDATED_BY_ID,
-        variables: { eventId },
-      })
-    ).data.initialInvitationBalanceUpdatedEvents[0]
+  ): Promise<InitialInvitationBalanceUpdatedEventFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetInitialInvitationBalanceUpdatedEventsByEventIdQuery,
+      GetInitialInvitationBalanceUpdatedEventsByEventIdQueryVariables
+    >(
+      GetInitialInvitationBalanceUpdatedEventsByEventId,
+      { eventId: this.getQueryNodeEventId(blockNumber, indexInBlock) },
+      'initialInvitationBalanceUpdatedEvents'
+    )
   }
 
   public async getInitialInvitationCountUpdatedEvent(
     blockNumber: number,
     indexInBlock: number
-  ): Promise<InitialInvitationCountUpdatedEvent | undefined> {
-    const INITIAL_INVITATION_COUNT_UPDATED_BY_ID = gql`
-      query($eventId: ID!) {
-        initialInvitationCountUpdatedEvents(where: { eventId_eq: $eventId }) {
-          ${EVENT_GENERIC_FIELDS}
-          newInitialInvitationCount
-        }
-      }
-    `
-
-    const eventId = `${blockNumber}-${indexInBlock}`
-    this.queryDebug(`Executing getInitialInvitationCountUpdatedEvent(${eventId})`)
-
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'initialInvitationCountUpdatedEvents'>>({
-        query: INITIAL_INVITATION_COUNT_UPDATED_BY_ID,
-        variables: { eventId },
-      })
-    ).data.initialInvitationCountUpdatedEvents[0]
+  ): Promise<InitialInvitationCountUpdatedEventFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetInitialInvitationCountUpdatedEventsByEventIdQuery,
+      GetInitialInvitationCountUpdatedEventsByEventIdQueryVariables
+    >(
+      GetInitialInvitationCountUpdatedEventsByEventId,
+      { eventId: this.getQueryNodeEventId(blockNumber, indexInBlock) },
+      'initialInvitationCountUpdatedEvents'
+    )
   }
 
-  public async getOpeningById(
-    id: OpeningId,
-    group: WorkingGroupModuleName
-  ): Promise<ApolloQueryResult<Pick<Query, 'workingGroupOpeningByUniqueInput'>>> {
-    const OPENING_BY_ID = gql`
-      query($openingId: ID!) {
-        workingGroupOpeningByUniqueInput(where: { id: $openingId }) {
-          id
-          runtimeId
-          group {
-            name
-            leader {
-              runtimeId
-            }
-          }
-          applications {
-            id
-            runtimeId
-            status {
-              __typename
-              ... on ApplicationStatusCancelled {
-                openingCancelledEventId
-              }
-              ... on ApplicationStatusWithdrawn {
-                applicationWithdrawnEventId
-              }
-              ... on ApplicationStatusAccepted {
-                openingFilledEventId
-              }
-              ... on ApplicationStatusRejected {
-                openingFilledEventId
-              }
-            }
-          }
-          type
-          status {
-            __typename
-            ... on OpeningStatusFilled {
-              openingFilledEventId
-            }
-            ... on OpeningStatusCancelled {
-              openingCancelledEventId
-            }
-          }
-          metadata {
-            shortDescription
-            description
-            hiringLimit
-            expectedEnding
-            applicationDetails
-            applicationFormQuestions {
-              question
-              type
-              index
-            }
-          }
-          stakeAmount
-          unstakingPeriod
-          rewardPerBlock
-          createdAtBlock {
-            number
-            timestamp
-            network
-          }
-          createdAt
-        }
-      }
-    `
-
-    const openingId = `${group}-${id.toString()}`
-    this.queryDebug(`Executing getOpeningById(${openingId})`)
-
-    return this.queryNodeProvider.query<Pick<Query, 'workingGroupOpeningByUniqueInput'>>({
-      query: OPENING_BY_ID,
-      variables: { openingId },
-    })
+  public async getOpeningById(id: OpeningId, group: WorkingGroupModuleName): Promise<OpeningFieldsFragment | null> {
+    return this.uniqueEntityQuery<GetOpeningByIdQuery, GetOpeningByIdQueryVariables>(
+      GetOpeningById,
+      { openingId: `${group}-${id.toString()}` },
+      'workingGroupOpeningByUniqueInput'
+    )
   }
 
   public async getApplicationById(
     id: ApplicationId,
     group: WorkingGroupModuleName
-  ): Promise<ApolloQueryResult<Pick<Query, 'workingGroupApplicationByUniqueInput'>>> {
-    const APPLICATION_BY_ID = gql`
-      query($applicationId: ID!) {
-        workingGroupApplicationByUniqueInput(where: { id: $applicationId }) {
-          id
-          runtimeId
-          createdAtBlock {
-            number
-            timestamp
-            network
-          }
-          createdAt
-          opening {
-            id
-            runtimeId
-          }
-          applicant {
-            id
-          }
-          roleAccount
-          rewardAccount
-          stakingAccount
-          status {
-            __typename
-            ... on ApplicationStatusCancelled {
-              openingCancelledEventId
-            }
-            ... on ApplicationStatusWithdrawn {
-              applicationWithdrawnEventId
-            }
-            ... on ApplicationStatusAccepted {
-              openingFilledEventId
-            }
-            ... on ApplicationStatusRejected {
-              openingFilledEventId
-            }
-          }
-          answers {
-            question {
-              question
-            }
-            answer
-          }
-          stake
-        }
-      }
-    `
-
-    const applicationId = `${group}-${id.toString()}`
-    this.queryDebug(`Executing getApplicationById(${applicationId})`)
-
-    return this.queryNodeProvider.query<Pick<Query, 'workingGroupApplicationByUniqueInput'>>({
-      query: APPLICATION_BY_ID,
-      variables: { applicationId },
-    })
+  ): Promise<ApplicationFieldsFragment | null> {
+    return this.uniqueEntityQuery<GetApplicationByIdQuery, GetApplicationByIdQueryVariables>(
+      GetApplicationById,
+      { applicationId: `${group}-${id.toString()}` },
+      'workingGroupApplicationByUniqueInput'
+    )
   }
 
   public async getAppliedOnOpeningEvent(
     blockNumber: number,
     indexInBlock: number
-  ): Promise<AppliedOnOpeningEvent | undefined> {
-    const APPLIED_ON_OPENING_BY_ID = gql`
-      query($eventId: ID!) {
-        appliedOnOpeningEvents(where: { eventId_eq: $eventId }) {
-          ${EVENT_GENERIC_FIELDS}
-          group {
-            name
-          }
-          opening {
-            id
-            runtimeId
-          }
-          application {
-            id
-            runtimeId
-          }
-        }
-      }
-    `
-
-    const eventId = `${blockNumber}-${indexInBlock}`
-    this.queryDebug(`Executing getAppliedOnOpeningEvent(${eventId})`)
-
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'appliedOnOpeningEvents'>>({
-        query: APPLIED_ON_OPENING_BY_ID,
-        variables: { eventId },
-      })
-    ).data.appliedOnOpeningEvents[0]
-  }
-
-  public async getOpeningAddedEvent(blockNumber: number, indexInBlock: number): Promise<OpeningAddedEvent | undefined> {
-    const OPENING_ADDED_BY_ID = gql`
-      query($eventId: ID!) {
-        openingAddedEvents(where: { eventId_eq: $eventId }) {
-          ${EVENT_GENERIC_FIELDS}
-          group {
-            name
-          }
-          opening {
-            id
-            runtimeId
-          }
-        }
-      }
-    `
-
-    const eventId = `${blockNumber}-${indexInBlock}`
-    this.queryDebug(`Executing getOpeningAddedEvent(${eventId})`)
+  ): Promise<AppliedOnOpeningEventFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetAppliedOnOpeningEventsByEventIdQuery,
+      GetAppliedOnOpeningEventsByEventIdQueryVariables
+    >(
+      GetAppliedOnOpeningEventsByEventId,
+      { eventId: this.getQueryNodeEventId(blockNumber, indexInBlock) },
+      'appliedOnOpeningEvents'
+    )
+  }
 
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'openingAddedEvents'>>({
-        query: OPENING_ADDED_BY_ID,
-        variables: { eventId },
-      })
-    ).data.openingAddedEvents[0]
+  public async getOpeningAddedEvent(
+    blockNumber: number,
+    indexInBlock: number
+  ): Promise<OpeningAddedEventFieldsFragment | null> {
+    return this.firstEntityQuery<GetOpeningAddedEventsByEventIdQuery, GetOpeningAddedEventsByEventIdQueryVariables>(
+      GetOpeningAddedEventsByEventId,
+      { eventId: this.getQueryNodeEventId(blockNumber, indexInBlock) },
+      'openingAddedEvents'
+    )
   }
 
   public async getOpeningFilledEvent(
     blockNumber: number,
     indexInBlock: number
-  ): Promise<OpeningFilledEvent | undefined> {
-    const OPENING_FILLED_BY_ID = gql`
-      query($eventId: ID!) {
-        openingFilledEvents(where: { eventId_eq: $eventId }) {
-          ${EVENT_GENERIC_FIELDS}
-          group {
-            name
-          }
-          opening {
-            id
-            runtimeId
-          }
-          workersHired {
-            id
-            runtimeId
-            group {
-              name
-            }
-            membership {
-              id
-            }
-            roleAccount
-            rewardAccount
-            stakeAccount
-            status {
-              __typename
-            }
-            isLead
-            stake
-            payouts {
-              id
-            }
-            hiredAtBlock {
-              number
-              timestamp
-              network
-            }
-            hiredAtTime
-            application {
-              id
-              runtimeId
-            }
-            storage
-          }
-        }
-      }
-    `
-
-    const eventId = `${blockNumber}-${indexInBlock}`
-    this.queryDebug(`Executing getOpeningFilledEvent(${eventId})`)
-
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'openingFilledEvents'>>({
-        query: OPENING_FILLED_BY_ID,
-        variables: { eventId },
-      })
-    ).data.openingFilledEvents[0]
+  ): Promise<OpeningFilledEventFieldsFragment | null> {
+    return this.firstEntityQuery<GetOpeningFilledEventsByEventIdQuery, GetOpeningFilledEventsByEventIdQueryVariables>(
+      GetOpeningFilledEventsByEventId,
+      { eventId: this.getQueryNodeEventId(blockNumber, indexInBlock) },
+      'openingFilledEvents'
+    )
   }
 
   public async getApplicationWithdrawnEvent(
     blockNumber: number,
     indexInBlock: number
-  ): Promise<ApplicationWithdrawnEvent | undefined> {
-    const APPLICATION_WITHDRAWN_BY_ID = gql`
-      query($eventId: ID!) {
-        applicationWithdrawnEvents(where: { eventId_eq: $eventId }) {
-          ${EVENT_GENERIC_FIELDS}
-          group {
-            name
-          }
-          application {
-            id
-            runtimeId
-          }
-        }
-      }
-    `
-
-    const eventId = `${blockNumber}-${indexInBlock}`
-    this.queryDebug(`Executing getApplicationWithdrawnEvent(${eventId})`)
-
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'applicationWithdrawnEvents'>>({
-        query: APPLICATION_WITHDRAWN_BY_ID,
-        variables: { eventId },
-      })
-    ).data.applicationWithdrawnEvents[0]
+  ): Promise<ApplicationWithdrawnEventFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetApplicationWithdrawnEventsByEventIdQuery,
+      GetApplicationWithdrawnEventsByEventIdQueryVariables
+    >(
+      GetApplicationWithdrawnEventsByEventId,
+      { eventId: this.getQueryNodeEventId(blockNumber, indexInBlock) },
+      'applicationWithdrawnEvents'
+    )
   }
 
   public async getOpeningCancelledEvent(
     blockNumber: number,
     indexInBlock: number
-  ): Promise<OpeningCanceledEvent | undefined> {
-    const OPENING_CANCELLED_BY_ID = gql`
-      query($eventId: ID!) {
-        openingCanceledEvents(where: { eventId_eq: $eventId }) {
-          ${EVENT_GENERIC_FIELDS}
-          group {
-            name
-          }
-          opening {
-            id
-            runtimeId
-          }
-        }
-      }
-    `
-
-    const eventId = `${blockNumber}-${indexInBlock}`
-    this.queryDebug(`Executing getOpeningCancelledEvent(${eventId})`)
-
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'openingCanceledEvents'>>({
-        query: OPENING_CANCELLED_BY_ID,
-        variables: { eventId },
-      })
-    ).data.openingCanceledEvents[0]
+  ): Promise<OpeningCanceledEventFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetOpeningCancelledEventsByEventIdQuery,
+      GetOpeningCancelledEventsByEventIdQueryVariables
+    >(
+      GetOpeningCancelledEventsByEventId,
+      { eventId: this.getQueryNodeEventId(blockNumber, indexInBlock) },
+      'openingCanceledEvents'
+    )
   }
 
   public async getStatusTextChangedEvent(
     blockNumber: number,
     indexInBlock: number
-  ): Promise<StatusTextChangedEvent | undefined> {
-    const STATUS_TEXT_CHANGED_BY_ID = gql`
-      query($eventId: ID!) {
-        statusTextChangedEvents(where: { eventId_eq: $eventId }) {
-          ${EVENT_GENERIC_FIELDS}
-          group {
-            name
-          }
-          metadata
-          result {
-            ... on UpcomingOpeningAdded {
-              upcomingOpeningId
-            }
-            ... on UpcomingOpeningRemoved {
-              upcomingOpeningId
-            }
-            ... on WorkingGroupMetadataSet {
-              metadataId
-            }
-            ... on InvalidActionMetadata {
-              reason
-            }
-          }
-        }
-      }
-    `
-
-    const eventId = `${blockNumber}-${indexInBlock}`
-    this.queryDebug(`Executing getStatusTextChangedEvent(${eventId})`)
-
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'statusTextChangedEvents'>>({
-        query: STATUS_TEXT_CHANGED_BY_ID,
-        variables: { eventId },
-      })
-    ).data.statusTextChangedEvents[0]
-  }
-
-  public async getUpcomingOpeningByCreatedInEventId(eventId: string): Promise<UpcomingWorkingGroupOpening | undefined> {
-    const UPCOMING_OPENING_BY_ID = gql`
-      query($eventId: ID!) {
-        upcomingWorkingGroupOpenings(where: { createdInEventId_eq: $eventId }) {
-          id
-          group {
-            name
-          }
-          metadata {
-            shortDescription
-            description
-            hiringLimit
-            expectedEnding
-            applicationDetails
-            applicationFormQuestions {
-              question
-              type
-              index
-            }
-          }
-          expectedStart
-          stakeAmount
-          rewardPerBlock
-          createdAtBlock {
-            number
-            timestamp
-            network
-          }
-          createdAt
-        }
-      }
-    `
-
-    this.queryDebug(`Executing getUpcomingOpeningByCreatedInEventId(${eventId})`)
-
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'upcomingWorkingGroupOpenings'>>({
-        query: UPCOMING_OPENING_BY_ID,
-        variables: { eventId },
-      })
-    ).data.upcomingWorkingGroupOpenings[0]
-  }
-
-  public async getWorkingGroup(name: WorkingGroupModuleName): Promise<WorkingGroup | undefined> {
-    const GROUP_BY_NAME = gql`
-      query($name: String!) {
-        workingGroupByUniqueInput(where: { name: $name }) {
-          name
-          metadata {
-            id
-            status
-            statusMessage
-            about
-            description
-            setAtBlock {
-              number
-            }
-          }
-          leader {
-            id
-          }
-          budget
-        }
-      }
-    `
+  ): Promise<StatusTextChangedEventFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetStatusTextChangedEventsByEventIdQuery,
+      GetStatusTextChangedEventsByEventIdQueryVariables
+    >(
+      GetStatusTextChangedEventsByEventId,
+      { eventId: this.getQueryNodeEventId(blockNumber, indexInBlock) },
+      'statusTextChangedEvents'
+    )
+  }
 
-    this.queryDebug(`Executing getWorkingGroup(${name})`)
+  public async getUpcomingOpeningByCreatedInEventId(eventId: string): Promise<UpcomingOpeningFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetUpcomingOpeningByCreatedInEventIdQuery,
+      GetUpcomingOpeningByCreatedInEventIdQueryVariables
+    >(GetUpcomingOpeningByCreatedInEventId, { createdInEventId: eventId }, 'upcomingWorkingGroupOpenings')
+  }
 
-    return (
-      (
-        await this.queryNodeProvider.query<Pick<Query, 'workingGroupByUniqueInput'>>({
-          query: GROUP_BY_NAME,
-          variables: { name },
-        })
-      ).data.workingGroupByUniqueInput || undefined
+  public async getWorkingGroup(name: WorkingGroupModuleName): Promise<WorkingGroupFieldsFragment | null> {
+    return this.uniqueEntityQuery<GetWorkingGroupByNameQuery, GetWorkingGroupByNameQueryVariables>(
+      GetWorkingGroupByName,
+      { name },
+      'workingGroupByUniqueInput'
     )
   }
 
   // FIXME: Use blockheights once possible
-  public async getGroupMetaSnapshot(
-    timestamp: number,
-    matchType: 'eq' | 'lt' | 'lte' | 'gt' | 'gte' = 'eq'
-  ): Promise<WorkingGroupMetadata | undefined> {
-    const GROUP_META_SNAPSHOT_BY_TIMESTAMP = gql`
-      query($timestamp: DateTime!) {
-        workingGroupMetadata(where: { createdAt_${matchType}: $timestamp, createdAt_lte: $toTime }, orderBy: createdAt_DESC, limit: 1) {
-          id
-          status
-          statusMessage
-          about
-          description
-          setAtBlock {
-            number
-          }
-        }
-      }
-    `
-
-    this.queryDebug(`Executing getGroupMetaSnapshot(${timestamp}, ${matchType})`)
+  public async getGroupMetaSnapshotAt(
+    groupId: string,
+    timestamp: number
+  ): Promise<WorkingGroupMetadataFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetWorkingGroupMetadataSnapshotAtQuery,
+      GetWorkingGroupMetadataSnapshotAtQueryVariables
+    >(GetWorkingGroupMetadataSnapshotAt, { groupId, timestamp: new Date(timestamp) }, 'workingGroupMetadata')
+  }
 
-    return (
-      await this.queryNodeProvider.query<Pick<Query, 'workingGroupMetadata'>>({
-        query: GROUP_META_SNAPSHOT_BY_TIMESTAMP,
-        variables: { timestamp: new Date(timestamp) },
-      })
-    ).data.workingGroupMetadata[0]
+  public async getGroupMetaSnapshotBefore(
+    groupId: string,
+    timestamp: number
+  ): Promise<WorkingGroupMetadataFieldsFragment | null> {
+    return this.firstEntityQuery<
+      GetWorkingGroupMetadataSnapshotBeforeQuery,
+      GetWorkingGroupMetadataSnapshotBeforeQueryVariables
+    >(GetWorkingGroupMetadataSnapshotBefore, { groupId, timestamp: new Date(timestamp) }, 'workingGroupMetadata')
   }
 }

+ 104 - 125
tests/integration-tests/src/fixtures/membershipModule.ts

@@ -6,20 +6,7 @@ import { MemberId } from '@joystream/types/common'
 import Debugger from 'debug'
 import { QueryNodeApi } from '../QueryNodeApi'
 import { BuyMembershipParameters, Membership } from '@joystream/types/members'
-import {
-  Membership as QueryNodeMembership,
-  MembershipEntryMethod,
-  MembershipBoughtEvent,
-  EventType,
-  MemberProfileUpdatedEvent,
-  MemberAccountsUpdatedEvent,
-  MemberInvitedEvent,
-  InvitesTransferredEvent,
-  StakingAccountAddedEvent,
-  StakingAccountConfirmedEvent,
-  StakingAccountRemovedEvent,
-  MembershipSystemSnapshot,
-} from '../QueryNodeApiSchema.generated'
+import { EventType, MembershipEntryMethod } from '../graphql/generated/schema'
 import { blake2AsHex } from '@polkadot/util-crypto'
 import { SubmittableExtrinsic } from '@polkadot/api/types'
 import { CreateInterface, createType } from '@joystream/types'
@@ -32,6 +19,18 @@ import {
   MembershipBoughtEventDetails,
   MembershipEventName,
 } from '../types'
+import {
+  InvitesTransferredEventFieldsFragment,
+  MemberAccountsUpdatedEventFieldsFragment,
+  MemberInvitedEventFieldsFragment,
+  MemberProfileUpdatedEventFieldsFragment,
+  MembershipBoughtEventFieldsFragment,
+  MembershipFieldsFragment,
+  MembershipSystemSnapshotFieldsFragment,
+  StakingAccountAddedEventFieldsFragment,
+  StakingAccountConfirmedEventFieldsFragment,
+  StakingAccountRemovedEventFieldsFragment,
+} from '../graphql/generated/queries'
 
 // FIXME: Retrieve from runtime when possible!
 const MINIMUM_STAKING_ACCOUNT_BALANCE = 200
@@ -95,8 +94,10 @@ export class BuyMembershipHappyCaseFixture extends MembershipFixture implements
     return this.memberIds.slice()
   }
 
-  private assertMemberMatchQueriedResult(member: Membership, qMember?: QueryNodeMembership | null) {
-    assert.isOk(qMember, 'Membership query result is empty')
+  private assertMemberMatchQueriedResult(member: Membership, qMember: MembershipFieldsFragment | null) {
+    if (!qMember) {
+      throw new Error('Query node: Membership not found!')
+    }
     const {
       handle,
       rootAccount,
@@ -104,7 +105,7 @@ export class BuyMembershipHappyCaseFixture extends MembershipFixture implements
       metadata: { name, about },
       isVerified,
       entry,
-    } = qMember as QueryNodeMembership
+    } = qMember
     const txParams = this.generateParamsFromAccountId(rootAccount)
     const metadata = MembershipMetadata.deserializeBinary(txParams.metadata.toU8a(true))
     assert.equal(blake2AsHex(handle), member.handle_hash.toString())
@@ -122,10 +123,11 @@ export class BuyMembershipHappyCaseFixture extends MembershipFixture implements
     eventDetails: MembershipBoughtEventDetails,
     account: string,
     txHash: string,
-    qEvents: MembershipBoughtEvent[]
+    qEvent: MembershipBoughtEventFieldsFragment | null
   ) {
-    assert.equal(qEvents.length, 1, `Invalid number of MembershipBoughtEvents recieved`)
-    const [qEvent] = qEvents
+    if (!qEvent) {
+      throw new Error('Query node: MembershipBought event not found!')
+    }
     const txParams = this.generateParamsFromAccountId(account)
     const metadata = MembershipMetadata.deserializeBinary(txParams.metadata.toU8a(true))
     assert.equal(qEvent.event.inBlock.number, eventDetails.blockNumber)
@@ -178,16 +180,11 @@ export class BuyMembershipHappyCaseFixture extends MembershipFixture implements
         const memberId = this.memberIds[i]
         await this.query.tryQueryWithTimeout(
           () => this.query.getMemberById(memberId),
-          (r) => this.assertMemberMatchQueriedResult(member, r.data.membershipByUniqueInput)
+          (qMember) => this.assertMemberMatchQueriedResult(member, qMember)
         )
         // Ensure the query node event is valid
-        const res = await this.query.getMembershipBoughtEvents(memberId)
-        this.assertEventMatchQueriedResult(
-          this.events[i],
-          this.accounts[i],
-          this.extrinsics[i].hash.toString(),
-          res.data.membershipBoughtEvents
-        )
+        const qEvent = await this.query.getMembershipBoughtEvent(memberId)
+        this.assertEventMatchQueriedResult(this.events[i], this.accounts[i], this.extrinsics[i].hash.toString(), qEvent)
       })
     )
   }
@@ -252,12 +249,14 @@ export class UpdateProfileHappyCaseFixture extends MembershipFixture {
     this.memberContext = memberContext
   }
 
-  private assertProfileUpdateSuccesful(qMember?: QueryNodeMembership | null) {
-    assert.isOk(qMember, 'Membership query result is empty')
+  private assertProfileUpdateSuccesful(qMember: MembershipFieldsFragment | null) {
+    if (!qMember) {
+      throw new Error('Query node: Membership not found!')
+    }
     const {
       handle,
       metadata: { name, about },
-    } = qMember as QueryNodeMembership
+    } = qMember
     assert.equal(name, this.newName)
     assert.equal(handle, this.newHandle)
     // TODO: avatar
@@ -267,7 +266,7 @@ export class UpdateProfileHappyCaseFixture extends MembershipFixture {
   private assertQueryNodeEventIsValid(
     eventDetails: EventDetails,
     txHash: string,
-    qEvents: MemberProfileUpdatedEvent[]
+    qEvents: MemberProfileUpdatedEventFieldsFragment[]
   ) {
     const qEvent = this.findMatchingQueryNodeEvent(eventDetails, qEvents)
     const {
@@ -305,10 +304,10 @@ export class UpdateProfileHappyCaseFixture extends MembershipFixture {
     await super.runQueryNodeChecks()
     await this.query.tryQueryWithTimeout(
       () => this.query.getMemberById(this.memberContext.memberId),
-      (res) => this.assertProfileUpdateSuccesful(res.data.membershipByUniqueInput)
+      (qMember) => this.assertProfileUpdateSuccesful(qMember)
     )
-    const res = await this.query.getMemberProfileUpdatedEvents(this.memberContext.memberId)
-    this.assertQueryNodeEventIsValid(this.event!, this.tx!.hash.toString(), res.data.memberProfileUpdatedEvents)
+    const qEvents = await this.query.getMemberProfileUpdatedEvents(this.memberContext.memberId)
+    this.assertQueryNodeEventIsValid(this.event!, this.tx!.hash.toString(), qEvents)
   }
 }
 
@@ -336,9 +335,11 @@ export class UpdateAccountsHappyCaseFixture extends MembershipFixture {
     this.newControllerAccount = newControllerAccount
   }
 
-  private assertAccountsUpdateSuccesful(qMember?: QueryNodeMembership | null) {
-    assert.isOk(qMember, 'Membership query result is empty')
-    const { rootAccount, controllerAccount } = qMember as QueryNodeMembership
+  private assertAccountsUpdateSuccesful(qMember: MembershipFieldsFragment | null) {
+    if (!qMember) {
+      throw new Error('Query node: Membership not found!')
+    }
+    const { rootAccount, controllerAccount } = qMember
     assert.equal(rootAccount, this.newRootAccount)
     assert.equal(controllerAccount, this.newControllerAccount)
   }
@@ -346,7 +347,7 @@ export class UpdateAccountsHappyCaseFixture extends MembershipFixture {
   private assertQueryNodeEventIsValid(
     eventDetails: EventDetails,
     txHash: string,
-    qEvents: MemberAccountsUpdatedEvent[]
+    qEvents: MemberAccountsUpdatedEventFieldsFragment[]
   ) {
     const qEvent = this.findMatchingQueryNodeEvent(eventDetails, qEvents)
     const {
@@ -378,10 +379,10 @@ export class UpdateAccountsHappyCaseFixture extends MembershipFixture {
     await super.runQueryNodeChecks()
     await this.query.tryQueryWithTimeout(
       () => this.query.getMemberById(this.memberContext.memberId),
-      (res) => this.assertAccountsUpdateSuccesful(res.data.membershipByUniqueInput)
+      (qMember) => this.assertAccountsUpdateSuccesful(qMember)
     )
-    const res = await this.query.getMemberAccountsUpdatedEvents(this.memberContext.memberId)
-    this.assertQueryNodeEventIsValid(this.event!, this.tx!.hash.toString(), res.data.memberAccountsUpdatedEvents)
+    const qEvents = await this.query.getMemberAccountsUpdatedEvents(this.memberContext.memberId)
+    this.assertQueryNodeEventIsValid(this.event!, this.tx!.hash.toString(), qEvents)
   }
 }
 
@@ -401,8 +402,10 @@ export class InviteMembersHappyCaseFixture extends MembershipFixture {
     this.accounts = accounts
   }
 
-  private assertMemberCorrectlyInvited(account: string, qMember?: QueryNodeMembership | null) {
-    assert.isOk(qMember, 'Membership query result is empty')
+  private assertMemberCorrectlyInvited(account: string, qMember: MembershipFieldsFragment | null) {
+    if (!qMember) {
+      throw new Error('Query node: Membership not found!')
+    }
     const {
       handle,
       rootAccount,
@@ -411,7 +414,7 @@ export class InviteMembersHappyCaseFixture extends MembershipFixture {
       isVerified,
       entry,
       invitedBy,
-    } = qMember as QueryNodeMembership
+    } = qMember
     const txParams = this.generateParamsFromAccountId(account)
     const metadata = MembershipMetadata.deserializeBinary(txParams.metadata.toU8a(true))
     assert.equal(handle, txParams.handle)
@@ -430,11 +433,11 @@ export class InviteMembersHappyCaseFixture extends MembershipFixture {
     eventDetails: MemberInvitedEventDetails,
     account: string,
     txHash: string,
-    qEvents: MemberInvitedEvent[]
+    qEvent: MemberInvitedEventFieldsFragment | null
   ) {
-    assert.isNotEmpty(qEvents)
-    assert.equal(qEvents.length, 1, 'Unexpected number of MemberInvited events returned by query node')
-    const [qEvent] = qEvents
+    if (!qEvent) {
+      throw new Error('Query node: MemberInvitedEvent not found!')
+    }
     const txParams = this.generateParamsFromAccountId(account)
     const metadata = MembershipMetadata.deserializeBinary(txParams.metadata.toU8a(true))
     assert.equal(qEvent.event.inBlock.number, eventDetails.blockNumber)
@@ -478,23 +481,18 @@ export class InviteMembersHappyCaseFixture extends MembershipFixture {
         const memberId = invitedMembersIds[i]
         await this.query.tryQueryWithTimeout(
           () => this.query.getMemberById(memberId),
-          (res) => this.assertMemberCorrectlyInvited(account, res.data.membershipByUniqueInput)
-        )
-        const res = await this.query.getMemberInvitedEvents(memberId)
-        this.aseertQueryNodeEventIsValid(
-          this.events[i],
-          account,
-          this.extrinsics[i].hash.toString(),
-          res.data.memberInvitedEvents
+          (qMember) => this.assertMemberCorrectlyInvited(account, qMember)
         )
+        const qEvent = await this.query.getMemberInvitedEvent(memberId)
+        this.aseertQueryNodeEventIsValid(this.events[i], account, this.extrinsics[i].hash.toString(), qEvent)
       })
     )
 
-    const {
-      data: { membershipByUniqueInput: inviter },
-    } = await this.query.getMemberById(this.inviterContext.memberId)
-    assert.isOk(inviter)
-    const { inviteCount, invitees } = inviter as QueryNodeMembership
+    const qInviter = await this.query.getMemberById(this.inviterContext.memberId)
+    if (!qInviter) {
+      throw new Error('Query node: Inviter member not found!')
+    }
+    const { inviteCount, invitees } = qInviter
     // Assert that inviteCount was correctly updated
     assert.equal(inviteCount, this.initialInvitesCount! - this.accounts.length)
     // Assert that all invited members are part of "invetees" field
@@ -531,8 +529,14 @@ export class TransferInvitesHappyCaseFixture extends MembershipFixture {
     this.invitesToTransfer = invitesToTransfer
   }
 
-  private assertQueryNodeEventIsValid(eventDetails: EventDetails, txHash: string, qEvents: InvitesTransferredEvent[]) {
-    const qEvent = this.findMatchingQueryNodeEvent(eventDetails, qEvents)
+  private assertQueryNodeEventIsValid(
+    eventDetails: EventDetails,
+    txHash: string,
+    qEvent: InvitesTransferredEventFieldsFragment | null
+  ) {
+    if (!qEvent) {
+      throw new Error('Query node: InvitesTransferredEvent not found!')
+    }
     const {
       event: { inExtrinsic, type },
       sourceMember,
@@ -571,29 +575,25 @@ export class TransferInvitesHappyCaseFixture extends MembershipFixture {
     // Check "from" member
     await this.query.tryQueryWithTimeout(
       () => this.query.getMemberById(fromContext.memberId),
-      ({ data: { membershipByUniqueInput: queriedFromMember } }) => {
-        if (!queriedFromMember) {
-          throw new Error('Source member not found')
+      (qSourceMember) => {
+        if (!qSourceMember) {
+          throw new Error('Query node: Source member not found')
         }
-        assert.equal(queriedFromMember.inviteCount, this.fromMemberInitialInvites! - invitesToTransfer)
+        assert.equal(qSourceMember.inviteCount, this.fromMemberInitialInvites! - invitesToTransfer)
       }
     )
 
     // Check "to" member
-    const {
-      data: { membershipByUniqueInput: queriedToMember },
-    } = await this.query.getMemberById(toContext.memberId)
-    if (!queriedToMember) {
-      throw new Error('Target member not found')
+    const qTargetMember = await this.query.getMemberById(toContext.memberId)
+    if (!qTargetMember) {
+      throw new Error('Query node: Target member not found')
     }
-    assert.equal(queriedToMember.inviteCount, this.toMemberInitialInvites! + invitesToTransfer)
+    assert.equal(qTargetMember.inviteCount, this.toMemberInitialInvites! + invitesToTransfer)
 
     // Check event
-    const {
-      data: { invitesTransferredEvents },
-    } = await this.query.getInvitesTransferredEvents(fromContext.memberId)
+    const qEvent = await this.query.getInvitesTransferredEvent(fromContext.memberId)
 
-    this.assertQueryNodeEventIsValid(this.event!, this.tx!.hash.toString(), invitesTransferredEvents)
+    this.assertQueryNodeEventIsValid(this.event!, this.tx!.hash.toString(), qEvent)
   }
 }
 
@@ -618,7 +618,7 @@ export class AddStakingAccountsHappyCaseFixture extends MembershipFixture {
     eventDetails: EventDetails,
     account: string,
     txHash: string,
-    qEvents: StakingAccountAddedEvent[]
+    qEvents: StakingAccountAddedEventFieldsFragment[]
   ) {
     const qEvent = this.findMatchingQueryNodeEvent(eventDetails, qEvents)
     assert.equal(qEvent.event.inExtrinsic, txHash)
@@ -631,7 +631,7 @@ export class AddStakingAccountsHappyCaseFixture extends MembershipFixture {
     eventDetails: EventDetails,
     account: string,
     txHash: string,
-    qEvents: StakingAccountConfirmedEvent[]
+    qEvents: StakingAccountConfirmedEventFieldsFragment[]
   ) {
     const qEvent = this.findMatchingQueryNodeEvent(eventDetails, qEvents)
     assert.equal(qEvent.event.inExtrinsic, txHash)
@@ -669,34 +669,25 @@ export class AddStakingAccountsHappyCaseFixture extends MembershipFixture {
     const { memberContext, accounts, addEvents, confirmEvents, addExtrinsics, confirmExtrinsics } = this
     await this.query.tryQueryWithTimeout(
       () => this.query.getMemberById(memberContext.memberId),
-      ({ data: { membershipByUniqueInput: membership } }) => {
-        if (!membership) {
-          throw new Error('Member not found')
+      (qMember) => {
+        if (!qMember) {
+          throw new Error('Query node: Member not found')
         }
-        assert.isNotEmpty(membership.boundAccounts)
-        assert.includeMembers(membership.boundAccounts, accounts)
+        assert.isNotEmpty(qMember.boundAccounts)
+        assert.includeMembers(qMember.boundAccounts, accounts)
       }
     )
 
     // Check events
-    const {
-      data: { stakingAccountAddedEvents },
-    } = await this.query.getStakingAccountAddedEvents(memberContext.memberId)
-    const {
-      data: { stakingAccountConfirmedEvents },
-    } = await this.query.getStakingAccountConfirmedEvents(memberContext.memberId)
+    const qAddedEvents = await this.query.getStakingAccountAddedEvents(memberContext.memberId)
+    const qConfirmedEvents = await this.query.getStakingAccountConfirmedEvents(memberContext.memberId)
     accounts.forEach(async (account, i) => {
-      this.assertQueryNodeAddAccountEventIsValid(
-        addEvents[i],
-        account,
-        addExtrinsics[i].hash.toString(),
-        stakingAccountAddedEvents
-      )
+      this.assertQueryNodeAddAccountEventIsValid(addEvents[i], account, addExtrinsics[i].hash.toString(), qAddedEvents)
       this.assertQueryNodeConfirmAccountEventIsValid(
         confirmEvents[i],
         account,
         confirmExtrinsics[i].hash.toString(),
-        stakingAccountConfirmedEvents
+        qConfirmedEvents
       )
     })
   }
@@ -721,7 +712,7 @@ export class RemoveStakingAccountsHappyCaseFixture extends MembershipFixture {
     eventDetails: EventDetails,
     account: string,
     txHash: string,
-    qEvents: StakingAccountRemovedEvent[]
+    qEvents: StakingAccountRemovedEventFieldsFragment[]
   ) {
     const qEvent = this.findMatchingQueryNodeEvent(eventDetails, qEvents)
     assert.equal(qEvent.event.inExtrinsic, txHash)
@@ -750,26 +741,19 @@ export class RemoveStakingAccountsHappyCaseFixture extends MembershipFixture {
     // Check member
     await this.query.tryQueryWithTimeout(
       () => this.query.getMemberById(memberContext.memberId),
-      ({ data: { membershipByUniqueInput: membership } }) => {
-        if (!membership) {
-          throw new Error('Membership not found!')
+      (qMember) => {
+        if (!qMember) {
+          throw new Error('Query node: Membership not found!')
         }
-        accounts.forEach((a) => assert.notInclude(membership.boundAccounts, a))
+        accounts.forEach((a) => assert.notInclude(qMember.boundAccounts, a))
       }
     )
 
     // Check events
-    const {
-      data: { stakingAccountRemovedEvents },
-    } = await this.query.getStakingAccountRemovedEvents(memberContext.memberId)
+    const qEvents = await this.query.getStakingAccountRemovedEvents(memberContext.memberId)
     await Promise.all(
       accounts.map(async (account, i) => {
-        this.assertQueryNodeRemoveAccountEventIsValid(
-          events[i],
-          account,
-          extrinsics[i].hash.toString(),
-          stakingAccountRemovedEvents
-        )
+        this.assertQueryNodeRemoveAccountEventIsValid(events[i], account, extrinsics[i].hash.toString(), qEvents)
       })
     )
   }
@@ -806,7 +790,7 @@ export class SudoUpdateMembershipSystem extends MembershipFixture {
     }
   }
 
-  private async assertBeforeSnapshotIsValid(beforeSnapshot: MembershipSystemSnapshot) {
+  private async assertBeforeSnapshotIsValid(beforeSnapshot: MembershipSystemSnapshotFieldsFragment) {
     assert.isNumber(beforeSnapshot.snapshotBlock.number)
     const chainValues = await this.getMembershipSystemValuesAt(beforeSnapshot.snapshotBlock.number)
     assert.equal(beforeSnapshot.referralCut, chainValues.referralCut)
@@ -816,8 +800,8 @@ export class SudoUpdateMembershipSystem extends MembershipFixture {
   }
 
   private assertAfterSnapshotIsValid(
-    beforeSnapshot: MembershipSystemSnapshot,
-    afterSnapshot: MembershipSystemSnapshot
+    beforeSnapshot: MembershipSystemSnapshotFieldsFragment,
+    afterSnapshot: MembershipSystemSnapshotFieldsFragment
   ) {
     const { newValues } = this
     const expectedValue = (field: keyof MembershipSystemValues) => {
@@ -830,7 +814,7 @@ export class SudoUpdateMembershipSystem extends MembershipFixture {
     assert.equal(afterSnapshot.defaultInviteCount, expectedValue('defaultInviteCount'))
   }
 
-  private checkEvent<T extends AnyQueryNodeEvent>(qEvent: T | undefined, txHash: string): T {
+  private checkEvent<T extends AnyQueryNodeEvent>(qEvent: T | null, txHash: string): T {
     if (!qEvent) {
       throw new Error('Missing query-node event')
     }
@@ -873,23 +857,18 @@ export class SudoUpdateMembershipSystem extends MembershipFixture {
   async runQueryNodeChecks(): Promise<void> {
     await super.runQueryNodeChecks()
     const { events, extrinsics, eventNames } = this
-    const beforeSnapshotMaxBlockTimestamp = (
-      await this.api.query.timestamp.now.at(
-        await this.api.getBlockHash(Math.min(...events.map((e) => e.blockNumber)) - 1)
-      )
-    ).toNumber()
     const afterSnapshotBlockTimestamp = Math.max(...events.map((e) => e.blockTimestamp))
 
     // Fetch "afterSnapshot" first to make sure query node has progressed enough
     const afterSnapshot = (await this.query.tryQueryWithTimeout(
-      () => this.query.getMembershipSystemSnapshot(afterSnapshotBlockTimestamp),
+      () => this.query.getMembershipSystemSnapshotAt(afterSnapshotBlockTimestamp),
       (snapshot) => assert.isOk(snapshot)
-    )) as MembershipSystemSnapshot
+    )) as MembershipSystemSnapshotFieldsFragment
 
-    const beforeSnapshot = await this.query.getMembershipSystemSnapshot(beforeSnapshotMaxBlockTimestamp, 'lte')
+    const beforeSnapshot = await this.query.getMembershipSystemSnapshotBefore(afterSnapshotBlockTimestamp)
 
     if (!beforeSnapshot) {
-      throw new Error(`MembershipSystemSnapshot before timestamp ${beforeSnapshotMaxBlockTimestamp} not found!`)
+      throw new Error(`Query node: MembershipSystemSnapshot before timestamp ${afterSnapshotBlockTimestamp} not found!`)
     }
 
     // Validate snapshots

+ 74 - 64
tests/integration-tests/src/fixtures/workingGroupsModule.ts

@@ -4,24 +4,7 @@ import { assert } from 'chai'
 import { BaseFixture } from '../Fixture'
 import Debugger from 'debug'
 import { QueryNodeApi } from '../QueryNodeApi'
-import {
-  ApplicationFormQuestionType,
-  AppliedOnOpeningEvent,
-  EventType,
-  OpeningAddedEvent,
-  OpeningFilledEvent,
-  WorkingGroupApplication,
-  WorkingGroupOpening,
-  WorkingGroupOpeningType,
-  Worker,
-  ApplicationWithdrawnEvent,
-  OpeningCanceledEvent,
-  StatusTextChangedEvent,
-  UpcomingWorkingGroupOpening,
-  WorkingGroupOpeningMetadata,
-  WorkingGroup,
-  WorkingGroupMetadata as QWorkingGroupMetadata,
-} from '../QueryNodeApiSchema.generated'
+import { ApplicationFormQuestionType, EventType, WorkingGroupOpeningType } from '../graphql/generated/schema'
 import {
   AddUpcomingOpening,
   ApplicationMetadata,
@@ -47,6 +30,22 @@ import _ from 'lodash'
 import { SubmittableExtrinsic } from '@polkadot/api/types'
 import { JoyBTreeSet } from '@joystream/types/common'
 import { registry } from '@joystream/types'
+import {
+  OpeningFieldsFragment,
+  OpeningMetadataFieldsFragment,
+  OpeningAddedEventFieldsFragment,
+  ApplicationFieldsFragment,
+  AppliedOnOpeningEventFieldsFragment,
+  OpeningFilledEventFieldsFragment,
+  WorkerFieldsFragment,
+  ApplicationWithdrawnEventFieldsFragment,
+  OpeningCanceledEventFieldsFragment,
+  StatusTextChangedEventFieldsFragment,
+  UpcomingOpeningFieldsFragment,
+  WorkingGroupMetadataFieldsFragment,
+  WorkingGroupFieldsFragment,
+  ApplicationBasicFieldsFragment,
+} from '../graphql/generated/queries'
 
 // TODO: Fetch from runtime when possible!
 const MIN_APPLICATION_STAKE = new BN(2000)
@@ -124,7 +123,7 @@ abstract class BaseCreateOpeningFixture extends BaseFixture {
     return metadata
   }
 
-  protected assertQueriedOpeningMetadataIsValid(qOpeningMeta: WorkingGroupOpeningMetadata) {
+  protected assertQueriedOpeningMetadataIsValid(qOpeningMeta: OpeningMetadataFieldsFragment) {
     assert.equal(qOpeningMeta.shortDescription, this.openingParams.metadata.shortDescription)
     assert.equal(qOpeningMeta.description, this.openingParams.metadata.description)
     assert.equal(new Date(qOpeningMeta.expectedEnding).getTime(), this.openingParams.metadata.expectedEndingTimestamp)
@@ -171,7 +170,7 @@ export class CreateOpeningFixture extends BaseCreateOpeningFixture {
 
   private assertOpeningMatchQueriedResult(
     eventDetails: OpeningAddedEventDetails,
-    qOpening?: WorkingGroupOpening | null
+    qOpening: OpeningFieldsFragment | null
   ) {
     if (!qOpening) {
       throw new Error('Query node: Opening not found')
@@ -191,7 +190,7 @@ export class CreateOpeningFixture extends BaseCreateOpeningFixture {
   private assertQueriedOpeningAddedEventIsValid(
     eventDetails: OpeningAddedEventDetails,
     txHash: string,
-    qEvent?: OpeningAddedEvent
+    qEvent: OpeningAddedEventFieldsFragment | null
   ) {
     if (!qEvent) {
       throw new Error('Query node: OpeningAdded event not found')
@@ -230,7 +229,7 @@ export class CreateOpeningFixture extends BaseCreateOpeningFixture {
     // Query the opening
     await this.query.tryQueryWithTimeout(
       () => this.query.getOpeningById(eventDetails.openingId, this.group),
-      (r) => this.assertOpeningMatchQueriedResult(eventDetails, r.data.workingGroupOpeningByUniqueInput)
+      (qOpening) => this.assertOpeningMatchQueriedResult(eventDetails, qOpening)
     )
     // Query the event
     const qOpeningAddedEvent = await this.query.getOpeningAddedEvent(
@@ -290,7 +289,7 @@ export class ApplyOnOpeningHappyCaseFixture extends BaseFixture {
 
   private assertApplicationMatchQueriedResult(
     eventDetails: AppliedOnOpeningEventDetails,
-    qApplication?: WorkingGroupApplication | null
+    qApplication: ApplicationFieldsFragment | null
   ) {
     if (!qApplication) {
       throw new Error('Application not found')
@@ -318,7 +317,7 @@ export class ApplyOnOpeningHappyCaseFixture extends BaseFixture {
   private assertQueriedOpeningAddedEventIsValid(
     eventDetails: AppliedOnOpeningEventDetails,
     txHash: string,
-    qEvent?: AppliedOnOpeningEvent
+    qEvent: AppliedOnOpeningEventFieldsFragment | null
   ) {
     if (!qEvent) {
       throw new Error('Query node: AppliedOnOpening event not found')
@@ -362,7 +361,7 @@ export class ApplyOnOpeningHappyCaseFixture extends BaseFixture {
     // Query the application
     await this.query.tryQueryWithTimeout(
       () => this.query.getApplicationById(eventDetails.applicationId, this.group),
-      (r) => this.assertApplicationMatchQueriedResult(eventDetails, r.data.workingGroupApplicationByUniqueInput)
+      (qApplication) => this.assertApplicationMatchQueriedResult(eventDetails, qApplication)
     )
     // Query the event
     const qAppliedOnOpeningEvent = await this.query.getAppliedOnOpeningEvent(
@@ -423,7 +422,7 @@ export class SudoFillLeadOpeningFixture extends BaseFixture {
   private assertQueriedOpeningFilledEventIsValid(
     eventDetails: OpeningFilledEventDetails,
     txHash: string,
-    qEvent?: OpeningFilledEvent
+    qEvent: OpeningFilledEventFieldsFragment | null
   ) {
     if (!qEvent) {
       throw new Error('Query node: OpeningFilledEvent not found')
@@ -461,7 +460,7 @@ export class SudoFillLeadOpeningFixture extends BaseFixture {
     applicationId: ApplicationId,
     application: Application,
     applicationStake: BN,
-    qWorker: Worker
+    qWorker: WorkerFieldsFragment
   ) {
     assert.equal(qWorker.group.name, this.group)
     assert.equal(qWorker.membership.id, application.member_id.toString())
@@ -483,12 +482,10 @@ export class SudoFillLeadOpeningFixture extends BaseFixture {
     const qEvent = (await this.query.tryQueryWithTimeout(
       () => this.query.getOpeningFilledEvent(eventDetails.blockNumber, eventDetails.indexInBlock),
       (qEvent) => this.assertQueriedOpeningFilledEventIsValid(eventDetails, tx.hash.toString(), qEvent)
-    )) as OpeningFilledEvent
+    )) as OpeningFilledEventFieldsFragment
 
     // Check opening status
-    const {
-      data: { workingGroupOpeningByUniqueInput: qOpening },
-    } = await this.query.getOpeningById(this.openingId, this.group)
+    const qOpening = await this.query.getOpeningById(this.openingId, this.group)
     if (!qOpening) {
       throw new Error(`Query node: Opening ${this.openingId.toString()} not found!`)
     }
@@ -565,7 +562,7 @@ export class WithdrawApplicationsFixture extends BaseFixture {
   private assertQueriedApplicationWithdrawnEventIsValid(
     applicationId: ApplicationId,
     txHash: string,
-    qEvent?: ApplicationWithdrawnEvent
+    qEvent: ApplicationWithdrawnEventFieldsFragment | null
   ) {
     if (!qEvent) {
       throw new Error('Query node: ApplicationWithdrawnEvent not found!')
@@ -577,8 +574,8 @@ export class WithdrawApplicationsFixture extends BaseFixture {
   }
 
   private assertApplicationStatusIsValid(
-    qEvent: ApplicationWithdrawnEvent,
-    qApplication?: WorkingGroupApplication | null
+    qEvent: ApplicationWithdrawnEventFieldsFragment,
+    qApplication: ApplicationFieldsFragment | null
   ) {
     if (!qApplication) {
       throw new Error('Query node: Application not found!')
@@ -605,14 +602,12 @@ export class WithdrawApplicationsFixture extends BaseFixture {
             )
         )
       )
-    )) as ApplicationWithdrawnEvent[]
+    )) as ApplicationWithdrawnEventFieldsFragment[]
 
     // Check application statuses
     await Promise.all(
       this.applicationIds.map(async (id, i) => {
-        const {
-          data: { workingGroupApplicationByUniqueInput: qApplication },
-        } = await this.query.getApplicationById(id, this.group)
+        const qApplication = await this.query.getApplicationById(id, this.group)
         this.assertApplicationStatusIsValid(qEvents[i], qApplication)
       })
     )
@@ -645,7 +640,10 @@ export class CancelOpeningFixture extends BaseFixture {
     this.event = await this.api.retrieveWorkingGroupsEventDetails(result, this.group, 'OpeningCanceled')
   }
 
-  private assertQueriedOpeningIsValid(qEvent: OpeningCanceledEvent, qOpening?: WorkingGroupOpening | null) {
+  private assertQueriedOpeningIsValid(
+    qEvent: OpeningCanceledEventFieldsFragment,
+    qOpening: OpeningFieldsFragment | null
+  ) {
     if (!qOpening) {
       throw new Error('Query node: Opening not found!')
     }
@@ -656,7 +654,10 @@ export class CancelOpeningFixture extends BaseFixture {
     qOpening.applications.forEach((a) => this.assertApplicationStatusIsValid(qEvent, a))
   }
 
-  private assertApplicationStatusIsValid(qEvent: OpeningCanceledEvent, qApplication: WorkingGroupApplication) {
+  private assertApplicationStatusIsValid(
+    qEvent: OpeningCanceledEventFieldsFragment,
+    qApplication: ApplicationBasicFieldsFragment
+  ) {
     // It's possible that some of the applications have been withdrawn
     assert.oneOf(qApplication.status.__typename, ['ApplicationStatusWithdrawn', 'ApplicationStatusCancelled'])
     if (qApplication.status.__typename === 'ApplicationStatusCancelled') {
@@ -664,7 +665,7 @@ export class CancelOpeningFixture extends BaseFixture {
     }
   }
 
-  private assertQueriedOpeningCancelledEventIsValid(txHash: string, qEvent?: OpeningCanceledEvent) {
+  private assertQueriedOpeningCancelledEventIsValid(txHash: string, qEvent: OpeningCanceledEventFieldsFragment | null) {
     if (!qEvent) {
       throw new Error('Query node: OpeningCancelledEvent not found!')
     }
@@ -681,10 +682,8 @@ export class CancelOpeningFixture extends BaseFixture {
     const qEvent = (await this.query.tryQueryWithTimeout(
       () => this.query.getOpeningCancelledEvent(event.blockNumber, event.indexInBlock),
       (qEvent) => this.assertQueriedOpeningCancelledEventIsValid(tx.hash.toString(), qEvent)
-    )) as OpeningCanceledEvent
-    const {
-      data: { workingGroupOpeningByUniqueInput: qOpening },
-    } = await this.query.getOpeningById(this.openingId, this.group)
+    )) as OpeningCanceledEventFieldsFragment
+    const qOpening = await this.query.getOpeningById(this.openingId, this.group)
     this.assertQueriedOpeningIsValid(qEvent, qOpening)
   }
 }
@@ -743,7 +742,7 @@ export class CreateUpcomingOpeningFixture extends BaseCreateOpeningFixture {
 
   private assertQueriedUpcomingOpeningIsValid(
     eventDetails: EventDetails,
-    qUpcomingOpening?: UpcomingWorkingGroupOpening | null
+    qUpcomingOpening: UpcomingOpeningFieldsFragment | null
   ) {
     if (!qUpcomingOpening) {
       throw new Error('Query node: Upcoming opening not found!')
@@ -756,7 +755,10 @@ export class CreateUpcomingOpeningFixture extends BaseCreateOpeningFixture {
     this.assertQueriedOpeningMetadataIsValid(qUpcomingOpening.metadata)
   }
 
-  private assertQueriedStatusTextChangedEventIsValid(txHash: string, qEvent?: StatusTextChangedEvent) {
+  private assertQueriedStatusTextChangedEventIsValid(
+    txHash: string,
+    qEvent: StatusTextChangedEventFieldsFragment | null
+  ) {
     if (!qEvent) {
       throw new Error('Query node: StatusTextChangedEvent not found!')
     }
@@ -778,7 +780,7 @@ export class CreateUpcomingOpeningFixture extends BaseCreateOpeningFixture {
     const qEvent = (await this.query.tryQueryWithTimeout(
       () => this.query.getStatusTextChangedEvent(event.blockNumber, event.indexInBlock),
       (qEvent) => this.assertQueriedStatusTextChangedEventIsValid(tx.hash.toString(), qEvent)
-    )) as StatusTextChangedEvent
+    )) as StatusTextChangedEventFieldsFragment
     // Query the opening
     const qUpcomingOpening = await this.query.getUpcomingOpeningByCreatedInEventId(qEvent.id)
     this.assertQueriedUpcomingOpeningIsValid(event, qUpcomingOpening)
@@ -824,7 +826,10 @@ export class RemoveUpcomingOpeningFixture extends BaseFixture {
     this.event = await this.api.retrieveWorkingGroupsEventDetails(result, this.group, 'StatusTextChanged')
   }
 
-  private assertQueriedStatusTextChangedEventIsValid(txHash: string, qEvent?: StatusTextChangedEvent) {
+  private assertQueriedStatusTextChangedEventIsValid(
+    txHash: string,
+    qEvent: StatusTextChangedEventFieldsFragment | null
+  ) {
     if (!qEvent) {
       throw new Error('Query node: StatusTextChangedEvent not found!')
     }
@@ -849,11 +854,11 @@ export class RemoveUpcomingOpeningFixture extends BaseFixture {
     const qEvent = (await this.query.tryQueryWithTimeout(
       () => this.query.getStatusTextChangedEvent(event.blockNumber, event.indexInBlock),
       (qEvent) => this.assertQueriedStatusTextChangedEventIsValid(tx.hash.toString(), qEvent)
-    )) as StatusTextChangedEvent
+    )) as StatusTextChangedEventFieldsFragment
     // Query the opening and make sure it doesn't exist
     if (qEvent.result && qEvent.result.__typename === 'UpcomingOpeningRemoved') {
       const qUpcomingOpening = await this.query.getUpcomingOpeningByCreatedInEventId(qEvent.result.upcomingOpeningId)
-      assert.isUndefined(qUpcomingOpening)
+      assert.isNull(qUpcomingOpening)
     }
   }
 }
@@ -905,7 +910,10 @@ export class UpdateGroupStatusFixture extends BaseFixture {
     this.event = await this.api.retrieveWorkingGroupsEventDetails(result, this.group, 'StatusTextChanged')
   }
 
-  private assertQueriedStatusTextChangedEventIsValid(txHash: string, qEvent?: StatusTextChangedEvent) {
+  private assertQueriedStatusTextChangedEventIsValid(
+    txHash: string,
+    qEvent: StatusTextChangedEventFieldsFragment | null
+  ) {
     if (!qEvent) {
       throw new Error('Query node: StatusTextChangedEvent not found!')
     }
@@ -919,10 +927,7 @@ export class UpdateGroupStatusFixture extends BaseFixture {
     assert.equal(qEvent.result.__typename, 'WorkingGroupMetadataSet')
   }
 
-  private assertQueriedGroupIsValid(qGroup: WorkingGroup | undefined, qMeta: QWorkingGroupMetadata) {
-    if (!qGroup) {
-      throw new Error(`Query node: Group ${this.group} not found!`)
-    }
+  private assertQueriedGroupIsValid(qGroup: WorkingGroupFieldsFragment, qMeta: WorkingGroupMetadataFieldsFragment) {
     if (!qGroup.metadata) {
       throw new Error(`Query node: Group metadata is empty!`)
     }
@@ -931,13 +936,13 @@ export class UpdateGroupStatusFixture extends BaseFixture {
 
   private assertQueriedMetadataSnapshotsAreValid(
     eventDetails: EventDetails,
-    beforeSnapshot?: QWorkingGroupMetadata,
-    afterSnapshot?: QWorkingGroupMetadata
+    beforeSnapshot: WorkingGroupMetadataFieldsFragment | null,
+    afterSnapshot: WorkingGroupMetadataFieldsFragment | null
   ) {
     if (!afterSnapshot) {
       throw new Error('Query node: WorkingGroupMetadata snapshot not found!')
     }
-    const expectedMeta = _.merge(this.metadata, beforeSnapshot)
+    const expectedMeta = _.merge(beforeSnapshot, this.metadata)
     assert.equal(afterSnapshot.status, expectedMeta.status)
     assert.equal(afterSnapshot.statusMessage, expectedMeta.statusMessage)
     assert.equal(afterSnapshot.description, expectedMeta.description)
@@ -953,15 +958,20 @@ export class UpdateGroupStatusFixture extends BaseFixture {
     const qEvent = (await this.query.tryQueryWithTimeout(
       () => this.query.getStatusTextChangedEvent(event.blockNumber, event.indexInBlock),
       (qEvent) => this.assertQueriedStatusTextChangedEventIsValid(tx.hash.toString(), qEvent)
-    )) as StatusTextChangedEvent
+    )) as StatusTextChangedEventFieldsFragment
+
+    // Query the group
+    const qGroup = await this.query.getWorkingGroup(this.group)
+    if (!qGroup) {
+      throw new Error('Query node: Working group not found!')
+    }
 
     // Query & check the metadata snapshots
-    const beforeSnapshot = await this.query.getGroupMetaSnapshot(event.blockTimestamp, 'lt')
-    const afterSnapshot = await this.query.getGroupMetaSnapshot(event.blockTimestamp, 'eq')
+    const beforeSnapshot = await this.query.getGroupMetaSnapshotBefore(qGroup.id, event.blockTimestamp)
+    const afterSnapshot = await this.query.getGroupMetaSnapshotAt(qGroup.id, event.blockTimestamp)
     this.assertQueriedMetadataSnapshotsAreValid(event, beforeSnapshot, afterSnapshot)
 
-    // Query & check the group
-    const qGroup = await this.query.getWorkingGroup(this.group)
+    // Check the group
     this.assertQueriedGroupIsValid(qGroup, afterSnapshot!)
 
     // Check event relation

+ 1254 - 0
tests/integration-tests/src/graphql/generated/queries.ts

@@ -0,0 +1,1254 @@
+import * as Types from './schema'
+
+import gql from 'graphql-tag'
+export type BlockFieldsFragment = { number: number; timestamp: any; network: Types.Network }
+
+export type EventFieldsFragment = {
+  inExtrinsic?: Types.Maybe<string>
+  indexInBlock: number
+  type: Types.EventType
+  inBlock: BlockFieldsFragment
+}
+
+export type MemberMetadataFieldsFragment = { name?: Types.Maybe<string>; about?: Types.Maybe<string> }
+
+export type MembershipFieldsFragment = {
+  id: string
+  handle: string
+  controllerAccount: string
+  rootAccount: string
+  registeredAtTime: any
+  entry: Types.MembershipEntryMethod
+  isVerified: boolean
+  inviteCount: number
+  boundAccounts: Array<string>
+  metadata: MemberMetadataFieldsFragment
+  registeredAtBlock: BlockFieldsFragment
+  invitedBy?: Types.Maybe<{ id: string }>
+  invitees: Array<{ id: string }>
+}
+
+export type GetMemberByIdQueryVariables = Types.Exact<{
+  id: Types.Scalars['ID']
+}>
+
+export type GetMemberByIdQuery = { membershipByUniqueInput?: Types.Maybe<MembershipFieldsFragment> }
+
+export type MembershipSystemSnapshotFieldsFragment = {
+  snapshotTime: any
+  referralCut: number
+  invitedInitialBalance: any
+  defaultInviteCount: number
+  membershipPrice: any
+  snapshotBlock: BlockFieldsFragment
+}
+
+export type GetMembershipSystemSnapshotAtQueryVariables = Types.Exact<{
+  time: Types.Scalars['DateTime']
+}>
+
+export type GetMembershipSystemSnapshotAtQuery = {
+  membershipSystemSnapshots: Array<MembershipSystemSnapshotFieldsFragment>
+}
+
+export type GetMembershipSystemSnapshotBeforeQueryVariables = Types.Exact<{
+  time: Types.Scalars['DateTime']
+}>
+
+export type GetMembershipSystemSnapshotBeforeQuery = {
+  membershipSystemSnapshots: Array<MembershipSystemSnapshotFieldsFragment>
+}
+
+export type MembershipBoughtEventFieldsFragment = {
+  id: string
+  rootAccount: string
+  controllerAccount: string
+  handle: string
+  event: EventFieldsFragment
+  newMember: { id: string }
+  metadata: MemberMetadataFieldsFragment
+  referrer?: Types.Maybe<{ id: string }>
+}
+
+export type GetMembershipBoughtEventsByMemberIdQueryVariables = Types.Exact<{
+  memberId: Types.Scalars['ID']
+}>
+
+export type GetMembershipBoughtEventsByMemberIdQuery = {
+  membershipBoughtEvents: Array<MembershipBoughtEventFieldsFragment>
+}
+
+export type MemberProfileUpdatedEventFieldsFragment = {
+  id: string
+  newHandle?: Types.Maybe<string>
+  event: EventFieldsFragment
+  member: { id: string }
+  newMetadata: { name?: Types.Maybe<string>; about?: Types.Maybe<string> }
+}
+
+export type GetMemberProfileUpdatedEventsByMemberIdQueryVariables = Types.Exact<{
+  memberId: Types.Scalars['ID']
+}>
+
+export type GetMemberProfileUpdatedEventsByMemberIdQuery = {
+  memberProfileUpdatedEvents: Array<MemberProfileUpdatedEventFieldsFragment>
+}
+
+export type MemberAccountsUpdatedEventFieldsFragment = {
+  id: string
+  newRootAccount?: Types.Maybe<string>
+  newControllerAccount?: Types.Maybe<string>
+  event: EventFieldsFragment
+  member: { id: string }
+}
+
+export type GetMemberAccountsUpdatedEventsByMemberIdQueryVariables = Types.Exact<{
+  memberId: Types.Scalars['ID']
+}>
+
+export type GetMemberAccountsUpdatedEventsByMemberIdQuery = {
+  memberAccountsUpdatedEvents: Array<MemberAccountsUpdatedEventFieldsFragment>
+}
+
+export type MemberInvitedEventFieldsFragment = {
+  id: string
+  rootAccount: string
+  controllerAccount: string
+  handle: string
+  event: EventFieldsFragment
+  invitingMember: { id: string }
+  newMember: { id: string }
+  metadata: MemberMetadataFieldsFragment
+}
+
+export type GetMemberInvitedEventsByNewMemberIdQueryVariables = Types.Exact<{
+  newMemberId: Types.Scalars['ID']
+}>
+
+export type GetMemberInvitedEventsByNewMemberIdQuery = { memberInvitedEvents: Array<MemberInvitedEventFieldsFragment> }
+
+export type InvitesTransferredEventFieldsFragment = {
+  id: string
+  numberOfInvites: number
+  event: EventFieldsFragment
+  sourceMember: { id: string }
+  targetMember: { id: string }
+}
+
+export type GetInvitesTransferredEventsBySourceMemberIdQueryVariables = Types.Exact<{
+  sourceMemberId: Types.Scalars['ID']
+}>
+
+export type GetInvitesTransferredEventsBySourceMemberIdQuery = {
+  invitesTransferredEvents: Array<InvitesTransferredEventFieldsFragment>
+}
+
+export type StakingAccountAddedEventFieldsFragment = {
+  id: string
+  account: string
+  event: EventFieldsFragment
+  member: { id: string }
+}
+
+export type GetStakingAccountAddedEventsByMemberIdQueryVariables = Types.Exact<{
+  memberId: Types.Scalars['ID']
+}>
+
+export type GetStakingAccountAddedEventsByMemberIdQuery = {
+  stakingAccountAddedEvents: Array<StakingAccountAddedEventFieldsFragment>
+}
+
+export type StakingAccountConfirmedEventFieldsFragment = {
+  id: string
+  account: string
+  event: EventFieldsFragment
+  member: { id: string }
+}
+
+export type GetStakingAccountConfirmedEventsByMemberIdQueryVariables = Types.Exact<{
+  memberId: Types.Scalars['ID']
+}>
+
+export type GetStakingAccountConfirmedEventsByMemberIdQuery = {
+  stakingAccountConfirmedEvents: Array<StakingAccountConfirmedEventFieldsFragment>
+}
+
+export type StakingAccountRemovedEventFieldsFragment = {
+  id: string
+  account: string
+  event: EventFieldsFragment
+  member: { id: string }
+}
+
+export type GetStakingAccountRemovedEventsByMemberIdQueryVariables = Types.Exact<{
+  memberId: Types.Scalars['ID']
+}>
+
+export type GetStakingAccountRemovedEventsByMemberIdQuery = {
+  stakingAccountRemovedEvents: Array<StakingAccountRemovedEventFieldsFragment>
+}
+
+export type ReferralCutUpdatedEventFieldsFragment = { id: string; newValue: number; event: EventFieldsFragment }
+
+export type GetReferralCutUpdatedEventsByEventIdQueryVariables = Types.Exact<{
+  eventId: Types.Scalars['ID']
+}>
+
+export type GetReferralCutUpdatedEventsByEventIdQuery = {
+  referralCutUpdatedEvents: Array<ReferralCutUpdatedEventFieldsFragment>
+}
+
+export type MembershipPriceUpdatedEventFieldsFragment = { id: string; newPrice: any; event: EventFieldsFragment }
+
+export type GetMembershipPriceUpdatedEventsByEventIdQueryVariables = Types.Exact<{
+  eventId: Types.Scalars['ID']
+}>
+
+export type GetMembershipPriceUpdatedEventsByEventIdQuery = {
+  membershipPriceUpdatedEvents: Array<MembershipPriceUpdatedEventFieldsFragment>
+}
+
+export type InitialInvitationBalanceUpdatedEventFieldsFragment = {
+  id: string
+  newInitialBalance: any
+  event: EventFieldsFragment
+}
+
+export type GetInitialInvitationBalanceUpdatedEventsByEventIdQueryVariables = Types.Exact<{
+  eventId: Types.Scalars['ID']
+}>
+
+export type GetInitialInvitationBalanceUpdatedEventsByEventIdQuery = {
+  initialInvitationBalanceUpdatedEvents: Array<InitialInvitationBalanceUpdatedEventFieldsFragment>
+}
+
+export type InitialInvitationCountUpdatedEventFieldsFragment = {
+  id: string
+  newInitialInvitationCount: number
+  event: EventFieldsFragment
+}
+
+export type GetInitialInvitationCountUpdatedEventsByEventIdQueryVariables = Types.Exact<{
+  eventId: Types.Scalars['ID']
+}>
+
+export type GetInitialInvitationCountUpdatedEventsByEventIdQuery = {
+  initialInvitationCountUpdatedEvents: Array<InitialInvitationCountUpdatedEventFieldsFragment>
+}
+
+export type ApplicationBasicFieldsFragment = {
+  id: string
+  runtimeId: number
+  status:
+    | { __typename: 'ApplicationStatusPending' }
+    | { __typename: 'ApplicationStatusAccepted'; openingFilledEventId: string }
+    | { __typename: 'ApplicationStatusRejected'; openingFilledEventId: string }
+    | { __typename: 'ApplicationStatusWithdrawn'; applicationWithdrawnEventId: string }
+    | { __typename: 'ApplicationStatusCancelled'; openingCancelledEventId: string }
+}
+
+type OpeningStatusFields_OpeningStatusOpen_Fragment = { __typename: 'OpeningStatusOpen' }
+
+type OpeningStatusFields_OpeningStatusFilled_Fragment = {
+  __typename: 'OpeningStatusFilled'
+  openingFilledEventId: string
+}
+
+type OpeningStatusFields_OpeningStatusCancelled_Fragment = {
+  __typename: 'OpeningStatusCancelled'
+  openingCancelledEventId: string
+}
+
+export type OpeningStatusFieldsFragment =
+  | OpeningStatusFields_OpeningStatusOpen_Fragment
+  | OpeningStatusFields_OpeningStatusFilled_Fragment
+  | OpeningStatusFields_OpeningStatusCancelled_Fragment
+
+export type ApplicationFormQuestionFieldsFragment = {
+  question: string
+  type: Types.ApplicationFormQuestionType
+  index: number
+}
+
+export type OpeningMetadataFieldsFragment = {
+  shortDescription: string
+  description: string
+  hiringLimit?: Types.Maybe<number>
+  expectedEnding?: Types.Maybe<any>
+  applicationDetails: string
+  applicationFormQuestions: Array<ApplicationFormQuestionFieldsFragment>
+}
+
+export type WorkerFieldsFragment = {
+  id: string
+  runtimeId: number
+  roleAccount: string
+  rewardAccount: string
+  stakeAccount: string
+  isLead: boolean
+  stake: any
+  hiredAtTime: any
+  storage?: Types.Maybe<string>
+  group: { name: string }
+  membership: { id: string }
+  status:
+    | { __typename: 'WorkerStatusActive' }
+    | { __typename: 'WorkerStatusLeft' }
+    | { __typename: 'WorkerStatusTerminated' }
+  payouts: Array<{ id: string }>
+  hiredAtBlock: BlockFieldsFragment
+  application: ApplicationBasicFieldsFragment
+}
+
+export type WorkingGroupMetadataFieldsFragment = {
+  id: string
+  status?: Types.Maybe<string>
+  statusMessage?: Types.Maybe<string>
+  about?: Types.Maybe<string>
+  description?: Types.Maybe<string>
+  setAtBlock: BlockFieldsFragment
+}
+
+export type OpeningFieldsFragment = {
+  id: string
+  runtimeId: number
+  type: Types.WorkingGroupOpeningType
+  stakeAmount: any
+  unstakingPeriod: number
+  rewardPerBlock: any
+  createdAt: any
+  group: { name: string; leader?: Types.Maybe<{ runtimeId: number }> }
+  applications: Array<ApplicationBasicFieldsFragment>
+  status:
+    | OpeningStatusFields_OpeningStatusOpen_Fragment
+    | OpeningStatusFields_OpeningStatusFilled_Fragment
+    | OpeningStatusFields_OpeningStatusCancelled_Fragment
+  metadata: OpeningMetadataFieldsFragment
+  createdAtBlock: BlockFieldsFragment
+}
+
+export type GetOpeningByIdQueryVariables = Types.Exact<{
+  openingId: Types.Scalars['ID']
+}>
+
+export type GetOpeningByIdQuery = { workingGroupOpeningByUniqueInput?: Types.Maybe<OpeningFieldsFragment> }
+
+export type ApplicationFieldsFragment = {
+  createdAt: any
+  roleAccount: string
+  rewardAccount: string
+  stakingAccount: string
+  stake: any
+  createdAtBlock: BlockFieldsFragment
+  opening: { id: string; runtimeId: number }
+  applicant: { id: string }
+  answers: Array<{ answer: string; question: { question: string } }>
+} & ApplicationBasicFieldsFragment
+
+export type GetApplicationByIdQueryVariables = Types.Exact<{
+  applicationId: Types.Scalars['ID']
+}>
+
+export type GetApplicationByIdQuery = { workingGroupApplicationByUniqueInput?: Types.Maybe<ApplicationFieldsFragment> }
+
+export type WorkingGroupFieldsFragment = {
+  id: string
+  name: string
+  budget: any
+  metadata?: Types.Maybe<WorkingGroupMetadataFieldsFragment>
+  leader?: Types.Maybe<{ id: string }>
+}
+
+export type GetWorkingGroupByNameQueryVariables = Types.Exact<{
+  name: Types.Scalars['String']
+}>
+
+export type GetWorkingGroupByNameQuery = { workingGroupByUniqueInput?: Types.Maybe<WorkingGroupFieldsFragment> }
+
+export type UpcomingOpeningFieldsFragment = {
+  id: string
+  expectedStart: any
+  stakeAmount: any
+  rewardPerBlock: any
+  createdAt: any
+  group: { name: string }
+  metadata: OpeningMetadataFieldsFragment
+  createdAtBlock: BlockFieldsFragment
+}
+
+export type GetUpcomingOpeningByCreatedInEventIdQueryVariables = Types.Exact<{
+  createdInEventId: Types.Scalars['ID']
+}>
+
+export type GetUpcomingOpeningByCreatedInEventIdQuery = {
+  upcomingWorkingGroupOpenings: Array<UpcomingOpeningFieldsFragment>
+}
+
+export type GetWorkingGroupMetadataSnapshotAtQueryVariables = Types.Exact<{
+  groupId: Types.Scalars['ID']
+  timestamp: Types.Scalars['DateTime']
+}>
+
+export type GetWorkingGroupMetadataSnapshotAtQuery = { workingGroupMetadata: Array<WorkingGroupMetadataFieldsFragment> }
+
+export type GetWorkingGroupMetadataSnapshotBeforeQueryVariables = Types.Exact<{
+  groupId: Types.Scalars['ID']
+  timestamp: Types.Scalars['DateTime']
+}>
+
+export type GetWorkingGroupMetadataSnapshotBeforeQuery = {
+  workingGroupMetadata: Array<WorkingGroupMetadataFieldsFragment>
+}
+
+export type AppliedOnOpeningEventFieldsFragment = {
+  id: string
+  event: EventFieldsFragment
+  group: { name: string }
+  opening: { id: string; runtimeId: number }
+  application: { id: string; runtimeId: number }
+}
+
+export type GetAppliedOnOpeningEventsByEventIdQueryVariables = Types.Exact<{
+  eventId: Types.Scalars['ID']
+}>
+
+export type GetAppliedOnOpeningEventsByEventIdQuery = {
+  appliedOnOpeningEvents: Array<AppliedOnOpeningEventFieldsFragment>
+}
+
+export type OpeningAddedEventFieldsFragment = {
+  id: string
+  event: EventFieldsFragment
+  group: { name: string }
+  opening: { id: string; runtimeId: number }
+}
+
+export type GetOpeningAddedEventsByEventIdQueryVariables = Types.Exact<{
+  eventId: Types.Scalars['ID']
+}>
+
+export type GetOpeningAddedEventsByEventIdQuery = { openingAddedEvents: Array<OpeningAddedEventFieldsFragment> }
+
+export type OpeningFilledEventFieldsFragment = {
+  id: string
+  event: EventFieldsFragment
+  group: { name: string }
+  opening: { id: string; runtimeId: number }
+  workersHired: Array<WorkerFieldsFragment>
+}
+
+export type GetOpeningFilledEventsByEventIdQueryVariables = Types.Exact<{
+  eventId: Types.Scalars['ID']
+}>
+
+export type GetOpeningFilledEventsByEventIdQuery = { openingFilledEvents: Array<OpeningFilledEventFieldsFragment> }
+
+export type ApplicationWithdrawnEventFieldsFragment = {
+  id: string
+  event: EventFieldsFragment
+  group: { name: string }
+  application: { id: string; runtimeId: number }
+}
+
+export type GetApplicationWithdrawnEventsByEventIdQueryVariables = Types.Exact<{
+  eventId: Types.Scalars['ID']
+}>
+
+export type GetApplicationWithdrawnEventsByEventIdQuery = {
+  applicationWithdrawnEvents: Array<ApplicationWithdrawnEventFieldsFragment>
+}
+
+export type OpeningCanceledEventFieldsFragment = {
+  id: string
+  event: EventFieldsFragment
+  group: { name: string }
+  opening: { id: string; runtimeId: number }
+}
+
+export type GetOpeningCancelledEventsByEventIdQueryVariables = Types.Exact<{
+  eventId: Types.Scalars['ID']
+}>
+
+export type GetOpeningCancelledEventsByEventIdQuery = {
+  openingCanceledEvents: Array<OpeningCanceledEventFieldsFragment>
+}
+
+export type StatusTextChangedEventFieldsFragment = {
+  id: string
+  metadata?: Types.Maybe<string>
+  event: EventFieldsFragment
+  group: { name: string }
+  result:
+    | { __typename: 'UpcomingOpeningAdded'; upcomingOpeningId: string }
+    | { __typename: 'UpcomingOpeningRemoved'; upcomingOpeningId: string }
+    | { __typename: 'WorkingGroupMetadataSet'; metadataId: string }
+    | { __typename: 'InvalidActionMetadata'; reason: string }
+}
+
+export type GetStatusTextChangedEventsByEventIdQueryVariables = Types.Exact<{
+  eventId: Types.Scalars['ID']
+}>
+
+export type GetStatusTextChangedEventsByEventIdQuery = {
+  statusTextChangedEvents: Array<StatusTextChangedEventFieldsFragment>
+}
+
+export const MemberMetadataFields = gql`
+  fragment MemberMetadataFields on MemberMetadata {
+    name
+    about
+  }
+`
+export const BlockFields = gql`
+  fragment BlockFields on Block {
+    number
+    timestamp
+    network
+  }
+`
+export const MembershipFields = gql`
+  fragment MembershipFields on Membership {
+    id
+    handle
+    metadata {
+      ...MemberMetadataFields
+    }
+    controllerAccount
+    rootAccount
+    registeredAtBlock {
+      ...BlockFields
+    }
+    registeredAtTime
+    entry
+    isVerified
+    inviteCount
+    invitedBy {
+      id
+    }
+    invitees {
+      id
+    }
+    boundAccounts
+  }
+  ${MemberMetadataFields}
+  ${BlockFields}
+`
+export const MembershipSystemSnapshotFields = gql`
+  fragment MembershipSystemSnapshotFields on MembershipSystemSnapshot {
+    snapshotBlock {
+      ...BlockFields
+    }
+    snapshotTime
+    referralCut
+    invitedInitialBalance
+    defaultInviteCount
+    membershipPrice
+  }
+  ${BlockFields}
+`
+export const EventFields = gql`
+  fragment EventFields on Event {
+    inBlock {
+      ...BlockFields
+    }
+    inExtrinsic
+    indexInBlock
+    type
+  }
+  ${BlockFields}
+`
+export const MembershipBoughtEventFields = gql`
+  fragment MembershipBoughtEventFields on MembershipBoughtEvent {
+    id
+    event {
+      ...EventFields
+    }
+    newMember {
+      id
+    }
+    rootAccount
+    controllerAccount
+    handle
+    metadata {
+      ...MemberMetadataFields
+    }
+    referrer {
+      id
+    }
+  }
+  ${EventFields}
+  ${MemberMetadataFields}
+`
+export const MemberProfileUpdatedEventFields = gql`
+  fragment MemberProfileUpdatedEventFields on MemberProfileUpdatedEvent {
+    id
+    event {
+      ...EventFields
+    }
+    member {
+      id
+    }
+    newHandle
+    newMetadata {
+      name
+      about
+    }
+  }
+  ${EventFields}
+`
+export const MemberAccountsUpdatedEventFields = gql`
+  fragment MemberAccountsUpdatedEventFields on MemberAccountsUpdatedEvent {
+    id
+    event {
+      ...EventFields
+    }
+    member {
+      id
+    }
+    newRootAccount
+    newControllerAccount
+  }
+  ${EventFields}
+`
+export const MemberInvitedEventFields = gql`
+  fragment MemberInvitedEventFields on MemberInvitedEvent {
+    id
+    event {
+      ...EventFields
+    }
+    invitingMember {
+      id
+    }
+    newMember {
+      id
+    }
+    rootAccount
+    controllerAccount
+    handle
+    metadata {
+      ...MemberMetadataFields
+    }
+  }
+  ${EventFields}
+  ${MemberMetadataFields}
+`
+export const InvitesTransferredEventFields = gql`
+  fragment InvitesTransferredEventFields on InvitesTransferredEvent {
+    id
+    event {
+      ...EventFields
+    }
+    sourceMember {
+      id
+    }
+    targetMember {
+      id
+    }
+    numberOfInvites
+  }
+  ${EventFields}
+`
+export const StakingAccountAddedEventFields = gql`
+  fragment StakingAccountAddedEventFields on StakingAccountAddedEvent {
+    id
+    event {
+      ...EventFields
+    }
+    member {
+      id
+    }
+    account
+  }
+  ${EventFields}
+`
+export const StakingAccountConfirmedEventFields = gql`
+  fragment StakingAccountConfirmedEventFields on StakingAccountConfirmedEvent {
+    id
+    event {
+      ...EventFields
+    }
+    member {
+      id
+    }
+    account
+  }
+  ${EventFields}
+`
+export const StakingAccountRemovedEventFields = gql`
+  fragment StakingAccountRemovedEventFields on StakingAccountRemovedEvent {
+    id
+    event {
+      ...EventFields
+    }
+    member {
+      id
+    }
+    account
+  }
+  ${EventFields}
+`
+export const ReferralCutUpdatedEventFields = gql`
+  fragment ReferralCutUpdatedEventFields on ReferralCutUpdatedEvent {
+    id
+    event {
+      ...EventFields
+    }
+    newValue
+  }
+  ${EventFields}
+`
+export const MembershipPriceUpdatedEventFields = gql`
+  fragment MembershipPriceUpdatedEventFields on MembershipPriceUpdatedEvent {
+    id
+    event {
+      ...EventFields
+    }
+    newPrice
+  }
+  ${EventFields}
+`
+export const InitialInvitationBalanceUpdatedEventFields = gql`
+  fragment InitialInvitationBalanceUpdatedEventFields on InitialInvitationBalanceUpdatedEvent {
+    id
+    event {
+      ...EventFields
+    }
+    newInitialBalance
+  }
+  ${EventFields}
+`
+export const InitialInvitationCountUpdatedEventFields = gql`
+  fragment InitialInvitationCountUpdatedEventFields on InitialInvitationCountUpdatedEvent {
+    id
+    event {
+      ...EventFields
+    }
+    newInitialInvitationCount
+  }
+  ${EventFields}
+`
+export const ApplicationBasicFields = gql`
+  fragment ApplicationBasicFields on WorkingGroupApplication {
+    id
+    runtimeId
+    status {
+      __typename
+      ... on ApplicationStatusCancelled {
+        openingCancelledEventId
+      }
+      ... on ApplicationStatusWithdrawn {
+        applicationWithdrawnEventId
+      }
+      ... on ApplicationStatusAccepted {
+        openingFilledEventId
+      }
+      ... on ApplicationStatusRejected {
+        openingFilledEventId
+      }
+    }
+  }
+`
+export const OpeningStatusFields = gql`
+  fragment OpeningStatusFields on WorkingGroupOpeningStatus {
+    __typename
+    ... on OpeningStatusFilled {
+      openingFilledEventId
+    }
+    ... on OpeningStatusCancelled {
+      openingCancelledEventId
+    }
+  }
+`
+export const ApplicationFormQuestionFields = gql`
+  fragment ApplicationFormQuestionFields on ApplicationFormQuestion {
+    question
+    type
+    index
+  }
+`
+export const OpeningMetadataFields = gql`
+  fragment OpeningMetadataFields on WorkingGroupOpeningMetadata {
+    shortDescription
+    description
+    hiringLimit
+    expectedEnding
+    applicationDetails
+    applicationFormQuestions {
+      ...ApplicationFormQuestionFields
+    }
+  }
+  ${ApplicationFormQuestionFields}
+`
+export const OpeningFields = gql`
+  fragment OpeningFields on WorkingGroupOpening {
+    id
+    runtimeId
+    group {
+      name
+      leader {
+        runtimeId
+      }
+    }
+    applications {
+      ...ApplicationBasicFields
+    }
+    type
+    status {
+      ...OpeningStatusFields
+    }
+    metadata {
+      ...OpeningMetadataFields
+    }
+    stakeAmount
+    unstakingPeriod
+    rewardPerBlock
+    createdAtBlock {
+      ...BlockFields
+    }
+    createdAt
+  }
+  ${ApplicationBasicFields}
+  ${OpeningStatusFields}
+  ${OpeningMetadataFields}
+  ${BlockFields}
+`
+export const ApplicationFields = gql`
+  fragment ApplicationFields on WorkingGroupApplication {
+    ...ApplicationBasicFields
+    createdAtBlock {
+      ...BlockFields
+    }
+    createdAt
+    opening {
+      id
+      runtimeId
+    }
+    applicant {
+      id
+    }
+    roleAccount
+    rewardAccount
+    stakingAccount
+    answers {
+      question {
+        question
+      }
+      answer
+    }
+    stake
+  }
+  ${ApplicationBasicFields}
+  ${BlockFields}
+`
+export const WorkingGroupMetadataFields = gql`
+  fragment WorkingGroupMetadataFields on WorkingGroupMetadata {
+    id
+    status
+    statusMessage
+    about
+    description
+    setAtBlock {
+      ...BlockFields
+    }
+  }
+  ${BlockFields}
+`
+export const WorkingGroupFields = gql`
+  fragment WorkingGroupFields on WorkingGroup {
+    id
+    name
+    metadata {
+      ...WorkingGroupMetadataFields
+    }
+    leader {
+      id
+    }
+    budget
+  }
+  ${WorkingGroupMetadataFields}
+`
+export const UpcomingOpeningFields = gql`
+  fragment UpcomingOpeningFields on UpcomingWorkingGroupOpening {
+    id
+    group {
+      name
+    }
+    metadata {
+      ...OpeningMetadataFields
+    }
+    expectedStart
+    stakeAmount
+    rewardPerBlock
+    createdAtBlock {
+      ...BlockFields
+    }
+    createdAt
+  }
+  ${OpeningMetadataFields}
+  ${BlockFields}
+`
+export const AppliedOnOpeningEventFields = gql`
+  fragment AppliedOnOpeningEventFields on AppliedOnOpeningEvent {
+    id
+    event {
+      ...EventFields
+    }
+    group {
+      name
+    }
+    opening {
+      id
+      runtimeId
+    }
+    application {
+      id
+      runtimeId
+    }
+  }
+  ${EventFields}
+`
+export const OpeningAddedEventFields = gql`
+  fragment OpeningAddedEventFields on OpeningAddedEvent {
+    id
+    event {
+      ...EventFields
+    }
+    group {
+      name
+    }
+    opening {
+      id
+      runtimeId
+    }
+  }
+  ${EventFields}
+`
+export const WorkerFields = gql`
+  fragment WorkerFields on Worker {
+    id
+    runtimeId
+    group {
+      name
+    }
+    membership {
+      id
+    }
+    roleAccount
+    rewardAccount
+    stakeAccount
+    status {
+      __typename
+    }
+    isLead
+    stake
+    payouts {
+      id
+    }
+    hiredAtBlock {
+      ...BlockFields
+    }
+    hiredAtTime
+    application {
+      ...ApplicationBasicFields
+    }
+    storage
+  }
+  ${BlockFields}
+  ${ApplicationBasicFields}
+`
+export const OpeningFilledEventFields = gql`
+  fragment OpeningFilledEventFields on OpeningFilledEvent {
+    id
+    event {
+      ...EventFields
+    }
+    group {
+      name
+    }
+    opening {
+      id
+      runtimeId
+    }
+    workersHired {
+      ...WorkerFields
+    }
+  }
+  ${EventFields}
+  ${WorkerFields}
+`
+export const ApplicationWithdrawnEventFields = gql`
+  fragment ApplicationWithdrawnEventFields on ApplicationWithdrawnEvent {
+    id
+    event {
+      ...EventFields
+    }
+    group {
+      name
+    }
+    application {
+      id
+      runtimeId
+    }
+  }
+  ${EventFields}
+`
+export const OpeningCanceledEventFields = gql`
+  fragment OpeningCanceledEventFields on OpeningCanceledEvent {
+    id
+    event {
+      ...EventFields
+    }
+    group {
+      name
+    }
+    opening {
+      id
+      runtimeId
+    }
+  }
+  ${EventFields}
+`
+export const StatusTextChangedEventFields = gql`
+  fragment StatusTextChangedEventFields on StatusTextChangedEvent {
+    id
+    event {
+      ...EventFields
+    }
+    group {
+      name
+    }
+    metadata
+    result {
+      __typename
+      ... on UpcomingOpeningAdded {
+        upcomingOpeningId
+      }
+      ... on UpcomingOpeningRemoved {
+        upcomingOpeningId
+      }
+      ... on WorkingGroupMetadataSet {
+        metadataId
+      }
+      ... on InvalidActionMetadata {
+        reason
+      }
+    }
+  }
+  ${EventFields}
+`
+export const GetMemberById = gql`
+  query getMemberById($id: ID!) {
+    membershipByUniqueInput(where: { id: $id }) {
+      ...MembershipFields
+    }
+  }
+  ${MembershipFields}
+`
+export const GetMembershipSystemSnapshotAt = gql`
+  query getMembershipSystemSnapshotAt($time: DateTime!) {
+    membershipSystemSnapshots(where: { snapshotTime_eq: $time }, orderBy: snapshotTime_DESC, limit: 1) {
+      ...MembershipSystemSnapshotFields
+    }
+  }
+  ${MembershipSystemSnapshotFields}
+`
+export const GetMembershipSystemSnapshotBefore = gql`
+  query getMembershipSystemSnapshotBefore($time: DateTime!) {
+    membershipSystemSnapshots(where: { snapshotTime_lt: $time }, orderBy: snapshotTime_DESC, limit: 1) {
+      ...MembershipSystemSnapshotFields
+    }
+  }
+  ${MembershipSystemSnapshotFields}
+`
+export const GetMembershipBoughtEventsByMemberId = gql`
+  query getMembershipBoughtEventsByMemberId($memberId: ID!) {
+    membershipBoughtEvents(where: { newMemberId_eq: $memberId }) {
+      ...MembershipBoughtEventFields
+    }
+  }
+  ${MembershipBoughtEventFields}
+`
+export const GetMemberProfileUpdatedEventsByMemberId = gql`
+  query getMemberProfileUpdatedEventsByMemberId($memberId: ID!) {
+    memberProfileUpdatedEvents(where: { memberId_eq: $memberId }) {
+      ...MemberProfileUpdatedEventFields
+    }
+  }
+  ${MemberProfileUpdatedEventFields}
+`
+export const GetMemberAccountsUpdatedEventsByMemberId = gql`
+  query getMemberAccountsUpdatedEventsByMemberId($memberId: ID!) {
+    memberAccountsUpdatedEvents(where: { memberId_eq: $memberId }) {
+      ...MemberAccountsUpdatedEventFields
+    }
+  }
+  ${MemberAccountsUpdatedEventFields}
+`
+export const GetMemberInvitedEventsByNewMemberId = gql`
+  query getMemberInvitedEventsByNewMemberId($newMemberId: ID!) {
+    memberInvitedEvents(where: { newMemberId_eq: $newMemberId }) {
+      ...MemberInvitedEventFields
+    }
+  }
+  ${MemberInvitedEventFields}
+`
+export const GetInvitesTransferredEventsBySourceMemberId = gql`
+  query getInvitesTransferredEventsBySourceMemberId($sourceMemberId: ID!) {
+    invitesTransferredEvents(where: { sourceMemberId_eq: $sourceMemberId }) {
+      ...InvitesTransferredEventFields
+    }
+  }
+  ${InvitesTransferredEventFields}
+`
+export const GetStakingAccountAddedEventsByMemberId = gql`
+  query getStakingAccountAddedEventsByMemberId($memberId: ID!) {
+    stakingAccountAddedEvents(where: { memberId_eq: $memberId }) {
+      ...StakingAccountAddedEventFields
+    }
+  }
+  ${StakingAccountAddedEventFields}
+`
+export const GetStakingAccountConfirmedEventsByMemberId = gql`
+  query getStakingAccountConfirmedEventsByMemberId($memberId: ID!) {
+    stakingAccountConfirmedEvents(where: { memberId_eq: $memberId }) {
+      ...StakingAccountConfirmedEventFields
+    }
+  }
+  ${StakingAccountConfirmedEventFields}
+`
+export const GetStakingAccountRemovedEventsByMemberId = gql`
+  query getStakingAccountRemovedEventsByMemberId($memberId: ID!) {
+    stakingAccountRemovedEvents(where: { memberId_eq: $memberId }) {
+      ...StakingAccountRemovedEventFields
+    }
+  }
+  ${StakingAccountRemovedEventFields}
+`
+export const GetReferralCutUpdatedEventsByEventId = gql`
+  query getReferralCutUpdatedEventsByEventId($eventId: ID!) {
+    referralCutUpdatedEvents(where: { eventId_eq: $eventId }) {
+      ...ReferralCutUpdatedEventFields
+    }
+  }
+  ${ReferralCutUpdatedEventFields}
+`
+export const GetMembershipPriceUpdatedEventsByEventId = gql`
+  query getMembershipPriceUpdatedEventsByEventId($eventId: ID!) {
+    membershipPriceUpdatedEvents(where: { eventId_eq: $eventId }) {
+      ...MembershipPriceUpdatedEventFields
+    }
+  }
+  ${MembershipPriceUpdatedEventFields}
+`
+export const GetInitialInvitationBalanceUpdatedEventsByEventId = gql`
+  query getInitialInvitationBalanceUpdatedEventsByEventId($eventId: ID!) {
+    initialInvitationBalanceUpdatedEvents(where: { eventId_eq: $eventId }) {
+      ...InitialInvitationBalanceUpdatedEventFields
+    }
+  }
+  ${InitialInvitationBalanceUpdatedEventFields}
+`
+export const GetInitialInvitationCountUpdatedEventsByEventId = gql`
+  query getInitialInvitationCountUpdatedEventsByEventId($eventId: ID!) {
+    initialInvitationCountUpdatedEvents(where: { eventId_eq: $eventId }) {
+      ...InitialInvitationCountUpdatedEventFields
+    }
+  }
+  ${InitialInvitationCountUpdatedEventFields}
+`
+export const GetOpeningById = gql`
+  query getOpeningById($openingId: ID!) {
+    workingGroupOpeningByUniqueInput(where: { id: $openingId }) {
+      ...OpeningFields
+    }
+  }
+  ${OpeningFields}
+`
+export const GetApplicationById = gql`
+  query getApplicationById($applicationId: ID!) {
+    workingGroupApplicationByUniqueInput(where: { id: $applicationId }) {
+      ...ApplicationFields
+    }
+  }
+  ${ApplicationFields}
+`
+export const GetWorkingGroupByName = gql`
+  query getWorkingGroupByName($name: String!) {
+    workingGroupByUniqueInput(where: { name: $name }) {
+      ...WorkingGroupFields
+    }
+  }
+  ${WorkingGroupFields}
+`
+export const GetUpcomingOpeningByCreatedInEventId = gql`
+  query getUpcomingOpeningByCreatedInEventId($createdInEventId: ID!) {
+    upcomingWorkingGroupOpenings(where: { createdInEventId_eq: $createdInEventId }) {
+      ...UpcomingOpeningFields
+    }
+  }
+  ${UpcomingOpeningFields}
+`
+export const GetWorkingGroupMetadataSnapshotAt = gql`
+  query getWorkingGroupMetadataSnapshotAt($groupId: ID!, $timestamp: DateTime!) {
+    workingGroupMetadata(where: { createdAt_eq: $timestamp, groupId_eq: $groupId }, orderBy: createdAt_DESC, limit: 1) {
+      ...WorkingGroupMetadataFields
+    }
+  }
+  ${WorkingGroupMetadataFields}
+`
+export const GetWorkingGroupMetadataSnapshotBefore = gql`
+  query getWorkingGroupMetadataSnapshotBefore($groupId: ID!, $timestamp: DateTime!) {
+    workingGroupMetadata(where: { createdAt_lt: $timestamp, groupId_eq: $groupId }, orderBy: createdAt_DESC, limit: 1) {
+      ...WorkingGroupMetadataFields
+    }
+  }
+  ${WorkingGroupMetadataFields}
+`
+export const GetAppliedOnOpeningEventsByEventId = gql`
+  query getAppliedOnOpeningEventsByEventId($eventId: ID!) {
+    appliedOnOpeningEvents(where: { eventId_eq: $eventId }) {
+      ...AppliedOnOpeningEventFields
+    }
+  }
+  ${AppliedOnOpeningEventFields}
+`
+export const GetOpeningAddedEventsByEventId = gql`
+  query getOpeningAddedEventsByEventId($eventId: ID!) {
+    openingAddedEvents(where: { eventId_eq: $eventId }) {
+      ...OpeningAddedEventFields
+    }
+  }
+  ${OpeningAddedEventFields}
+`
+export const GetOpeningFilledEventsByEventId = gql`
+  query getOpeningFilledEventsByEventId($eventId: ID!) {
+    openingFilledEvents(where: { eventId_eq: $eventId }) {
+      ...OpeningFilledEventFields
+    }
+  }
+  ${OpeningFilledEventFields}
+`
+export const GetApplicationWithdrawnEventsByEventId = gql`
+  query getApplicationWithdrawnEventsByEventId($eventId: ID!) {
+    applicationWithdrawnEvents(where: { eventId_eq: $eventId }) {
+      ...ApplicationWithdrawnEventFields
+    }
+  }
+  ${ApplicationWithdrawnEventFields}
+`
+export const GetOpeningCancelledEventsByEventId = gql`
+  query getOpeningCancelledEventsByEventId($eventId: ID!) {
+    openingCanceledEvents(where: { eventId_eq: $eventId }) {
+      ...OpeningCanceledEventFields
+    }
+  }
+  ${OpeningCanceledEventFields}
+`
+export const GetStatusTextChangedEventsByEventId = gql`
+  query getStatusTextChangedEventsByEventId($eventId: ID!) {
+    statusTextChangedEvents(where: { eventId_eq: $eventId }) {
+      ...StatusTextChangedEventFields
+    }
+  }
+  ${StatusTextChangedEventFields}
+`

File diff suppressed because it is too large
+ 0 - 127
tests/integration-tests/src/graphql/generated/schema.ts


+ 14 - 0
tests/integration-tests/src/graphql/queries/common.graphql

@@ -0,0 +1,14 @@
+fragment BlockFields on Block {
+  number
+  timestamp
+  network
+}
+
+fragment EventFields on Event {
+  inBlock {
+    ...BlockFields
+  }
+  inExtrinsic
+  indexInBlock
+  type
+}

+ 57 - 0
tests/integration-tests/src/graphql/queries/membership.graphql

@@ -0,0 +1,57 @@
+fragment MemberMetadataFields on MemberMetadata {
+  name
+  about
+}
+
+fragment MembershipFields on Membership {
+  id
+  handle
+  metadata {
+    ...MemberMetadataFields
+  }
+  controllerAccount
+  rootAccount
+  registeredAtBlock {
+    ...BlockFields
+  }
+  registeredAtTime
+  entry
+  isVerified
+  inviteCount
+  invitedBy {
+    id
+  }
+  invitees {
+    id
+  }
+  boundAccounts
+}
+
+query getMemberById ($id: ID!) {
+  membershipByUniqueInput(where: { id: $id }) {
+    ...MembershipFields
+  }
+}
+
+fragment MembershipSystemSnapshotFields on MembershipSystemSnapshot {
+  snapshotBlock {
+    ...BlockFields
+  }
+  snapshotTime
+  referralCut
+  invitedInitialBalance
+  defaultInviteCount
+  membershipPrice
+}
+
+query getMembershipSystemSnapshotAt ($time: DateTime!) {
+  membershipSystemSnapshots(where: { snapshotTime_eq: $time }, orderBy: snapshotTime_DESC, limit: 1) {
+    ...MembershipSystemSnapshotFields
+  }
+}
+
+query getMembershipSystemSnapshotBefore ($time: DateTime!) {
+  membershipSystemSnapshots(where: { snapshotTime_lt: $time }, orderBy: snapshotTime_DESC, limit: 1) {
+    ...MembershipSystemSnapshotFields
+  }
+}

+ 218 - 0
tests/integration-tests/src/graphql/queries/membershipEvents.graphql

@@ -0,0 +1,218 @@
+
+# TODO: Always query events by eventId instead of memberid?
+
+fragment MembershipBoughtEventFields on MembershipBoughtEvent {
+  id
+  event {
+    ...EventFields
+  }
+  newMember {
+    id
+  }
+  rootAccount
+  controllerAccount
+  handle
+  metadata {
+    ...MemberMetadataFields
+  }
+  referrer {
+    id
+  }
+}
+# FIXME: Can this be a unique result?
+query getMembershipBoughtEventsByMemberId ($memberId: ID!) {
+  membershipBoughtEvents(where: { newMemberId_eq: $memberId }) {
+    ...MembershipBoughtEventFields
+  }
+}
+
+fragment MemberProfileUpdatedEventFields on MemberProfileUpdatedEvent {
+  id
+  event {
+    ...EventFields
+  }
+  member {
+    id
+  }
+  newHandle
+  newMetadata {
+    name
+    about
+  }
+}
+
+query getMemberProfileUpdatedEventsByMemberId ($memberId: ID!) {
+  memberProfileUpdatedEvents(where: { memberId_eq: $memberId }) {
+    ...MemberProfileUpdatedEventFields
+  }
+}
+
+fragment MemberAccountsUpdatedEventFields on MemberAccountsUpdatedEvent {
+  id
+  event {
+    ...EventFields
+  }
+  member {
+    id
+  }
+  newRootAccount
+  newControllerAccount
+}
+
+query getMemberAccountsUpdatedEventsByMemberId ($memberId: ID!) {
+  memberAccountsUpdatedEvents(where: { memberId_eq: $memberId }) {
+    ...MemberAccountsUpdatedEventFields
+  }
+}
+
+fragment MemberInvitedEventFields on MemberInvitedEvent {
+  id
+  event {
+    ...EventFields
+  }
+  invitingMember {
+    id
+  }
+  newMember {
+    id
+  }
+  rootAccount
+  controllerAccount
+  handle
+  metadata {
+    ...MemberMetadataFields
+  }
+}
+
+query getMemberInvitedEventsByNewMemberId ($newMemberId: ID!) {
+  memberInvitedEvents(where: { newMemberId_eq: $newMemberId }) {
+    ...MemberInvitedEventFields
+  }
+}
+
+fragment InvitesTransferredEventFields on InvitesTransferredEvent {
+  id
+  event {
+    ...EventFields
+  }
+  sourceMember {
+    id
+  }
+  targetMember {
+    id
+  }
+  numberOfInvites
+}
+
+query getInvitesTransferredEventsBySourceMemberId ($sourceMemberId: ID!) {
+  invitesTransferredEvents(where: { sourceMemberId_eq: $sourceMemberId }) {
+    ...InvitesTransferredEventFields
+  }
+}
+
+fragment StakingAccountAddedEventFields on StakingAccountAddedEvent {
+  id
+  event {
+    ...EventFields
+  }
+  member {
+    id
+  }
+  account
+}
+
+query getStakingAccountAddedEventsByMemberId ($memberId: ID!) {
+  stakingAccountAddedEvents(where: { memberId_eq: $memberId }) {
+    ...StakingAccountAddedEventFields
+  }
+}
+
+fragment StakingAccountConfirmedEventFields on StakingAccountConfirmedEvent {
+  id
+  event {
+    ...EventFields
+  }
+  member {
+    id
+  }
+  account
+}
+
+query getStakingAccountConfirmedEventsByMemberId ($memberId: ID!) {
+  stakingAccountConfirmedEvents(where: { memberId_eq: $memberId }) {
+    ...StakingAccountConfirmedEventFields
+  }
+}
+
+fragment StakingAccountRemovedEventFields on StakingAccountRemovedEvent {
+  id
+  event {
+    ...EventFields
+  }
+  member {
+    id
+  }
+  account
+}
+
+query getStakingAccountRemovedEventsByMemberId ($memberId: ID!) {
+  stakingAccountRemovedEvents(where: { memberId_eq: $memberId }) {
+    ...StakingAccountRemovedEventFields
+  }
+}
+
+fragment ReferralCutUpdatedEventFields on ReferralCutUpdatedEvent {
+  id
+  event {
+    ...EventFields
+  }
+  newValue
+}
+
+query getReferralCutUpdatedEventsByEventId ($eventId: ID!) {
+  referralCutUpdatedEvents(where: { eventId_eq: $eventId }) {
+    ...ReferralCutUpdatedEventFields
+  }
+}
+
+fragment MembershipPriceUpdatedEventFields on MembershipPriceUpdatedEvent {
+  id
+  event {
+    ...EventFields
+  }
+  newPrice
+}
+
+query getMembershipPriceUpdatedEventsByEventId ($eventId: ID!) {
+  membershipPriceUpdatedEvents(where: { eventId_eq: $eventId }) {
+    ...MembershipPriceUpdatedEventFields
+  }
+}
+
+fragment InitialInvitationBalanceUpdatedEventFields on InitialInvitationBalanceUpdatedEvent {
+  id
+  event {
+    ...EventFields
+  }
+  newInitialBalance
+}
+
+query getInitialInvitationBalanceUpdatedEventsByEventId ($eventId: ID!) {
+  initialInvitationBalanceUpdatedEvents(where: { eventId_eq: $eventId }) {
+    ...InitialInvitationBalanceUpdatedEventFields
+  }
+}
+
+fragment InitialInvitationCountUpdatedEventFields on InitialInvitationCountUpdatedEvent {
+  id
+  event {
+    ...EventFields
+  }
+  newInitialInvitationCount
+}
+
+query getInitialInvitationCountUpdatedEventsByEventId ($eventId: ID!) {
+  initialInvitationCountUpdatedEvents(where: { eventId_eq: $eventId }) {
+    ...InitialInvitationCountUpdatedEventFields
+  }
+}

+ 206 - 0
tests/integration-tests/src/graphql/queries/workingGroups.graphql

@@ -0,0 +1,206 @@
+fragment ApplicationBasicFields on WorkingGroupApplication {
+  id
+  runtimeId
+  status {
+    __typename
+    ... on ApplicationStatusCancelled {
+      openingCancelledEventId
+    }
+    ... on ApplicationStatusWithdrawn {
+      applicationWithdrawnEventId
+    }
+    ... on ApplicationStatusAccepted {
+      openingFilledEventId
+    }
+    ... on ApplicationStatusRejected {
+      openingFilledEventId
+    }
+  }
+}
+
+fragment OpeningStatusFields on WorkingGroupOpeningStatus {
+  __typename
+  ... on OpeningStatusFilled {
+    openingFilledEventId
+  }
+  ... on OpeningStatusCancelled {
+    openingCancelledEventId
+  }
+}
+
+fragment ApplicationFormQuestionFields on ApplicationFormQuestion {
+  question
+  type
+  index
+}
+
+fragment OpeningMetadataFields on WorkingGroupOpeningMetadata {
+  shortDescription
+  description
+  hiringLimit
+  expectedEnding
+  applicationDetails
+  applicationFormQuestions {
+    ...ApplicationFormQuestionFields
+  }
+}
+
+fragment WorkerFields on Worker {
+  id
+  runtimeId
+  group {
+    name
+  }
+  membership {
+    id
+  }
+  roleAccount
+  rewardAccount
+  stakeAccount
+  status {
+    __typename
+  }
+  isLead
+  stake
+  payouts {
+    id
+  }
+  hiredAtBlock {
+    ...BlockFields
+  }
+  hiredAtTime
+  application {
+    ...ApplicationBasicFields
+  }
+  storage
+}
+
+fragment WorkingGroupMetadataFields on WorkingGroupMetadata {
+  id
+  status
+  statusMessage
+  about
+  description
+  setAtBlock {
+    ...BlockFields
+  }
+}
+
+# TODO: Don't query group leader here?
+fragment OpeningFields on WorkingGroupOpening {
+  id
+  runtimeId
+  group {
+    name
+    leader {
+      runtimeId
+    }
+  }
+  applications {
+    ...ApplicationBasicFields
+  }
+  type
+  status {
+    ...OpeningStatusFields
+  }
+  metadata {
+    ...OpeningMetadataFields
+  }
+  stakeAmount
+  unstakingPeriod
+  rewardPerBlock
+  createdAtBlock {
+    ...BlockFields
+  }
+  createdAt
+}
+
+query getOpeningById ($openingId: ID!) {
+  workingGroupOpeningByUniqueInput(where: { id: $openingId }) {
+    ...OpeningFields
+  }
+}
+
+fragment ApplicationFields on WorkingGroupApplication {
+  ...ApplicationBasicFields
+  createdAtBlock {
+    ...BlockFields
+  }
+  createdAt
+  opening {
+    id
+    runtimeId
+  }
+  applicant {
+    id
+  }
+  roleAccount
+  rewardAccount
+  stakingAccount
+  answers {
+    question {
+      question
+    }
+    answer
+  }
+  stake
+}
+
+query getApplicationById ($applicationId: ID!) {
+  workingGroupApplicationByUniqueInput(where: { id: $applicationId }) {
+    ...ApplicationFields
+  }
+}
+
+fragment WorkingGroupFields on WorkingGroup {
+  id
+  name
+  metadata {
+    ...WorkingGroupMetadataFields
+  }
+  leader {
+    id
+  }
+  budget
+}
+
+query getWorkingGroupByName ($name: String!) {
+  workingGroupByUniqueInput(where: { name: $name }) {
+    ...WorkingGroupFields
+  }
+}
+
+fragment UpcomingOpeningFields on UpcomingWorkingGroupOpening {
+  id
+  group {
+    name
+  }
+  metadata {
+    ...OpeningMetadataFields
+  }
+  expectedStart
+  stakeAmount
+  rewardPerBlock
+  createdAtBlock {
+    ...BlockFields
+  }
+  createdAt
+}
+
+query getUpcomingOpeningByCreatedInEventId ($createdInEventId: ID!) {
+  upcomingWorkingGroupOpenings(where: { createdInEventId_eq: $createdInEventId }) {
+    ...UpcomingOpeningFields
+  }
+}
+
+query getWorkingGroupMetadataSnapshotAt ($groupId: ID!, $timestamp: DateTime!) {
+  workingGroupMetadata(where: { createdAt_eq: $timestamp, groupId_eq: $groupId }, orderBy: createdAt_DESC, limit: 1) {
+    ...WorkingGroupMetadataFields
+  }
+}
+
+query getWorkingGroupMetadataSnapshotBefore ($groupId: ID!, $timestamp: DateTime!) {
+  workingGroupMetadata(where: { createdAt_lt: $timestamp, groupId_eq: $groupId }, orderBy: createdAt_DESC, limit: 1) {
+    ...WorkingGroupMetadataFields
+  }
+}

+ 138 - 0
tests/integration-tests/src/graphql/queries/workingGroupsEvents.graphql

@@ -0,0 +1,138 @@
+fragment AppliedOnOpeningEventFields on AppliedOnOpeningEvent {
+  id
+  event {
+    ...EventFields
+  }
+  group {
+    name
+  }
+  opening {
+    id
+    runtimeId
+  }
+  application {
+    id
+    runtimeId
+  }
+}
+
+query getAppliedOnOpeningEventsByEventId ($eventId: ID!) {
+  appliedOnOpeningEvents(where: { eventId_eq: $eventId }) {
+    ...AppliedOnOpeningEventFields
+  }
+}
+
+fragment OpeningAddedEventFields on OpeningAddedEvent {
+  id
+  event {
+    ...EventFields
+  }
+  group {
+    name
+  }
+  opening {
+    id
+    runtimeId
+  }
+}
+
+query getOpeningAddedEventsByEventId ($eventId: ID!) {
+  openingAddedEvents(where: { eventId_eq: $eventId }) {
+    ...OpeningAddedEventFields
+  }
+}
+
+fragment OpeningFilledEventFields on OpeningFilledEvent {
+  id
+  event {
+    ...EventFields
+  }
+  group {
+    name
+  }
+  opening {
+    id
+    runtimeId
+  }
+  workersHired {
+    ...WorkerFields
+    }
+}
+
+query getOpeningFilledEventsByEventId ($eventId: ID!) {
+  openingFilledEvents(where: { eventId_eq: $eventId }) {
+    ...OpeningFilledEventFields
+  }
+}
+
+fragment ApplicationWithdrawnEventFields on ApplicationWithdrawnEvent {
+  id
+  event {
+    ...EventFields
+  }
+  group {
+    name
+  }
+  application {
+    id
+    runtimeId
+  }
+}
+
+query getApplicationWithdrawnEventsByEventId ($eventId: ID!) {
+  applicationWithdrawnEvents(where: { eventId_eq: $eventId }) {
+    ...ApplicationWithdrawnEventFields
+  }
+}
+
+fragment OpeningCanceledEventFields on OpeningCanceledEvent {
+  id
+  event {
+    ...EventFields
+  }
+  group {
+    name
+  }
+  opening {
+    id
+    runtimeId
+  }
+}
+
+query getOpeningCancelledEventsByEventId ($eventId: ID!) {
+  openingCanceledEvents(where: { eventId_eq: $eventId }) {
+    ...OpeningCanceledEventFields
+  }
+}
+
+fragment StatusTextChangedEventFields on  StatusTextChangedEvent {
+  id
+  event {
+    ...EventFields
+  }
+  group {
+    name
+  }
+  metadata
+  result {
+    __typename
+    ... on UpcomingOpeningAdded {
+      upcomingOpeningId
+    }
+    ... on UpcomingOpeningRemoved {
+      upcomingOpeningId
+    }
+    ... on WorkingGroupMetadataSet {
+      metadataId
+    }
+    ... on InvalidActionMetadata {
+      reason
+    }
+  }
+}
+
+query getStatusTextChangedEventsByEventId ($eventId: ID!) {
+  statusTextChangedEvents(where: { eventId_eq: $eventId }) {
+    ...StatusTextChangedEventFields
+  }
+}

+ 2 - 2
tests/integration-tests/src/types.ts

@@ -2,14 +2,14 @@ import { MemberId } from '@joystream/types/common'
 import { ApplicationId, OpeningId, WorkerId, ApplyOnOpeningParameters } from '@joystream/types/working-group'
 import { Event } from '@polkadot/types/interfaces/system'
 import { BTreeMap } from '@polkadot/types'
-import { Event as GenericEventData } from './QueryNodeApiSchema.generated'
+import { EventFieldsFragment } from './graphql/generated/queries'
 
 export type MemberContext = {
   account: string
   memberId: MemberId
 }
 
-export type AnyQueryNodeEvent = { event: GenericEventData }
+export type AnyQueryNodeEvent = { event: EventFieldsFragment }
 
 export interface EventDetails {
   event: Event

+ 103 - 48
yarn.lock

@@ -1901,13 +1901,21 @@
   dependencies:
     prop-types "^15.7.2"
 
-"@graphql-codegen/cli@^1.21.3":
-  version "1.21.3"
-  resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-1.21.3.tgz#3506c5d019c6995be1927bd4d9c67a739fafe5e6"
-  integrity sha512-jwg0mKhseg0QI4/T4IQcttTBCZgnahiTWqnYWIK+E8nrbXCE9o2hxvaYin/Kq9+5oFtxDePED56cjVs/ESRw6g==
+"@graphql-codegen/add@^2.0.2":
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-2.0.2.tgz#4acbb95be9ebb859a3cebfe7132fdf49ffe06dd8"
+  integrity sha512-0X1ofeSvAjCNcLar2ZR1EOmm5dvyKJMFbgM+ySf1PaHyoi3yf/xRI2Du81ONzQ733Lhmn3KTX1VKybm/OB1Qtg==
+  dependencies:
+    "@graphql-codegen/plugin-helpers" "^1.18.2"
+    tslib "~2.0.1"
+
+"@graphql-codegen/cli@^1.21.4":
+  version "1.21.4"
+  resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-1.21.4.tgz#41ce6abc6b33e369a3ee795621373b8ffa1aadeb"
+  integrity sha512-fCrPn7DeGnCU6d9xjo04VSzIGioVLj1BAjuZpgRJNNjyzTmv+qZJJVTWfD55J17XxlhFsfKJBprtdmRxZ3V2hw==
   dependencies:
     "@graphql-codegen/core" "1.17.9"
-    "@graphql-codegen/plugin-helpers" "^1.18.4"
+    "@graphql-codegen/plugin-helpers" "^1.18.5"
     "@graphql-tools/apollo-engine-loader" "^6"
     "@graphql-tools/code-file-loader" "^6"
     "@graphql-tools/git-loader" "^6"
@@ -1920,7 +1928,7 @@
     "@graphql-tools/utils" "^7.0.0"
     ansi-escapes "^4.3.1"
     chalk "^4.1.0"
-    change-case-all "1.0.12"
+    change-case-all "1.0.14"
     chokidar "^3.4.3"
     common-tags "^1.8.0"
     cosmiconfig "^7.0.0"
@@ -1941,7 +1949,7 @@
     mkdirp "^1.0.4"
     string-env-interpolation "^1.0.1"
     ts-log "^2.2.3"
-    tslib "~2.1.0"
+    tslib "~2.2.0"
     valid-url "^1.0.9"
     wrap-ansi "^7.0.0"
     yaml "^1.10.0"
@@ -1957,7 +1965,17 @@
     "@graphql-tools/utils" "^6"
     tslib "~2.0.1"
 
-"@graphql-codegen/plugin-helpers@^1.18.2", "@graphql-codegen/plugin-helpers@^1.18.3", "@graphql-codegen/plugin-helpers@^1.18.4":
+"@graphql-codegen/import-types-preset@^1.18.1":
+  version "1.18.1"
+  resolved "https://registry.yarnpkg.com/@graphql-codegen/import-types-preset/-/import-types-preset-1.18.1.tgz#86d7c41243e1a5fca9ecfff5c0fa5e3f9d0ab3aa"
+  integrity sha512-kEu47bv/q/hn8JdaLu8dj34CM7zwCePq7OcnsIGtMtIA0SMZ5TvpO3VkDuYUQd0lh/dckVwIfasZv/3U8Tw2pQ==
+  dependencies:
+    "@graphql-codegen/add" "^2.0.2"
+    "@graphql-codegen/plugin-helpers" "^1.18.2"
+    "@graphql-codegen/visitor-plugin-common" "^1.17.20"
+    tslib "~2.0.1"
+
+"@graphql-codegen/plugin-helpers@^1.18.2":
   version "1.18.4"
   resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.18.4.tgz#0adc4c0f88386a50b7a69d358080b6ee54fc3b16"
   integrity sha512-dpfhUmn9GOS8ByoOPIN3V4Nn9HX7sl9NR7Hf26TgN6Clg7cQvkT6XjHdS2e56Q3kWrxZT1zJ1sEa67D3tj9ZtQ==
@@ -1968,31 +1986,63 @@
     lodash "~4.17.20"
     tslib "~2.1.0"
 
-"@graphql-codegen/typescript@^1.21.1":
-  version "1.21.1"
-  resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-1.21.1.tgz#9bce3254b8ef30a6bf64e57ba3991f9be7a19b53"
-  integrity sha512-JF6Vsu5HSv3dAoS2ca3PFLUN0qVxotex/+BgWw/6SKhtd83MUPnzJ/RU3lACg4vuNTCWeQSeGvg8x5qrw9Go9w==
+"@graphql-codegen/plugin-helpers@^1.18.5":
+  version "1.18.5"
+  resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.18.5.tgz#e1d875cfb6a2f7bf4b4318135f7fee6e1200f3b0"
+  integrity sha512-xY8dWdU4+mm+253esLYcKQIgWZEgI3spKPmMRQ+oAxlrCn9oIcdhhiMqNxa9rHS20xpZtlAjozxHulrqjFLuyA==
   dependencies:
-    "@graphql-codegen/plugin-helpers" "^1.18.3"
-    "@graphql-codegen/visitor-plugin-common" "^1.19.0"
+    "@graphql-tools/utils" "^7.0.0"
+    common-tags "1.8.0"
+    import-from "3.0.0"
+    lodash "~4.17.20"
+    tslib "~2.2.0"
+
+"@graphql-codegen/typescript-document-nodes@^1.17.11":
+  version "1.17.11"
+  resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-document-nodes/-/typescript-document-nodes-1.17.11.tgz#0d1af3be2f99d993a9dc8d71c3a17853dfabe5c9"
+  integrity sha512-ZBQeJBUZTwcAMDasCkR7RrKHVMy4d9mIWVgfuY0P+oDAQDIBbVSxA1A+sgxw92MQjVLozkqsZ6c5tmEArJA2Vg==
+  dependencies:
+    "@graphql-codegen/plugin-helpers" "^1.18.5"
+    "@graphql-codegen/visitor-plugin-common" "^1.20.0"
     auto-bind "~4.0.0"
-    tslib "~2.1.0"
+    tslib "~2.2.0"
 
-"@graphql-codegen/visitor-plugin-common@^1.19.0":
-  version "1.19.1"
-  resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.19.1.tgz#2584588351a343a2ad8cd76dde62eb2ec25abe18"
-  integrity sha512-MJZXe5vXxV6PLOgHhQoz93gnjzJtbnVQXQKqVEcbyB9W8ImoKuTHsEf/eJ6yCL79f7X/2dnOOM84d5Osh1eaKg==
+"@graphql-codegen/typescript-operations@^1.17.16":
+  version "1.17.16"
+  resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-1.17.16.tgz#75eb389f268b2dbd2e46b235bcb957be561c31cb"
+  integrity sha512-DoWIhg/c2XS4IpgRLRXRBJDwmKoIl6KuzL2iO9GElX0rdjYguwbqx6iDV1pgktTTajMy8pXP56oZhmEcZLRD2Q==
+  dependencies:
+    "@graphql-codegen/plugin-helpers" "^1.18.5"
+    "@graphql-codegen/typescript" "^1.22.0"
+    "@graphql-codegen/visitor-plugin-common" "^1.20.0"
+    auto-bind "~4.0.0"
+    tslib "~2.2.0"
+
+"@graphql-codegen/typescript@^1.22.0":
+  version "1.22.0"
+  resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-1.22.0.tgz#d05be3a971e5d75a076a43e123b6330f4366a6ab"
+  integrity sha512-YzN/3MBYHrP110m8JgUWQIHt7Ivi3JXiq0RT5XNx/F9mVOSbZz6Ezbaji8YJA3y04Gl2f6ZgtdGazWANUvcOcg==
+  dependencies:
+    "@graphql-codegen/plugin-helpers" "^1.18.5"
+    "@graphql-codegen/visitor-plugin-common" "^1.20.0"
+    auto-bind "~4.0.0"
+    tslib "~2.2.0"
+
+"@graphql-codegen/visitor-plugin-common@^1.17.20", "@graphql-codegen/visitor-plugin-common@^1.20.0":
+  version "1.20.0"
+  resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.20.0.tgz#38d829eab7370c79aa5229190788f94adcae8f76"
+  integrity sha512-AYrpy8NA3DpvhDLqYGerQRv44S+YAMPKtwT8x9GNVjzP0gVfmqi3gG1bDWbP5sm6kOZKvDC0kTxGePuBSZerxw==
   dependencies:
-    "@graphql-codegen/plugin-helpers" "^1.18.4"
+    "@graphql-codegen/plugin-helpers" "^1.18.5"
     "@graphql-tools/optimize" "^1.0.1"
     "@graphql-tools/relay-operation-optimizer" "^6"
     array.prototype.flatmap "^1.2.4"
     auto-bind "~4.0.0"
-    change-case-all "1.0.12"
+    change-case-all "1.0.14"
     dependency-graph "^0.11.0"
     graphql-tag "^2.11.0"
     parse-filepath "^1.0.2"
-    tslib "~2.1.0"
+    tslib "~2.2.0"
 
 "@graphql-tools/apollo-engine-loader@^6":
   version "6.2.5"
@@ -8958,23 +9008,23 @@ chalk@^4.0.0, chalk@^4.1.0:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
-change-case-all@1.0.12:
-  version "1.0.12"
-  resolved "https://registry.yarnpkg.com/change-case-all/-/change-case-all-1.0.12.tgz#ae3e0faf5e610e8e25c5d5eaa4a6d5c2f1d68797"
-  integrity sha512-zdQus7R0lkprF99lrWUC5bFj6Nog4Xt4YCEjQ/vM4vbc6b5JHFBQMxRPAjfx+HJH8WxMzH0E+lQ8yQJLgmPCBg==
-  dependencies:
-    change-case "^4.1.1"
-    is-lower-case "^2.0.1"
-    is-upper-case "^2.0.1"
-    lower-case "^2.0.1"
-    lower-case-first "^2.0.1"
-    sponge-case "^1.0.0"
-    swap-case "^2.0.1"
-    title-case "^3.0.2"
-    upper-case "^2.0.1"
-    upper-case-first "^2.0.1"
-
-change-case@^4.1.1:
+change-case-all@1.0.14:
+  version "1.0.14"
+  resolved "https://registry.yarnpkg.com/change-case-all/-/change-case-all-1.0.14.tgz#bac04da08ad143278d0ac3dda7eccd39280bfba1"
+  integrity sha512-CWVm2uT7dmSHdO/z1CXT/n47mWonyypzBbuCy5tN7uMg22BsfkhwT6oHmFCAk+gL1LOOxhdbB9SZz3J1KTY3gA==
+  dependencies:
+    change-case "^4.1.2"
+    is-lower-case "^2.0.2"
+    is-upper-case "^2.0.2"
+    lower-case "^2.0.2"
+    lower-case-first "^2.0.2"
+    sponge-case "^1.0.1"
+    swap-case "^2.0.2"
+    title-case "^3.0.3"
+    upper-case "^2.0.2"
+    upper-case-first "^2.0.2"
+
+change-case@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/change-case/-/change-case-4.1.2.tgz#fedfc5f136045e2398c0410ee441f95704641e12"
   integrity sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==
@@ -16422,7 +16472,7 @@ is-ipfs@^0.6.0, is-ipfs@~0.6.0, is-ipfs@~0.6.1:
     multibase "~0.6.0"
     multihashes "~0.4.13"
 
-is-lower-case@^2.0.1:
+is-lower-case@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-2.0.2.tgz#1c0884d3012c841556243483aa5d522f47396d2a"
   integrity sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==
@@ -16692,7 +16742,7 @@ is-unc-path@^1.0.0:
   dependencies:
     unc-path-regex "^0.1.2"
 
-is-upper-case@^2.0.1:
+is-upper-case@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-2.0.2.tgz#f1105ced1fe4de906a5f39553e7d3803fd804649"
   integrity sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==
@@ -19102,7 +19152,7 @@ loud-rejection@^1.0.0:
     currently-unhandled "^0.4.1"
     signal-exit "^3.0.0"
 
-lower-case-first@^2.0.1:
+lower-case-first@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/lower-case-first/-/lower-case-first-2.0.2.tgz#64c2324a2250bf7c37c5901e76a5b5309301160b"
   integrity sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==
@@ -19114,7 +19164,7 @@ lower-case@^1.1.1:
   resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
   integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
 
-lower-case@^2.0.1, lower-case@^2.0.2:
+lower-case@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
   integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
@@ -25808,7 +25858,7 @@ split@^1.0.0, split@^1.0.1:
   dependencies:
     through "2"
 
-sponge-case@^1.0.0:
+sponge-case@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/sponge-case/-/sponge-case-1.0.1.tgz#260833b86453883d974f84854cdb63aecc5aef4c"
   integrity sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==
@@ -26573,7 +26623,7 @@ swagger-schema-official@2.0.0-bab6bed:
   resolved "https://registry.yarnpkg.com/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz#70070468d6d2977ca5237b2e519ca7d06a2ea3fd"
   integrity sha1-cAcEaNbSl3ylI3suUZyn0Gouo/0=
 
-swap-case@^2.0.1:
+swap-case@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/swap-case/-/swap-case-2.0.2.tgz#671aedb3c9c137e2985ef51c51f9e98445bf70d9"
   integrity sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==
@@ -27007,7 +27057,7 @@ tinycolor2@^1.4.1:
   resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
   integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==
 
-title-case@^3.0.2:
+title-case@^3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982"
   integrity sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==
@@ -27420,6 +27470,11 @@ tslib@^2, tslib@~2.1.0:
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
   integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
 
+tslib@~2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
+  integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
+
 tsscmp@1.0.6:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
@@ -28012,7 +28067,7 @@ update-notifier@^5.0.0:
     semver-diff "^3.1.1"
     xdg-basedir "^4.0.0"
 
-upper-case-first@^2.0.1, upper-case-first@^2.0.2:
+upper-case-first@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-2.0.2.tgz#992c3273f882abd19d1e02894cc147117f844324"
   integrity sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==
@@ -28024,7 +28079,7 @@ upper-case@^1.1.1:
   resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
   integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=
 
-upper-case@^2.0.1, upper-case@^2.0.2:
+upper-case@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-2.0.2.tgz#d89810823faab1df1549b7d97a76f8662bae6f7a"
   integrity sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==

Some files were not shown because too many files changed in this diff