v2.0.0-pre.18
What's New in 2.0
Feathers-Pinia 2.0 is a huge update with some great new features. This page will go over some of the highlights.
Huge Performance Boost 🚀
Feathers-Pinia is SO MUCH faster than its predecessor. You'll see massive benefits from the faster reactive types under the hood of Pinia and Vue 3. But we've gone a step further and fine-tuned and tested Feathers-Pinia to never perform extra work. Some of the biggest improvements are:
- No unnecessary stack frames happen under the hood. We stand firmly against wasted CPU cycles!
- As from the beginning, you still have full control over adding instances to the store with
instance.addToStore()
. - For the features that require objects to be in the store (for example,
useClones
) feathers-pinia will implicitly add items to the store when needed.
Composition API Stores 🎉
Feathers-Pinia has been completely rewritten as a set of Composition API utilities for creating Pinia setup stores. The advantages include a better TypeScript experience, cleaner customization, and fewer limitations.
useService 🎁
The new useService
utility takes the place of the Feathers-Pinia defineStore
utility (not to be confused Pinia's defineStore
utility) and gives you a starting point for defining your own setup store for each service. The object returned from calling useService
has the same shape as service stores from older versions.
The useService
utility only requires a Feathers service and not the full Feathers client instance, anymore. It also requires the use of a Model Function, which is covered further down this page and not shown in this example. This example shows the creation and instantiation of a setup store with useService
:
ts
import { defineStore, createPinia } from 'pinia'
const pinia = createPinia()
// Create a tasks store
export const useTaskStore = defineStore('tasks', () => {
const serviceUtils = useService<TaskInstance, TasksData, TasksQuery, typeof modelFn>({
service,
idField: '_id',
Model: Task, // see the section on Model Functions
})
return { ...serviceUtils }
})
const taskStore = useTaskStore(pinia)
To customize a setup
store, you declare additional variables, computed properties, and functions inside of the call to defineStore
.
Learn more about the new useService utility.
useAuth 🎁
Create ultra-flexible setup stores
with the new useAuth utility:
ts
// src/store/store.auth.ts
import { defineStore, acceptHMRUpdate } from 'pinia'
import { useAuth } from 'feathers-pinia'
export const useAuthStore = defineStore('auth', () => {
const { userStore } = useUserStore()
const { api } = useFeathers()
const auth = useAuth({
api,
userStore,
})
auth.reAuthenticate()
return auth
})
Learn more about the new useAuth utility
Feathers v5 Dove TS Support 🎉
The new utilities in Feathers-Pinia 2.0 bring support for the new TypeScript enhancements in Feathers v5 Dove. Now you can directly import the types from your backend and use them in your Feathers-Pinia frontend. The types integrate directly into the new Model Functions, as well.
Learn more about Feathers v5 Dove types in the Feathers documentation:
- Creating types with TypeBox
- Reusing server types with the Feathers Client
Model Functions, not Classes 🔮
Data modeling is one of the most-loved features in Feathers-Pinia. In Feathers-Pinia 2.0, we replace Model Classes with Model Functions. The developer experience is so much better! You just create a function that receives an object, performs modifications to it, then returns it. There are two utilities for wrapping Model Functions: useFeathersModel
and useBaseModel
.
Learn more about Model Functions
useFeathersModel 🎁
The useFeathersModel
utility is most similar to the old BaseModel class from FeathersVuex and previous versions of Feathers-Pinia. You get the full Feathers service experience. Feathers Models have Feathers-related methods, like find
, count
, get
, etc., directly on the model interface.
Note about Feathers Types
Replace my-feathers-api
in the below example with the package installed from your Feathers v5 Dove API. You can also provide manual types that describe the shape of your data.
ts
import type { Tasks, TasksData, TasksQuery } from 'my-feathers-api'
import { type ModelInstance, useFeathersModel, useInstanceDefaults } from 'feathers-pinia'
import { api } from '../feathers'
const modelFn = (data: ModelInstance<Tasks>) => {
const withDefaults = useInstanceDefaults({ description: '', isComplete: false }, data)
return withDefaults
}
const Task = useFeathersModel<Tasks, TasksData, TasksQuery, typeof modelFn>(
{ name: 'Task', idField: '_id', service },
modelFn,
)
Models now come with a lightweight, built-in store. This means that they work without a Pinia store, but you give up the Pinia devtools compatibility. To upgrade to a full Pinia store, use the setStore
method to provide the instantiated pinia store:
ts
// upgrading the Task Model's store to be a Pinia store
Task.setStore(taskStore)
To create a model "instance" you just call the function WITHOUT the new
operator:
ts
const task = Task({ description: 'Do the dishes' })
Learn more about the new useFeathersModel utility
useBaseModel 🎁
The useBaseModel
utility gives you all of the BaseModel functionality without the Feathers parts. This means you can work with non-service data using the same APIs. Base model functions also come with a built-in store, and you can even perform queries on the data with store getters. Also, useBaseModel
instances come with clone
, commit
and reset
methods.
Note about Feathers Types
Replace my-feathers-api
in the below example with the package installed from your Feathers v5 Dove API. You can also provide manual types that describe the shape of your data.
ts
import type { Tasks, TasksData, TasksQuery } from 'my-feathers-api'
import { type ModelInstance, useBaseModel, useInstanceDefaults } from 'feathers-pinia'
const modelFn = (data: ModelInstance<Tasks>) => {
const withDefaults = useInstanceDefaults({ description: '', isComplete: false }, data)
return withDefaults
}
const Task = useBaseModel<Tasks, TasksQuery, typeof modelFn>({ name: 'Task', idField: '_id' }, modelFn)
BaseModel functions also have a store, which can be upgraded to a full Pinia store using the setStore
method:
ts
// upgrading the Task Model's store to be a Pinia store
Task.setStore(taskStore)
To create a model "instance" you just call the function WITHOUT the new
operator:
ts
const task = Task({ description: 'Do the dishes' })
Learn more about the new useBaseModel utility
useInstanceDefaults 🎁
You can define default values for instances using the useInstanceDefaults
. This takes the place of the former BaseModel class's instanceDefaults
method.
Learn more about the new useInstanceDefaults utility
useFeathersInstance 🎁
There's also a useFeathersInstance
utility which you can use with useBaseModel
. It's used inside of your Model function to update a model instance to support Feathers-related methods, like instance.save()
.
Learn more about the new useFeathersInstance utility
New Model Methods
FeathersModels now include methods for useFind, useGet, and useGetOnce.
Learn more on the useFeathersModel page.
Official Nuxt Module 🎁
Feathers-Pinia v2 comes with a module for Nuxt which registers auto-imports and provides Nuxt-specific utilities for data modeling.
Learn more about the new Nuxt Module
Auto-Imports Support ⚡️
Since Feathers-Pinia v2 is so modular, import statements can be verbose. New Auto-Import support for Nuxt, Vite, Webpack, Rollup, and more, is provided through the new unplugin-auto-imports
preset.
Learn more about the new Auto-Imports Support.
Feathers Client Hooks 🪝
Feathers-Pinia now fully integrates with the Feathers Client through a new set of feathersPiniaHooks
. The majority of the store logic was moved into the hooks, so you get the same experience whether you use the Feathers client, the Model Functions, or the store methods. The hooks are required in order for Feathers-connected Models or stores to work.
The above example builds on the Model function that was created in the previous useFeathersModel
example. For an all-in-one example, see one of the setup pages.
ts
import { feathersPiniaHooks } from 'feathers-pinia'
import { api } from '../feathers'
/* setup the Model function as shown earlier */
// Pass the model function to the utility in the `around all` hooks.
api.service('tasks').hooks({ around: { all: [...feathersPiniaHooks(Task)] } })
Learn more about the new hooks for Feathers Client.
Query API Reference 📖
The documentation now includes a page about supported query props. It's a great reference for what query props are supported by:
- the Feathers Query Syntax (all adapters)
- the @feathersjs/mongodb adapter
- the @feathersjs/knex adapter
- other SQL-based adapters
Learn more on the new Querying Data page.
SQL $like
Operators 🎁
The most-requested feature has finally landed: built-in support for SQL LIKE
. This means the queries made to the store will match the queries made to your SQL-backed API. This brings querying features up to parity with the built-in MongoDB support which uses the $regex
key.
These are the newly-supported SQL operators:
$like
and$notLike
for case-sensitive matches$ilike
,$iLike
, and$notILike
for case-insensitive matches
Let's have a look at them in action. First, assume that we have the following messages in the store:
json
[
{ "id": 1, "text": "Moose" },
{ "id": 2, "text": "moose" },
{ "id": 3, "text": "Goose" },
{ "id": 4, "text": "Loose" },
]
Now see the fancy new query operators in action:
ts
import { useMessages } from '../stores/messages'
const messageStore = useMessages
// $like
const { data: data1 } = messageStore.findInStore({
query: { text: { $like: '%Mo%' } }
})
expect(data1.map((m) => m.id)).toEqual([1])
// $notLike
const { data: data2 } = messageStore.findInStore({
query: { text: { $notLike: '%Mo%' } }
})
expect(data2.map((m) => m.id)).toEqual([2, 3, 4])
// $ilike
const { data: data3 } = messageStore.findInStore({
query: { text: { $ilike: '%Mo%' } }
})
expect(data3.map((m) => m.id)).toEqual([1, 2])
// $iLike
const { data: data4 } = messageStore.findInStore({
query: { text: { $iLike: '%Mo%' } }
})
expect(data4.map((m) => m.id)).toEqual([1, 2])
// $notILike
const { data: data5 } = messageStore.findInStore({
query: { text: { $notILike: '%Mo%' } }
})
expect(data5.map((m) => m.id)).toEqual([3, 4])
These new operators support queries made with SQL-backed adapters like the official, core SQL service adapter in Feathers v5 Dove:
These adapters will also work:
- feathers-knex, the Feathers v4 Crow version of
@feathersjs/knex
, above - feathers-objection
- feathers-sequelize
If you use any of the above database adapters, give the new query operators a try! Enjoy your new superpowers!
Read more about all supported query filters and operators on the Querying Data page.
params.clones
You can now pass params.clones
to either findInStore
or getFromStore
to return all matching data as clones of the original data. This was formerly known as params.copies
in Feathers-Vuex.
Learn more in the Querying Data page
Built-in Patch Diffing 🎁
Efficiency Tip
Don't waste bandwidth! Just send the props that change!
Patch diffing, which originated in Feathers-Vuex, is now back in Feathers-Pinia with a smarter, faster algorithm that will work for any scenario you can dream up.
Diffing only occurs on patch
requests (and when calling instance.save()
calls a patch
).
ts
// clone a record
const clone = user.clone()
// make changes
clone.name = 'Feathers is Amazing!'
// save
await clone.save(). // --> Only the changed props go to the server!
How It Works
- By default, all keys are deep-compared between the original record and the clone.
- Once all changes are found, only the top-level keys are sent to the server.
Diffing will work on all databases without data loss.
Customize the Diff
You can use the diff
option to customize which values are compared. Only props that have changed will be sent to the server.
ts
// string: diff only this prop
await clone.save({ diff: 'teamId' )
// array: diff only these props
await clone.save({ diff: ['teamId', 'username'] )
// object: merge and diff these props
await clone.save({ diff: { teamId: 1, username: 'foo' } )
// or turn off diffing and send everything
await clone.save({ diff: false })
Always Save Certain Props
If there are certain props that need to always go with the request, use the with
option:
ts
// string: always include this prop
await clone.save({ with: 'teamId' )
// array: always include these props
await clone.save({ with: ['teamId', 'username'] )
// object: merge and include these props
await clone.save({ with: { teamId: 1, username: 'foo' } )
Specify Patch Data
When calling .save()
or .patch()
, you can provide an object as params.data
, and Feathers-Pinia will use it as the patch data. This bypasses the diff
and with
params.
js
const task = Task({ description: 'Do Something', isComplete: false })
await task.patch({ data: { isComplete: true } })
Read more about FeathersModel Instances
Reactive Model Instances ➕
Thanks to the Model Function API, all Model instances are now always reactive, even when not added to the store.
ts
import { Task } from '../models/task'
const task = Task({ description: 'Bind me to a template. I am ready.' })
Read more about Model Instances.
Handle Associations
Two new utilities make it easier to add relationships between records without depending on associations in-memory. You can setup associations in both directions between models.
Read more about Association Patterns.
associateFind
🎁
The associateFind
utility allows you to define one-to-many relationships on your Model functions. The makeParams
property allows you to specify the query that defines the relationship. This example is truncated. For a full example, see the "Start a Project" pages.
ts
import type { Users } from 'my-feathers-api'
import { type ModelInstance, useInstanceDefaults, associateFind } from 'feathers-pinia'
import { Message } from './message'
const modelFn = (data: ModelInstance<Users>) => {
const withDefaults = useInstanceDefaults({ email: '', password: '' }, data)
const withMessages = associateFind(withDefaults, 'messages', {
Model: Message,
makeParams: (data) => ({ query: { userId: data.id } }),
handleSetInstance(message) {
message.userId = data.id
},
})
return withMessages
}
The handleSetInstance
function allows you to write data to the messages
property and make sure each record becomes properly associated.
Read more about associateFind
associateGet
🎁
The associateGet
utility allows you to define one-to-one
relationships on your Model functions. The getId
property allows you to specify the id to use to get the related data.
ts
import type { Messages } from 'my-feathers-api'
import { type ModelInstance, useInstanceDefaults, associateGet } from 'feathers-pinia'
import { User } from './user'
const modelFn = (data: ModelInstance<Messages>) => {
const withDefaults = useInstanceDefaults({ text: '', userId: null }, data)
const withUser = associateGet(withDefaults, 'user', {
Model: User,
getId: (data) => data.messageId
})
return withUser
}
Read more about associateGet
New useFind
API 🎁
The useFind
API has been completely rewritten from scratch. A couple of its best features include
- Intelligent Fall-Through Caching - Like SWR, but way smarter.
- Pagination Support - Built in, sharing the same logic with
usePagination
.
See all of the features on the useFind
page.
See the component example of server-side pagination on the useFind
page.
useFindWatched
API ⚠️
To make migration to the new useFind
API easier, the old useFind
API has been renamed and is now called useFindWatched
.
Learn more about the old API on the useFindWatched
page.
See the new API on the useFind
page.
Store API Improvements
The useFind
utility -- for implementing fall-through-cached find
requests -- is now available directly on the store, further reducing boilerplate.
store.useFind
➕
With the old way, you have to import useFind
and provide the model to it from the instantiated store.
ts
import { useFind } from 'feathers-pinia'
import { useTutorials } from '../store/tutorials'
const tutorialStore = useTutorials()
const tutorialsParams = computed(() => {
return { query: {}, }
})
const { items: tutorials } = useFind({ model: tutorialStore.Model, params: tutorialsParams })
In the new way, there's no need to import useFind. Call it as a method on the store and don't pass model
ts
import { useTutorials } from '../store/tutorials'
const tutorialStore = useTutorials()
const tutorialsParams = computed(() => {
return { query: {}, }
})
const { items: tutorials } = tutorialStore.useFind({ params: tutorialsParams })
Think of all of the extra time you'll have instead of having to write those 1.5 lines of code over and over again! 😁
store.useGet
➕
The useGet
utility -- for implementing fall-through-cached get
requests -- is now available directly on the store, further reducing boilerplate.
Removals ➖
The following modules are no longer included:
LZW Storage is Out
Prior to this version, Feathers-Pinia included a localStorage plugin that used LZW compression. It came with the benefit of doubling the amount of data you could put in storage. The downside was that it made the bundle size big, so we removed it. It will be published as an independent package at a later date.
Our LocalStorage adapter remains part of the package and is so fast that it makes Single Page Apps feel like they're doing Server Side Rendering. If you haven't tried it, yet, it's easy to setup and it's worth it!
No More defineAuthStore
The useAuth utility takes the place of defineAuthStore
.
See how to migrate from defineAuthStore to useAuth
No instance.update()
method
The rarely-used update
method has been removed from the instance interface. Use the patch method, instead, to take advantage of patch diffing and partial updates. You can still replace an entire object by just sending all of the data through patch
. The Model Functions and Feathers-connected stores continue to have an update
method, which an also be used.
Read more about Model Instances.