Git Product home page Git Product logo

modelizr's Introduction

modelizr

Coverage Status Build Status npm version Gitter

A Combination of normalizr, fakerjs and GraphQL that allows you to define multipurpose models that can generate GraphQL queries, mock deeply nested data and normalize

Installation

$ yarn add modelizr

What can I use this for?

  • Easily generating GraphQL queries from models.
  • Flat-mapping responses using normalizr.
  • Mocking deeply nested data that match the structure of a GraphQL query.

Read my medium post on why I wrote modelizr.

What does it look like?

import { Modelizr } from 'modelizr'

const ModelData = {
  Person: {
    normalizeAs: "People",
    fields: {
      id: Number,
      firstName: String,
      Books: ["Book"]
    }
  },
    
  Book: {
    normalizeAs: "Books",
    fields: {
      id: Number,
      title: String,
      Author: "Person"
    }
  }
}

const {query, models: {Person, Book}} = new Modelizr({
  models: ModelData,
  config: {
    endpoint: "http:// ..."
  }
})

query(
  Person({id: 1}
    Book("Books")
  ),
  
  Book("Books", {ids: [4, 5]})
).then((res, normalize) => {
  normalize(res.body) // -> normalized response.
})

This will generate the following query and make a request using it.

{
  Person(id: 1) {
    id,
    firstName,
    Books {
      id,
      title
    }
  },
  
  Books(ids: [4, 5]) {
    id,
    title,
    Author {
      id,
      firstName
    }
  }
}

Documentation

NOTE: Documentation for pre-v1.0.0 can be found Here

All documentation is located at julienvincent.github.io/modelizr

Example

  • $ yarn
  • $ yarn start

navigate to http://localhost:8000 in your browser

modelizr's People

Contributors

julienvincent avatar ming436534 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  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  avatar  avatar

modelizr's Issues

Additional/Alternative Query Syntax Proposal

The current 'query language' conflates queries with the type system and is difficult to read due to field names (as mutator/modifier) being at the bottom. This could be simplified by using a json tree and leveraging the type system. Thought I would give a proper example after our discussion earlier.

current implementation

import { query } from 'modelizr'
import { event, organization, user, book } from './schema.js'

query(
    event(
        organization(
            user(
                book(
                    user(
                        company(
                            user().as('father')
                        ).as('employer')
                    ).as('author'),
                    book(
                        user(
                            user(
                                user().as('mother')
                            ).as('guardian')
                        ).as('author'),
                    ).as('inspiredBy')
                ).as('favoriteBook')
            ).as('founder')
        ).as('company')
    )
)

proposal

import { query } from 'modelizr'
import { event } from './schema.js'

query(
    event({
        company: {
            founder: {
                favoriteBook: {
                    author: {
                        employer: {
                            father: {}
                        }
                    },
                    inspiredBy: {
                        author: {
                            guardian: {
                                mother: {}
                            }
                        }
                    }
                }
            }
        }
    })
)

and alternatively for the adventurous

import { query } from 'modelizr'
import { event } from './schema.js'

query(
    event({
        company: {
            founder: {
                favoriteBook: {
                    author: {
                        employer: {
                            father: {}}},
                    inspiredBy: {
                        author: {
                            guardian: {
                                mother: {}}}}}}}})
)

How to improve working with models

The Problem

The current implementation of models aren't flexible enough for use in real world applications. As soon as queries require models with different keys, things start to feel hacky and, well, wrong. Take for example the following case:

import { model, query } from 'modelizr'

const user = model('users', { ... })

query(
   user()
)

This makes sense and is easy to use, however things can quickly become messy:

query(
   user(
      book(
         user().as('author')
      )
   ).as('user'),

   book().as('book')
)

Introducing the alias() util and proposal #2 helped clean this up a bit to look like this:

const author = alias(user)

query(
   user('user',
      book(
         author()
      )
   ),

   book('book')
)

However this still feels wrong to me.

As a side effect, specifying whether or not a top level models in a query should be mocked as a collection of or single entity is hard to do. The current implementation is to pass in the models primary key as a parameter, but this won't be possible if querying a type that takes no parameters and returns a singular entity.

Some Alternatives

Here are some alternatives that have crossed my mind. I will add more as I think of them.

Allowing models to infer child model keys

When creating models, part of the process is defining their relationships with other models

user.define({
   textbooks: [book]
})

