Giter VIP home page Giter VIP logo

feathers-pinia's Introduction

feathers-pinia's People

Contributors

amoghpalnitkar avatar daffl avatar dallinbjohnson avatar davidbludlow avatar eshanj19 avatar fossprime avatar fratzinger avatar gorango avatar granipouss avatar gustojs avatar himanshu735319 avatar jd1378 avatar laurensiusadi avatar luke3butler avatar majesticpotatoe avatar marshallswain avatar miguelrk avatar philipimperato avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

feathers-pinia's Issues

Auth Store documentation errors

The documentation shows an example of being able to get the authenticated user through a getter on the Auth store:
https://feathers-pinia.pages.dev/guide/auth-stores.html#setup

The code referenced is:

const authStore = defineAuthStore({
  feathersClient,
  state: () => ({
    userId: null
  }),
  getters: {
    user() {
      return this.userId ? User.getFromStore(this.userId) : null
    },
  },
  actions: {
    handleResponse(response: any) {
      this.userId = response.user.id || response.user._id
      User.addToStore(response.user)
      return response
    },
  },
})

There are a couple of problems with this:

  1. response: any should be typed appropriately.
  2. The getter supplied above, if used as is, will throw Typescript errors for the following reasons:
    • Property 'userId' does not exist on type '{ user(): any; }'. Did you mean 'user'?
    • 'user' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
  3. If you update the getter above to something like the following code, then there are no Typescript errors, but Vue will forcefully redirect you to the login page, even if your token is still valid.
user(): User | null {
      return (this.userId ? User.getFromStore(this.userId) : null) as User | null
}

bug: infinite loop with wrong record Id

using const user = computed(() => props.id ? userStore.useGetOnce(props.id).data.value : null), a record that does not exist on server will trigger an infinite loop of querying the server with an uncaught 404 error.

I've tried wrapping the call to useGetOnce in a try/catch without success

Feature request: Add ability to pass params to saveHandler.

Using handleClones, the save handler sends the data to patch, but we can't provide params as an argument.

An example use case:

When we use feathers-graphql-populate, this leads to the returned value being merged to the store without populated data, because we didn't pass $populateParams for the patch event.

Update useGet with getId, getParams

Hey! I'm looking at the useGet.ts source and I'm wondering why you didn't use the getId and getParams methods within the computed / get methods. Do you lose reactivity that way?

Screen Shot 2022-03-24 at 12 59 32 PM

The defineStore() wrapper expects state to be an object and not a function like pinia

Context:

pinia: the native defineStore() method requires state to be a function which returns an object e.g. state: () => ({...}).

feathers-pinia: the defineStore() method expects an object directly e.g. state: {...}.

Problem:

Developers can easily forget to pass an object causing store state to not be merged correctly.

Solution:

The feathers-pinia defineStore() wrapper should also only expect a function so that the wrapper can pass the options 'as is' to the original defineStore().

useFind causes small UI disruption when changing query params

This appears to only occur when $sort is set in the params sent to useFind, but it may occur under similar scenarios. As seen below, as the query parameters change, such as changing the $sort value, it causes a slight disruption in the UI before rendering the new data set.

UI

This appears to be caused by this block of code in useFind. What ends up happening is a call to makeUseFindItems is made multiple times when $sort changes. When this occurs, the results from makeUseFindItems are the following:

  • []
  • []
  • The results from the updated search query

This is ultimately the cause of the UI disruption. To ensure it's not just an implementation, here are the following code blocks currently used.

domainStore

export class Domain extends BaseModel {
  _id: string;
  domain: string;
  description: string;
  createdAt: Date;
  createdBy: string;
  createdByUser?: User;
  updatedAt: Date;
  updatedBy: string;
  updatedByUser?: User;
}

const servicePath = "system/domains";
export const useDomainStore = defineStore({ servicePath, Model: Domain });

Vue component

const domainStore = useDomainStore();
const { storeParams, pagination, loading, fetchRecords, setPaginationParams, setPagination } = useFeathersFindStore({ sortBy: "domain" });
const { data: domains, limit, currentPage, total, find } = domainStore.useFind(storeParams);

