Breaking changes
Throughout the codebase, the term Model
was changed to Collection
to prevent confusion with Vue's own model
API. A lot of APIs also were simplified and cleaned up, or changed to improve type checking with TypeScript.
New Features
Collection Hooks
To make it easier to get started, you can now directly define some functions on the collections themselves to implement logic for fetching and mutating the data.
import { defineCollection } from '@rstore/vue'
export const todoCollection = defineCollection({
name: 'todos',
// Interact with a REST/GraphQL/etc. API
hooks: {
fetchFirst: ({ key }) => fetch(`/api/todos/${key}`).then(r => r.json()),
fetchMany: ({ params }) => fetch('/api/todos').then(r => r.json()),
create: ({ item }) => { /* ... */ },
update: ({ key, item }) => { /* ... */ },
delete: ({ key }) => { /* ... */ },
},
})
New Relations API
You should now use defineRelations
instead of the collection relations
option, to enjoy better typechecking. The syntax has also changed to be less error-prone, allow matching on multiple fields and with a custom filter
function.
import { createStore, defineCollection, defineRelations } from '@rstore/vue'
const collection1 = defineCollection({ name: 'collection1' })
const collection2 = defineCollection({ name: 'collection2' })
const collection1Relations = defineRelations(collection1, ({ collection }) => ({
relatedItems: {
to: collection(collection2, {
on: {
'collection2.foreignKey': 'collection1.id', // Type checked!
},
// filter: (itemCol1, itemCol2) => true
}),
many: true, // One-to-many relation
},
}))
const store = await createStore({
schema: [
collection1,
collection2,
collection1Relations,
],
})
Simplified Nuxt setup
You can remove the export default [...]
array and instead directly export the collections from the schema files in Nuxt projects, which is much simpler and less error-prone.
export const users = RStoreSchema.withItemType<User>().defineCollection({
name: 'users',
})
export const bots = RStoreSchema.withItemType<Bot>().defineCollection({
name: 'bots',
})
New Query API
The new query
and liveQuery
methods replace queryFirst
, queryMany
, liveQueryFirst
and liveQueryMany
to provide improved TypeScript checking (see the Migration Guide for more details).
const { data: todo } = store.Todo.query(q => q.first('some-key'))
const email = ref('@acme.com')
const { data: someUsers } = store.User.query(q => q.many({
filter: item => item.email.endsWith(email.value),
params: {
email: {
$like: `%${email.value}`,
},
},
}))
Simplified Modules API
The syntax to define Modules has been greatly simplified (see the Migration Guide for more details).
import { defineModule } from '@rstore/vue'
export const useAuth = defineModule('auth', ({ store, defineState, defineMutation, onResolve }) => {
const state = defineState({
// Create some state here
currentUserKey: null as string | null,
})
return {
// Expose things here
}
})
The mutation state also got simplified, without the need for .value
:
<script setup lang="ts">
const auth = useAuth() // A module defined with defineModule
</script>
<template>
<UAlert
v-if="auth.login.$error"
:description="auth.login.$error.message"
color="error"
/>
<UButton
type="submit"
:loading="auth.login.$loading"
/>
</template>
Optimistic updates
Mutations automatically apply the changes without waiting for server responses, making the UI feel more responsive. You can disable optimistic updates by passing the optimistic: false
option to the mutation method.
const newTodo = await store.todos.create({
title: 'New Todo',
completed: false,
}, {
optimistic: false,
})
You can customize the expected result of the mutation by passing an object to the optimistic
option. It will be merged with the data you pass to the mutation method but can override it.
const newTodo = await store.todos.create({
title: 'New Todo',
completed: false,
}, {
optimistic: {
id: 'temp-id',
createdAt: new Date().toISOString(),
},
})
/*
The optimistic object will be:
{
id: 'temp-id', // added
title: 'New Todo',
completed: false,
createdAt: '2024-06-01T12:00:00.000Z', // added
}
*/
To know if a record is optimistic, you can check the $isOptimistic
property on the record.
const todo = await store.todos.findFirst('some-id')
if (todo.$isOptimistic) {
console.log('This record is optimistic')
}
Experimental Garbage Collection
You can enable a new experimental experimentalGarbageCollection
option so rstore will automatically purge the cache from items that are not returned by queries (usually when they are deleted on the servers).
createStore({
experimentalGarbageCollection: true,
})
For nuxt:
// nuxt.config.ts
export default defineNuxtConfig({
rstore: {
experimentalGarbageCollection: true,
},
});
Layered Plugins
Several new Plugins features allow handling complex layered data flows:
- Plugin Categories to automatically sort the call order of plugins:
'virtual', 'local', 'remote', 'processing'
. - Sorting overrides in the plugin definitions.
- Abort API to prevent calling further plugin callbacks for a hook.
- Auto-aborting of
fetchFirst
,fetchMany
,createItem
andupdateItem
when callingsetResult
with a non-empty result.
import { definePlugin } from '@rstore/vue'
export default definePlugin({
name: 'my-plugin',
// Will be after 'virtual' and 'local' plugins and before 'processing' plugins
category: 'remote',
// Override sorting
before: {
plugins: ['another-plugin'],
categories: ['remote'],
},
// after: { ... },
setup({ hook }) {
hook('deleteItem', ({ abort }) => {
// ...
abort()
})
},
})
Drizzle Hooks
The Nuxt+Drizzle module received a host of new features around hooking into the generated API.
import * as tables from 'path-to-your-drizzle-schema'
export default defineNitroPlugin(() => {
// Deny access to all the other tables
allowTables([
tables.todos,
])
// Implement permissions or other logic
hooksForTable(tables.todos, {
'index.get.before': async (payload) => {
console.log('Specific hook for todos - index.get.before', payload.collection, payload.query, payload.params)
},
'index.get.after': async (payload) => {
console.log('Specific hook for todos - index.get.after', payload.collection, payload.result.map(r => r.id))
},
'item.patch.after': async (payload) => {
console.log('Specific hook for todos - item.patch.after', payload.collection, payload.result.id)
},
})
})
Devtools improvements
- New Cache Tab

