npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

appc.composite

v1.1.7

Published

Composite connector

Downloads

6

Readme

Composite Connector

This is a composite connector for Arrow. It lets you composite together models from other connectors in to a single model. Models are queried in parallel and the results are returned once the set of calls is completed.

Installation

$ appc install connector/appc.composite

Usage

Example Models

var User = Arrow.Model.extend('user', {
		fields: {
			first_name: { type: String },
			last_name: { type: String }
		},
		connector: 'appc.mysql'
	}),
	Post = Arrow.Model.extend('post', {
		fields: {
			title: { type: String },
			content: { type: String },
			author_id: { type: Number },
			attachment_id: { type: String }
		},
		connector: 'appc.mongo'
	}),
	Attachment = Arrow.Model.extend('attachment', {
		fields: {
			attachment_content: { name: 'content', type: String }
		},
		connector: 'appc.mongo'
	});

Joining

The composite connector can join multiple models together in to a single model. It does this through the use of various joins.

Single Left Join

Let's say we have a table "post" with a field "author_id". author_id contains a string that maps to an "id" in a "user" table. Therefore, we can do a left join to look up the author, and mix its fields in to the model, as follows:

Arrow.Model.extend('article', {
	fields: {
		title: { type: String, model: 'post' },
		content: { type: String, model: 'post' },
		author_id: { type: Number, model: 'post' },
		author_first_name: { type: String, name: 'first_name', required: false, model: 'user' },
		author_last_name: { type: String, name: 'last_name', required: false, model: 'user' }
	},
	connector: 'appc.composite',

	metadata: {
		'appc.composite': {
			left_join: {
				model: 'user',
				join_properties: {
					'id': 'author_id'
				}
			}
		}
	}
})

The often difficult bit to understand is that "left_join" property, so let's unpack it together. Notice that we specify a model of "user" or "post" on each of the fields, and in the join, only model "user". This implies that "post" is our main table, and all results will be drawn first from it. An equivalent SQL statement might look like this:

SELECT * FROM post p LEFT JOIN user u ON u.id = p.author_id;

The composite connector will thus do a findAll, query, update, or whatever other method you specify on "post" first. Having received the results from post, it will then continue and do a query on "user", searching for the specific "author_id" from each result, one at a time. It then merges the results together and returns them as one unified model.

Single Inner Join

The only practical difference between a left join and an inner join is for you to specify "inner_join" instead of "left_join" in your composite model's metadata. With this property set, only results that successfully join on their children will be returned. (In other words, the intersection of both sets.)

Multiple Joins

To join on multiple models, just change your left_join or inner_join to be an array of joins. Let's update our previous example to also lookup an "attachment" table for our article:

Arrow.Model.extend('article', {
	fields: {
		title: { type: String, model: 'post' },
		content: { type: String, model: 'post' },
		author_id: { type: Number, model: 'post' },
		author_first_name: { type: String, name: 'first_name', required: false, model: 'user' },
		author_last_name: { type: String, name: 'last_name', required: false, model: 'user' },
		attachment_id: { type: String, model: 'post' },
		attachment_content: { type: String, name: 'attachment_content', required: false, model: 'attachment' }
	},
	connector: 'appc.composite',

	metadata: {
		'appc.composite': {
			left_join: [
				{
					model: 'user',
					join_properties: {
						'id': 'author_id'
					}
				},
				{
					model: 'attachment',
					join_properties: {
						'id': 'attachment_id'
					}
				}
			]
		}
	}
})

The connector will go through the left_joins in order, looking them up and merging the results together.

Selecting Whole Models Instead of Fields

Instead of specifying the precise fields you want, you can instead include the entire joined model in your model.

For example:

Arrow.Model.extend('accountContract', {
	fields: {
		account: { type: Object, model: 'account' },
		contract: { type: Object, model: 'contract' }
	},
	connector: 'appc.composite',

	metadata: {
		'appc.composite': {
			left_join: {
				model: 'contract',
				join_properties: {
					'AccountId': 'id'
				}
			}
		}
	}
})

This will look up accounts and each instance will have the account stored in an "account" sub-dictionary. Then it will look up contracts that have an AccountId of the account's id, and store one in a "contract" sub-dictionary.

Joining with Multiple Children

