github directus/rstore v0.7.0

7 hours ago

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.

Read the Migration Guide

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 and updateItem when calling setResult 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
image
  • Improved Plugins Tab
image
  • 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

  • Faster relation lookup (#44)
  • Normalize relations eagerly (1a25f54)

๐Ÿฉน 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)

Don't miss a new rstore release

NewReleases is sending notifications on new releases.