Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/tokens-cache-invalidation-567.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@vuetify/v0": patch
---

fix(createTokens): a token removed via its own `ticket.unregister()` no longer leaves a stale value in the resolution cache (#567)

`resolve()` results were only invalidated through the context-level mutator methods, so removing a token via its ticket's own `unregister()` — which is bound to the underlying registry — left the cache stale and subsequent `resolve()` calls returned the removed value. Cache invalidation now runs off registry mutation events, covering every removal and update path uniformly.
68 changes: 14 additions & 54 deletions packages/0/src/composables/createTokens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import { isObject, isString, isUndefined, UNSAFE_KEYS } from '#v0/utilities'
// Types
import type { RegistryContext, RegistryContextOptions, RegistryOptions, RegistryTicket } from '#v0/composables/createRegistry'
import type { ContextTrinity } from '#v0/composables/createTrinity'
import type { ID } from '#v0/types'

export interface TokenAlias<T = unknown> {
[key: string]: unknown
Expand Down Expand Up @@ -167,10 +166,23 @@ export function createTokens<
options: TokenOptions = {},
): E {
const logger = useLogger()
const registry = createRegistry<Z>(options)
const registry = createRegistry<Z>({ ...options, events: true })

const cache = new Map<string, unknown | undefined>()

// Invalidate the resolution cache on any structural mutation, including a
// ticket's own `unregister()` — subscribing covers every path uniformly,
// whereas wrapping the context methods misses ticket-level self-removal.
function invalidate () {
cache.clear()
}

registry.on('register:ticket', invalidate)
registry.on('unregister:ticket', invalidate)
registry.on('update:ticket', invalidate)
registry.on('reindex:registry', invalidate)
registry.on('clear:registry', invalidate)

registry.onboard(flatten(tokens, options.prefix, !!options.flat) as Partial<Z>[])

function isAlias (token: unknown): token is string {
Expand Down Expand Up @@ -266,60 +278,8 @@ export function createTokens<
return result
}

const {
register: _register,
upsert: _upsert,
unregister: _unregister,
onboard: _onboard,
offboard: _offboard,
move: _move,
clear: _clear,
} = registry

function register (registration?: Partial<Z & RegistryTicket>) {
cache.clear()
return _register(registration)
}

function upsert (id: ID, registration?: Partial<Z>, event?: string) {
cache.clear()
return _upsert(id, registration, event)
}

function unregister (id: ID) {
cache.clear()
return _unregister(id)
}

function onboard (registrations: Partial<Z & RegistryTicket>[]) {
cache.clear()
return _onboard(registrations)
}

function offboard (ids: ID[]) {
cache.clear()
return _offboard(ids)
}

function move (id: ID, index: number) {
cache.clear()
return _move(id, index)
}

function clear () {
cache.clear()
return _clear()
}

return {
...registry,
register,
upsert,
unregister,
onboard,
offboard,
move,
clear,
resolve,
isAlias,
get size () {
Expand Down
Loading