We have heretofore assumed that an article will have just a single author. But what if we want to join with multiple results? For example, let's say we have a "author" model, and we want to select all of their posts. Just add a field with type: Array and model: "post" and the connector will handle the rest:

Arrow.Model.extend('authorWithArticles', {
	fields: {
		first_name: { type: String, model: 'user' },
		last_name: { type: String, model: 'user' },
		posts: { type: Array, model: 'post' }
	},
	connector: 'appc.composite',

	metadata: {
		'appc.composite': {
			left_join: {
				model: 'post',
				join_properties: {
					'author_id': 'id'
				}
			}
		}
	}
})
Controlling the Children Number

By default the number of joined children is 10. However, this number can be controlled by providing a value between 1 and 1000. To achieve this specify "limit" parameter on the field of type Array like this:

Arrow.Model.extend('authorWithArticles', {
	fields: {
		name: { type: String, model: 'user' },
		posts: { type: Array, model: 'post', limit: '50' }
	},
	connector: 'appc.composite',
	metadata: {
		'appc.composite': {
			left_join: {
				model: 'post',
				join_properties: {
					'author_id': 'id'
				}
			}
		}
	}
})

Unrelated Model Batching

What if your models aren't strongly related, but you want them returned together nonetheless? That's also supported:

module.exports = function(Arrow) {
	return Arrow.Model.extend('user_post', {
		fields: {
			users: { type: Array, model: 'user' },
			posts: { type: Array, model: 'post' }
		},
		connector: 'appc.composite'
	});
}

Notice that we don't need any metadata. This just batches the two models together, so a findAll on the composite model will result in the same being applied to each sub-model, and the results are returned together.

You can query by passing in the relevant arguments as sub-dictionaries:

{
	user: {
		limit: 1
	},
	post: {
		where: { title: 'Title1' }
	}
}

This applies to all the methods. For example, a findOne could look like this:

{
	user: '9bcfd7d35d3f2ad0ad069665d0120',
	post: 61204
}

That findOne results in user.findOne('9bc...') being called, and post.findOne(61204).

Development

This section is for individuals developing the Composite Connector and not intended for end-users.

npm install
node app.js

Running Unit Tests

To use the tests, you'll want to create a database in MySQL with the following tables:

CREATE DATABASE IF NOT EXISTS connector;
USE connector;
CREATE TABLE IF NOT EXISTS Composite_UserTable
(
	id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
	first_name VARCHAR(255),
	last_name VARCHAR(255)
);
INSERT INTO Composite_UserTable (first_name, last_name) VALUES ('Dawson', 'Toth');
CREATE TABLE IF NOT EXISTS nolan_user (
	id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
	first_name VARCHAR(40),
	last_name VARCHAR(50),
	email_address VARCHAR(100),
	phone_number VARCHAR(20),
	home_address VARCHAR(30)
);
CREATE TABLE IF NOT EXISTS nolan_user_bad_habits(
	id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
	user_id INT NOT NULL,
	habit VARCHAR(100) NOT NULL,
	FOREIGN KEY (user_id) REFERENCES nolan_user (id) on delete cascade
);

Then you can create an article with a JSON body like this:

{ "title": "My Test Title", "content": "My articles content goes here.", "author_id": 1 }

Run the unit tests:

npm test

Contributing

This project is open source and licensed under the Apache Public License (version 2). Please consider forking this project to improve, enhance or fix issues. If you feel like the community will benefit from your fork, please open a pull request.

To protect the interests of the contributors, Appcelerator, customers and end users we require contributors to sign a Contributors License Agreement (CLA) before we pull the changes into the main repository. Our CLA is simple and straightforward - it requires that the contributions you make to any Appcelerator open source project are properly licensed and that you have the legal authority to make those changes. This helps us significantly reduce future legal risk for everyone involved. It is easy, helps everyone, takes only a few minutes, and only needs to be completed once.

You can digitally sign the CLA online. Please indicate your email address in your first pull request so that we can make sure that will locate your CLA. Once you've submitted it, you no longer need to send one for subsequent submissions.

Legal Stuff

Appcelerator is a registered trademark of Appcelerator, Inc. Arrow and associated marks are trademarks of Appcelerator. All other marks are intellectual property of their respective owners. Please see the LEGAL information about using our trademarks, privacy policy, terms of usage and other legal information at http://www.appcelerator.com/legal.