Skip to content
Oxide
Esc
navigateopen⌘Jpreview
On this page

Query Cache

Cache RPC results by method path and input. Dedupe in-flight calls, invalidate on mutations, or wrap any async function.

Tacho ships a result cache so reads don’t refetch what hasn’t changed. It dedupes identical in-flight calls, keeps results fresh for a window you choose, and never caches failures. There are two surfaces over the same engine:

  • client.query() / client.cache — cached calls through an HTTP client, keyed by method path.
  • query(fn) — a wrapper that gives any async function the same semantics.

Cached client calls

Every HTTP client exposes .query() and .cache next to the plain surface:

import { const tacho: <C extends Context = {}>() => Builder<C, undefined, unique symbol>tacho } from "tacho";
import { function createClient<R>(opts: ClientOptions): RPCClient<R> & QueryExtras<R>createClient } from "tacho/client/http";

const const rpc: Builder<{}, undefined, unique symbol>rpc = tacho<{}>(): Builder<{}, undefined, unique symbol>tacho();
const 
const router: {
    ping: ProcedureDef<{}, undefined, "pong">;
}
router
=
const rpc: <{
    ping: ProcedureDef<{}, undefined, "pong">;
}>(def: {
    ping: ProcedureDef<{}, undefined, "pong">;
}) => {
    ping: ProcedureDef<{}, undefined, "pong">;
}
rpc
({ ping: ProcedureDef<{}, undefined, "pong">ping: const rpc: Builder<{}, undefined, unique symbol>rpc.
run: <"pong">(fn: (opts: {
    input: undefined;
    ctx: {};
    signal: AbortSignal;
}) => "pong" | Promise<"pong">) => ProcedureDef<{}, undefined, "pong">
run
(() => "pong" as type const = "pong"const) });
type
type Router = {
    ping: ProcedureDef<{}, undefined, "pong">;
}
Router
= typeof
const router: {
    ping: ProcedureDef<{}, undefined, "pong">;
}
router
;
const
const client: RPCClient<{
    ping: ProcedureDef<{}, undefined, "pong">;
}> & QueryExtras<{
    ping: ProcedureDef<{}, undefined, "pong">;
}>
client
=
createClient<{
    ping: ProcedureDef<{}, undefined, "pong">;
}>(opts: ClientOptions): RPCClient<{
    ping: ProcedureDef<{}, undefined, "pong">;
}> & QueryExtras<{
    ping: ProcedureDef<{}, undefined, "pong">;
}>
createClient
<
type Router = {
    ping: ProcedureDef<{}, undefined, "pong">;
}
Router
>({ url: stringurl: "/rpc" });
await
const client: RPCClient<{
    ping: ProcedureDef<{}, undefined, "pong">;
}> & QueryExtras<{
    ping: ProcedureDef<{}, undefined, "pong">;
}>
client
.ping: (input?: undefined, opts?: CallOptions | undefined) => Promise<"pong">ping(); // always hits the wire
await
const client: RPCClient<{
    ping: ProcedureDef<{}, undefined, "pong">;
}> & QueryExtras<{
    ping: ProcedureDef<{}, undefined, "pong">;
}>
client
.
query: (opts?: QueryOptions) => RPCClient<{
    ping: ProcedureDef<{}, undefined, "pong">;
}>
query
().ping: (input?: undefined, opts?: CallOptions | undefined) => Promise<"pong">ping(); // same call, but cached with key ["ping"]

The default key is [methodPath, input], hashed stably — object key order in the input does not matter. Identical in-flight calls share one request instead of racing. Pass options to the surface:

import { const tacho: <C extends Context = {}>() => Builder<C, undefined, unique symbol>tacho } from "tacho";
import { function createClient<R>(opts: ClientOptions): RPCClient<R> & QueryExtras<R>createClient } from "tacho/client/http";
import { import zz } from "zod";

const const rpc: Builder<{}, undefined, unique symbol>rpc = tacho<{}>(): Builder<{}, undefined, unique symbol>tacho();
const 
const router: {
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}
router
=
const rpc: <{
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}>(def: {
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}) => {
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}
rpc
({
task: {
    list: ProcedureDef<{}, {
        done: boolean;
    }, string[]>;
}
task
: {
list: ProcedureDef<{}, {
    done: boolean;
}, string[]>
list
: const rpc: Builder<{}, undefined, unique symbol>rpc
.
input<{
    done: boolean;
}>(schema: Schema<{
    done: boolean;
}>): Builder<{}, {
    done: boolean;
}, unique symbol>
input
(import zz.
function object<{
    done: z.ZodBoolean;
}>(shape?: {
    done: z.ZodBoolean;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    done: z.ZodBoolean;
}, z.core.$strip>
object
({ done: z.ZodBooleandone: import zz.function boolean(params?: string | z.core.$ZodBooleanParams): z.ZodBooleanboolean() }))
.
run: <string[]>(fn: (opts: {
    input: {
        done: boolean;
    };
    ctx: {};
    signal: AbortSignal;
}) => string[] | Promise<string[]>) => ProcedureDef<{}, {
    done: boolean;
}, string[]>
run
(() => [] as string[]),
}, }); type
type Router = {
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}
Router
= typeof
const router: {
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}
router
;
const
const client: RPCClient<{
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}> & QueryExtras<{
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}>
client
=
createClient<{
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}>(opts: ClientOptions): RPCClient<{
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}> & QueryExtras<{
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}>
createClient
<
type Router = {
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}
Router
>({ url: stringurl: "/rpc" });
const
const q: RPCClient<{
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}>
q
=
const client: RPCClient<{
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}> & QueryExtras<{
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}>
client
.
query: (opts?: QueryOptions) => RPCClient<{
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}>
query
({ staleTime?: number | undefined
How long results stay fresh in ms. Default: forever, until invalidated.
staleTime
: 30_000 });
await
const q: RPCClient<{
    task: {
        list: ProcedureDef<{}, {
            done: boolean;
        }, string[]>;
    };
}>
q
.
task: RPCClient<{
    list: ProcedureDef<{}, {
        done: boolean;
    }, string[]>;
}>
task
.
list: (input: {
    done: boolean;
}, opts?: CallOptions | undefined) => Promise<string[]>
list
({ done: booleandone: false }); // fresh for 30s, then refetched

A custom key namespaces every call made through that surface; entries still split per input:

import { const tacho: <C extends Context = {}>() => Builder<C, undefined, unique symbol>tacho } from "tacho";
import { function createClient<R>(opts: ClientOptions): RPCClient<R> & QueryExtras<R>createClient } from "tacho/client/http";

const const rpc: Builder<{}, undefined, unique symbol>rpc = tacho<{}>(): Builder<{}, undefined, unique symbol>tacho();
const 
const router: {
    ping: ProcedureDef<{}, undefined, "pong">;
}
router
=
const rpc: <{
    ping: ProcedureDef<{}, undefined, "pong">;
}>(def: {
    ping: ProcedureDef<{}, undefined, "pong">;
}) => {
    ping: ProcedureDef<{}, undefined, "pong">;
}
rpc
({ ping: ProcedureDef<{}, undefined, "pong">ping: const rpc: Builder<{}, undefined, unique symbol>rpc.
run: <"pong">(fn: (opts: {
    input: undefined;
    ctx: {};
    signal: AbortSignal;
}) => "pong" | Promise<"pong">) => ProcedureDef<{}, undefined, "pong">
run
(() => "pong" as type const = "pong"const) });
type
type Router = {
    ping: ProcedureDef<{}, undefined, "pong">;
}
Router
= typeof
const router: {
    ping: ProcedureDef<{}, undefined, "pong">;
}
router
;
const
const client: RPCClient<{
    ping: ProcedureDef<{}, undefined, "pong">;
}> & QueryExtras<{
    ping: ProcedureDef<{}, undefined, "pong">;
}>
client
=
createClient<{
    ping: ProcedureDef<{}, undefined, "pong">;
}>(opts: ClientOptions): RPCClient<{
    ping: ProcedureDef<{}, undefined, "pong">;
}> & QueryExtras<{
    ping: ProcedureDef<{}, undefined, "pong">;
}>
createClient
<
type Router = {
    ping: ProcedureDef<{}, undefined, "pong">;
}
Router
>({ url: stringurl: "/rpc" });
await
const client: RPCClient<{
    ping: ProcedureDef<{}, undefined, "pong">;
}> & QueryExtras<{
    ping: ProcedureDef<{}, undefined, "pong">;
}>
client
.
query: (opts?: QueryOptions) => RPCClient<{
    ping: ProcedureDef<{}, undefined, "pong">;
}>
query
({ key?: unknown[] | undefined
Custom cache key prefix. Default for RPC clients is the method path; default for wrapped functions is a session identity plus the arguments.
key
: ["page", 1] }).ping: (input?: undefined, opts?: CallOptions | undefined) => Promise<"pong">ping(); // cached under ["page", 1, ...]

What is never cached:

  • Streams (async function* procedures). They come back as generators.
  • File uploads — inputs containing File/Blob have no stable hash and bypass the cache.
  • Failures. A rejected call drops its entry so the next call retries.

Invalidation

The .cache handle controls everything the client has cached:

await 
const client: RPCClient<{
    user: {
        get: ProcedureDef<{}, undefined, {
            id: string;
        }>;
        rename: ProcedureDef<{}, undefined, undefined>;
    };
}> & QueryExtras<{
    user: {
        get: ProcedureDef<{}, undefined, {
            id: string;
        }>;
        rename: ProcedureDef<{}, undefined, undefined>;
    };
}>
client
.cache: QueryCacheApicache.
function invalidate(match?: InvalidateMatch, opts?: {
    refetch?: boolean;
}): Promise<void>
Drop matching cache entries. With `{ refetch: true }`, re-run them from the wire.
invalidate
(["user.get"]); // prefix match on key segments
await
const client: RPCClient<{
    user: {
        get: ProcedureDef<{}, undefined, {
            id: string;
        }>;
        rename: ProcedureDef<{}, undefined, undefined>;
    };
}> & QueryExtras<{
    user: {
        get: ProcedureDef<{}, undefined, {
            id: string;
        }>;
        rename: ProcedureDef<{}, undefined, undefined>;
    };
}>
client
.cache: QueryCacheApicache.
function invalidate(match?: InvalidateMatch, opts?: {
    refetch?: boolean;
}): Promise<void>
Drop matching cache entries. With `{ refetch: true }`, re-run them from the wire.
invalidate
((e: CacheEntryInfoe) => e: CacheEntryInfoe.method: stringmethod.String.startsWith(searchString: string, position?: number): boolean
Returns true if the sequence of elements of searchString converted to a String is the same as the corresponding elements of this object (converted to a String) starting at position. Otherwise returns false.
startsWith
("user."));
await
const client: RPCClient<{
    user: {
        get: ProcedureDef<{}, undefined, {
            id: string;
        }>;
        rename: ProcedureDef<{}, undefined, undefined>;
    };
}> & QueryExtras<{
    user: {
        get: ProcedureDef<{}, undefined, {
            id: string;
        }>;
        rename: ProcedureDef<{}, undefined, undefined>;
    };
}>
client
.cache: QueryCacheApicache.
function invalidate(match?: InvalidateMatch, opts?: {
    refetch?: boolean;
}): Promise<void>
Drop matching cache entries. With `{ refetch: true }`, re-run them from the wire.
invalidate
(["user.get"], { refetch?: boolean | undefinedrefetch: true }); // re-run stored params now
const client: RPCClient<{
    user: {
        get: ProcedureDef<{}, undefined, {
            id: string;
        }>;
        rename: ProcedureDef<{}, undefined, undefined>;
    };
}> & QueryExtras<{
    user: {
        get: ProcedureDef<{}, undefined, {
            id: string;
        }>;
        rename: ProcedureDef<{}, undefined, undefined>;
    };
}>
client
.cache: QueryCacheApicache.function keys(): unknown[][]
Keys of cached entries. Order is unspecified.
keys
();
const client: RPCClient<{
    user: {
        get: ProcedureDef<{}, undefined, {
            id: string;
        }>;
        rename: ProcedureDef<{}, undefined, undefined>;
    };
}> & QueryExtras<{
    user: {
        get: ProcedureDef<{}, undefined, {
            id: string;
        }>;
        rename: ProcedureDef<{}, undefined, undefined>;
    };
}>
client
.cache: QueryCacheApicache.function clear(): void
Drop everything.
clear
();

After a mutation, invalidate what it touched:

await 
const client: RPCClient<{
    user: {
        get: ProcedureDef<{}, {
            id: string;
        }, {
            id: string;
        }>;
        rename: ProcedureDef<{}, {
            id: string;
        }, undefined>;
    };
}> & QueryExtras<{
    user: {
        get: ProcedureDef<{}, {
            id: string;
        }, {
            id: string;
        }>;
        rename: ProcedureDef<{}, {
            id: string;
        }, undefined>;
    };
}>
client
.
user: RPCClient<{
    get: ProcedureDef<{}, {
        id: string;
    }, {
        id: string;
    }>;
    rename: ProcedureDef<{}, {
        id: string;
    }, undefined>;
}>
user
.
rename: (input: {
    id: string;
}, opts?: CallOptions | undefined) => Promise<undefined>
rename
({ id: stringid: "1" });
await
const client: RPCClient<{
    user: {
        get: ProcedureDef<{}, {
            id: string;
        }, {
            id: string;
        }>;
        rename: ProcedureDef<{}, {
            id: string;
        }, undefined>;
    };
}> & QueryExtras<{
    user: {
        get: ProcedureDef<{}, {
            id: string;
        }, {
            id: string;
        }>;
        rename: ProcedureDef<{}, {
            id: string;
        }, undefined>;
    };
}>
client
.cache: QueryCacheApicache.
function invalidate(match?: InvalidateMatch, opts?: {
    refetch?: boolean;
}): Promise<void>
Drop matching cache entries. With `{ refetch: true }`, re-run them from the wire.
invalidate
(["user.get"], { refetch?: boolean | undefinedrefetch: true });

Caveat: a router with root-level query or cache procedures shadows these handles on the typed surface.

Shared and persistent caches: createCache()

A named, sharable, optionally persistent cache. Pass the handle to wrappers or clients so they share one store — invalidating on the handle hits every consumer:

const const cache: QueryCacheApicache = 
function createCache(opts?: {
    name?: string;
    driver?: CacheDriver;
}): CacheHandle
Creates a cache handle backed by your driver. Multiple surfaces can share one handle; give them explicit `key` prefixes so cross-surface invalidation stays meaningful. ```ts const cache = createCache({ name: "myapp", driver: localStorageDriver() }); const listTasks = query(listTasksRaw, { cache, key: ["tasks"] }); await cache.invalidate(["tasks"]); ```
createCache
({
name?: string | undefinedname: "myapp", driver?: CacheDriver | undefineddriver: function localStorageDriver(storage?: Storage): CacheDriver
Storage adapter over Web Storage (`localStorage` / `sessionStorage`).
localStorageDriver
(),
}); const
const loadTasks: (() => Promise<unknown>) & {
    invalidate(match?: ((info: {
        args: [];
    }) => boolean) | undefined): void;
    clear(): void;
}
loadTasks
=
query<[], unknown>(fn: () => Promise<unknown>, opts?: QueryOptions): (() => Promise<unknown>) & {
    invalidate(match?: ((info: {
        args: [];
    }) => boolean) | undefined): void;
    clear(): void;
}
Memoizes an async function — tacho server action stubs included. ```ts import { query } from "tacho"; import { getUser } from "./users.server"; const getUserCached = query(getUser, { staleTime: 30_000 }); await getUserCached({ id: "1" }); // deduped + cached per distinct input await getUserCached.invalidate(); // drop every cached input of this function ``` The default cache identity ties to this wrapper instance plus a stable hash of the arguments. Pass `{ key: ["users"] }` to pin the invalidation scope explicitly. Streams bypass the cache; failures are never cached.
query
(const listTasks: () => Promise<unknown>listTasks, { cache?: QueryCacheApi | undefined
A shared, optionally persistent cache returned by {@link createCache } .
cache
, key?: unknown[] | undefined
Custom cache key prefix. Default for RPC clients is the method path; default for wrapped functions is a session identity plus the arguments.
key
: ["tasks"] });
const
const loadUsers: (() => Promise<unknown>) & {
    invalidate(match?: ((info: {
        args: [];
    }) => boolean) | undefined): void;
    clear(): void;
}
loadUsers
=
query<[], unknown>(fn: () => Promise<unknown>, opts?: QueryOptions): (() => Promise<unknown>) & {
    invalidate(match?: ((info: {
        args: [];
    }) => boolean) | undefined): void;
    clear(): void;
}
Memoizes an async function — tacho server action stubs included. ```ts import { query } from "tacho"; import { getUser } from "./users.server"; const getUserCached = query(getUser, { staleTime: 30_000 }); await getUserCached({ id: "1" }); // deduped + cached per distinct input await getUserCached.invalidate(); // drop every cached input of this function ``` The default cache identity ties to this wrapper instance plus a stable hash of the arguments. Pass `{ key: ["users"] }` to pin the invalidation scope explicitly. Streams bypass the cache; failures are never cached.
query
(const getUsers: () => Promise<unknown>getUsers, { cache?: QueryCacheApi | undefined
A shared, optionally persistent cache returned by {@link createCache } .
cache
, key?: unknown[] | undefined
Custom cache key prefix. Default for RPC clients is the method path; default for wrapped functions is a session identity plus the arguments.
key
: ["users"] });
await const cache: QueryCacheApicache.
function invalidate(match?: InvalidateMatch, opts?: {
    refetch?: boolean;
}): Promise<void>
Drop matching cache entries. With `{ refetch: true }`, re-run them from the wire.
invalidate
(["tasks"]); // tasks only, users untouched

HTTP clients accept the same handle so .query() surfaces share it:

const client = createClient<Router>({ url: "/rpc", cache });

A driver persists settled values as namespaced JSON envelopes and hydrates them once up front; entries whose fresh window expired on disk are ignored. Anything that cannot survive a JSON round-trip (File, Blob, class instances) is not persisted. Failures and in-flight calls never touch storage.

localStorageDriver() wraps Web Storage. For Redis, IndexedDB, or anything else, implement four methods:

const const redisDriver: CacheDriverredisDriver: 
type CacheDriver = {
    keys(): string[] | Promise<string[]>;
    get(key: string): string | undefined | Promise<string | undefined>;
    set(key: string, value: string): void | Promise<void>;
    delete(key: string): void | Promise<void>;
}
Minimal storage contract for {@link createCache } . Values are JSON strings.
CacheDriver
= {
function keys(): string[] | Promise<string[]>keys: async () => (await
const redis: {
    keys(p: string): Promise<string[]>;
    get(k: string): Promise<string | undefined>;
    set(k: string, v: string): Promise<void>;
    del(k: string): Promise<void>;
}
redis
.function keys(p: string): Promise<string[]>keys("app:*")).Array<string>.map<string>(callbackfn: (value: string, index: number, array: string[]) => string, thisArg?: any): string[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((k: stringk) => k: stringk.String.slice(start?: number, end?: number): string
Returns a section of a string.
@paramstart The index to the beginning of the specified portion of stringObj.@paramend The index to the end of the specified portion of stringObj. The substring includes the characters up to, but not including, the character indicated by end. If this value is not specified, the substring continues to the end of stringObj.
slice
(4)),
function get(key: string): string | undefined | Promise<string | undefined>get: (_key: string_key) => var undefinedundefined, function set(key: string, value: string): void | Promise<void>set: async () => {}, function delete(key: string): void | Promise<void>delete: async () => {}, };

When persisting, pass explicit keys to your wrappers — default wrapper identities reset with each session.

Wrap any function: query(fn, opts?)

No typed router client in hand? Wrap the function directly. Works for plain fetch helpers or any async call:

// @filename: app.ts
import { 
function query<A extends unknown[], T>(fn: (...args: A) => Promise<T>, opts?: QueryOptions): ((...args: A) => Promise<T>) & {
    invalidate(match?: (info: {
        args: A;
    }) => boolean): void;
    clear(): void;
}
Memoizes an async function — tacho server action stubs included. ```ts import { query } from "tacho"; import { getUser } from "./users.server"; const getUserCached = query(getUser, { staleTime: 30_000 }); await getUserCached({ id: "1" }); // deduped + cached per distinct input await getUserCached.invalidate(); // drop every cached input of this function ``` The default cache identity ties to this wrapper instance plus a stable hash of the arguments. Pass `{ key: ["users"] }` to pin the invalidation scope explicitly. Streams bypass the cache; failures are never cached.
query
} from "tacho";
import {
const getUser: (i: {
    id: string;
}) => Promise<{
    id: string;
}>
getUser
} from "./api";
const
const loadUser: ((i: {
    id: string;
}) => Promise<{
    id: string;
}>) & {
    invalidate(match?: ((info: {
        args: [i: {
            id: string;
        }];
    }) => boolean) | undefined): void;
    clear(): void;
}
loadUser
=
query<[i: {
    id: string;
}], {
    id: string;
}>(fn: (i: {
    id: string;
}) => Promise<{
    id: string;
}>, opts?: QueryOptions): ((i: {
    id: string;
}) => Promise<{
    id: string;
}>) & {
    invalidate(match?: ((info: {
        args: [i: {
            id: string;
        }];
    }) => boolean) | undefined): void;
    clear(): void;
}
Memoizes an async function — tacho server action stubs included. ```ts import { query } from "tacho"; import { getUser } from "./users.server"; const getUserCached = query(getUser, { staleTime: 30_000 }); await getUserCached({ id: "1" }); // deduped + cached per distinct input await getUserCached.invalidate(); // drop every cached input of this function ``` The default cache identity ties to this wrapper instance plus a stable hash of the arguments. Pass `{ key: ["users"] }` to pin the invalidation scope explicitly. Streams bypass the cache; failures are never cached.
query
(
const getUser: (i: {
    id: string;
}) => Promise<{
    id: string;
}>
getUser
, { staleTime?: number | undefined
How long results stay fresh in ms. Default: forever, until invalidated.
staleTime
: 30_000 });
await
const loadUser: (i: {
    id: string;
}) => Promise<{
    id: string;
}>
loadUser
({ id: stringid: "1" }); // deduped + cached per distinct input
await
const loadUser: ((i: {
    id: string;
}) => Promise<{
    id: string;
}>) & {
    invalidate(match?: ((info: {
        args: [i: {
            id: string;
        }];
    }) => boolean) | undefined): void;
    clear(): void;
}
loadUser
.
function invalidate(match?: ((info: {
    args: [i: {
        id: string;
    }];
}) => boolean) | undefined): void
Drop cached entries; pass a predicate over the call args to filter.
invalidate
();
await
const loadUser: ((i: {
    id: string;
}) => Promise<{
    id: string;
}>) & {
    invalidate(match?: ((info: {
        args: [i: {
            id: string;
        }];
    }) => boolean) | undefined): void;
    clear(): void;
}
loadUser
.
function invalidate(match?: ((info: {
    args: [i: {
        id: string;
    }];
}) => boolean) | undefined): void
Drop cached entries; pass a predicate over the call args to filter.
invalidate
(({
args: [i: {
    id: string;
}]
args
}) =>
args: [i: {
    id: string;
}]
args
[0].id: stringid === "1"); // selective
const loadUser: ((i: {
    id: string;
}) => Promise<{
    id: string;
}>) & {
    invalidate(match?: ((info: {
        args: [i: {
            id: string;
        }];
    }) => boolean) | undefined): void;
    clear(): void;
}
loadUser
.function clear(): voidclear();

The default cache identity ties to the wrapper instance plus a stable hash of the arguments. Two wrappers never collide. Pass { key: ["users"] } to pin the invalidation scope explicitly — useful across hot reloads, since instance identity resets when code reloads.

Options are shared with the client surface: staleTime (default: forever, until invalidated) and key.

Was this page helpful?