feat: add persistent cache and optimistic updates
This commit is contained in:
committed by
liyasthomas
parent
e6707c1e4a
commit
d5123c793a
@@ -87,6 +87,7 @@ const myTeams = useGQLQuery<
|
|||||||
myRole: TeamMemberRole
|
myRole: TeamMemberRole
|
||||||
ownersCount: number
|
ownersCount: number
|
||||||
members: Array<{
|
members: Array<{
|
||||||
|
membershipID: string
|
||||||
user: {
|
user: {
|
||||||
photoURL: string | null
|
photoURL: string | null
|
||||||
displayName: string
|
displayName: string
|
||||||
@@ -107,6 +108,7 @@ const myTeams = useGQLQuery<
|
|||||||
myRole
|
myRole
|
||||||
ownersCount
|
ownersCount
|
||||||
members {
|
members {
|
||||||
|
membershipID
|
||||||
user {
|
user {
|
||||||
photoURL
|
photoURL
|
||||||
displayName
|
displayName
|
||||||
|
|||||||
@@ -13,17 +13,19 @@ import {
|
|||||||
OperationResult,
|
OperationResult,
|
||||||
dedupExchange,
|
dedupExchange,
|
||||||
OperationContext,
|
OperationContext,
|
||||||
cacheExchange,
|
|
||||||
fetchExchange,
|
fetchExchange,
|
||||||
makeOperation,
|
makeOperation,
|
||||||
} from "@urql/core"
|
} from "@urql/core"
|
||||||
import { authExchange } from "@urql/exchange-auth"
|
import { authExchange } from "@urql/exchange-auth"
|
||||||
|
import { offlineExchange } from "@urql/exchange-graphcache"
|
||||||
|
import { makeDefaultStorage } from "@urql/exchange-graphcache/default-storage"
|
||||||
import { devtoolsExchange } from "@urql/devtools"
|
import { devtoolsExchange } from "@urql/devtools"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe, constVoid } from "fp-ts/function"
|
import { pipe, constVoid } from "fp-ts/function"
|
||||||
import { subscribe } from "wonka"
|
import { subscribe } from "wonka"
|
||||||
import clone from "lodash/clone"
|
import clone from "lodash/clone"
|
||||||
|
import gql from "graphql-tag"
|
||||||
import {
|
import {
|
||||||
getAuthIDToken,
|
getAuthIDToken,
|
||||||
probableUser$,
|
probableUser$,
|
||||||
@@ -35,12 +37,105 @@ const BACKEND_GQL_URL =
|
|||||||
? "https://api.hoppscotch.io/graphql"
|
? "https://api.hoppscotch.io/graphql"
|
||||||
: "https://api.hoppscotch.io/graphql"
|
: "https://api.hoppscotch.io/graphql"
|
||||||
|
|
||||||
|
const storage = makeDefaultStorage({
|
||||||
|
idbName: "hoppcache-v1",
|
||||||
|
maxAge: 7,
|
||||||
|
})
|
||||||
|
|
||||||
const client = createClient({
|
const client = createClient({
|
||||||
url: BACKEND_GQL_URL,
|
url: BACKEND_GQL_URL,
|
||||||
exchanges: [
|
exchanges: [
|
||||||
devtoolsExchange,
|
devtoolsExchange,
|
||||||
dedupExchange,
|
dedupExchange,
|
||||||
cacheExchange,
|
// TODO: Extract this outttttttt
|
||||||
|
offlineExchange({
|
||||||
|
keys: {
|
||||||
|
User: (data) => (data as any).uid,
|
||||||
|
TeamMember: (data) => (data as any).membershipID,
|
||||||
|
Team: (data) => data.id as any,
|
||||||
|
},
|
||||||
|
optimistic: {
|
||||||
|
deleteTeam: () => true,
|
||||||
|
leaveTeam: () => true,
|
||||||
|
},
|
||||||
|
updates: {
|
||||||
|
Mutation: {
|
||||||
|
deleteTeam: (_r, { teamID }, cache, _info) => {
|
||||||
|
cache.updateQuery(
|
||||||
|
{
|
||||||
|
query: gql`
|
||||||
|
query {
|
||||||
|
myTeams {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
(data: any) => {
|
||||||
|
console.log(data)
|
||||||
|
data.myTeams = (data as any).myTeams.filter(
|
||||||
|
(x: any) => x.id !== teamID
|
||||||
|
)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
cache.invalidate({
|
||||||
|
__typename: "Team",
|
||||||
|
id: teamID as any,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
leaveTeam: (_r, { teamID }, cache, _info) => {
|
||||||
|
cache.updateQuery(
|
||||||
|
{
|
||||||
|
query: gql`
|
||||||
|
query {
|
||||||
|
myTeams {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
(data: any) => {
|
||||||
|
console.log(data)
|
||||||
|
data.myTeams = (data as any).myTeams.filter(
|
||||||
|
(x: any) => x.id !== teamID
|
||||||
|
)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
cache.invalidate({
|
||||||
|
__typename: "Team",
|
||||||
|
id: teamID as any,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
createTeam: (result, _args, cache, _info) => {
|
||||||
|
cache.updateQuery(
|
||||||
|
{
|
||||||
|
query: gql`
|
||||||
|
{
|
||||||
|
myTeams {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
(data: any) => {
|
||||||
|
console.log(result)
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
|
data.myTeams.push(result.createTeam)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
storage,
|
||||||
|
}),
|
||||||
authExchange({
|
authExchange({
|
||||||
addAuthToOperation({ authState, operation }) {
|
addAuthToOperation({ authState, operation }) {
|
||||||
if (!authState || !authState.authToken) {
|
if (!authState || !authState.authToken) {
|
||||||
@@ -145,7 +240,9 @@ export function useGQLQuery<
|
|||||||
let subscription: { unsubscribe(): void } | null = null
|
let subscription: { unsubscribe(): void } | null = null
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const gqlQuery = client.query<any, QueryVariables>(query, variables)
|
const gqlQuery = client.query<any, QueryVariables>(query, variables, {
|
||||||
|
requestPolicy: "cache-and-network",
|
||||||
|
})
|
||||||
|
|
||||||
const processResult = (result: OperationResult<any, QueryVariables>) =>
|
const processResult = (result: OperationResult<any, QueryVariables>) =>
|
||||||
pipe(
|
pipe(
|
||||||
@@ -218,18 +315,21 @@ export const runMutation = <
|
|||||||
TE.tryCatch(
|
TE.tryCatch(
|
||||||
() =>
|
() =>
|
||||||
client
|
client
|
||||||
.mutation<MutationReturnType>(mutation, variables, additionalConfig)
|
.mutation<MutationReturnType>(mutation, variables, {
|
||||||
|
requestPolicy: "cache-and-network",
|
||||||
|
...additionalConfig,
|
||||||
|
})
|
||||||
.toPromise(),
|
.toPromise(),
|
||||||
() => constVoid() as never // The mutation function can never fail, so this will never be called ;)
|
() => constVoid() as never // The mutation function can never fail, so this will never be called ;)
|
||||||
),
|
),
|
||||||
TE.chainEitherK((result) =>
|
TE.chainEitherK((result) =>
|
||||||
pipe(
|
pipe(
|
||||||
result.data as MutationReturnType, // If we have the result, then okay
|
result.data as MutationReturnType,
|
||||||
E.fromNullable(
|
E.fromNullable(
|
||||||
// Result is null
|
// Result is null
|
||||||
pipe(
|
pipe(
|
||||||
result.error?.networkError, // Check for network error
|
result.error?.networkError,
|
||||||
E.fromNullable(result.error?.name), // If it is null, then it is a GQL error
|
E.fromNullable(result.error?.name),
|
||||||
E.match(
|
E.match(
|
||||||
// The left case (network error was null)
|
// The left case (network error was null)
|
||||||
(gqlErr) =>
|
(gqlErr) =>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { pipe } from "fp-ts/function"
|
|||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { runMutation } from "../GQLClient"
|
import { runMutation } from "../GQLClient"
|
||||||
import { TeamName } from "../types/TeamName"
|
import { TeamName } from "../types/TeamName"
|
||||||
|
import { TeamMemberRole } from "../types/TeamMemberRole"
|
||||||
|
|
||||||
type DeleteTeamErrors =
|
type DeleteTeamErrors =
|
||||||
| "team/not_required_role"
|
| "team/not_required_role"
|
||||||
@@ -24,6 +25,11 @@ export const createTeam = (name: TeamName) =>
|
|||||||
createTeam: {
|
createTeam: {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
members: Array<{ membershipID: string }>
|
||||||
|
myRole: TeamMemberRole
|
||||||
|
ownersCount: number
|
||||||
|
editorsCount: number
|
||||||
|
viewersCount: number
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CreateTeamErrors
|
CreateTeamErrors
|
||||||
@@ -33,6 +39,13 @@ export const createTeam = (name: TeamName) =>
|
|||||||
createTeam(name: $name) {
|
createTeam(name: $name) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
members {
|
||||||
|
membershipID
|
||||||
|
}
|
||||||
|
myRole
|
||||||
|
ownersCount
|
||||||
|
editorsCount
|
||||||
|
viewersCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
"@nuxtjs/toast": "^3.3.1",
|
"@nuxtjs/toast": "^3.3.1",
|
||||||
"@urql/core": "^2.3.3",
|
"@urql/core": "^2.3.3",
|
||||||
"@urql/exchange-auth": "^0.1.6",
|
"@urql/exchange-auth": "^0.1.6",
|
||||||
|
"@urql/exchange-graphcache": "^4.3.5",
|
||||||
"acorn": "^8.5.0",
|
"acorn": "^8.5.0",
|
||||||
"acorn-walk": "^8.2.0",
|
"acorn-walk": "^8.2.0",
|
||||||
"axios": "^0.22.0",
|
"axios": "^0.22.0",
|
||||||
|
|||||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@@ -85,6 +85,7 @@ importers:
|
|||||||
'@urql/core': ^2.3.3
|
'@urql/core': ^2.3.3
|
||||||
'@urql/devtools': ^2.0.3
|
'@urql/devtools': ^2.0.3
|
||||||
'@urql/exchange-auth': ^0.1.6
|
'@urql/exchange-auth': ^0.1.6
|
||||||
|
'@urql/exchange-graphcache': ^4.3.5
|
||||||
'@vue/runtime-dom': ^3.2.19
|
'@vue/runtime-dom': ^3.2.19
|
||||||
'@vue/test-utils': ^1.2.2
|
'@vue/test-utils': ^1.2.2
|
||||||
>>>>>>> ecdc7919 (fix: queries not waiting for authentication)
|
>>>>>>> ecdc7919 (fix: queries not waiting for authentication)
|
||||||
@@ -170,7 +171,11 @@ importers:
|
|||||||
'@nuxtjs/toast': 3.3.1
|
'@nuxtjs/toast': 3.3.1
|
||||||
'@urql/core': 2.3.3_graphql@15.6.0
|
'@urql/core': 2.3.3_graphql@15.6.0
|
||||||
'@urql/exchange-auth': 0.1.6_graphql@15.6.0
|
'@urql/exchange-auth': 0.1.6_graphql@15.6.0
|
||||||
|
<<<<<<< HEAD
|
||||||
>>>>>>> ecdc7919 (fix: queries not waiting for authentication)
|
>>>>>>> ecdc7919 (fix: queries not waiting for authentication)
|
||||||
|
=======
|
||||||
|
'@urql/exchange-graphcache': 4.3.5_graphql@15.6.0
|
||||||
|
>>>>>>> 96cf7746 (feat: add persistent cache and optimistic updates)
|
||||||
acorn: 8.5.0
|
acorn: 8.5.0
|
||||||
acorn-walk: 8.2.0
|
acorn-walk: 8.2.0
|
||||||
axios: 0.22.0
|
axios: 0.22.0
|
||||||
@@ -5995,6 +6000,16 @@ packages:
|
|||||||
wonka: 4.0.15
|
wonka: 4.0.15
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@urql/exchange-graphcache/4.3.5_graphql@15.6.0:
|
||||||
|
resolution: {integrity: sha512-q5/CzNtSxd5fW/YZ94KABmtQ34XliyS+KKKhyJ+/y66D7mYrN/ZEiuKTlnB7FTt9GLZ0yRtgIfXjwoGicB/Tlw==}
|
||||||
|
peerDependencies:
|
||||||
|
graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0
|
||||||
|
dependencies:
|
||||||
|
'@urql/core': 2.3.3_graphql@15.6.0
|
||||||
|
graphql: 15.6.0
|
||||||
|
wonka: 4.0.15
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@vue/babel-helper-vue-jsx-merge-props/1.2.1:
|
/@vue/babel-helper-vue-jsx-merge-props/1.2.1:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user