GithubHelp home page GithubHelp logo

joinjs's Introduction

JoinJS

Build Status Coverage Status

JoinJS is a JavaScript library to map complex database joins to nested objects. It's a simpler alternative to a full-blown Object-Relation Mapper (ORM), and gives you direct control over your database interactions.

Motivation: Direct, no-nonsense control over your database

Traditional ORMs introduce a thick layer of abstraction between objects and database tables. This usually hinders, rather than helps, developer productivity. In complex use cases, it is difficult enough to devise efficient queries, but with ORMs you also have to teach them to generate the same query. It takes extra time to do this and you may not be able to produce the same query. In the worst case scenario, the ORM may hit the database multiple times for something that you were able to do in a single query.

JoinJS takes a much simpler and straightforward approach inspired by a popular Java mapping framework called MyBatis (see the post on MyBatis vs. other ORMs. You can use any database driver or query builder (such as Knex.js) to query your database, however you use JoinJS to convert the returned results into a hierarchy of nested objects.

Example

Suppose you have a one-to-many relationship between a Team and its Players. You want to retrieve all teams along with their players. Here's the query for to do this:

SELECT t.id              AS team_id,
       t.name            AS team_name,
       p.id              AS player_id,
       p.name            AS player_name
FROM   teams t
       LEFT OUTER JOIN players p
                    ON t.id = p.team_id;

Assume that this query returns the following result set:

let resultSet = [
    { team_id: 1, team_name: 'New England Patriots', player_id: 1, player_name: 'Tom Brady'      },
    { team_id: 1, team_name: 'New England Patriots', player_id: 2, player_name: 'Rob Gronkowski' },
    { team_id: 2, team_name: 'New York Jets',        player_id: 3, player_name: 'Geno Smith'     },
    { team_id: 2, team_name: 'New York Jets',        player_id: 4, player_name: 'Darrelle Revis' }
];

You can use JoinJS to convert this result set in to an array of teams with nested players:

[
    {
        id: 1,
        name: 'New England Patriots',
        players: [
            { id: 1, name: 'Tom Brady'      },
            { id: 2, name: 'Rob Gronkowski' }
        ]
    },
    {
        id: 2,
        name: 'New York Jets',
        players: [
            { id: 3, name: 'Geno Smith'     },
            { id: 4, name: 'Darrelle Revis' }
        ]
    }
]

To teach JoinJS how to do this, you must create two result maps that describe your objects:

const resultMaps = [
    {
        mapId: 'teamMap',
        idProperty: 'id',
        properties: ['name'],
        collections: [
            {name: 'players', mapId: 'playerMap', columnPrefix: 'player_'}
        ]
    },
    {
        mapId: 'playerMap',
        idProperty: 'id',
        properties: ['name']
    }
]

Once you have created these result maps, you can simply call JoinJS to convert your result set in to objects:

let mappedResult = joinjs.map(resultSet, resultMaps, 'teamMap', 'team_');

That's it! It doesn't matter how deep or complex your object hierarchy is, JoinJS can map it for you. Read the documentation below for more details. You can find more examples in the test suite. Follow the step-by-step tutorial for a hands-on introduction. Once you have mastered the basics, check out the Manage My Money project to see how you can build a full-fledged application complete with a front-end using JoinJS and other useful libraries.

Installation

$ npm install --save join-js

Don't forget the dash in the package name (join-js).

Using with ES5:

var joinjs = require('join-js').default;

Using with ES6:

import joinjs from 'join-js';

Documentation

ResultMap

ResultMaps are used to teach JoinJS how to map database results to objects. Each result map focuses on a single object. The properties of a ResultMap are described below. You can find several examples in the test suite.

  • mapId {String} - A unique identifier for the map

  • createNew {function} (optional) - A function that returns a blank new instance of the mapped object. Use this property to construct a custom object instead of a generic JavaScript Object.

  • idProperty {String | Object | Array(String|Object)} (optional) - specifies the name of the id property in the mapped object and in the result set. Default is id, which implies that the name of the id property in the mapped object as well as the column name in the result set are both id. If the two names are different, then you must specify the Object form, e.g. {name: 'id', column: 'person_id'}.

    • name - property that identifies the mapped object
    • column - property that identifies the database record in the result set

    In addition, you can specify composite key by passing an array of string and/or object, e.g. ['person_id', {name: 'language', column: 'language_id'}]

  • properties {Array} (optional) - names of other properties. For any property that has a different name in the mapped object vs. the result set, you must specify the object form, e.g. {name: 'firstName', column: 'first_name'}. The properties of the object form are:

    • name - property name in the mapped object
    • column - property name in the result set
  • associations {Array} (optional) - mappings for associations to other objects. Each mapping contains:

    • name - property name of the association in the mapped object
    • mapId - identifier of the result map of the associated object
    • columnPrefix (optional) - a prefix to apply to every column of the associated object. Default is an empty string.
  • collections {Array} (optional) - mappings for collections of other objects. Each mapping contains:

    • name - property name of the collection in the mapped object
    • mapId - identifier of the result map of the associated objects
    • columnPrefix (optional) - a prefix to apply to every column of the associated object. Default is an empty string.

API

JoinJS exposes two very simple functions that give you the full power to map any result set to one of more JavaScript objects.

map(resultSet, maps, mapId, columnPrefix)

Maps a resultSet to an array of objects.

  • resultSet {Array} - an array of database results
  • maps {Array} - an array of result maps
  • mapId {String} - mapId of the top-level objects in the resultSet
  • columnPrefix {String} (optional) - prefix that should be applied to the column names of the top-level objects

Returns an array of mapped objects.

mapOne(resultSet, maps, mapId, columnPrefix, isRequired)

This is a convenience method that maps a resultSet to a single object. It is used when your select query is expected to return only one result (e.g. SELECT * FROM table WHERE id = 1234).

  • resultSet {Array} - an array of database results
  • maps {Array} - an array of result maps
  • mapId {String} - mapId of the top-level object in the resultSet
  • columnPrefix {String} (optional) - prefix that should be applied to the column names of the top-level objects
  • isRequired {boolean} (optional) - is it required to have a mapped object as a return value? Default is true.

Returns the mapped object or null if no object was mapped.

Throws a NotFoundError if no object is mapped and isRequired is true.

Resources

joinjs's People

Contributors

kelvien avatar leeoniya avatar nareshbhatia avatar quramy 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  avatar

joinjs's Issues

The automated release is failing 🚨

🚨 The automated release from the master branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this πŸ’ͺ.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the master branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here is some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


Invalid npm token.

The npm token configured in the NPM_TOKEN environment variable must be a valid token allowing to publish to the registry https://registry.npmjs.org/.

If you are using Two-Factor Authentication, make configure the auth-only level is supported. semantic-release cannot publish with the default auth-and-writes level.

Please make sure to set the NPM_TOKEN environment variable in your CI with the exact value of the npm token.


Good luck with your project ✨

Your semantic-release bot πŸ“¦πŸš€

Cannot find module 'fs-base'

I just install joinjs, as soon start ugin got that message looking at the file

node_modules/joinjs/lib/index.js:1:72

I found

image

but such module doesn't exist on npmjs

Composite keys

Have you considered adding a feature for composite key scenarios?

Here is some sample DDL:

CREATE TABLE person(
    personId INT(11) NOT NULL,
    personName VARCHAR(20) NOT NULL,
    PRIMARY KEY(personId)
)

CREATE TABLE language(
    languageId INT(11) NOT NULL,
    languageName VARCHAR(20) NOT NULL,
    PRIMARY KEY(personId)
)

CREATE TABLE personLanguage(
    personId INT(11) NOT NULL,
    languageId INT(11) NOT NULL,
    description VARCHAR(20) NOT NULL,
    PRIMARY KEY(personId, languageId),
    FOREIGN KEY (personId) REFERENCES person(personId) ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (languageId) REFERENCES language(languageId) ON UPDATE CASCADE ON DELETE CASCADE
)

Last release does not include latest fixes

Version 0.4.0 at NPM does not seem to include the fixes of PR #3 for empty associations/collections. It'd be great to have a version 0.4.1 with the fix.

(It cost me quite a bit of time to investigate and fix it myself only to realize that there already has been a merged PR for it.)

Associations/collections with undefined properties when columns not present in result set

When I map a result set with a result map where every possible association and collection is defined but the respective columns are missing (not just null), at least one object gets added as association/to the collection anyway, with every property undefined.

Version used: 1.1.2
This worked in older versions because the check there was for a falsy id property which included undefined and now the check uses the identity operator for null.

This should be fixed by also checking for undefined id properties and not just for null ids.

Designed to work with multiple collections?

Hi there - great work on joinjs.

One thing, let's say I do the following:

select A.A, B.B as B_B, C.C as C_C from A
left join B on ...
left join C on ... 

and get back

A   B   C
1   1   null
2   null 1

I'd expect to have [ { A: 1, Bs: [{B: 1 }], Cs: [] }, { A: 2, Bs: [], Cs: [{C:1}] }], but currently I get [ { A: 1, Bs: [{B:1}], Cs: [{C:null}}, ... with the following JoinJS (might be slightly off, but you get the idea: I've done this correctly and end up with null rows for every top-level row which doesn't match all left joins)

 [
    {
        mapId: 'A',
        idProperty: 'A',
        properties: ['A'],
        collections: [
            {name: 'Bs', mapId: 'B', columnPrefix: 'B_'},
            {name: 'Cs', mapId: 'C', columnPrefix: 'C_'}
        ]
    },
    {
        mapId: 'B',
        idProperty: 'B',
        properties: ['B']
    },
    {
        mapId: 'C',
        idProperty: 'C',
        properties: ['C']
    }
]

I couldn't see a test case that included a collections array of >1 element.

Mapping associations with no join included

What If a want to group some properties on a nested object.

Here's a sample:

resultMaps = [
      {
        mapId: 'teamMap',
        idProperty: 'id',
        properties: ['name'],
        associations: [
          { name: 'players', mapId: 'playerMap', columnPrefix: 'player_' }
        ]
      },
      {
        mapId: 'playerMap',
        properties: ['name']
      }
    ]

    resultSet = [
      { team_id: 1, team_name: 'New England Patriots', player_name: 'Tom Brady' }
    ];

    mappedResult = joinjs.map(resultSet, resultMaps, 'teamMap', 'team_');

    expect(mappedResult).to.deep.equal [
      {
        id: 1
        name: 'New England Patriots'
        player: {
          name: 'Tom Brady'
        }
      }
    ]

Is this possible?

Typo in index.d.ts

Hi!

Typescript definition contains 3 typos.

index.d.ts:

export type ResultMap = {
  ...
  idProperty?: string | { name: string, colmun: string } | (string | { name: string, colmun: string })[];
  properties?: (string | {
    name: string;
    colmun: string;
  })[];
  ...
}

Please see the 3 typos: colmun instead of column

Continuous Integration

It is probably a good idea to have one since this is a library that may soon be used by many developers.
Keeping all commits tested is one step to making sure of its quality.
Issues can be easily caught with regards to other versions of node js.
Also, we'll only be needing to review changes and not manually test it before merging.

Numeric values being parsed as string

I find that the mapping function injectResultInObject()
has mapped some numeric values as string, which is undesirable for me.

I had solved by doing below (line 139-142)

index.txt

        var value = result[columnPrefix+column];
        value = !isNaN(value) && typeof value !=="undefined" && value !== "" ? Number(value):value;

Hope you can consider this fix.

Add possibility to parse/convert property values

I needed a way to convert some properties, after fetching them from the DB. And since I wanted to prevent traversing the whole result set (before JoinJS) or object tree (after JoinJS) again, I thought it'd be great, if JoinJS could handle this in one go. So, I played around with something like this in injectResultInObject:

// Copy id property
//[…]

let properties = {};

// Gather other properties
_.each(resultMap.properties, function(property) {
    // If property is a string, convert it to an object
    if (typeof property === 'string') {
        property = {name: property, column: property};
    }

    // Copy only if property does not exist already
    if (!mappedObject[property.name]) {
        // The default for column name is property name
        let column = (property.column) ? property.column : property.name;
        properties[property.name] = result[columnPrefix + column];
        //mappedObject[property.name] = result[columnPrefix + column];
    }
});

// Check for conversion
if (resultMap.parse) {
    properties = resultMap.parse(properties);
}

// Copy other properties
_.merge(mappedObject, properties);

// Copy associations
// […]

The reason I don't pass the whole mappedObject to the parse function is, that this way, the ID property and any other properties or functions the mappedObject could have when createNew is used are omitted and the chances of completely messing things up during parsing/converting are lower. ;)

What do you think? Would this be something you see within the scope of JoinJS?

Bad definition of NotFoundError function type in index.d.ts

Here is what compiler says:

node_modules/join-js/index.d.ts:21:3 - error TS1038: A 'declare' modifier cannot be used in an already ambient context.

21   declare function NotFoundError(message?: string): void;
     ~~~~~~~
node_modules/join-js/index.d.ts:25:20 - error TS2749: 'NotFoundError' refers to a value, but is being used as a type here. Did you mean 'typeof NotFoundError'?

25     NotFoundError: NotFoundError

Is the package being maintained?

Hi! First of all, what a great library!
I like how you deal with the problem of mapping objects and sql results unlike the traditional ORM approach.
I like how it can just deal with the structure that I want to make by just using a configuration object.
This is what I do manually most of the times using db drivers, builders and lodash.

One thing though, is the package being maintained?

I was thinking of incorporating this package into my projects but I doubt that this repo is being maintained? Also, do you have a roadmap that you can share for others to contribute as well?

I really hope that this push through as this is probably the best and simple solution for object-relation mapping I have researched yet.

issue with associations from many to many relationship

Hello,

I am having issues with a many to many table relationship where table a and table c are associated by table b. The relationship shouldn't be the issue as I am returned the following information:

 [ anonymous {
    pl_id: 97,
    pl_public_name: 'North',
    pl_identifier: '66143c4a-2c2c-11e6-bd89-0242ac160002',
    ca_id: 201,
    ca_public_name: 'Name 10',
    ca_description: 'Description 10',
    ca_icon: null,
    ca_start_date: 2016-06-06T21:19:48.951Z,
    ca_end_date: 2016-06-11T21:19:48.951Z },
  anonymous {
    pl_id: 98,
    pl_public_name: 'South',
    pl_identifier: '6614645e-2c2c-11e6-bd89-0242ac160002',
    ca_id: 201,
    ca_public_name: 'Name 10',
    ca_description: 'Description 10',
    ca_icon: null,
    ca_start_date: 2016-06-06T21:19:48.951Z,
    ca_end_date: 2016-06-11T21:19:48.951Z },
  anonymous {
    pl_id: 99,
    pl_public_name: 'East',
    pl_identifier: '661464f4-2c2c-11e6-bd89-0242ac160002',
    ca_id: 201,
    ca_public_name: 'Name 10',
    ca_description: 'Description 10',
    ca_icon: null,
    ca_start_date: 2016-06-06T21:19:48.951Z,
    ca_end_date: 2016-06-11T21:19:48.951Z },
  anonymous {
    pl_id: 100,
    pl_public_name: 'West',
    pl_identifier: '66146562-2c2c-11e6-bd89-0242ac160002',
    ca_id: 201,
    ca_public_name: 'Name 10',
    ca_description: 'Description 10',
    ca_icon: null,
    ca_start_date: 2016-06-06T21:19:48.951Z,
    ca_end_date: 2016-06-11T21:19:48.951Z } ]

The maps are as follows

let result_maps = [
                {
                    mapId: 'ca_map',
                    idProperty: 'id',
                    properties: ['public_name', 'description', 'icon', 'start_date', 'end_date'],
                    collections: [
                        {name: 'pl', mapId: 'pl_map', column_prefix: 'pl_'}
                    ]

                },
                {
                    mapId: 'pl_map',
                    idProperty: 'id',
                    properties: ['public_name', 'identifier']
                }
            ];

What I am expecting is

id: 201,
public_name: "Name 10",
description: "Description 10",
icon: null,
start_date: "2016-06-06T21:19:48.951Z",
end_date: "2016-06-11T21:19:48.951Z",
pl: [
{ },
{ },
{ },
{ }
]
}
]

But as you can see the pl array is returning empty objects.

What am I missing?

Thanks.

drop lodash

Hi @nareshbhatia,

I wanted to try joinjs and noticed it carried a lodash dependency but used few of lodash's functions. While this lib is a few KB and 1 file, requiring lodash makes it 1.35MB and 1,053 files. It also has a very heavy and slow Babel build system. All of this seemed like extreme overkill to me, so I have forked this repo [1] and made a more streamlined version that still passes all tests, drops lodash, replaces Babel with Rollup & Buble and removes a bunch of other dev deps, greatly reducing dev install size.

Installed npm i join-js size of node_modules:

Before:      1.35 MB,    1,053 Files,    4 Folders
After:       8.05 KB,        1 Files,    1 Folder

Installed with devDependenices node_modules before & after:

node_modules

even if you're not on board with the devDeps changes, i encourage you to drop lodash from your deps, too.

cheers!

[1] https://github.com/leeoniya/joinjs

Allow column name to default to property name

For the usual case of column name being the same as the property name, allow column name to default to the property name. Thus {name: 'age', column: 'age'} should be the same as {name: 'age'}.

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.