Giter VIP home page Giter VIP logo

feathers-graph-populate's Introduction

feathers-graph-populate

Github Actions libraries.io Download Status

NOTE: This is the version for Feathers v5. For Feathers v4 use feathers-graph-populate v3

Feathers Graph Populate

Add lightning fast, GraphQL-like populates to your FeathersJS API.

This project is built for FeathersJS. An open source web framework for building modern real-time applications.

Documentation

See https://feathers-graph-populate.netlify.app/ for full documentation

Getting Started

Define your relationships

The top-level keys in the populates represent the name of the relationship.

const populates = {
  posts: {
    service: 'posts',
    nameAs: 'posts',
    keyHere: '_id',
    keyThere: 'authorId',
    asArray: true,
    params: {}
  },
  comments: {
    service: 'comments',
    nameAs: 'comments',
    keyHere: '_id',
    keyThere: 'userId',
    asArray: true,
    params: {}
  },
  openTasks: {
    service: 'tasks',
    nameAs: 'openTasks',
    keyHere: '_id',
    keyThere: 'ownerIds',
    asArray: true,
    params: {
      query: {
        completedAt: null
      }
    }
  },
  role: {
    service: 'roles',
    nameAs: 'role',
    keyHere: 'roleId',
    keyThere: '_id',
    asArray: false,
    params: {}
  }
}

Options for each relationship

Each populate object must/can have the following properties:

Option Description
service The service for the relationship

required
Type: {String}
nameAs The property to be assigned to on this entry. It's recommended that you make the populate object key name match the nameAs property.

required
Type: {String}
keyHere The primary or secondary key for the current entry

required
Type: {String}
keyThere The primary or secondary key for the referenced entry/entries

required
Type: {String}
asArray Is the referenced item a single entry or an array of entries?

optional - default: true
Type: {Boolean}
params Additional params to be passed to the underlying service.
You can mutate the passed params object or return a newly created params object which gets merged deeply
Merged deeply after the params are generated internally.
ProTip: You can use this for adding a '$select' property or passing authentication and user data from 'context' to 'params' to restrict accesss

