v2.0.0-pre.18
useService - Service Stores
In Feathers-Pinia 2.0, the defineStore utility has been replaced by the useService Composition API utility.
useService
The useService utility gives you a Feathers-service enabled store which can be easily customized:
ts
// src/store/users.ts
import { defineStore, acceptHMRUpdate } from 'pinia'
import { useService } from 'feathers-pinia'
import { User } from '../models'
export const useUserStore = defineStore('users', () => {
const { $api } = useFeathers()
const service = $api.service('messages')
const utils = useService({
service,
idField: 'id',
Model: User,
})
return { ...utils }
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))
}
Options
Here's a look at the UseServiceOptions interface. An explanation of each follows this code snippet:
ts
import { Application as FeathersClient } from '@feathersjs/feathers'
interface UseServiceOptions<
M extends AnyData,
D extends AnyData,
Q extends Query,
ModelFunc extends (data: ModelInstance<M>) => any
> {
service: FeathersClientService<FeathersInstance<M, Q>, D, Params<Q>>
idField: string
Model: ModelFunction
whitelist?: string[]
paramsForServer?: string[]
skipRequestIfExists?: boolean
ssr?: MaybeRef<boolean>
handleEvents?: HandleEvents<M>
debounceEventsTime?: number
debounceEventsGuarantee?: boolean
}
interface HandleEvents {
created?: Function
patched?: Function
updated?: Function
removed?: Function
}
Here are a few more details about each option:
service {FeathersClientService}the Feathers Client service object. requiredidField {String}is the attribute on the record that will serve as the unique identifier or "primary key" in the database.Model {ModelFunction}is the class to use for each instance. See Model Functionswhitelistis an array of keys to allow in thefindInStoregetter'sparams.queryobject.paramsForServeris an array of query keys forfindInStoreto ignore and pass to thefindaction's query.skipRequestIfExists {Boolean}when enabled will cause a.getrequest to automatically resolve with the stored record with matching id, if one exists. If not, the request will be made as normal.ssr {Boolean}indicates if Feathers-Pinia is loading in an SSR environment. Paginated queries made during SSR will be marked withssr: true. When a matching request is made on the client (whenssris false) the store data will be used and the request will not be remade.handleEvents {Object}is an object that lets you customize how realtime events are handled. Each key is a name of a realtime event handler function:created,patched,updated, orremoved. You can provide your own handler to customize and override individual events.debounceEventsTime {Number}determines how long to wait until flushing a batch of events. Defaults to20. If no events have been received in a 20 millisecond period, all gathered events will be processed.debounceEventsGuarantee {Boolean}forces accumulated events to flush everydebounceEventsTimeinterval. Off by default.
Returned API
The object returned from useService is built on top of the BaseModel store. Refer to the BaseModel store documentation for API details. The following sections will cover store APIs not in the BaseModel store.
The following sections cover additional APIs returned when calling useService. APIs are grouped by functionality.
Additional State
serviceparamsForServerskipRequestIfExistsisSsr
Model Config
Modelgives access to the Model Function provided in the options.setModel(Model)Allows setting/replacing the Model. This means you can calluseFindwithout a Model and set the model afterwards.
Pagination State
paginationkeeps track of the latest pagination data for each paginated request to the server. You generally won't manually modify this.updatePaginationForQuery()unflagSsr()
Pending State
isPendingcreatePendingByIdkeeps track of individual records, by id, that have pendingcreaterequests.updatePendingByIdkeeps track of individual records, by id, that have pendingupdaterequests.patchPendingByIdkeeps track of individual records, by id, that have pendingpatchrequests.removePendingByIdkeeps track of individual records, by id, that have pendingremoverequests.isFindPendingis a boolean computed which will be true if anyfindrequest is pending.isCountPendingis a boolean computed which will be true if anycountrequest is pending.isGetPendingis a boolean computed which will be true if anygetrequest is pending.isCreatePendingis a boolean computed which will be true if anycreaterequest is pending.isUpdatePendingis a boolean computed which will be true if anyupdaterequest is pending.isPatchPendingis a boolean computed which will be true if anypatchrequest is pending.isRemovePendingis a boolean computed which will be true if anyremoverequest is pending.setPending()allows setting a method as pending.setPendingById()allows setting a record as pending by method name and record id.unsetPendingById()allows unsetting a record's pending status.clearAllPending()resets pending state back to its original, empty state.
Event Locks
Event locks are automatically managed and require no manual upkeep.
eventLockshelps prevent receiving normal, duplicate responses from the API server during CRUD actions. Instead of processing both the CRUD response AND the realtime event data, it only handles one of them.toggleEventLock()used to toggle an event lock.clearEventLock()used to turn off an event lock
Service Methods
Service methods are convenience wrappers around the Feathers Client service provided in the options.
find(params)
Uses the Feathers Client to retrieve records from the API server. On an SSR server, find data will be marked as ssr: true, which allows extra queries to be skipped on the client.
vue
<script setup>
import { useTodos } from '../store/todos'
const todoStore = useTodos()
todoStore.find({ query: {} }).then(/* ... */)
</script>
count(params)
Like find, but returns the number of records that match the query. It does not return the actual records.
vue
<script setup lang="ts">
import { useTodos } from '../store/todos'
const todoStore = useTodos()
await todoStore.count({ query: { isComplete: false } })
</script>
get(id, params)
Uses the Feathers Client to retrieve a single record from the API server.
vue
<script setup lang="ts">
import { useTodos } from '../store/todos'
const todoStore = useTodos()
await todoStore.get(1)
</script>
update(id, data, params)
Uses the Feathers Client to send an update request to the API server.
vue
<script setup lang="ts">
import { useTodos } from '../store/todos'
const todoStore = useTodos()
await todoStore.update(1, { description: 'foo', isComplete: true })
</script>
patch(id, data, params)
Uses the Feathers Client to send an patch request to the API server.
vue
<script setup lang="ts">
import { useTodos } from '../store/todos'
const todoStore = useTodos()
await todoStore.patch(1, { isComplete: true })
</script>
remove(id, params)
Uses the Feathers Client to send a remove request to the API server.
vue
<script setup lang="ts">
import { useTodos } from '../store/todos'
const todoStore = useTodos()
await todoStore.remove(1)
</script>
Service Utils
These utilities use a combination of multiple store methods to eliminate boilerplate and improve developer experience.
- useFind()
- useGet()
useGetOnce()has the same API as useGet, but only queries once per record.- useFindWatched()
- useGetWatched()
Customize the Store
You can customize the store by changing the values that are returned. This contrived example shows how you can return additional values.
ts
// src/store/users.ts
import { defineStore, acceptHMRUpdate } from 'pinia'
import { useService } from 'feathers-pinia'
import { User } from '../models'
export const useUserStore = defineStore('users', () => {
const { $api } = useFeathers()
const service = $api.service('messages')
const utils = useService({
service,
idField: 'id',
Model: Task,
})
const myCustomState = false
return { ...utils, myCustomState }
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))
}
Warning
It's possible to overwrite store attributes if you provide a variable after the ...utils spread and that variable has the same name as a key returned by utils. This example return would overwrite the find method as a boolean:
ts
// oops, I broke `find`
return { ...utils, find: true }
Conversely, if you provide the conflicting variable before the ...utils spread, the value will be overwritten by the one returned from utils:
ts
// utils.find overwrites the find boolean, since it's declared last.
return { find: true, ...utils }
Server Side Rendering (SSR)
Both Pinia and Feathers-Pinia require to be configured to work with SSR.
- Pinia takes care of SSR Hydration, making sure the data on the server is transferred to the client.
- Feathers-Pinia prevents duplicate requests with proper reutilization of server-fetched and hydrated data.
Pinia SSR
SSR hydration is done at the Pinia level, which makes SSR a breeze with any SSR server. The Pinia docs include two section on SSR:
Be sure to follow the instructions that most closely match your setup.
Feathers-Pinia SSR
The only real requirement to get SSR working is to properly provide the ssr option to be true on the server and false on the client. The correct way to do this will vary depending on the environment. Some environments will work by using !!process.server. Other environments might use a different variable to indicate SSR or client. You'll need to get that information from your SSR framework.
Here's an example of a setup that uses process.server.
ts
export const useUserStore = defineStore('users', () => {
const { $api } = useFeathers()
const service = $api.service('messages')
const utils = useService({
service,
idField: 'id',
Model: Task,
ssr: !!process.server
})
return { ...utils }
})
Once the ssr option is set correctly on the server and client, SSR should just work!
Automatic Instance Hydration
🚧 This section requires updating after app examples are completed. 🚧
Normally, during SSR, after the rendered page has been delivered to the client, the browser takes any inline JSON payload and pushes it into the store. This is called hydration. The problem is that basic hydration does not assure that records are Model instances. You'll recognize the problem when the browser throws an error like this:
text
Error: object has no method named `.save()`
That's because plain objects don't actually have a save() method. Only once it has been turned back into a Model instance does it have a save() method. So we need to make sure that data is fully hydrated into an actual instance before using its methods.
Instead of automatically hydrating every instance in the store, Feathers-Pinia uses a more performant rule: Only hydrate the instances actually in use. It achieves this through the Query Getters. Any plain record returned by store.findInStore or store.getFromStore will automagically be turned into a fully hydrated model instance.