mirror of
https://github.com/vernu/textbee.git
synced 2026-07-30 17:07:46 -04:00
Both bugs were hand-written query keys. lib/api/query-keys.ts exists to prevent exactly this and neither site used it. 1. /auth/who-am-i was cached under two keys. Four call sites used ['whoAmI'] and seven used ['currentUser'], including the typed useCurrentUser hook. So the endpoint was fetched twice on any page using both, and invalidating one never invalidated the other: edit-profile-form refreshed ['currentUser'] and left the email verification banner reading stale data under ['whoAmI'], while use-onboarding had the mirror-image bug. verify-email/page.tsx additionally cached the whole axios response where the others cached the unwrapped user, so unifying the key meant normalising that shape too. One key demands one shape. 2. generate-api-key invalidated ['apiKeys', 'stats'], which no query uses. react-query matches by prefix, so it matched neither the key list (['apiKeys', status]) nor the dashboard stats (['stats']). Generating an API key refreshed nothing at all. It is now three invalidations against queryKeys, using a new apiKeysAll prefix so every status filter refreshes rather than just 'active'. Also added accountDeletionRequestedAt to the User type. It is a real field on the user schema and the deletion banner already reads it; only the type did not know about it. Both bugs are invisible on inspection, which is how they shipped, so the regression tests were each confirmed to fail against the pre-change code before being trusted. Verified: build clean, 0 lint errors (21 warnings, unchanged), 155 unit tests (from 153), 78 e2e. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
26 lines
1.2 KiB
TypeScript
26 lines
1.2 KiB
TypeScript
import type { ApiKeyStatusFilter } from './types'
|
|
|
|
// Single source of truth for react-query cache keys. Values intentionally match
|
|
// the ad-hoc string keys used across the app before this refactor (e.g.
|
|
// ['devices'], ['currentSubscription'], ['apiKeys', 'active']) so migrated and
|
|
// not-yet-migrated components still share the same cache entries.
|
|
export const queryKeys = {
|
|
currentUser: ['currentUser'] as const,
|
|
subscription: ['currentSubscription'] as const,
|
|
stats: ['stats'] as const,
|
|
devices: ['devices'] as const,
|
|
webhooks: ['webhooks'] as const,
|
|
billingPlans: ['billingPlans'] as const,
|
|
apiKeys: (status: ApiKeyStatusFilter = 'active') =>
|
|
['apiKeys', status] as const,
|
|
// Prefix covering every apiKeys list regardless of status filter. Use this
|
|
// to invalidate: a new or revoked key changes the active, revoked and all
|
|
// lists at once, and invalidating only apiKeys('active') leaves the other
|
|
// two serving stale data.
|
|
apiKeysAll: ['apiKeys'] as const,
|
|
deviceMessages: (deviceId: string, filters?: Record<string, unknown>) =>
|
|
filters
|
|
? (['messages', deviceId, filters] as const)
|
|
: (['messages', deviceId] as const),
|
|
}
|