Skip to content

Commit

Permalink
Add method to reset cache (#1107)
Browse files Browse the repository at this point in the history
Co-authored-by: Alec Aivazis <[email protected]>
  • Loading branch information
m4tr1k and AlecAivazis committed Jun 22, 2023
1 parent 35cc897 commit 743d85d
Show file tree
Hide file tree
Showing 10 changed files with 327 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/nine-pillows-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'houdini': patch
---

Add method to reset cache state
21 changes: 21 additions & 0 deletions packages/houdini/src/runtime/cache/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,27 @@ export class Cache {
this.#notifySubscribers(toNotify)
}

// reset the whole cache
reset() {
// Reset Subscriptions
const subSpecs = this._internal_unstable.subscriptions.reset()

// Reset StaleManager
this._internal_unstable.staleManager.reset()

// Reset GarbageCollector
this._internal_unstable.lifetimes.reset()

// Reset Lists
this._internal_unstable.lists.reset()

// Reset InMemory Storage
this._internal_unstable.storage.reset()

// Notify Subscribers
this.#notifySubscribers(subSpecs)
}

#notifySubscribers(subs: SubscriptionSpec[]) {
// if there's no one to notify, its a no-op
if (subs.length === 0) {
Expand Down
4 changes: 4 additions & 0 deletions packages/houdini/src/runtime/cache/gc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export class GarbageCollector {
this.cache = cache
}

reset() {
this.lifetimes.clear()
}

resetLifetime(id: string, field: string) {
// if this is the first time we've seen the id
if (!this.lifetimes.get(id)) {
Expand Down
5 changes: 5 additions & 0 deletions packages/houdini/src/runtime/cache/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ export class ListManager {
// delete the lists by field lookups
this.listsByField.get(parentID)!.delete(field)
}

reset() {
this.lists.clear()
this.listsByField.clear()
}
}

export class List {
Expand Down
4 changes: 4 additions & 0 deletions packages/houdini/src/runtime/cache/staleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,8 @@ export class StaleManager {
}
}
}

reset() {
this.fieldsTime.clear()
}
}
4 changes: 4 additions & 0 deletions packages/houdini/src/runtime/cache/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ export class InMemoryStorage {
layer.fields = fields
layer.links = links
}

reset() {
this.data = []
}
}

export class Layer {
Expand Down
21 changes: 20 additions & 1 deletion packages/houdini/src/runtime/cache/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
SubscriptionSpec,
NestedList,
} from '../lib/types'
import type { Cache } from './cache'
import { rootID, type Cache } from './cache'
import { evaluateKey } from './stuff'

export type FieldSelection = [
Expand Down Expand Up @@ -344,6 +344,25 @@ export class InMemorySubscriptions {
}
}

reset() {
// Get all subscriptions that do not start with the rootID
const subscribers = Object.entries(this.subscribers).filter(
([id]) => !id.startsWith(rootID)
)

// Remove those subcribers from this.subscribers
for (const [id, _fields] of subscribers) {
delete this.subscribers[id]
}

// Get list of all SubscriptionSpecs of subscribers
const subscriptionSpecs = subscribers.flatMap(([_id, fields]) =>
Object.values(fields).flatMap((field) => field.map(([spec]) => spec))
)

return subscriptionSpecs
}

