cf-workers-query
Version:
Automatically cache and revalidate data in Cloudflare Workers. Using the Cache API and Execution Context
163 lines (105 loc) • 4.82 kB
Markdown
# Cf-Workers-Query
Automatically cache and revalidate data in Cloudflare Workers. Using the [Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache) and [`waitUntil`](https://developers.cloudflare.com/workers/runtime-apis/context/) from `cloudflare:workers`.
## Example
```ts
import { createQuery } from 'cf-workers-query';
const { data, error, invalidate } = await createQuery({
queryKey: ['user', userId],
queryFn: async () => {
const user = await fetchUser(userId);
return user;
},
gcTime: 60, // 60 seconds
});
```
## Stale Revalidation
```ts
import { createQuery } from 'cf-workers-query';
export default {
async fetch(request, env, ctx) {
const { data } = await createQuery({
queryKey: ['user', userId],
queryFn: async () => {
const user = await fetchUser(userId);
return user;
},
staleTime: 30, // 30 seconds
gcTime: 60, // 60 seconds
});
return new Response(JSON.stringify(data), {
headers: { 'content-type': 'application/json' },
});
},
};
```
Background revalidation uses `waitUntil` from `cloudflare:workers` to keep the worker alive after responding.
### Hono Example
```ts
import { cache } from 'cf-workers-query/hono';
app.get(
'/user/:id',
cache({
handler: async (ctx) => {
const user = await fetchUser(ctx.req.param('id'));
return ctx.json(user);
},
cacheKey: (ctx) => ['user', ctx.req.param('id')],
gcTime: 3600, // 1 hour in seconds
staleTime: 60, // 60 seconds
})
);
```
## Deduplication
By default, `cf-workers-query` uses a best-effort deduplication strategy via Cache API "processing markers". When multiple concurrent requests hit the same query key:
- **Initial fetches**: A brief (50ms) check is performed to see if another request already wrote the result to cache. If found, the cached result is returned instead of re-fetching.
- **SWR background refreshes**: If a refresh is already in progress for a key, additional refresh attempts are skipped entirely.
This is **best-effort** because CF Cache API has no atomic compare-and-swap operations. In practice, it reduces N concurrent executions to ~1-3.
### Guaranteed Single-Flight with Durable Objects (Optional)
For use cases that require **guaranteed** single-flight execution, an optional Durable Object class is provided:
```ts
// worker entry
export { QueryDeduper } from 'cf-workers-query/durable-object';
```
```toml
# wrangler.toml
[[durable_objects.bindings]]
name = "QUERY_DEDUPER"
class_name = "QueryDeduper"
```
This requires Durable Objects billing and is opt-in.
## API Reference
### queryKey
Type: `QueryKey | null`
An optional key that uniquely identifies the query. Can be an array of values or a `URL`. If set to `null`, the query is not cached.
### queryFn
Type: `() => Promise<Data>`
A function that returns a promise resolving with the data for the query.
### staleTime
Type: `number`
Optional. The amount of time **in seconds** before the query data is considered stale. Default is `0`.
### gcTime
Type: `number`
Optional. The amount of time **in seconds** to keep data in the cache. Maps to `Cache-Control: max-age`. If `0` or falsy, caching is skipped entirely.
### revalidate
Type: `boolean`
Optional. If `true`, the query will directly revalidate data (bypass stale check).
### revalidateMode
Type: `'default' | 'probabilistic'`
Optional. If `'probabilistic'`, uses a probability function to decide whether to revalidate stale data. Based on [Cloudflare's cache stampede prevention](https://blog.cloudflare.com/sometimes-i-cache).
### retry
Type: `number | ((failureCount: number, error: Error) => boolean)`
Optional. Number of retry attempts on failure, or a function that receives the failure count and error and returns whether to retry.
### retryDelay
Type: `number | ((failureCount: number, error: Error) => number)`
Optional. Delay between retries in **milliseconds** (note: unlike `staleTime` and `gcTime` which are in seconds, `retryDelay` is in milliseconds). Can be a fixed number or a function. Defaults to exponential backoff: `Math.min(1000 * 2^attempt, 30000)`.
### cacheName
Type: `string`
Optional. The name of the cache to use. Default is `cf-workers-query-cache`.
### throwOnError
Type: `boolean`
Optional. If `true`, errors from `queryFn` are thrown instead of returned in the `error` field. Useful with Hono's error handling or when you want try/catch control flow.
### enabled
Type: `boolean | ((data: Data) => boolean)`
Optional. Controls whether the query executes and/or caches. If `false`, the query is skipped. If a function, receives the fetched data and returns whether to cache it.
## Credits
Inspired by [TanStack Query](https://tanstack.com/query/latest) but for Cloudflare Workers.