Cache API

There are times where Houdini’s automatic cache updates or list operation fragments are not sufficient. For those times, Houdini provides a programatic API for interacting with the cache directly. If you find yourself relying on this API for a considerable amount of your business logic, please open an issue or discussion on GitHub so we can try to figure out if there is something that Houdini could be doing better. This should be considered an advanced escape hatch for one-off situations.

Enabling the API

This API is currently considered experimental and while we refine it, we might need to dramatically change it overall shape. Until it’s ready, we want to reserve the ability to break its API on any minor version. We understand this is not technically semantic versioning but ultimately it will let us refine the API against real applications and lead to a better solution faster.

In order to acknowledge this, you will need to enable the imperativeCache flag in your config file:

houdini.config.js
export default {
    // ...
    features: {
        imperativeCache: true,
    }
}

Records

The primary unit of the cache api is the Record. It acts as a proxy for interacting with entities in Houdini’s cache and does not actually hold onto any values. Creating a record is done with the get method on the cache exported from $houdini:

import { cache } from '$houdini'

const user = cache.get('User', { id: '1' })

Retrieving Values

Once you have the reference, you can query any cached fields or related records using the read method on the record:

import { cache, graphql } from '$houdini'

const user = cache.get('User', { id: '1' })

const info = user.read({
	fragment: graphql(`
		fragment UserInfo on User {
			firstName
		}
	`)
})

console.log(`hello ${info.data.firstName}!`)

To extract values from the root of your cache (the Query type), use the read method on the cache:

import { cache, graphql } from '$houdini'

const allUsers = cache.read({
	query: graphql(`
		query AllUsersCache {
			users {
				name
			}
		}
	`)
})

console.log('Users:', allUsers.data?.users)

Your documents can use variables to read dynamic fields to match any situation. This includes both queries and fragments:

import { cache, graphql } from '$houdini'

// read data from the root of your api
cache.read({
	query: graphql(`
		query AllUsersCache($pattern: String!) {
			users {
				firstName(pattern: $pattern)
			}
		}
	`)
	variables: {
		pattern: "capitalize"
	}
})


// use fragment variables to read a specific field/argument combo:
cache.get('User', { id: '1' }).read({
	fragment: graphql(`
		fragment UserInfo on User
			@arguments(pattern: { type: "String" }) {
				firstName(pattern: $pattern)
			}
	`),
	variables: {
		pattern: "capitalize"
	}
})

For more information about fragment variables, head over to the fragment api reference.

Updating Cache Values

To update your cache’s data, you can use the write method on any record:

import { cache, graphql } from '$houdini'

const user = cache.get('User', { id: '1' })

user.write({
	fragment: graphql(`
		fragment UserInfo on User {
			firstName
		}
	`),
	data: {
		firstName: 'New name'
	}
})

Just like read, there is also a way to write values to the root of your api:

import { cache, graphql } from '$houdini'

cache.write({
	query: graphql(`
		query AllUsersCache {
			users {
				name
			}
		}
	`)
	data: {
		users: [
			{ name: "Harry" },
		]
	}
})

Your documents can use variables to write dynamic fields to match any situation. This includes both queries and fragments

import { cache, graphql } from '$houdini'

// update data at the root of your api
cache.write({
	query: graphql(`
		query AllUsersCache($pattern: String!) {
			users {
				firstName(pattern: $pattern)
			}
		}
	`)
	data: {
		users: [
			{ name: "Harry" },
		]
	},
	variables: {
		pattern: "capitalize"
	}
})


// use fragment variables to update a specific field/argument combo:
cache.get('User', { id: '1' }).write({
	fragment: graphql(`
		fragment UserInfo on User
			@arguments(pattern: { type: "String" }) {
				firstName(pattern: $pattern)
			}
	`),
	data: {
		firstName: 'New Name'
	},
	variables: {
		pattern: "capitalize"
	}
})

For more information about fragment variables, head over to the fragment api reference.

Updating Relationships

The fragment and query that you pass do not have to be limited to only fields on the record. You can use the same API in order to change a link between two records.

import { cache, graphql } from '$houdini'

const user = cache.get('User', { id: '1' })

// set the parent field to be user 2
user.write({
	fragment: graphql(`
		fragment UserInfo on User {
			parent {
				id
			}
		}
	`),
	data: {
		parent: {
			id: '2'
		}
	}
})

// set the list of friends to be users 2 and 3
user.write({
	fragment: graphql(`
		fragment UserInfo on User {
			friends {
				id
			}
		}
	`),
	data: {
		friends: [{ id: '2' }, { id: '3' }]
	}
})

Deleting Records

You can delete a record from the cache using the delete method:

const user = cache.get('User', { id: '1' })

user.delete()

Lists

Another primitive provided by the cache instance is List and it provide a programatic API for the same operations supported by the list operation fragments.

Accessing a list can be done with the list method:

const allUsers = cache.list('All_Users')
const userFriends = cache.list('User_Friends', { parentID: '1' })
const allFriends = cache.list('User_Friends', { allLists: true })

You can mutate the list using the prepend, append, remove, and toggle methods:

const allUsers = cache.list('All_Users')
const user1 = cache.get('User', { id: '1' })

// add it to the beginning
allUsers.prepend(user1)

// add it to the end
allFriends.append(user1)

// remove it from the list
allFriends.remove(user1)

// if its in list, remove it. Otherwise, add it to the front.
// You can also also pass 'last' to insert it to the end of the list
allFriends.toggle('first', user1)

If you only want to operate on the list depending on argument values, you can use the when method

allFriends.when({ favorites: true }).append(user1)

Stale Data

If you want fine-grained logic for marking data as stale, you can use the programmatic api. For more information on stale data in Houdini, check out the Caching Data guide.

import { cache, graphql } from '$houdini'

// Mark everything stale
cache.markStale()

// Mark all type 'UserNodes' stale
cache.markStale('UserNodes')

// Mark all type 'UserNodes' field 'totalCount' stale
cache.markStale('UserNodes', { field: 'totalCount' })

// Mark the User 1 stale
const user = cache.get('User', { id: '1' })
user.markStale()

// Mark the User 1 field name stale
const user = cache.get('User', { id: '1' })
user.markStale('name')

// Mark the name field when the pattern field argument is 'capitalize'
const user = cache.get('User', { id: '1' })
user.markStale('name' { when: { pattern: 'capitalize' } })

Resetting the Cache

In some situations, it is necessary to reset the cache to a totally blank state. To do this, you should use the reset method:

import { cache } from '$houdini'

cache.reset()