• Stars
    star
    152
  • Rank 244,685 (Top 5 %)
  • Language
    CoffeeScript
  • Created over 10 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Transparently divide a single meteor app into several different instances shared between different groups of users.

partitioner Build Status

Transparently divide a single Meteor app into several different instances shared between different groups of users.

What's this do?

Provides facilities to transparently separate your Meteor app into different instances where a group of users sees the data in each instance. You can write client-side and server-side code as if one particular set of users has the app all to themselves. This is illustrated in the following diagram:

Partition Diagram

On the left is a simple app where the same view of a particular collection is published to all users at once. However, we often have a situation where we want some users (1 and 2) to see a common view of the collection that is different from some other users (3 and 4). This pattern is common in settings where multiple users interact with each other in groups, such as multiplayer games, chat rooms, etc.

This package allows you to structure your code without thinking about how to separate data across the different groups. Both server and client side code automatically only affect the current group of a user unless directed otherwise.

Installation

Install with Meteor:

meteor add mizzao:partitioner

Usage

Partitioner uses the collection-hooks package to transparently intercept collection operations on the client and server side so that writing code for each group of users is almost the same as writing for the whole app. Only minor modifications from a standalone app designed for a single group of users is necessary.

Partitioner operates at the collection level. On the server and client, call Partition.partitionCollection immediately after declaring a collection:

Foo = new Mongo.Collection("foo");
Partitioner.partitionCollection(Foo, options);

options determines how the partitioned collection will behave. The fields that are supported are

  • index: an optional index argument that will be transformed and passed to Collection._ensureIndex; the default indexing behavior is to just index by group. This is useful if each partition will contain a lot of data and require efficient database lookups.
  • indexOptions: options passed for the second argument of ensureIndex.

Collections that have been partitioned will behave as if there is a separate instance for each group. In particular, on the server and client, the user's current group is used to do the following:

  • find and findOne operations will only return documents for the current group.
  • insert will cause documents to appear only in the current group.
  • update and remove operations will only affect documents for the current group.
  • Attempting any operations on a partitioned collection for which a user has not been assigned to a group will result in an error.

This is accomplished using selector rewriting based on the current userId both on the client and in server methods, and Meteor's environment variables. For more details see the source.

Common (Client/Server) API

Partitioner.partitionCollection(Mongo.Collection, options)

Adds hooks to a particular collection so that it supports partition operations. This should be declared immediately after new Mongo.Collection on both the server and the client.

NOTE: Any documents in the collection that were not created from a group will not be visible to any groups in the partition. You should think of creating a partitioned collection as an atomic operation consisting of declaring the collection and calling partitionCollection; we will consider rolling this into a single API call in the future.

Partitioner.group()

On the server and client, gets the group of the current user. Returns undefined if the user is not logged in or not part of a group. A reactive variable.

Server API

Partitioner.setUserGroup(userId, groupId)

Adds a particular user to the group identified by groupId. The user will now be able to operate on partitioned collections and will only be able to affect documents scoped to the group. An error will be thrown if the user is already in a group.

Partitioner.getUserGroup(userId)

Gets the group of the current user.

Partitioner.clearUserGroup(userId)

Removes the current group assignment of the user. The user will no longer be able to operate on any partitioned collections.

Partitioner.bindGroup(groupId, func)

Run a function (presumably doing collection operations) masquerading as a particular group. This is necessary for server-originated code that isn't caused by one particular user.

Partitioner.bindUserGroup(userId, func)

A convenience function for running Partitioner.bindGroup as the group of a particular user.

Partitioner.directOperation(func)

Sometimes we need to do operations over the entire underlying collection, including all groups. This provides a way to do that, and will not throw an error if the current user method invocation context is not part of a group.

Configuring Subscriptions

Suppose you have a publication on the server such as the following:

Meteor.publish("fooPub", function(bar) {
  selector = doSomethingWith(bar);
  return Foo.find(bar);
});

On the client, you would subscribe to this with

Meteor.subscribe("fooPub", bar);

Normally, all users would get the same view of the data as long as bar is the same. However, Partitioner will return a different view of the data depending on the current group of the user.

Note that due to a current limitation of Meteor (see below), this subscription will not update if the user's group changes on the server. Hence, you will need to update subscriptions to partitioned collections in a reactive computation:

Deps.autorun(function() {
  var group = Partitioner.group();
  Meteor.subscribe("fooPub", bar, group);
});

