Plugin Setup
Plugins allow you to extend the functionality of the store. You can create your own plugins or use existing ones. The main use case for plugins is to add support for different data sources, such as REST APIs, GraphQL APIs, or local storage. Plugins can also be used to add support for different data formats, such as JSON, XML, or CSV.
INFO
In the future rstore will provide some builtin plugins for GraphQL, OpenAPI and other popular standards. Feel free to also share your own plugins with the community! 😸
Defining a plugin
To define a plugin, you can use the definePlugin
helper to get auto-completion and type checking. The plugin is an object with a name
and a setup
function.
import { definePlugin } from '@rstore/vue'
export default definePlugin({
name: 'my-plugin',
setup(pluginApi) {
// Plugin code goes here
},
})
The setup
function is called when the plugin is registered. It receives a pluginApi
object that contains useful methods to customize the store.
Vue
You can then add plugins to your store when creating it:
import { createStore } from '@rstore/vue'
import { todoCollection } from './collection'
import myPlugin from './plugin'
export async function setupRstore(app) {
const store = await createStore({
schema: [
todoCollection,
],
plugins: [
myPlugin,
],
})
}
Nuxt
In Nuxt, you can add plugins by creating a file for each in the app/rstore/plugins
directory. The plugin will be automatically registered when the store is created.
package.json
nuxt.config.ts
app/
rstore/
some-collection.ts
plugins/
my-plugin.ts
Nuxt Layers
You can also add an app/rstore
folder in Nuxt layers! rstore will automatically add those files too.
Category
Plugins can be categorized to define their role in the data flow. The available categories are:
virtual
: Plugins that provide virtual/in-memory collections that do not have any persistent storage.local
: Plugins that handle local data sources in the current device, such as saving it to a client-side database or storage such as IndexedDB or LocalStorage.remote
: Plugins that handle remote data sources, such as REST APIs or GraphQL APIs.processing
: Plugins that process data, such as transforming or validating it.
By default plugins will be sorted based on their category in the following order:
virtual
local
remote
processing
You can customize the sorting using the before
and after
options (see Sorting plugins).
import { definePlugin } from '@rstore/vue'
export default definePlugin({
name: 'my-plugin',
// Will be after 'virtual' and 'local' plugins
// and before 'processing' plugins
category: 'remote',
setup(pluginApi) {
// Plugin code goes here
},
})
Hooks
Hooks are the primary way to extend the functionality of the store. They allow you to run custom code at different points in the lifecycle of the store. The hooks are called in the order they are defined.
To register a callback to a hook, you can use the pluginApi.hook
method that takes the name of the hook and a callback function.
Example:
import { definePlugin } from '@rstore/vue'
export default definePlugin({
name: 'my-plugin',
setup(pluginApi) {
pluginApi.hook('fetchFirst', async (payload) => {
if (payload.key) {
const result = await fetch(`/api/${payload.collection.name}/${payload.key}`)
.then(r => r.json())
payload.setResult(result)
}
})
pluginApi.hook('fetchMany', async (payload) => {
const result = await fetch(`/api/${payload.collection.name}`)
.then(r => r.json())
payload.setResult(result)
})
},
})
Explore the Plugin hooks for a complete list of available hooks.
Scope ID
The scope ID allows filtering which plugins will handle the collection. For example, if a collection has a scope A, only plugins with the scope A will be able to handle it by default. This is very useful to handle multiple data sources.
export default definePlugin({
name: 'my-plugin',
scopeId: 'my-scope',
})
In the following example, the fetchMany
hook will only be called with the collections that have the my-scope
scope ID:
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'
})
}
})
You can opt-out of the filtering with the ignoreScope
option of the hooks:
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,
})
}
})
Learn more about federation and multi-source here.
Adding collection defaults
Plugins can define default options for all collections using the addCollectionDefaults
method:
import { definePlugin } from '@rstore/vue'
export default definePlugin({
name: 'my-rstore-plugin',
setup() {
addCollectionDefaults({
getKey: (collectionName, item) => item.customId,
// ...
})
}
})
Sorting plugins
Plugins are sorted based on their dependencies and category. You can specify that a plugin should be loaded before or after another plugin or category using the before
and after
options:
import { definePlugin } from '@rstore/vue'
export default definePlugin({
name: 'my-plugin',
before: {
plugins: ['another-plugin'],
categories: ['remote'],
},
after: {
plugins: ['yet-another-plugin'],
categories: ['virtual'],
},
})
Each property of before
and after
is optional, you can either specify plugins
, categories
, or both.
WARNING
Be mindful of circular dependencies when using before
and after
. For example, if Plugin A is set to load after Plugin B, and Plugin B is set to load after Plugin A, this will create a circular dependency that cannot be resolved. The system will detect such circular dependencies and handle them gracefully by skipping the remaining sorting rules (with a warning printed to the console).
Prioritization is done in the following order:
before.plugins
andafter.plugins
have the highest priority.before.categories
andafter.categories
have the next priority.- Default category order is applied last.