// This is called by Quasar when it is necessary to get records for the table.
const fetchDomains: QTableProps["onRequest"] = async (requestProps) => {
      setPaginationParams(requestProps);
      await fetchRecords(find, limit.value, total.value, currentPage.value);
};

useFeathersFindStore

import { ref, reactive } from "vue";
import { QTableProps } from "quasar";
import { FindFn, BaseModel, FindClassParams } from "feathers-pinia";
import { IFeathersStore, Pagination } from "src/types";

export const useFeathersFindStore = ({
  sortBy = "_id",
  sortDirection = 1
}: IFeathersStore) => {
  const loading = ref(false);
  const pagination = ref(new Pagination(sortBy));
  const requestPagination = ref(new Pagination(sortBy));
  const storeParams = reactive<FindClassParams>({
    query: {
      $limit: 10,
      $skip: 0,
      $sort: {
        [sortBy]: sortDirection
      }
    },
    paginate: true,
    onServer: true,
    immediate: false
  });

  const setPaginationParams: QTableProps["onRequest"] = (requestProps) => {
    requestPagination.value = requestProps.pagination;
    const {
      sortBy: sort,
      descending,
      rowsPerPage,
      page
    } = requestProps.pagination;

    storeParams.query = {
      ...storeParams.query,
      $limit: rowsPerPage,
      $skip: (page - 1) * rowsPerPage,
      $sort: {
        [sort]: descending ? -1 : 1
      }
    };
  };

  const setPagination = (limit: number, total: number, currentPage: number) => {
    pagination.value = {
      sortBy: requestPagination.value.sortBy,
      descending: requestPagination.value.descending,
      rowsPerPage: limit,
      rowsNumber: total,
      page: currentPage
    };
  };

  const setLoading = (value: boolean) => {
    loading.value = value;
  };

  const fetchRecords = async (
    find: FindFn<BaseModel>,
    limit: number,
    total: number,
    currentPage: number
  ) => {
    setLoading(true);

    try {
      await find();
      setPagination(limit, total, currentPage);
    } catch (err: unknown) {
      console.log("failed", err);
    }

    setLoading(false);
  };

  return {
    storeParams,
    pagination,
    loading,
    fetchRecords,
    setPaginationParams,
    setPagination,
    setLoading
  };
};

No types after 0.36.3

Only in 0.36.3
image

Adding "types": "src/", to package.json fixes.

Are types really added to bundled?

Feature Parity with Feathers-Vuex

  • Service Stores
  • Model Classes
  • Temps Support
  • Clone & Commit
  • Relationships
  • Feathers Events support with Event Queue
  • useFind and useGet
  • LocalStorage Sync
  • Custom State
  • Custom Getters
  • Custom Actions
  • Auth Module
  • Test for overwriting the built-in state, getters, and actions
  • Add static create, update, and patch functions to BaseModel.
  • Allow customizing tempIdField.
  • Back-port removeFromStore to Feathers-Vuex
  • Bring over params.copies from Feathers-Vuex
  • Headless Components
    • FeathersForm wrapper around useClones
    • FeathersFind wrapper around useFind
    • FeathersGet wrapper around useGet
    • FeathersPagination wrapper around usePagination
  • TypeScript Improvements
  • Docs
    • Intro
    • Module Overview
    • Setup
    • Service Stores
    • Auth Stores
    • Model Classes
    • BaseModel
    • Model Instances
    • useFind
    • useGet
    • usePagination
    • useClones
    • syncWithStorage

clone & reset handling different than feathers-vuex

In feathers-vuex cloning a clone isn't possible. item.clone().clone() throws an error: 'You cannot clone a copy'.

// see https://github.com/feathersjs-ecosystem/feathers-vuex/blob/8643bd7b0cd045fcfd1e407143c3221ae58b7486/src/service-module/make-base-model.ts#L305-L313
if (this.__isClone) {
  throw new Error('You cannot clone a copy')
}

In feathers-pinia cloning a clone is totally possible. You already have a test for it. See:

