Skip to content

RFC: Intelligent Cache Key System #59

@AliiiBenn

Description

@AliiiBenn

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?

  1. Flexibility - Let the client decide how to cache
  2. No coupling - No internal cache implementation
  3. Works everywhere - tRPC, React Query, Next.js, custom

Cache Key Format

Cache keys are generated automatically:

{collection}:{method}:{key}={value}

Examples:

  • posts:find:status=published
  • users:findById:id=1
  • posts:count:status=draft
  • users:list

Wildcards

Mutations can invalidate patterns:

  • posts:* - invalidates all posts keys
  • posts: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 side

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions