What's New in v4
Version 4 of Feathers-Pinia is about improving developer experience. It focuses on the useFind
, findInStore
, and useGet
APIs.
New in v4.5
🎁 Custom Query Filters
Filters are top-level, custom parameters inside your query
objects. You can now define custom operators for local queries (the findInStore
method). This new capability powers the new, built-in fuzzy search operator. Read more about Custom Query Filters.
🎁 Fuzzy Search Filter
The createPiniaClient
function now accepts a customFilters
option. Custom operators are easier to define than customSiftOperators
because they need no prior knowledge of custom interfaces. Custom operators run before the rest of the query operators. Read how to set up the uFuzzy Custom Filter.
const { data } = await api.service('users').findInStore({
query: {
$fuzzy: {
search: 'john',
fields: ['firstName', 'lastName', 'email']
}
}
})
💯 No Local Query Validation
There should be no more need for a local whitelist
option, since query validation has been removed from local queries. You should be able to use $regex
and other operators without seeing any message about invalid operators or filters. At least... not from the client side. They should still arrive from your Feathers server if you're properly implementing query validation there.
createPiniaClient(feathersClient, {
pinia,
idField: '_id',
// whitelist: ['$regex'], no need for whitelist anymore
})
New in v4.2
🎁 useBackup
The new useBackup utility is the opposite of working with clones. Instead of binding your form to a clone, you use the original record. It keeps a copy, letting you call backup
or restore
to revert changes. The save
method auto-diffs from the backup, keeping data size to a minimum.
🎁 New Relationship Utilities
New methods have been added to the FeathersPinia Client. These utilities can be used inside setupInstance
functions to help form relationships between services:
- app.pushToStore pushes data into other service stores. It replaces
storeAssociated
. - app.defineVirtualProperty sets up a virtual property on an object.
- app.defineVirtualProperties sets up many virtual properties on an object.
🔥 storeAssociated
Deprecated
As of Feathers-Pinia v4.2, the storeAssociated
utility has been replaced with a suite of smaller, more-flexible, single-purpose utilities. See Data Modeling for the new way to store associated data.
API Changes
The useFind
, findInStore
, and useGet
APIs now return reactive
objects instead of objects of individual ref properties. This means that you can use the returned objects directly in your templates, without having to unwrap them with .value
. The end result is much cleaner script
code, especially when using multiple useFind
calls in the same component. Let's start with useFind
examples.
service.useFind
The useFind
service method now returns a reactive
object. Below are examples of the old way and the new way. Note that destructuring the object will break reactivity, so you'll want to use the object directly.
Since the new APIs return reactive
objects, you can keep everything together in a single object. This removes the need to use .value
to reference properties within the script
.
// the old way with destructuring
const { api } = useFeathers()
const $route = useRoute()
const orgsParams = computed(() => ({ query: { slug: $route.params.orgSlug } }))
const {
data: orgs,
request: orgsRequest,
isSsr, areOrgsPending,
next: orgsNext,
prev: orgsPrev
} = api.service('orgs').useFind(orgsParams, { paginateOn: 'hybrid' })
const projectsParams = computed(() => ({ query: { slug: $route.params.projectSlug } }))
const {
data: projects,
request: projectsRequest,
isSsr, areProjectsPending,
next: projectsNext,
prev: projectsPrev
} = api.service('projects').useFind(projectsParams, { paginateOn: 'hybrid' })
isSsr.value && await Promise.all([orgsRequest, projectsRequest])
// call destructured methods
await orgsNext()
await orgsPrev()
await projectsNext()
await projectsPrev()
// the new way with reactive objects
const { api } = useFeathers()
const $route = useRoute()
const orgsParams = computed(() => ({ query: { slug: $route.params.orgSlug } }))
const orgs$ = api.service('orgs').useFind(orgsParams)
const projectsParams = computed(() => ({ query: { slug: $route.params.projectSlug } }))
const projects$ = api.service('projects').useFind(projectsParams, { paginateOn: 'hybrid' })
projects$.isSsr && await Promise.all([orgs$.request, projects$.request])
// call methods within the same object
await orgs$.next()
await orgs$.prev()
await projects$.next()
await projects$.prev()
// if you still want to destructure, use toRefs from VueUse
import { toRefs } from '@vueuse/core'
const { api } = useFeathers()
const $route = useRoute()
const orgsParams = computed(() => ({ query: { slug: $route.params.orgSlug } }))
const {
data: orgs,
request: orgsRequest,
isSsr, areOrgsPending,
next: orgsNext,
prev: orgsPrev
} = toRefs(api.service('orgs').useFind(orgsParams, { paginateOn: 'hybrid' }))
const projectsParams = computed(() => ({ query: { slug: $route.params.projectSlug } }))
const {
data: projects,
request: projectsRequest,
isSsr, areProjectsPending,
next: projectsNext,
prev: projectsPrev
} = toRefs(api.service('projects').useFind(projectsParams, { paginateOn: 'hybrid' }))
isSsr.value && await Promise.all([orgsRequest.value, projectsRequest.value])
// utility functions referenced in `script` will need to be called with `.value()`
await orgsNext.value()
await orgsPrev.value()
await projectsNext.value()
await projectsPrev.value()
It's still possible to destructure the returned object if you use the toRefs
utility. This will restore previous functionality with one caveat: all utility methods will also be wrapped in a ref
. This means that you'll need to unwrap them with .value
when using them in your scripts. (Templates will still work without unwrapping.)
service.useGet
The useGet
method has been modified in exactly the same way as the useFind method, above.
service.findInStore
The findInStore
service method now returns a reactive
object. Below are examples of the old way and the new way.
// the old way with destructuring
const { api } = useFeathers()
const $route = useRoute()
const orgsParams = computed(() => ({ query: { slug: $route.params.orgSlug } }))
const {
data: orgs,
limit: orgsLimit,
skip, orgsSkip,
total: orgsTotal,
} = api.service('orgs').findInStore(orgsParams)
const projectsParams = computed(() => ({ query: { slug: $route.params.projectSlug } }))
const {
data: projects,
limit: projectsLimit,
skip: projectsSkip,
total: projectsTotal,
} = api.service('projects').findInStore(projectsParams)
// the new way with reactive objects
const { api } = useFeathers()
const $route = useRoute()
const orgsParams = computed(() => ({ query: { slug: $route.params.orgSlug } }))
const orgs$ = api.service('orgs').findInStore(orgsParams)
const projectsParams = computed(() => ({ query: { slug: $route.params.projectSlug } }))
const projects$ = api.service('projects').findInStore(projectsParams)
// if you still want to destructure, use toRefs from VueUse
import { toRefs } from '@vueuse/core'
const { api } = useFeathers()
const $route = useRoute()
const orgsParams = computed(() => ({ query: { slug: $route.params.orgSlug } }))
const {
data: orgs,
limit: orgsLimit,
skip, orgsSkip,
total: orgsTotal,
} = toRefs(api.service('orgs').findInStore(orgsParams))
const projectsParams = computed(() => ({ query: { slug: $route.params.projectSlug } }))
const {
data: projects,
limit: projectsLimit,
skip: projectsSkip,
total: projectsTotal,
} = toRefs(api.service('projects').findInStore(projectsParams))
Improved getFromStore
In addition to the above changes, the getFromStore
method has been updated so that the internal computed
now works for reactivity. This means that you no longer need to wrap it in a computed
in your script
code. You can just use it directly.
const { api } = useFeathers()
const $route = useRoute()
const org = computed(() => api.service('orgs').getFromStore($route.params.orgId))
const { api } = useFeathers()
const $route = useRoute()
const org = service.getFromStore($route.params.orgId)