N.B.: You need to pass group as a dummy last value to the subscription to ensure the publication is actually re-run on the server; otherwise, Meteor tries to be efficient by not actually re-subscribing if the arguments are the same.

Partitioning of Meteor.users

Meteor.users is partitioned by default. Users will only see other users in their group in default publications. However, unscoped operations do not throw an error, because server operations (login, etc) need to proceed as normal when groups are not specified. This generally causes everything to work as expected, but please report any unexpected behavior that you see.

Admin users

Partitioner treats users with admin: true as special. These users are able to see the entire contents of partitioned collections as well as all users when they are not assigned to a group, and operations will not result in errors.

However, when admin users join a group, they will only see the data and users in that group (if you set up the subscriptions as noted above.) They will also, currently, be unable to do any operations on partitioned collections. The idea is to allow admin users to be able to join games, chatrooms, etc for observational purposes, but to prevent them from making unintended edits from the user interface.

If you would like to see other ways to define admin permissions, please open an issue.

Examples

Before

Suppose you have a chat application with a ChatMessages collection, which is scoped by a field room. In vanilla Meteor, your publication might be written as follows:

Meteor.publish("messages", function(roomId) {
  return ChatMessages.find({room: roomId});
});

Then, on the client, you might reactively subscribe to the chat room:

Deps.autorun(function() {
  var roomId = Session.get("room");
  Meteor.subscribe("messages", roomId);
});

To send a message, a client or server method might do something like

ChatMessages.insert({text: "hello world", room: currentRoom, timestamp: Date.now()});

This looks simple enough, until you realize that you need to keep track of the room for each message that is entered in to the collection. Why not have some code do it for you automagically?

After

With this package, you can create a partition of the ChatMessages collection:

ChatMessages = new Mongo.Collection("messages");
Partitioner.partitionCollection(ChatMessages, {index: {timestamp: 1}});

The second argument tells the partitioner that you want an index of timestamp within each group. Partitioned lookups using timestamp will be done efficiently. Then, you can just write your publication as follows:

Meteor.publish("messages", function() {
  return ChatMessages.find();
});

The client's subscription would simply be the following:

Deps.autorun(function() {
  var group = Partitioner.group();
  Meteor.subscribe("messages", group);
});

Now, sending a chat message is as easy as this:

ChatMessages.insert({text: "hello world", timestamp: Date.now()});

To change chat rooms, simply have server code call Partitioner.setUserGroup for a particular user.

Note that the above code looks very similar to a Meteor app where there is only one chatroom, apart from the reactive subscription. This means you can think and reason about your data within each partition without worrying about accidentally touching things that you don't want. Chats, games, and other highly concurrent datasets can be designed for a single group of users, and quickly converted to multiple groups using this package.

Other Examples

See CrowdMapper for a highly concurrent mapping application that partitions several collections over groups of users.

Limitations

  • The admin role is currently fixed to users with admin: true, but this could certainly be extended with a custom rule.

  • Users, not connections/sessions, are assigned to groups. If one user's connection joins a group, all connections will be part of that group. (This is another reason you must set up subscriptions reactively to make sure client data is sane.)

  • Multiple partitions at once is not supported, i.e. Foo is partitioned one way and Bar is partitioned another way. There is only one set of groups over all users.

  • Changing the group relies on a small amount of client-side code. It would be great if the publication would do a reactive join with something like publish-with-relations, so that a single publication is controlled by the server and updated as the group changes. However, this would result in some potentially expensive observe operations, and can't be accomplished with transparent hooks - just to accomplish the same thing as a simple re-subscription. Hence, we've opted to use the latter for now.

Notes

More Repositories

1

meteor-autoform

AutoForm is a Meteor package that adds UI components and helpers to easily create basic forms with automatic insert and update events, and automatic reactive validation.
JavaScript
1,439
star
2

Meteor-CollectionFS

Reactive file manager for Meteor
JavaScript
1,051
star
3

meteor-collection2

A Meteor package that extends Mongo.Collection to provide support for specifying a schema and then validating against that schema when inserting and updating.
JavaScript
1,024
star
4

meteor-roles

Authorization package for Meteor, compatible with built-in accounts packages
JavaScript
920
star
5

meteor-simple-schema

Meteor integration package for simpl-schema
JavaScript
920
star
6

meteor-collection-hooks

Meteor Collection Hooks
JavaScript
657
star
7