optional - default: {}
Type: `{Object

Create named queries to use from connected clients.

The top-level keys in the nameQueries object are the query names. Nested keys under the query name refer to the name of the relationship, found in the populates object from the previous code snippet.

const namedQueries = {
  withPosts: {
    posts: {}
  },
  postsWithComments: {
    posts: {
      comments: {}
    }
  },
  postsWithCommentsWithUser: {
    posts: {
      comments: {
        user:{}
      }
    }
  }
}

Register the hook

const { populate } = require('feathers-graph-populate')

const hooks = {
  after: {
    all: [
      populate({ populates, namedQueries })
    ]
  }
}

Perform Queries

Use a named query from a connected client:

feathersClient.service('users').find({
  query: {},
  $populateParams: {
    name: 'postsWithCommentsWithUser'
  }
})

Use a query object for internal requests. (named queries also work, internally):

app.service('users').find({
  query: {},
  $populateParams: {
    query: {
      posts: {
        comments: {
          user:{}
        }
      }
    }
  }
})

Handling Custom Client-Side Params

Since FeathersJS only supports passing params.query from client to server, by default, we need to let it know about the new $populateParams object. We can do this using the paramsForServer and paramsFromCLient hooks:

const { paramsForServer } = require('feathers-graph-populate')

feathersClient.hooks({
  before: {
    all: [
      paramsForServer('$populateParams')
    ]
  }
})

Now to allow the API server to receive the custom param:

const { paramsFromClient } = require('feathers-graph-populate')

feathersClient.hooks({
  before: {
    all: [
      paramsFromClient('$populateParams')
    ]
  }
})

Testing

Simply run npm test and all your tests in the test/ directory will be run.

Help

For more information on all the things you can do, visit the generator, FeathersJS and extensions.

License

Licensed under the MIT license.

feathers-graph-populate's People

Contributors

dependabot[bot] avatar fratzinger avatar marshallswain 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

Watchers

 avatar  avatar  avatar

feathers-graph-populate's Issues

Order of items not preserved in the populated array

Hey, Will the order of items be preserved in the populated array?

I am not sure whether this is something that is happening because of ObjectIds (am using Mongoose) or if the library doesn't maintain the order for any scenarios.

My original document is -

"tagIds": [
        "5f45e63e75589a147c264a4",
        "5f44bc0675589a147c26486",
    ],

The populated document is -

"tagIds": [
        "5f45e63e75589a147c264a4",
        "5f44bc0675589a147c26486",
    ],
"tags": [
    {
        "_id": "5f44bc0675589a147c26486",
        "name": "Tag 2"
    },
    {
        "_id": "5f45e63e75589a147c264a4",
        "name": "Tag 1"
    }
]

Thanks!

`transformResult`

I've an app with customers and orders. In my frontend I've a table with customers and a column with ordersCount. I would like to populate that with graph-populate. So dynamically populate a number to an item.
A possible way would be:

ordersCount: {
    service: 'orders',
    nameAs: 'ordersCount',
    params: { query: { limit: 0 }, paginate: true }
    transformResult: (item, context) => item.total
  },

Make every option a function (starting with `service`)

My last changes with requestPerItem introduced a mechanism to populate based on conditions but there's another step to take.
Explanation: I've a service called changelogs with entries to almost every service with columns: associatedServicePath, associatedId, userId.
If I want to populate the associated Item, I need to take the associatedServicePath. The most elegant way would be to have:

associatedItem: {
    service: (item, context) => item.associatedServicePath,
    nameAs: associatedItem, // or even `nameAs: (item, context) => singularize(item.associatedServicePath)`
    asArray: false
  },

Possible to handle "through" tables?

For example --

users table
groups table

group_xref table with groupId and userId...

I have played around with things a bit myself, and have something working but it seems unideal.. I am using postgres and sequalize, so that may be most of the problem, or I am just missing something.

//users hooks

const populates = {
groups: {
service: "groups",
nameAs: "groups",
keyHere: "groupIds",
keyThere: "id",
asArray: true,
params: {}
}
};
const namedQueries = {
withGroups: {
groups: {}
}
};

If I set up users with a groupIds array column, all of the queries run as expected, but the results do not actually return unless I set up an association table, even though I dont have to actually use the xref table.. which is why I thought that the "sequalize way" may be part of my problem.

user.groups = user.belongsToMany(models.groups, {
through: "groupXref"
});

But, lets say I wanted to use the xref table, since I have had to thus far, would this library still work for me? I use feathers-vuex and handling associations has always seemed to be a pain, but this library seems like it might just be the magicalness I am looking for hahahha.

Passing function to params breaks nested populates

Passing a function to params breaks the nested populates it seems. Not sure how to preserve the $populateParams when passing a function to params in the relationship which utilizes context to create dynamic params.

For example - if a relationship uses a param function

    params: (params, context) => {
          params.scopeId= context.params.scopeId
        }

and has a nested query that needs to be populated

query: {
    posts: {
        users: {}
   }
}

Think it happens due to the way $populateParams are stored on the populates' before being passed to shallow-populate - which breaks when the params in the populate definition is a function.

Object.assign(data.params, {

This results in $populateParams being lost when the shallow-populate runs the param function which in turn results in all nested queries not getting executed.
https://github.com/Mattchewone/feathers-shallow-populate/blob/374dd961b47e2bc35dac86fdc609fa76e5096606/lib/shallow-populate.js#L115

Not really sure to resolve this (still trying to figure out what exactly is happening here)

RFC: move shallow-populate, `service` as function & `transformResult`

1. move feathers-shallow-populate to feathers-graph-populate:

Maintaining both feels like a blocker. It's hard to test if I have changes for both. It's inconvenient for you marshall to make it both a release. It's an open door for unexpected bugs.
Option 1: Leave that as is. Separate repositories.
Option 2: Include shallowPopulate hook into the repo. Also move the tests (of course)
Option 3: Make it a monorepo

2. Make every option a function (starting with service):

My last changes with requestPerItem introduced a mechanism to populate based on conditions but there's another step to take.
Explanation: I've a service called changelogs with entries to almost every service with columns: associatedServicePath, associatedId, userId.
If I want to populate the associated Item, I need to take the associatedServicePath. The most elegant way would be to have:

associatedItem: {
    service: (item, context) => item.associatedServicePath,
    nameAs: associatedItem, // or even `nameAs: (item, context) => singularize(item.associatedServicePath)`
    asArray: false
  },

3. transformResult:

I've an app with customers and orders. In my frontend I've a table with customers and a column with ordersCount. I would like to populate that with graph-populate. So dynamically populate a number to an item.
A possible way would be:

ordersCount: {
    service: 'orders',
    nameAs: 'ordersCount',
    params: { query: { limit: 0 }, paginate: true }
    transformResult: (item, context) => item.total
  },

I know that fades away from the awesome approach of batch loading, but (2) and (3) would add even more functionality to feathers-shallow-populate and feathers-graph-populate.

Marshall, what do you think about these points?

Mongo ObjectID && keyHere option

Hey! Thanks for the awesome work.

I am trying to integrate this, but cant seem to get it working. I've gone back thru the docs many times t be sure it's setup properly...

I am wondering if maybe it has to do with the ObjectID type of _ID that mongo uses. Would keyHere: "_id" map to the _id: ObjectID("j89f2nj8iofjf") or is it expecting a string/int only?

update feathers-shallow-populate

See Mattchewone/feathers-shallow-populate#22 from @areiterer

As of ^1.4.0 I come across the same problem as @areiterer figured out. Populating an empty object instead of not setting the item at all, leads to many items in my FeathersVuex which are empty temp records.

Version ^1.2.0 which I used lastly did not behave that way. I'm afraid it's a problem I introduced with my changes. Sorry about that!

Please @marshallswain, it would be really nice if you could make this a feathers-shallow-populate release and update the dependency in feathers-graph-populate to that version.

As always, I highly appreciate your work! Thanks in advance!

[Question] Populate Definitions - What is "params" used for?

First of all - Thanks a lot for pointing me to this earlier! I am exploring the library now and the overall ease of defining the relationships and named queries makes my whole flow so much cleaner. I've been using a mix of batch loaders and populates awkwardly to achieve what this library does out of the box in a much much better way!

Question - What exactly is the "params" in the populate definitions used for? Couldn't find any reference/documentation of that.

orgMemberships: {
    service: 'org-users',
    nameAs: 'orgMemberships',
    keyHere: '_id',
    keyThere: 'userId',
    asArray: true,
    params: {}
  }

Thanks!

how to handle n:m relationship

Hi, Thanks for this wonderful library,

What is the best way to handle a many-to-many relationship with this library?

Thanks

bug in shallow populate function when object includes "data" field

my user is set up with with a json column named data, this line below is what is causing all my confusion I believe! ( things working in some cases and not in others, leading me to assume its my own code issues lol )

In my case, it should be taking context.result to access the user object, as context.result.data is a field of my user so the dataMap function is never finding the keys to match up.

return function shallowPopulate (context) {
    const { app, type } = context
    let data = type === 'before' ? context.data : (context.result.data || context.result)
    data = [].concat(data || [])

    if (!data.length) {
      return Promise.resolve(context)
    }

paramsForServer and paramsFromClient is not a function

const { paramsForServer } = require('feathers-graph-populate'); const { paramsFromClient } = require('feathers-graph-populate');

When I use either of the two requires, I get the following error:

Uncaught TypeError: paramsForServer is not a function

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.