- Improved Plugins Tab

- Many other improvements and fixes
Detailed changelog
๐ Enhancements
- New schema and relation declarations (844635b)
- devtools: New cache tab (5be73c2)
- New query API (04c4e34)
- cache: ReadItems new limit option (bc7d1fa)
- Experimental garbage collection (a119680)
- devtools: Auto update cache (e7326da)
- Cache layers (43519e0)
- Optimistic updates (04a5c63)
- devtools: Gc + cache layer events in history (9a1ccef)
- UseStore + new setup docs (cbd97d6)
- New module API (07c2c9c)
- SetActiveStore for tests (d32cc67)
- nuxt-drizzle: Remove deprecated option drizzleImport.default (2e737ba)
- nuxt-drizzle: Drizzle hooks (c7cb660)
- nuxt-drizzle: Adapt to new relations syntax (f2a5088)
- nuxt-drizzle: Use new define model syntax (3edb432)
- Use nuxt module dependencies (9be4ab5)
- nuxt: Simpler schema syntax with export const (b6aa3fa)
- nuxt-drizzle: HooksForTable (6e9f3d0)
- nuxt-drizzle: AllowTables (c8361d3)
- Sort plugins (b727f31)
- plugin: Auto abort fetchFirst & fetchMany (7ce07ae)
- plugin: Category (ec1a41b)
- Collection hooks (c6824b1)
- module: $loading, $error, $time no longer Refs (d98f1c2)
- plugin: Abort API (541d41d)
- plugin: Abort API for fetchRelations (b72474a)
- plugin: Auto abort createItem and updateItem (0d8218a)
๐ฅ Performance
๐ฉน Fixes
- devtools: Cache model item height (12b5ee9)
- Disallow excess properties in withItemType().defineModel() (c7c2f1f)
- Always wrap items out of find methods (f7cc489)
- Store.$getModel should use
item.$model
(2383304) - devtools: Automatically update cache filtering (52c6da2)
- devtools: Sort models (a50c7de)
- Clone properties for mutations (c1d614d)
- Optimistic updates using serialized item (8f21f2f)
- Don't always clone properties in pickNonSpecialProps (86ceeff)
- devtools: Cache model item height (276b3c5)
- Lower minimum dep version of @nuxt/kit (8503aa5)
- nuxt-drizzle: Missing getQuery import (39d613e)
- nuxt-drizzle: Where.push is not a function (be74a82)
๐ Refactors
- Rename model to collection (00b661d)
๐ Documentation
- Typo (#49)
- Layers (65a7cc1)
- Not about modules and code splitting (418121c)
- Typo (#50)
- Note about plugin change (111f327)
- Not about StoreResolvedCollectionItem in migration (4243cdf)
- Typo in migration guide (9680a3a)
- Update Nuxt multiple models (5e32f67)
- nuxt-drizzle: Features (9db2c81)
- Code diff in
vue
code snippets (e128a72)
๐ Types
- Improvements and cleanup (c37c460)
- Recursively typed
FindOptions.include
(c64eb91) - form: CreateFormObject submit type can now be different (f4d8821)
- Fix query many without params + augment find options docs (8c79bc4)
- Improvements, stricter types and more checking (c29dafe)
๐ก Chore
โ Tests
๐ค CI
โค๏ธ Contributors
- Guillaume Chau (@Akryum)
- ใใคใใฆใณใฆในใฑ (@naitokosuke)