test('can reset', async () => {
const message = await messagesService.create({
text: 'Quick, what is the number to 911?',
})
const clone = message.clone({ foo: 'bar' })
const reset = clone.clone()
expect(reset.foo).toBeUndefined()
expect(clone === reset).toBeTruthy()

Don't know, if it's intended, though. In feathers-pinia there is item.reset() as in feathers-vuex, but it calls store.resetCopy(), which is not existent. See:

public reset(): this {
const { store } = this.constructor as typeof BaseModel
return (store as any).resetCopy(this)
}

If clone.clone() is intended to work, that's a breaking change from feathers-vuex worth mentioning in the docs!

Also constructor adds __isClone differently compared to feathers-vuex. In feathers-vuex it only gets added when it should be a clone:

// see https://github.com/feathersjs-ecosystem/feathers-vuex/blob/8643bd7b0cd045fcfd1e407143c3221ae58b7486/src/service-module/make-base-model.ts#L151-L157
// Mark as a clone
if (options.clone) {
  Object.defineProperty(this, '__isClone', {
    value: true,
    enumerable: false
  })
}

In feathers-pinia it always gets added as true or false:

this.__isClone = !!options.clone

You also make it explicitely defined in (see exclamation mark):

public __isClone!: boolean

But on clone.commit() you remove it. You have a test for that:

test('can commit ', async () => {
const message = await messagesService.create({
text: 'Quick, what is the number to 911?',
})
const clone = message.clone()
clone.foo = 'bar'
const committed = clone.commit()
expect(committed.foo).toBe('bar')
expect(committed.__isClone).toBeUndefined()
})

That's because fast-copy does not add the enumerable: false property of __isClone. Whats the intended way?

this[placeToStore][id] = fastCopy(this.clonesById[id])

Todos:

  • say if clone.clone() should work or not @marshallswain
  • say if clone.__isClone should always return a boolean or if it is not existent @marshallswain
  • get clone.reset() running again, add old resetCopy mutation as resetClone action @fratzinger
  • add resetClone renaming to the 'What's Different from Feathers-Vuex' section
  • add a test for clone.reset() @fratzinger
  • If clone.clone() is intended to work, add it to the 'What's Different from Feathers-Vuex' section @fratzinger
  • If clone.__isClone should always return a boolean, add Object.defineProperty to store.commit; change the test of commit() accordingly @fratzinger

Error behavior in useGet

const { item, error } = useGet({ model, id })

Bug behavior, error not showing properly

  1. Provide invalid id
  2. Error showing Not Found ✔
  3. Reset error manually error.value = null
  4. Error is gone
  5. Provide invalid id
  6. Error not showing Not Found ❌
  7. Provide valid id
  8. Provide invalid id
  9. Error showing Not Found ✔

item.create() does not add id to item after call

After a call to create, item should contain the id returned from server.

To reproduce:

const post = New Post({
  name: 'New post'
})
await post.create()
console.log(post.id) // returns undefined

Here is a quick fix :

let post = New Post({
  name: 'New post'
})
await post.create()
const { data } = postStore.findInStore({
        query: {
          name: 'New post'
        }
})
post = data[0]
console.log(post.id) // ok

But it's kind of broken as you could have many posts with the same name.

idea: clientside `paginate: false` flag for `find` action

Just a quick thought:

Problem:

MyModel.find({...}) returns MyModel[] | Paginated<MyModel>.

When I need an array of values I do this:

const arrOrPaginated = MyModel.find({ ... });
const arr = Array.isArray(arrOrPaginated) ? arrOrPaginated : arrOrPaginated.data;

// now I can use arr but why so complicated?!

Solution:

const arr = MyModel.find({ query: {}, paginate: false });
// I can safely know, that `arr` is an array

The asArray flag could do exactly this:

const arr = Array.isArray(arrOrPaginated) ? arrOrPaginated : arrOrPaginated.data;

Even the type could be inferred to be an array (pretty much as paginate: false on the server side).

Where to put this logic? Maybe here: https://github.com/marshallswain/feathers-pinia/blob/main/src/service-store/make-actions.ts#L112

We have afterFind, but it does not know about params. The signature of afterFind probably should be: afterFind(response, params) anyways.

save('name') feature

As the save_handler, it would be nice to have a save('name') option to send a PATCH with an object containing only the mentioned property.

TypeError: Cannot read properties of undefined (reading 'clone')

In case an error happens during server patch request on line 357, saved variable will be undefined.

That results in the following error below on line 366.

TypeError: Cannot read properties of undefined (reading 'clone')

if (__isClone && params.diff != false) {
const original = this.Model.getFromStore(id) as any
const data = diff(original, this, params.diff)
const rollbackData = fastCopy(original)
// Do eager updating.
if (params.commit !== false) this.commit(data)
// Always include matching values from `params.with`.
if (params.with) {
const dataFromWith = pickDiff(this, params.with)
// If params.with was an object, merge the values into dataFromWith
if (typeof params.with !== 'string' && !Array.isArray(params.with)) {
Object.assign(dataFromWith, params.with)
}
Object.assign(data, dataFromWith)
}
// If diff is empty, return the clone without making a request.
if (Object.keys(data).length === 0) return this as any
try {
saved = (await store.patch(id, data, params)) as this
} catch (error: any) {
// If saving fails, reverse the eager update
this.commit(rollbackData)
}
} else {
saved = (await store.patch(id, this, params)) as this
}
// Make sure a clone is returned if a clone was saved.
return __isClone ? saved.clone() : saved
}

feature request : hybrid pagination with useFind

When using useFind with onServer: true, if a new item is added to server from another source, this newItem is not automagically shown.
It is listed in the store's itemsById though.

@marshallswain said

oh yes. This is by design. I might need to clarify that in the docs. When you enable server-side pagination there is no way to know where the new items fit into pagination.
Actually, this is no different than previous behavior. Do get the automagic lists, you skip the onServer: true to take control on the client again. Then call find manually or with a watcher.

I think it’s time to implement hybrid pagination. A combination of the two techniques. Outer pagination has a high limit. Inner pagination has maybe 10x smaller limit.
We can definitely make it smarter.

Can't resolve vue-demi on fresh install

Vite throws ERROR: Could not resolve "vue-demi" when attempting to build a freshly generated Vue project using feathers-pinia.
Initially I ran into this problem when using Vue + feathers-pinia with a custom webpack build process. I thought I might have misconfigured something and attempted again with a fresh project.

These are the steps I took to run into this issue:

I generated a fresh Vue project with npm init vue@latest as instructed by Vue's quickstart.
After this, I installed pinia and feathers-pinia with npm. According to the current feathers-pinia documentation, these should be all the required steps to get the library up and running.
I added a feathers-pinia store according to the store.pinia.ts code snippet in the setup instructions and added the newly created pinia store to the app with .use(pinia).
At this point, attempting to run npm run dev results in the error in question.

Importing or dynamically inherit feathers dove schema as Model instance default?

Hi Marshall,

Well time to upgrade or at least start poking a stick at Feathers Dove with your Feathers Pinia lib. Real cool everything i read in your docs and not significant changes from the feathers-vuex pattern. Looks easy, good docs so far everything very readable and relatable.

So, i might have missed it - was hoping to find a param in the Models something like inheritSchema that would setup my instance default to whatever schema I create in Feathers for the service. Feathers Vuex saves a ton of time and boilerplate but the one thing - the only thing - I found tedious was keeping my feathers db models in sync with my feathers-vuex models.

Reading about dove schemas the first thing came to mind was yay now I can have a source of truth / write once and everything is magic from there.

I'm sure you've considered this. any feedback or link to look at?

Set up Experience 2 - Feathers v4 / Socket v3 error

Ok, crash after first npm install:

@feathersjs/feathers @feathersjs/authentication-client feathers-hooks-common @feathersjs/socketio-client  socket.io-client
Error: You are trying to use the Socket.io client version 3 or later with Feathers v4 which only supports Socket.io version 2. Please use socket.io-client version 2 instead.

But I will use Feathers v5/Dove anyway..., so I suppose doc is wrong? Why does not everybody has this?

Reported in Feathers, so suppose yo can close here...

Computed pageCount not updating properly

I'm making a pagination wrapper component, the idea is to have it reusable between pages with different data.
Here's the code laurensiusadi/vitesse-feathers-pinia-example@6e00d75

But the pageCount is not updating when limit, filter, or search is changing. It seems the computed isn't triggered.
I noticed it doesn't work when usePagination is in children component, or one level deeper than useFind.
The supplied pagination and latestQuery to usePagination comes from props.

In this next commit, I put usePagination up on same level as useFind and now pageCount works as expected.
laurensiusadi/vitesse-feathers-pinia-example@fca3941

What could be the problem?

Authenticate doesn't work out of the box

I just started a new project to move my older one to this new stack: Vue 3, Pinia, Feathers-Pinia. So I'm new to this.

Version:

"feathers-pinia": "^0.29.5",
"pinia": "^2.0.11",
"vue": "^3.2.30",

I found a bug when calling auth.authenticate.
The call to feathersClient.authenticate went through and got the response.
The error occurs here https://github.com/marshallswain/feathers-pinia/blob/main/src/define-auth-store.ts#L45
It can't assign this with the new object response to replace state.
Error message: TypeError: 'set' on proxy: trap returned falsish for property 'user'

Here's how I fixed it in my auth store:

async authenticate(authData) {
  try {
    const response = await feathersClient.authenticate(authData)
    Object.assign(this.$state, { ...response, isAuthenticated: true })
    return this.handleResponse(response) || response
  } catch (error) {
    this.error = error
    return this.handleError(error)
  }
},

The only fix being this.$state instead of just this.

Build fails when minify: true is set in vite.config.ts

Problem

Enabling 'build.minify': true in vite.config.ts leads to the following build error:

Uncaught (in promise) TypeError: n.api.User is not a constructor
    at setupInstance (index.7faf713a.js:formatted:42194)
    at new mo (index.7faf713a.js:formatted:14221)
    at new _1e (index.7faf713a.js:formatted:42192)
    at index.7faf713a.js:formatted:13772
    at Array.forEach (<anonymous>)
    at Object.addOrUpdate (index.7faf713a.js:formatted:13767)
    at Object.<anonymous> (index.7faf713a.js:formatted:6922)
    at Object.handleFindResponse (index.7faf713a.js:formatted:13678)
    at Object.<anonymous> (index.7faf713a.js:formatted:6922)
    at index.7faf713a.js:formatted:13665

Context

This is due to the setupInstance() method being minified to the following:

// at setupInstance (index.7faf713a.js:formatted:42194)

class _1e extends Ar {
    static setupInstance(t, {store: r, models: n}) {
        return t.user && (t.user = new n.api.User(t.user).addToStore()),
        t.workspace && (t.workspace = new n.api.Workspace(t.workspace).addToStore()),
        t
    }
}

The following is the vite.config.ts build setting that causes this issue. Note that setting build.minify: false solves this, but obviously does not minify the code (keeps it as is, therefore no minifying error).

// vite.config.ts

const config = defineConfig({
  ...
  build: {
    minify: true, // FIXME: this breaks on build due to setupInstance
  },
})

Might be an easy fix, not sure what might be leading to this though.

add `__isTemp` getter to BaseModel

feathers-vuex has __isTemp by Object.defineProperty.
feathers-pinia does not have it anymore. I use it in feathers-vuex. The removement is a breaking change and should be stated in the docs.
oooor we add a getter to the BaseModel:

get __isTemp() {
  const { idField } = this.constructor as typeof BaseModel
  return getId(this, idField) != null
}

Todo:

  • add __isTemp getter @fratzinger
  • add a test for it (item with __tempId, item with __tempId and [idField], item with [idField] @fratzinger

Model.associations must be defined on each model

Since associations is defined on the BaseModel, all associations are assigned to the BaseModel.associations property. This will cause collisions between Models, so it must be fixed.

The current workaround is to define the associations property on each class:

import { BaseModel } from '../store/store.pinia'
import { associateFind, BaseModelAssociations, type AssociateFindUtils } from 'feathers-pinia'
import { Task } from './task'

export class User extends BaseModel {
  _id?: string
  name = ''
  email = ''
  password = ''

  static associations: BaseModelAssociations = {}  // RIGHT HERE!!!
  tasks?: Task[]
  _tasks?: AssociateFindUtils<Task>

  constructor(data: Partial<User> = {}, options: Record<string, any> = {}) {
    super(data, options)
    this.init(data)
  }

  static setupInstance(data: Partial<User>) {
    associateFind(data as any, 'tasks', {
      Model: Task,
      makeParams: () => ({ query: { userId: data._id } }),
      handleSetInstance(task: Task) {
        task.userId = this._id
      },
    })
  }
}

Could not resolve "@feathersjs/rest-client" on latest

Getting the following error on latest version w/vite:

[ERROR] Could not resolve "@feathersjs/rest-client"

node_modules/feathers-pinia/dist/feathers-pinia.mjs:5:34:
  5 │ import { FetchClient as bt } from "@feathersjs/rest-client";
    ╵                                   ~~~~~~~~~~~~~~~~~~~~~~~~~

You can mark the path "@feathersjs/rest-client" as external to exclude it from the bundle, which
will remove this error.

Assuming this came about with the addition of the ohmyrest from pre.16

Setup experience - 1 - Adding Feathers-pinia if there is already Pinia installed

Currently trying to get Feathers Pinia installed. The doc's look awesome, so I thought peace of cake:

If you already have Pinia in your app, I suppose we can use the existing file? Maybe add this to the docs?

// src/plugins/pinia.ts
import { createPinia } from 'pinia'

// The setupFeathersPinia utility wraps defineStore
// ... provides a global configuration
// https://feathers-pinia.pages.dev/guide/setup.html#pinia

import { api } from '../feathers'
import { setupFeathersPinia } from 'feathers-pinia'

export const { defineStore, BaseModel } = setupFeathersPinia({
  clients: { api },
  idField: '_id'
})

// Feathers and other Pinia stores
//
export default createPinia()

useFind does not add id to results when using $select

I have a very basic query that uses $select:

const { data, find } = store.useFind({
  query: computed(() => ({
    $select: ['Text', 'Value'],
  })),
})

The server side data retrieval is as expected (server query always returns the ID field from feathers, even if not specified in $select query)
The local query however does not return the id in the data set causing it to set a _tempId instead.

Stores registered with feathers-pinia not showing-up in the devtools

TLDR: Original defineStore() called with options.storeId instead of options.id and pinia requires options.id to register store in vue devtools.

Issue

Stores created with feathers-pinia not showing-up in vue devtools (at least when using vue2).

Context

From pinia docs:

This name, also referred as id, is necessary and is used by Pinia to connect the store to the devtools.

Solution

In this case, the only fix should be to rename the expected options.storeId to options.id to at least have a more transparent/direct mapping to the pinia API. This change does not break tests and remains backwards compatible.

So basically change the following code:

  // Create and initialize the Pinia store.
  const storeOptions: any = makeServiceStore({
-   storeId: options.id || `service.${options.servicePath}`,
+   id: options.id || `service.${options.servicePath}`,
    idField: options.idField || idField || 'id',
    clientAlias,
    servicePath,
    clients,
    Model: options.Model,
    state,
    getters,
    actions,
    whitelist,
  })
  function useStore(pinia?: Pinia): any {
    const useStoreDefinition = definePiniaStore(storeOptions)
    ...
  }

Using saveHandler without an argument errors out

Following the example from the docs: https://feathers-pinia.pages.dev/guide/handle-clones.html#using-the-savehandlers

When save_user() saveHandler is called with no argument, it's supposed to compare all the clone object and then patch.

But when we pass no argument, it doesn't pass the check inside the handler, resulting in the error message:
The first argument to 'saveuser' must be a prop name, an array of prop names, or an object.

The patch doesn't happen (there's no websocket event sent).

[Questions] Support Custom Adapter ?

Hi, thanks for maintain this library and make it useful out of box!

Here is my question, I'm writing a custom adapter at the featherJS server side, the data return will become

{
    bookmark: data.bookmark || 'nil',
    total: data.docs.length,
    limit: filters.$limit  || 0,
    skip: filters.$skip || 0,
     data: data.docs,
};

If I using feathers-pinia , how do I add hooks / replace handleFindResponse method to add bookmark data into the pagination data ? Is this possible?
If this is not the way, can you guide me any method that I can store the bookmark for the next query? Any guidance will be appreciated. Thank you in advance.

SSR Support

  • Add ssr option
  • Add isSsr computed getter
  • useFind: Add isSsr and request properties
  • useGet: Add isSsr and request properties
  • Extract shared logic from Nuxt plugin
  • Give Nuxt/Vite plugin option to specify array of urls relative to root.
  • Finish Nuxt plugin and Vite plugin

.clone() returns a non-clone after item is updated from server

Here is a sample code to reproduce the error :

// init
const postStore = usePosts()
const { item: post, isPending } = useGet({ model: postStore.Model, id: props.id })

// clone
const postClone = post.clone()
postClone.name = 'new name'
postClone.commit() // WORKS

// -------- post changed from server ------------------

// clone again
postClone2 = post.clone()
postClone.name = 'other name'
postClone2.commit() // ERROR 'You cannot call commit on a non-copy'

To me, it seems to be an issue with the existing clone that gets updated with a __isClone: false when an updated item is received from server.

Here is a quick fix

// clone
const postClone = post.clone()
postClone.name = 'new name'
postClone.commit()

// -------- post changed from server ------------------

// reset existing clone
postStore.clonesById[props.id] = null // HOTFIX

postClone2 = post.clone()
postClone.name = 'other name'
postClone2.commit() // WORKS

Example with typescript

Hello,

could you give an example with typescript ?
It’s not clear for me how and where to define the types of a model.

Thx,
Filip

when using pagination, queryId is including $client params included with paramsForServer

I am using a before hooks in my feathers-client setup where I'm using feathers-hooks-common paramsForServer method to pass whitelisted params to the server. The $client object is being included in the queryId on useFind with pagination:true.

This is making it so I cannot use pagination because my params object that becomes the queryId never matches the local one being passed as an argument to useFind - because the hook is adding the $client object with paramsForServer.

This is causing some less show-stopping, but obnoxious side effects such as the long accessToken being included in the queryId as well.

Handling Vue2 reactivity caveats for useFind/useGet

TLDR: UI is blank (fails to populate with useFind/useGet data) due to reactivity issues when using vue2 with feathers-pinia.

Context

Basically when I refresh the page, all find()s and get()s are executed successfully (as shown by the devtools network tab), but they won't be rendered in the UI components (despite stores containing the fetched data). Only for example changing page, and then returning to the previous page re-flushes the UI and renders the components with the already fetched data (without refetch).

This I'm sure has to do with the vue2 reactivity caveats. After much thought and time spent trying to fix this, I figured these issues are inevitable for vue2 due to the Observer-bases reactivity (compared to the newer Proxy based reactivity of vue3 which fixes many of these issues).

Workaround for Vue2

For users stuck with vue2 for now, I settled with using a simple workaround by wrapping the useFind and useGet composables as follows:

import type { BaseModel, ServiceStore, UseFindOptionsStandalone, UseGetOptionsStandalone } from 'feathers-pinia'
import { useFind, useGet } from 'feathers-pinia'

interface M extends BaseModel {}

export function useFindVue2(
  store: ServiceStore,
  options: UseFindOptionsStandalone<M>,
) {
  const { items: _items, haveLoaded, ...rest } = useFind(options)

  const items = computed(() => haveLoaded.value
    ? _items.value
    : store.findInStore(options.params.value).data,
  )

  return { items, haveLoaded, ...rest }
}

export function useGetVue2(
  store: ServiceStore,
  options: UseGetOptionsStandalone<M>,
) {
  const { item: _item, hasLoaded, ...rest } = useGet(options)

  // NOTE: check if _item.value is truthy to force computed to re-evaluate ternary
  // operator since _item.value is sometimes null just after hasLoaded.value is set
  // to true, so watch both to trigger updates when _item finally updates (if it does)
  const item = computed(() => hasLoaded.value && _item.value // check both
    ? _item.value
    : store.getFromStore(options.id?.value ?? null, options.params?.value) ?? {},
  )

  return { item, hasLoaded, ...rest }
}

I've been using the above and it is working properly, however, there might be certain edge cases where this does not solve the reactivity issues, though I don't believe this will break.

@marshallswain mentioned conditionally returning these as useFind/useGet for vue2 apps might be something worth adding to feathers-pinia, so opening up this issue.

useGet fails when id is null

useGet throws an error when id is null.
It would be nice to allow id to be null for patterns where id is an optional prop.

import { useUsers } from '../store/users'

interface Props {
  id?: string | number
}
const props = defineProps<Props>()
const userStore = useUsers()
const { id } = toRefs(props)

const { data: user } = userStore.useGetOnce(id)

This triggers an error when id is null.

Here is how we fixed it for now :

import { useUsers } from '../store/users'

interface Props {
  id?: string | number
}
const props = defineProps<Props>()
const userStore = useUsers()

const user = computed(() => props.id ? userStore.useGetOnce(props.id).data.value : null)

finish/remove `eventLocksById`

What is eventLocksById for? I saw the mention in the docs and I saw the implementation in the code. As of now it's not used anywhere.

I stumbled upon this while adding types. See this as a friendly reminder to tackle this topic sometimes in the future. If you like to share, I would love to hear your idea behind this.

Type error when using useFind() from model and isPending

When running vue-tsc I am receiving multiple type errors: Property 'isPending' does not exist on type 'UseFindComputed<>'

This seems to happen when trying to access isPending from useFind() per the docs:
const { items, isPending } = someStore.useFind()

looking into the type definition and interface, seems like some of the state isn't defined as per the docs:

useFind: (options: UseFindOptions) => UseFindComputed<M>;

export interface UseFindComputed<M> {
    items: ComputedRef<M[]>;
    servicePath: ComputedRef<string>;
    paginationData: ComputedRef<AnyData>;
    isSsr: ComputedRef<boolean>;
}

idField seemingly ignored on service

So after a long amount of trial and error as to why nothing was being added to the store, it seem settings idField to any value other than 'id' seems to get ignored.

example code below:

import { defineStore, BaseModel } from '@/plugins/pinia'
import { api } from '@/plugins/feathers'

export class AuthUser extends BaseModel {}

const servicePath = 'users'
export const useAuthUser = defineStore({
  idField: 'userID',
  Model: AuthUser,
  servicePath,
})

api.service(servicePath).hooks({})

Results from get/find return results properly (which can be done by peeking at the results via a hook) but its never added (getFromStore / findInStore always return 0 records)

Currently just working around this by setting id to whatever the fieldID i want on my backend.

useFind keeps previous queries in cache

I've found an issue when changing query : previous queries are kept in cache inside useFind if a property is not explicitly changed

// Updates the _params with everything from _newParams except `$limit` and `$skip`
export function updateParamsExcludePage(_params: Ref<FindClassParams>, _newParams: MaybeRef<FindClassParams>) {
  _newParams = unref(_newParams)
  const query = _.omit(_newParams.query, '$limit', '$skip')
  const newParams = _.omit(_params.value, 'store', 'query')
  Object.assign(_params.value.query, query)
  Object.assign(_params.value, newParams)
}

if _params.value.query is { firstname: Emmanuel } and _newParams.value.query is { age: 16 }, resulting query is { firstname: Emmanuel , age: 16 }
I think expected behavior is to have resulting query be { age: 16 }

I encountered this issue when using a computed params.

Auth store type does not match function - setLoaded

The setLoaded function's type definition and function definition differ.

Interface - https://github.com/marshallswain/feathers-pinia/blob/main/src/define-auth-store.ts#L53
Function - https://github.com/marshallswain/feathers-pinia/blob/main/src/define-auth-store.ts#L125

The question is, which one should be the correct one to use? Should setLoaded always set isLoading to false? Or is the type definition correct in that the user should be able to toggle isLoading?

Whitelisted param with find returns only empty array

Hello,

(like already on discord): Using feathers-pinia with a whitelisted query argument does only return an empty value.

If you change the whitelist.test.ts in /tests with the following code you see the issue.

Thanks,
Martin


  test('enables custom query params for the find getter', async () => {
    const { defineStore, BaseModel } = setupFeathersPinia({
      clients: { api },
      whitelist: ['$regex'],
    })

    const useMessagesService = defineStore({ servicePath: 'messages' })
    const messagesService = useMessagesService(pinia)

    await messagesService.create({ text: 'test' })
    await messagesService.create({ text: 'yo!' })

    const dataREGEX = messagesService.findInStore({ query: { $regex: 'test' } }).data
    const dataNOREGEX = messagesService.findInStore({ query: {} }).data

    console.log('CHECK DATA regex', dataREGEX) // -> NO DATA RETURNED
    console.log('CHECK DATA NOregex', dataNOREGEX) // -> DATA RETURNED

    expect(Array.isArray(dataREGEX)).toBeTruthy()
    expect(Array.isArray(dataNOREGEX)).toBeTruthy()
  })

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.