rstore Documentation

repository·main·Indexed 19 days ago

https://github.com/directus/rstore

A local-first data store for Vue and Nuxt applications featuring a normalized reactive cache and a structured API for queries and mutations. It supports realtime, forms, and offline workflows with strong TypeScript support, a Nuxt module, and advanced features like operation batching and state serialization for SSR.

Tokens
169.5K
Snippets
535
Records
717
Agent score
59%

What's inside rstore

  1. Overview of rstore

    main
    rstore is a local-first data store designed for Vue and Nuxt applications. It provides a normalized reactive cache that is shared across your entire application, along with a structured API for queries and mutations. It is specifically built to support local-first, realtime, forms, and offline workflows, offering strong TypeScript support and a Nuxt module with DevTools integration.
  2. What is rstore?

    main

    rstore is a data management library designed for efficient application data handling. It allows you to define data collections and perform queries or mutations (create, update, delete) seamlessly.

    Key features include:

    • Normalized reactive cache: Maintains a single source of truth and keeps UI components in sync.
    • Adaptable plugin system: Supports fetching data from various sources like REST, GraphQL, or local storage.
    • Local-first architecture: Prioritizes local data access and client-side computation (filtering/sorting) for speed and offline capabilities.
    • TypeScript support: Provides full type safety and autocomplete.
  3. Overview of @rstore/devtools

    main

    The @rstore/devtools package allows you to embed the rstore Devtools UI into any application. It provides:

    • A prebuilt frontend served from a static route (default: /__rstore).
    • A RstoreDevtools Vue component which acts as an iframe wrapper.
    • A rstoreDevtoolsVite() plugin for Vite-based builds (including Nuxt).
  4. Integrate rstore with Directus using @rstore/directus

    main

    Use @rstore/directus as the shared adapter layer to bridge rstore collections with the Directus runtime and schema. It provides helpers for Directus REST query options, cache-side filtering, singleton handling, and primary key stripping. It is designed to be used before writing custom Directus fetch or mutation code around rstore collections.

    Recommended Pairing:

    • Pair with rstore-vue for collection, query, and form semantics.
    • Pair with framework-specific Directus skills (like Vite or Nuxt integrations) for wiring.
  5. Choose your rstore setup

    main

    Depending on your application architecture, choose one of the following packages:

    • @rstore/vue: For standard Vue applications where you want explicit control over store creation and plugin registration.
    • @rstore/nuxt: For Nuxt applications. Provides auto-registration, typed useStore(), SSR integration, and DevTools support.
    • @rstore/nuxt-drizzle: For Nuxt applications that already use a Drizzle schema. It generates rstore collections and matching server APIs from your existing schema.
  6. Compare rstore with Pinia

    main
    rstore is a high-level state management library focused on a normalized reactive cache and comprehensive query/mutation/live APIs. In contrast, Pinia is a low-level state management library for Vue.js that provides no built-in structure for data fetching or caching, requiring developers to implement those features manually using the Vue Composition API.
  7. Understand the generated schema in @rstore/nuxt-monospace

    main

    At Nuxt build time, the module processes the Monospace OpenAPI document and system schema metadata to generate rstore collections. This process produces:

    • getKey functions: Derived from the true primary keys (ordered columns of the primary index, including composite and non-id keys).
    • TypeScript interfaces: Generated from *CollectionOutput schemas.
    • rstore relations: Configured to join on real Foreign Key (FK) constraint columns.
    • Monospace collection metadata: Used by the REST plugin for runtime operations.

    Note on Primary Keys: The primaryKeys configuration is an override. If a collection has no primary index in the metadata and no override is provided, generation will fail with an explicit error because rstore cannot compute stable item keys.

  8. Filter plugins using Scope ID

    main

    The scopeId property allows you to restrict which collections a plugin handles. By default, a plugin with scopeId: 'my-scope' will only intercept collections that also have the my-scope scope ID.

    To allow a plugin to intercept all collections regardless of their scope, use the ignoreScope: true option when registering the hook.

    // Plugin only handles collections with 'my-scope'
    export default definePlugin({
      name: 'my-plugin',
      scopeId: 'my-scope',
      setup({ hook }) {
        hook('fetchMany', async (payload) => {
          // This will only be called for collections with the scopeId 'my-scope'
        })
      }
    })
    
    // Plugin handles all collections regardless of scope
    export default definePlugin({
      name: 'my-plugin',
      scopeId: 'my-scope',
      setup({ hook }) {
        hook('fetchMany', async (payload) => {
          // This will be called for all collections regardless of their scopeId
        }, {
          ignoreScope: true,
        })
      }
    })
  9. Manage subscription lifecycles and avoid leaks

    main

    Subscriptions in rstore follow an option-driven lifecycle. When you initiate a subscription via subscribe(), a connection or listener is established with the backend.

    Pitfall: Memory and Backend Leaks In long-lived contexts (such as Vue components that are not immediately destroyed or global services), failing to call the unsubscribe() method returned by the subscription will result in leaked backend subscriptions. Always ensure that await sub.unsubscribe() is called when the subscription is no longer needed.

  10. How rstore-vue works: Core Concepts

    main

    rstore-vue provides a typed, cache-first data flow for Vue applications. The workflow follows these mental models:

    • Schema Definition: Use withItemType(...).defineCollection(...) to create typed collection contracts and defineRelations(...) to define normalized cross-collection relations.
    • Store Creation: createStore({ schema, plugins, ... }) builds the core engine, including the cache, hook system, and per-collection API proxies.
    • Injection: Use RstorePlugin to inject the store into Vue components, or setActiveStore(store) for non-component contexts like tests.
    • Data Access: Access collections via store.<collection> or store.$collection(name). Use query or liveQuery for reactive data flows, and find* for one-shot async reads.
    • Mutations: Use createForm or updateForm for UI-driven mutations, which handle validation and change tracking.
    • Extensibility: Use definePlugin to extend fetch, cache, or subscription behavior, and defineModule to create reusable, store-scoped logic.
  11. Configure Optimistic Updates

    main

    By default, rstore performs optimistic updates: the store is updated immediately before the server confirms the change. If the mutation fails, the change is automatically reverted.

    Disabling Optimistic Updates

    Pass { optimistic: false } to any mutation method to wait for server confirmation before updating the local store.

    Customizing Optimistic Data

    You can provide a custom object to the optimistic option. This object is merged with your mutation data and can be used to provide temporary values (like a temporary ID or timestamp) before the server responds.

    Checking Optimistic State

    You can check if a record is currently in an optimistic state using the $isOptimistic property.

    // Disable optimistic updates
    await store.todos.create({ title: 'No Optimism' }, { optimistic: false })
    
    // Provide custom optimistic data
    const newTodo = await store.todos.create({
      title: 'New Todo',
      completed: false,
    }, {
      optimistic: {
        id: 'temp-id',
        createdAt: new Date().toISOString(),
      },
    })
    
    // Check if a record is optimistic
    if (todo.$isOptimistic) {
      console.log('This record is optimistic')
    }
  12. Compare rstore modules with Pinia

    main

    While both manage state, rstore modules differ from Pinia in several ways:

    Featurerstore Modules
    IntegrationDesigned for seamless use with rstore data collections and devtools
    Private StateSupports private state that is still SSR-compatible
    Async/HybridCan be awaited for async initialization; supports a 'hybrid promise' pattern where properties are available even without awaiting
    PiniaExternal library, synchronous stores