private removeSubscribers(id: string, fieldName: string, specs: SubscriptionSpec[]) {
// build up a list of the sets we actually need to remove after
// checking reference counts
Expand Down
245 changes: 245 additions & 0 deletions packages/houdini/src/runtime/cache/tests/reset.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
import { test, expect, vi } from 'vitest'

import { testConfigFile } from '../../../test'
import { RefetchUpdateMode, type SubscriptionSelection } from '../../lib'
import { Cache } from '../cache'

const config = testConfigFile()

test('make sure the cache data was reset', function () {
const cache = new Cache(config)

// save the data
const data = {
viewer: {
id: '1',
firstName: 'bob',
},
}

const selection: SubscriptionSelection = {
fields: {
viewer: {
type: 'User',
visible: true,
keyRaw: 'viewer',
selection: {
fields: {
id: {
type: 'ID',
visible: true,
keyRaw: 'id',
},
firstName: {
type: 'String',
visible: true,
keyRaw: 'firstName',
},
},
},
},
},
}

cache.write({
selection,
data,
})

// reset the cache
cache.reset()

// make sure the data is gone
expect(cache.read({ selection }).data).toBe(null)
})

test('make sure the cache lists were reset', function () {
const cache = new Cache(config)

const selection: SubscriptionSelection = {
fields: {
viewer: {
type: 'User',
visible: true,
keyRaw: 'viewer',
selection: {
fields: {
id: {
type: 'ID',
visible: true,
keyRaw: 'id',
},
friends: {
type: 'User',
visible: true,
keyRaw: 'friends',
updates: [RefetchUpdateMode.append],
list: {
name: 'All_Users',
connection: false,
type: 'User',
},
selection: {
fields: {
id: {
type: 'ID',
visible: true,
keyRaw: 'id',
},
firstName: {
type: 'String',
visible: true,
keyRaw: 'firstName',
},
},
},
},
},
},
},
},
}

cache.write({
selection,
data: {
viewer: {
id: '1',
friends: [
{
id: '2',
firstName: 'jane',
},
{
id: '3',
firstName: 'joe',
},
],
},
},
applyUpdates: [RefetchUpdateMode.append],
})
const set = vi.fn()
cache.subscribe({
rootType: 'Query',
set,
selection,
})

expect(() => cache.list('All_Users')).toBeDefined()

// reset the cache
cache.reset()

// make sure the list doesn't exist
expect(() => cache.list('All_Users')).toThrowError('Cannot find list with name')
})

test('make sure the cache subscribers were reset', function () {
const cache = new Cache(config)

const selection: SubscriptionSelection = {
fields: {
viewer: {
type: 'User',
visible: true,
keyRaw: 'viewer',
selection: {
fields: {
id: {
type: 'ID',
visible: true,
keyRaw: 'id',
},
firstName: {
type: 'String',
visible: true,
keyRaw: 'firstName',
},
friends: {
type: 'User',
visible: true,
keyRaw: 'friends',
updates: [RefetchUpdateMode.append],
selection: {
fields: {
id: {
type: 'ID',
visible: true,
keyRaw: 'id',
},
firstName: {
type: 'String',
visible: true,
keyRaw: 'firstName',
},
},
},
},
},
},
},
},
}

// subscribe to the cache
const set = vi.fn()
cache.subscribe({
rootType: 'Query',
set,
selection,
})

// reset the cache
cache.reset()

// write the data again
cache.write({
parent: 'User:1',
selection,
data: {
viewer: {
id: '1',
firstName: 'bob',
friends: [
{
id: '2',
firstName: 'jane',
},
{
id: '3',
firstName: 'joe',
},
],
},
},
applyUpdates: [RefetchUpdateMode.append],
})

// make sure the subscriber was not called
expect(set).not.toHaveBeenCalled()

cache.write({
selection,
data: {
viewer: {
id: '1',
firstName: 'bob',
friends: [
{
id: '4',
firstName: 'mary',
},
{
id: '5',
firstName: 'jill',
},
],
},
},
applyUpdates: [RefetchUpdateMode.append],
})

// make sure the root subscriber was called because parent was not defined
expect(set).toHaveBeenCalled()
})
8 changes: 8 additions & 0 deletions packages/houdini/src/runtime/public/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,12 @@ Please acknowledge this by setting acceptImperativeInstability to true in your c
): void {
return this._internal_unstable.markTypeStale(type ? { ...options, type } : undefined)
}

/**
* Reset the entire cache by clearing all records and lists
*/

reset(): void {
return this._internal_unstable.reset()
}
}
11 changes: 11 additions & 0 deletions site/src/routes/api/cache/+page.svx
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,14 @@ user.markStale('name')
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:

```typescript
import { cache } from '$houdini'

cache.reset()
```

0 comments on commit 743d85d

Please sign in to comment.