book.define(
   author: user
)

We could use these definitions to infer what the key should be inside a query

query(
   user(
      book()
   ),

   book(
      user()
   )
)

/*
{
  users {
    ...,
    textbooks {
      ...
    }
  },

  books {
    ...,
    author {
      ...
    }
  }
}
*/

This is already a lot cleaner, but there are some problems.

  1. Model definitions with two relationships using the same model will not be inferable
  2. Doesn't work on top level models
  3. Doesn't solve the problem of single entity mocking for top level models.

Adding a define modifier to our query tools would make this option more viable

query(
   user(
      book()
   ),

   book(
      user()
   )
).define({
  user: user,
  textbooks: arrayOf(book)
})

This will solve problems 2 and 3, albeit messily, but not problem 1.

Create singular and collection models separately

When creating a model, specify the entity key and query key separately. Define collection models by aliasing singular models and wrapping them with arrayOf or valuesOf.

import { model, alias, arrayOf } from 'modelizr'

const user = model('user', 'users', { ... })
const users = arrayOf(alias(user, 'users'))

Although we would still need to use .as in our queries, it would be much clearer as to what is going on, and this would solve the mocks problem

query(
   users(
       book(
           user('author')
       )
   ),

   user()
)

or

const author = alias(user, 'author')

query(
   users(
       book(
           author()
       )
   ),

   user()
)

Much more readable! But it still doesn't feel flexible to me. It's almost as if the confusion that was queries has just moved over to the model creation process.

Input

I hope to get some feedback from other people using or interested in modelizr.

Generalise Queries

I noticed that the current implementation assumes root level queries query and mutation (I think?). Please note that while this probably how most people are using graphql, modeller shouldn't make any assumptions about user top level queries.

A top level query can have the following example forms (Again, I'm pretty sure this is how it works):

query Alias {}
someThing Thingie {}
mutation lol {}

Users should be able to specify the top level exposed name and the specific query alias. Also, mutations are functionally equivalent to queries. The only difference is convention.

buildParameters problem

const buildParameters = (params: ?Object): string => {
	params = _.pickBy(params || {}, value => value !== null && value !== undefined && !Object.is(value, NaN))
	if (_.isEmpty(params)) return ""
	return `(${_.map(_.pickBy(params, param => param || param === false), (param, key) =>
		`${key}: ${JSON.stringify(param).replace(/"([^(")"]+)":/g, "$1:")}`)})`
}

May I ask what is the purpose of _.pickBy(params, param => param || param === false ? This part of code would omit my param if my param is like {page:0}. It makes problem as my pagination 0 based. May I ask if this is on purpose? Thank you.

Create next.js example

Hi there,

I think your approach is very interesting and it would be great to see it in action in a simple example with next.js which is also a very minimal approach to react apps.

As an example here's the one for apollo

It would also give more visibility to your work!

Cheers,

Jun

Exclude faker.js and chance.js

Modelizr is a large bundle which is a problem when building for production. There needs to be a way to reduce this bundle size.

json-schema-faker already allows faker and chance exclusion by requiring json-schema-faker/lib. This just needs to be handled in modelizr.

Multiple root models of the same type in a query generate conflicting entity ids

Using multiple root models of the same type in a query, like this:

query(
    book(),
    book()
)

or

query(
    user(
        book()
    ),

    book(
        user().as("author")
    )
)

Will cause entities with conflicting ids to be generated. This is as the mocking cache is not scoped at a query level, but at a root model level.

The cache needs to be scoped to the query.

normalizr for mutations?

hey this is a cool library, i'm starting to think about the same thing.

I was wondering if you thought about using normalizr going the other way for mutations. Something like this:

mutation:

user.create({
  name: 'matt',
  notes: [
    {
      id: 123,
      title: 'blah',
      body: 'hi'
    }
  ]
})

normalized:

entities: {
  users: [
    {
    name: 'matt',
    notes: [1]
    }
  ],
  notes: [
    {
      id: 123,
      title: 'blah',
      body: 'hi'
    }
  ]
}

database inserts:

INSERT INTO users VALUES (...)
INSERT INTO notes VALUES (...)

any downsides you foresee with this approach?

Unable to use the normalize function independently

Is the normalize function available globally?
What if I need to normalize something that is not being fetched via an API ?

It does not work when I import the normalizr package to my project and use that function with the modelizr schema

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.