Pinia Colada Client for consuming ZenStack v3's CRUD service
npm install zenstack-pinia-coladaPinia Colada client for ZenStack - The Smart Data Fetching Layer for Vue 3.
- 🔐 Type-safe - Full TypeScript support with automatic type inference
- ⚡️ Automatic caching - Smart caching powered by Pinia Colada
- 🔄 Optimistic updates - Update UI before server responds
- 🎯 Automatic invalidation - Cache invalidation based on data relationships
- 📦 Zero config - Works out of the box with your ZenStack schema
- 🌳 Tree-shakeable - Only bundle what you use
``bash`
npm install zenstack-pinia-colada @pinia/colada piniaor
pnpm add zenstack-pinia-colada @pinia/colada piniaor
yarn add zenstack-pinia-colada @pinia/colada pinia
1. You need a ZenStack project set up (v3.0.0 or higher). See ZenStack documentation for details.
2. Generate your ZenStack schema using npx zenstack generate
Note: This library requires ZenStack v3 to be installed in your project. The library will use your installed version of ZenStack packages.
`typescript
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { PiniaColada } from '@pinia/colada'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(PiniaColada)
app.mount('#app')
`
`vue`
`vue
Loading...
Error: {{ error.message }}
`
Each model in your schema gets the following query hooks:
- useFindUnique(args, options) - Find a unique recorduseFindFirst(args, options)
- - Find the first matching recorduseFindMany(args, options)
- - Find multiple recordsuseInfiniteFindMany(args, options)
- - Paginated query with infinite loadinguseCount(args, options)
- - Count recordsuseAggregate(args, options)
- - Aggregate datauseGroupBy(args, options)
- - Group records
Query Return Values:
`typescript`
{
data: Ref
error: Ref
status: Ref<'pending' | 'success' | 'error'>, // Query status
refresh: () => Promise
// ... and more from Pinia Colada
}
Each model gets these mutation hooks:
- useCreate(options) - Create a recorduseCreateMany(options)
- - Create multiple recordsuseCreateManyAndReturn(options)
- - Create and return multiple recordsuseUpdate(options)
- - Update a recorduseUpdateMany(options)
- - Update multiple recordsuseUpdateManyAndReturn(options)
- - Update and return multiple recordsuseUpsert(options)
- - Create or update a recorduseDelete(options)
- - Delete a recorduseDeleteMany(options)
- - Delete multiple records
Mutation Return Values:
`typescript`
{
mutate: (variables: T) => void, // Trigger mutation
mutateAsync: (variables: T) => Promise
status: Ref<'pending' | 'success' | 'error' | 'idle'>,
data: Ref
error: Ref
// ... and more from Pinia Colada
}
Pinia Colada automatically tracks reactive dependencies in your queries. When using reactive values (refs, computed), wrap your query arguments in a getter function:
`typescript
import { ref, computed } from 'vue'
const userId = ref('123')
const includeDeleted = ref(false)
// ✅ Correct: Use a getter function
const { data: posts } = queries.post.useFindMany(() => ({
where: {
authorId: userId.value, // Unwrap refs inside the getter
deletedAt: includeDeleted.value ? undefined : null
},
}))
// When userId or includeDeleted changes, the query automatically re-runs!
`
Why use a getter function?
The getter function () => ({...}) allows Pinia Colada to track when your reactive values change and automatically re-fetch the query. Inside the getter, unwrap refs with .value.
Alternative patterns:
`typescript
// Using computed (also works)
const queryArgs = computed(() => ({
where: { authorId: userId.value }
}))
const { data } = queries.post.useFindMany(queryArgs)
// Static queries (no reactivity needed)
const { data } = queries.post.useFindMany({
where: { published: true } // No getter needed for static values
})
`
Optimistic updates allow the UI to update immediately before the server responds:
`typescript
const updatePost = queries.post.useUpdate({
optimisticUpdate: true, // Enable optimistic updates
})
updatePost.mutate({
where: { id: '1' },
data: { title: 'New Title' },
})
// UI updates immediately, then syncs with server response
`
Pinia Colada provides many options to customize query behavior:
`typescript`
const { data } = queries.post.useFindMany(
{ where: { published: true } },
{
staleTime: 5000, // Consider data fresh for 5 seconds
gcTime: 300000, // Garbage collection time (default: 5 minutes)
refetchOnMount: true,
refetchOnWindowFocus: true,
enabled: computed(() => isReady.value), // Conditionally enable
}
)
For paginated data with infinite scrolling:
`typescript`
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = queries.post.useInfiniteFindMany(
{ take: 10, where: { published: true } },
{
getNextPageParam: (lastPage, pages) => {
// Return the cursor for the next page
return lastPage.length === 10 ? pages.length * 10 : undefined
},
}
)
By default, mutations automatically invalidate related queries. You can disable this:
`typescript`
const createPost = queries.post.useCreate({
invalidateQueries: false, // Don't auto-invalidate related queries
})
All hooks are fully typed based on your ZenStack schema:
`typescript
// TypeScript knows the exact shape of User
const { data: user } = queries.user.useFindUnique({
where: { id: '1' },
select: { id: true, name: true, email: true },
})
// user is typed as: { id: string; name: string; email: string } | null
`
If you're familiar with @zenstackhq/tanstack-query, the Pinia Colada client offers:
- 🎯 Vue-first design - Built specifically for Vue 3 composition API
- 📦 Smaller bundle - Tree-shakeable ESM-only package
- 🔧 Simpler API - Less configuration, sensible defaults
- 🏪 Pinia integration - Works seamlessly with your Pinia store
- ⚡️ Better performance - Optimized for Vue's reactivity system
Key API Differences:
- Returns Vue Ref objects instead of plain valuesstatus
- Uses instead of separate isLoading, isSuccess flagsrefresh()
- instead of refetch()` for manual updates
- Direct integration with Pinia's state management
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
- ZenStack Documentation
- Pinia Colada Documentation
- GitHub Repository