Collection
The structure of your data is presented in rstore with Collections:
import type { StoreSchema } from '@rstore/vue'
const schema: StoreSchema = [
{ name: 'todos' },
{ name: 'users' },
// more collections...
]
Each Collection defines information about the related item type. The only mandatory property is name
, which can be different from the key in the collection map (see the above example).
Various applications can have different collections based on their specific requirements. For instance, a blogging platform might include a Post
collection to denote a blog entry and a Comment
collection for user feedback. Conversely, a project management tool might feature collections such as Task
, Project
, or Milestone
.
const store = await createStore({
schema: [
{ name: 'todos' },
{ name: 'users' },
],
plugins: [],
})
const store = await createStore({
schema: [
withItemType<Todo>().defineCollection({ name: 'todos' }),
withItemType<User>().defineCollection({ name: 'users' }),
],
plugins: [],
})
Defining a Collection
For JavaScript, you can use the defineCollection
utility function to define a collection with auto-completion in your IDE:
import { createStore, defineCollection } from '@rstore/vue'
const todoCollection = defineCollection({
name: 'todos',
// other properties...
})
const store = await createStore({
schema: [
todoCollection
],
plugins: [],
})
For TypeScript, you should use the withItemType
utility function instead to specify the type of the item, then call collection
on it:
import { createStore, withItemType } from '@rstore/vue'
interface TodoType {
id: string
title: string
completed: boolean
}
const todoCollection = withItemType<TodoType>().defineCollection({
name: 'todos',
// other properties...
})
const store = await createStore({
schema: [
todoCollection
],
plugins: [],
})
INFO
The currying is necessary to specify the type of the item while still letting TypeScript infer the type of the collection. This is a limitation of TypeScript, and it might improve in the future.
Item Key
rstore uses a normalized cache to store the data. This means that each item is stored in a flat structure, and the key is used to identify the item in the cache.
The key is a unique identifier for the item. It can be a string, number, or any other type that can be used as a key in an object.
By default, rstore will try to use the id
or _id
property of the item as the key.
You can override this behavior by specifying the getKey
method on the collection:
const todoCollection = defineCollection({
name: 'todos',
getKey: item => item.customId,
})
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.
const todoCollection = defineCollection({
name: 'todos',
scopeId: 'main-backend',
// Only plugins with the scopeId 'main-backend'
// will be able to handle this collection by default
})
WARNING
If the scope ID is not defined, the collection will be handled by all plugins.
Learn more about federation and multi-source here.
Collection metadata
The collection can have metadata that can be used to customize the behavior of the collection.
If you are using TypeScript, you can augment the type of CustomCollectionMeta
to add your own properties:
declare module '@rstore/vue' {
export interface CustomCollectionMeta {
path: string
}
}
export {}
In the collection, you can add the metadata to the meta
property:
const todoCollection = defineCollection({
name: 'Todo',
meta: {
path: '/todos',
},
})
Field configuration
Each field can be configured with some processing functions, with the fields
property which is a map of field names to field configurations.
The field configuration can have the following properties:
parse
: a function that takes the raw value from the server and returns the parsed value. This is useful for parsing dates, numbers, etc.serialize
: a function that takes the value from the cache and returns the serialized value. This is useful for serializing dates, numbers, etc.
Example:
const todoCollection = defineCollection({
name: 'todos',
fields: {
createdAt: {
parse: value => new Date(value),
serialize: value => value.toISOString(),
},
completed: {
parse: value => Boolean(value),
serialize: value => Number(value),
},
},
})
Computed fields
You can define computed fields in the collection. Computed fields are not stored in the cache, but they can be used to derive values from other fields.
For example, you can define a fullName
computed field that concatenates the firstName
and lastName
fields:
const userCollection = defineCollection({
name: 'users',
computed: {
fullName: item => `${item.firstName} ${item.lastName}`,
},
})
You can then use the computed field in your application just like any other field:
const store = useStore()
const { data: user } = store.users.query(q => q.first('some-id'))
watchEffect(() => {
console.log(user.value?.fullName) // This is a computed field
// => John Doe
})
Schema validation
You can define some default schema validation for your collection using the schema
property. This is useful for validating the data for a create or update operation.
You can specify the schema for the create
and update
operations. The schema can be defined using any validation library that implements Standard Schema.
import { z } from 'zod'
const todoCollection = defineCollection({
name: 'todos',
formSchema: {
create: z.object({
title: z.string().min(1),
completed: z.boolean().default(false),
}),
update: z.object({
title: z.string().optional(),
completed: z.boolean().optional(),
}),
},
})
You can then use the $schema
property of the form objects to validate the data:
<!-- Example with Nuxt UI -->
<script setup>
const store = useStore()
const createTodo = store.todos.createForm()
</script>
<template>
<UForm
:state="createTodo"
:schema="createTodo.$schema"
@submit="createTodo.$submit()"
>
<!-- UFormFields here -->
</UForm>
</template>
Learn more about Form Objects.