ground-db

GroundDB is a thin layer providing Meteor offline database and methods
JavaScript
569
star
8

meteor-user-status

Track user connection state and inactivity in Meteor.
JavaScript
557
star
9

meteor-publish-composite

Meteor.publishComposite provides a flexible way to publish a set of related documents from various collections using a reactive join
JavaScript
553
star
10

raix-push

DEPRECATED: Push notifications for cordova (ios, android) browser (Chrome, Safari, Firefox)
JavaScript
515
star
11

meteor-tabular

Reactive datatables for large or small datasets
JavaScript
363
star
12

meteor-autocomplete

Client/server autocompletion designed for Meteor's collections and reactivity.
CoffeeScript
350
star
13

meteor-scss

Node-sass wrapped to work with meteor.
JavaScript
311
star
14

meteor-timesync

NTP-style time synchronization between server and client, and facilities to use server time reactively in Meteor applications.
JavaScript
118
star
15

meteor-link-accounts

Meteor link account package. based on this PR https://github.com/meteor/meteor/pull/1133
JavaScript
116
star
16

mongo-collection-instances

🗂 Meteor package allowing Mongo Collection instance lookup by collection name
JavaScript
73
star
17

Meteor-EventEmitter

Client and server event emitter
JavaScript
72
star
18

meteor-postcss

PostCSS for Meteor
JavaScript
68
star
19

meteor-mocha

A Mocha test driver package for Meteor 1.3+. This package reports server AND client test results in the server console and can be used for running tests on a CI server or locally.
JavaScript
67
star
20

meteor-elastic-apm

Meteor Elastic APM integration
JavaScript
57
star
21

organization

Discussions on organization of the organization 🎩
41
star
22

meteor-schema-index

Control some MongoDB indexing with schema options
JavaScript
38
star
23

meteor-publication-collector

Test a Meteor publication by collecting its output.
JavaScript
33
star
24

meteor-collection-extensions

Safely and easily extend the (Meteor/Mongo).Collection constructor with custom functionality.
JavaScript
31
star
25

stratosphere

Meteor private package server
JavaScript
28
star
26

meteor-method-hooks

JavaScript
26
star
27

meteor-autoform-select2

Custom select2 input type for AutoForm
JavaScript
25
star
28

meteor-autoform-bs-datepicker

Custom "bootstrap-datepicker" input type for AutoForm
JavaScript
25
star
29

react-router-ssr

Simple isomorphic React SSR for Meteor with subscribed data re-hydration
JavaScript
24
star
30

denormalize

Simple denormalization for Meteor
JavaScript
20
star
31

meteor-autoform-bs-datetimepicker

Custom bootstrap-datetimepicker input type with timezone support for AutoForm
JavaScript
17
star
32

meteor-packages

Client for Meteor Package Server API
JavaScript
14
star
33

Packosphere

A Meteor package explorer for you, and if you are so inclined to help build it, by you.
TypeScript
13
star
34

meteor-schema-deny

Deny inserting or updating certain properties through schema options
JavaScript
12
star
35

check-npm-versions

Enforces "peer" npm dependencies in Meteor 1.3+ Atmosphere packages.
TypeScript
11
star
36

meteor-browser-tests

A helper package for Meteor test driver packages. Runs client tests in a headless browser.
JavaScript
11
star
37

template-package

Template package with CI and everything else to get started quickly with creating a new FOSS Meteor package.
JavaScript
10
star
38

meteor-stylus

A fork of meteor:stylus with mquandalle:stylus plugins
JavaScript
8
star
39

meteor-minifiers-autoprefix

JavaScript
6
star
40

meteor-autoform-bs-button-group-input

A Bootstrap button group theme for the "select-checkbox" and "select-radio" AutoForm input types
JavaScript
6
star
41

ground-minimax

Minimax is a thin layer for Meteor providing EJSON.minify and EJSON.maxify
JavaScript
6
star
42

meteor-typescript

Typescript compiler package
TypeScript
5
star
43

meteor-api-untethered

A collection of packages to make Meteor available to other environments.
JavaScript
5
star
44

awesome-meteor

Curated list of awesome Meteor.js things.
5
star
45

Meteor-EventState

DEPRECATED: Evented state
JavaScript
4
star
46

meteor-autoform-themes

Officially supported themes for aldeed:autoform
JavaScript
3
star
47

push

JavaScript
2
star