Skip to content

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.

ts
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.

ts
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:

🔥 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.

ts
// 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()
ts
// 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()
ts
// 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.

ts
// 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)
ts
// 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)
ts
// 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.

ts
const { api } = useFeathers()
const $route = useRoute()

const org = computed(() => api.service('orgs').getFromStore($route.params.orgId))
ts
const { api } = useFeathers()
const $route = useRoute()

const org = service.getFromStore($route.params.orgId)

Many thanks go to the Vue and FeathersJS communities for keeping software development FUN!