diff --git a/.changeset/tokens-cache-invalidation-567.md b/.changeset/tokens-cache-invalidation-567.md new file mode 100644 index 000000000..f196e8f91 --- /dev/null +++ b/.changeset/tokens-cache-invalidation-567.md @@ -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. diff --git a/packages/0/src/composables/createTokens/index.ts b/packages/0/src/composables/createTokens/index.ts index ce69500d0..275f93274 100644 --- a/packages/0/src/composables/createTokens/index.ts +++ b/packages/0/src/composables/createTokens/index.ts @@ -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 { [key: string]: unknown @@ -167,10 +166,23 @@ export function createTokens< options: TokenOptions = {}, ): E { const logger = useLogger() - const registry = createRegistry(options) + const registry = createRegistry({ ...options, events: true }) const cache = new Map() + // 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[]) function isAlias (token: unknown): token is string { @@ -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) { - cache.clear() - return _register(registration) - } - - function upsert (id: ID, registration?: Partial, event?: string) { - cache.clear() - return _upsert(id, registration, event) - } - - function unregister (id: ID) { - cache.clear() - return _unregister(id) - } - - function onboard (registrations: Partial[]) { - 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 () {