-
Notifications
You must be signed in to change notification settings - Fork 1
RFC: Intelligent Cache Key System #59
Copy link
Copy link
Open
Description
Summary
Each database operation is classified as either a query or a mutation internally. This enables an intelligent cache key system where:
- Queries return metadata with
cacheKeys- keys that can be cached on the client - Mutations return
invalidateKeys- keys that should be revalidated after the mutation
How It Works
Query vs Mutation Classification
// These are queries (read operations) - cached by default
db.posts.find()
db.posts.findById()
db.posts.count()
// These are mutations (write operations) - invalidate cache
db.posts.create()
db.posts.update()
db.posts.delete()Return Metadata
Every operation returns data + metadata:
// Query returns cacheKeys
const result = await db.posts.find({ where: { status: 'published' } })
// result = {
// data: [...],
// meta: {
// cacheKeys: [
// 'posts:find:status=published',
// 'posts:count:status=published',
// 'posts:list'
// ]
// }
// }
// Mutation returns invalidateKeys
const created = await db.posts.create({ data: {...} })
// created = {
// data: {...},
// meta: {
// invalidateKeys: [
// 'posts:*',
// 'posts:count:*',
// 'posts:find:*'
// ]
// }
// }No Internal Caching
The built-in methods do NOT implement internal caching. They just return the data + metadata:
// Built-in methods do NOT cache internally
// They just return data + cache key metadata
const result = await db.posts.find({ where: { status: 'published' } })
// result = {
// data: [...], // The actual data
// meta: {
// cacheKeys: ['posts:find:status=published'] // Just metadata
// }
// }Why This Design?
- Flexibility - Let the client decide how to cache
- No coupling - No internal cache implementation
- Works everywhere - tRPC, React Query, Next.js, custom
Cache Key Format
Cache keys are generated automatically:
{collection}:{method}:{key}={value}
Examples:
posts:find:status=publishedusers:findById:id=1posts:count:status=draftusers:list
Wildcards
Mutations can invalidate patterns:
posts:*- invalidates all posts keysposts:count:*- invalidates all count queries
Custom Method Cache Keys
Custom methods can define their own cache keys:
const posts = collection({
slug: 'posts',
methods: {
findByAuthor: query({
args: z.object({ authorId: z.number() }),
cacheKey: (args) => \`posts:find:authorId=\${args.authorId}\`,
handler: async (ctx, args) => {
return ctx.db.posts.find({ where: { authorId: args.authorId } })
}
})
}
})Relationships and Cache Keys
When querying with relationships, cache keys should include related collections:
// Query with author relationship
const result = await db.posts.find({
where: { status: 'published' },
include: { author: true }
})
// Cache keys include both collections:
// meta.cacheKeys = [
// 'posts:find:status=published',
// 'users:find:posts.author=1' // related collection
// ]Mutations on a collection should invalidate related collections:
// Creating a post with author
await db.posts.create({
data: {
title: 'Hello',
authorId: 1
}
})
// Should invalidate both:
// meta.invalidateKeys = [
// 'posts:*',
// 'users:find:author=1' // related
// ]This requires the schema to know about relationships so invalidation can propagate.
Client Usage
On the client, you can use these keys with any caching solution:
// Example with custom cache
const { data, meta } = await db.posts.find({
where: { status: 'published' }
})
// Client can use cacheKeys however they want:
// - React Query: queryKey = meta.cacheKeys
// - Redis: cache.set(meta.cacheKeys[0], data)
// - Custom: etc.
// After mutation
await db.posts.create({ data: {...} })
// Use meta.invalidateKeys to clear cache on client sideReferences
- tRPC: https://trpc.io
- React Query: https://tanstack.com/query
- Next.js cache: https://nextjs.org/docs/app/building-your-application/data-fetching
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels