Introduction
A type-safe API toolkit with automatic cache invalidation and zero boilerplate
Spoosh is a type-safe API toolkit with automatic cache management. Define your API schema once, then get type-safe requests with automatic cache invalidation based on URL hierarchy.
// 1. Define your schema (or infer it from Hono / Elysia / OpenAPI)
type API = {
users: { GET: { data: User[]; query: { page?: number } } };
"users/:id": {
GET: { data: User };
PUT: { data: User; body: Partial<User> };
};
"users/:id/posts": {
GET: { data: Post[] };
POST: { data: Post; body: NewPost };
};
};
// 2. Create the client
const spoosh = new Spoosh<API, Error>("/api").use([
cachePlugin({ staleTime: 5 * 60 * 1000 }),
deduplicationPlugin(),
invalidationPlugin(),
// ...other plugins
]);
// 3. Use in components — paths are fully typed from your schema
export const { injectRead, injectWrite } = create(spoosh);Using Hono or Elysia? You can infer the schema directly from your server routes for end-to-end type safety with zero manual definitions.
What Makes Spoosh Different
Automatic Cache Invalidation
Spoosh uses a tag-based caching system. Each query is automatically tagged based on its resolved API path:
// Tags are generated from the resolved path:
injectRead((api) => api("users").GET());
// → tag: "users"
injectRead((api) => api("users/:id").GET({ params: { id: 123 } }));
// → tag: "users/123"
injectRead((api) => api("users/:id/posts").GET({ params: { id: 123 } }));
// → tag: "users/123/posts"When you mutate data, Spoosh automatically invalidates related queries using wildcard pattern matching.
write = injectWrite((api) => api("users/:id/posts").POST());
// Default invalidation pattern: ["users", "users/*"]
// Matches: "users", "users/123", "users/123/posts", etc.
await write.trigger({ params: { id: 123 }, body: newPost });Type-Safe Routing with Zero Strings
Define your API schema once, then use a function-based API that gives you autocomplete for every endpoint, parameter, and response.
// Fully typed paths
user = injectRead((api) => api("users/:id").GET({ params: { id: 123 } }));
posts = injectRead((api) =>
api("users/:id/posts").GET({ params: { id: 123 }, query: { page: 1 } })
);TypeScript knows every endpoint exists, every parameter is correct, and every response type matches your schema.
Composable Plugin System
Spoosh has a rich plugin ecosystem. Each plugin adds specific functionality, and TypeScript knows exactly what options are available based on which plugins you install.
import { Spoosh } from "@spoosh/core";
import { cachePlugin } from "@spoosh/plugin-cache";
import { retryPlugin } from "@spoosh/plugin-retry";
import { pollingPlugin } from "@spoosh/plugin-polling";
const spoosh = new Spoosh<API, Error>("/api").use([
cachePlugin({ staleTime: 5000 }),
retryPlugin({ retries: 3 }),
pollingPlugin({ interval: 30000 }),
]);
// Options are typed based on installed plugins
injectRead((api) => api("users").GET(), {
staleTime: 10000, // ✓ Known from cachePlugin
retry: { retries: 5 }, // ✓ Known from retryPlugin
interval: 60000, // ✓ Known from pollingPlugin
// randomOption: true // ✗ Type error
});Browse the full list of available plugins on the Plugins page.